Beiträge ändern

8

Zu ...% übersetzt

In diesem Kapitel wirst du:

  • Erstelle ein Formular für das Ändern eines Beitrags.
  • Richte Zugriffsberechtigungen ein.
  • Schränke ein, welche Felder geändert werden dürfen.
  • Da wir nun Beiträge anlegen können, ist der nächste Schritt, sie änderbar und löschbar zu machen. Der Code für das UI ist relativ einfach, deshalb nutzen wir die Gelegenheit, um in diesem Kapitel darüber zu sprechen, wie Meteor mit Benutzerberechtigungen umgeht.

    Im ersten Schritt erweitern wir unseren Router. Wir fügen eine Route zur Seite zum Ändern von Beitragen ein und setzen den Kontext dieser Seite entsprechend.

    Router.configure({
      layoutTemplate: 'layout'
    });
    
    Router.map(function() {
      this.route('postsList', {path: '/'});
    
      this.route('postPage', {
        path: '/posts/:_id',
        data: function() { return Posts.findOne(this.params._id); }
      });
    
      this.route('postEdit', {
        path: '/posts/:_id/edit',
        data: function() { return Posts.findOne(this.params._id); }
      });
    
      this.route('postSubmit', {
        path: '/submit'
      });
    });
    
    var requireLogin = function() {
      if (! Meteor.user()) {
        if (Meteor.loggingIn())
          this.render('loading')
        else
          this.render('accessDenied');
    
        this.stop();
      }
    }
    
    Router.before(requireLogin, {only: 'postSubmit'});
    
    lib/router.js

    Das Template für die Beitragsänderung

    Jetzt können wir uns auf das dazugehörige Template konzentrieren. Unser Template postEdit wird ein ziemlich gewöhnliches Formular:

    <template name="postEdit">
      <form class="main">
        <div class="control-group">
            <label class="control-label" for="url">URL</label>
            <div class="controls">
                <input name="url" type="text" value="{{url}}" placeholder="Your URL"/>
            </div>
        </div>
    
        <div class="control-group">
            <label class="control-label" for="title">Title</label>
            <div class="controls">
                <input name="title" type="text" value="{{title}}" placeholder="Name your post"/>
            </div>
        </div>
    
        <div class="control-group">
            <div class="controls">
                <input type="submit" value="Submit" class="btn btn-primary submit"/>
            </div>
        </div>
        <hr/>
        <div class="control-group">
            <div class="controls">
                <a class="btn btn-danger delete" href="#">Delete post</a>
            </div>
        </div>
      </form>
    </template>
    
    client/views/posts/post_edit.html

    Und hier ist der dazu gehörige Manager post_edit.js:

    Template.postEdit.events({
      'submit form': function(e) {
        e.preventDefault();
    
        var currentPostId = this._id;
    
        var postProperties = {
          url: $(e.target).find('[name=url]').val(),
          title: $(e.target).find('[name=title]').val()
        }
    
        Posts.update(currentPostId, {$set: postProperties}, function(error) {
          if (error) {
            // display the error to the user
            alert(error.reason);
          } else {
            Router.go('postPage', {_id: currentPostId});
          }
        });
      },
    
      'click .delete': function(e) {
        e.preventDefault();
    
        if (confirm("Delete this post?")) {
          var currentPostId = this._id;
          Posts.remove(currentPostId);
          Router.go('postsList');
        }
      }
    });
    
    client/views/posts/post_edit.js

    Mittlerweile sollte dir der meiste Code bekannt vorkommen.

    Wir haben zwei Event-Callbacks für das Template definiert: eines für das submit Event des Formulars und eines für das click Event des Löschen-Links.

    Der Callback zum Löschen ist sehr einfach aufgebaut: Nach dem Unterdrücken des Default-Events, wird eine Bestätigung durch den Benutzer angefordert. Wenn wir diese erhalten, holen wir die ID des aktuellen Beitrags aus dem Kontext und löschen den Beitrag unter Verwendung dieser ID. Am Ende wird der Benutzer zur Homepage geleitet.

    Der Callback zum Aktualisieren ist ein wenig länger, aber nicht wesentlich komplizierter: Nach dem Unterdrücken des Default-Events und dem Ermitteln der ID des Beitrags, entnehmen wir die neuen Werte aus dem Zielobjekt des Events und speichern diese in einem Objekt namens postProperties.

    Dieses Objekt übergeben wir an die Meteor-Methode Collection.update(), zusammen mit einem Callback als letzten Parameter der Methode. Der Callback wird am Ende der Operation ausgeführt. Im Fehlerfall wird der Grund für den Fehler ausgegeben. Im Falle eines Erfolgs leitet der Callback den Benutzer auf die Ansichts-Seite des Beitrags zurück.

    Hinzufügen von Links

    Es fehlen noch Berarbeiten-Links in unseren Beiträgen. Erst mit diesen, haben unsere Benutzer die Möglichkeit die Seite für das Ändern von Beiträgen zu erreichen.

    <template name="postItem">
      <div class="post">
        <div class="post-content">
          <h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
          <p>
            submitted by {{author}}
            {{#if ownPost}}<a href="{{pathFor 'postEdit'}}">Edit</a>{{/if}}
          </p>
        </div>
        <a href="{{pathFor 'postPage'}}" class="discuss btn">Discuss</a>
      </div>
    </template>
    
    client/views/posts/post_item.html

    Natürlich wollen wir nur einen Bearbeiten-Link anzeigen, wenn dem Benutzer der Beitrag auch gehört. Das erledigen wir mit dem Helper ownPost:

    Template.postItem.helpers({
      ownPost: function() {
        return this.userId == Meteor.userId();
      },
      domain: function() {
        var a = document.createElement('a');
        a.href = this.url;
        return a.hostname;
      }
    });
    
    client/views/posts/post_item.js
    Formular für das Ändern von Beiträgen.
    Formular für das Ändern von Beiträgen.

    Commit 8-1

    Added edit posts form.

    Unser Änderungsformular für Beiträge sieht gut aus. Aber das eigentliche Ändern der Beiträge ist noch gar nicht möglich. Warum ist das so?

    Einrichtung der Berechtigungen

    Da wir im letzten Kapitel das Package insecure entfernt haben, werden alle client-seitigen Änderungen derzeit abgewiesen.

    Um Änderungen wieder zu ermöglichen, werden wir Berechtigungsregeln anlegen. Zunächst erzeuge die neue Datei permissions.js im Verzeichnis lib. Diese wird unsere Berechtigungslogik als Erstes laden und ist sowohl auf dem Server als auch auf dem Client verfügbar.

    // check that the userId specified owns the documents
    ownsDocument = function(userId, doc) {
      return doc && doc.userId === userId;
    }
    
    lib/permissions.js

    Im Kapitel Beiträge anlegen, haben wir die Methode allow() entfernt, da wir neue Posts nur noch per Server-Methode angelegt haben (auf diese Weise umgehen wir allow() sowieso).

    Aber jetzt, wo wir Beiträge im Client ändern und löschen, schauen wir uns noch mal die Datei posts.js an. Wir fügen den folgenden Block allow() wieder hinzu:

    Posts = new Meteor.Collection('posts');
    
    Posts.allow({
      update: ownsDocument,
      remove: ownsDocument
    });
    
    Meteor.methods({
      ...
    
    collections/posts.js

    Commit 8-2

    Added basic permission to check the post’s owner.

    Einschränkung von Änderungen

    Nur weil du deine Beiträge ändern darfst soll das nicht heissen, dass dies für jede Eigenschaft des Beitrags gilt. Zum Beispiel wollen wir nicht, dass ein Benutzer einen Beiträg erstellen kann und ihn dann jemand anderem zuweist.

    Wir benutzen hierfür Meteors Callback deny() um sicherzustellen, dass Benutzer nur angegebene Felder ändern können:

    Posts = new Meteor.Collection('posts');
    
    Posts.allow({
      update: ownsDocument,
      remove: ownsDocument
    });
    
    Posts.deny({
      update: function(userId, post, fieldNames) {
        // may only edit the following two fields:
        return (_.without(fieldNames, 'url', 'title').length > 0);
      }
    });
    
    collections/posts.js

    Commit 8-3

    Only allow changing certain fields of posts.

    Als Parameter erhalten wir das Array fieldNames, dass eine Liste der Felder enthält, die geändert werden sollen. Durch die Verwendung von Underscores Methode without() reduzieren wir das Array um die Werte url und title.

    Im Normalfall sollte dieses Ergebnis-Array leer sein, denn nur url und title sollen geändert werden können. Wenn jemand etwas komisches probiert, wird die Länge des Arrays grösser gleich 1 sein. Dann liefert der Callback den Wert true zurück (und verhindert die Änderung).

    Methodenaufrufe vs. Clientseitige Datenmanipulation

    Um Beiträge zu erzeugen, benutzen wir die Server-Methode post. Aber zum Ändern und Löschen, rufen wir die Methoden update und remove direkt auf dem Client auf und regeln die Berechtigung mit allow und deny.

    Wann ist es sinnvoll das Eine und wann das Andere zu verwenden?

    Wenn der Sachverhalt unkompliziert ist und du die Regeln mit allow und deny adequat festlegen kannst, ist es normalerweise einfacher und weniger aufwendig, diese Dinge direkt auf dem Client zu regeln.

    Das Manipulieren der Daten auf dem Client trägt zum Gefühl von Direktheit und einem besseren Benutzererlebnis bei. Fehlerzustände müssen aber gesondert berücksichtigt und elegant eingeflochten werden. Zum Beispiel: der Server meldet sich asynchron zurück und teilt mit, dass die Änderung doch nicht stattgefunden hat.

    Aber, die Erstellung und Änderung von Daten, die der Benutzer nicht direkt manipulieren darf (zum Beispiel die Vergabe von Zeitstempeln oder die Zuordnung eines Benutzers), sollten besser in einer Server-Methode erfolgen.

    Server-Methoden sind auch in folgenden Szenarien eher angebracht:

    • Wenn du Daten und Ergebnisse direkt per Callback benötigst und nicht darauf warten willst, dass diese über den Reaktivitätsmechanismus synchronisiert werden.
    • Für Datenbankoperationen, bei denen viele Elemente geändert werden und damit eine große Menge an Daten übertragen werden muss.
    • Um Daten zusammenzufassen oder zu aggregieren (z.B. Anzahl der Elemente, Mittelwerte oder Summen)