Edytowanie postów

8

procent przetłumaczone

W tym rozdziale dowiesz się:

  • jak dodać formularz umożliwiający edytowanie postów.
  • jak ustawić prawa do edycji postóœ.
  • jak ograniczyć, które części posta mogą być edytowane.
  • Teraz skoro możemy tworzyć posty, następnym krokiem będzie umożliwienie edycji i usuwanie postóœ. Kod UI który to umożliwi jest releatywnie prosty i jest to dobry moment na podjęcie tematu zarządzania praw użytkowników.

    Zacznijmy od podłączenia routera. Dodamy ścieżkę, która umożliwi dostęp do strony modyfikacji posta i ustawi jej kontekst danych:

    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

    Szablon edytowania posta

    Możemy teraz skoncetrować się na szablonie. Nasz szablon postEdit będzie miał całkiem standardową formę:

    <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

    A poniżej znajdziesz manager post_edit.js który z nim współgra:

    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

    Jak do tej pory większość kodu powinna się Tobie wydać znajoma. Na początku mamy helper szablonu, który pobiera bieżący post z bazy danych i przekazuje go do szablonu.

    Następnie mamy dwo callbacki zdarzeń szablonu: jeden dla zdarzenia submit formularza, drugi dla usunięcia zdarzenia ‘click’ dla linka.

    Callback usuwający zdarzenie click jest bardzo prosty: omija domyślne zdarzenie click i pyta o potwierdzenie. Jeżeli je dostanie, korzysta z post ID obecnego posta otrzymanego z kontekstu danych szablonu, usuwa go i ostatecznie przekierowuje użytkownika do strony domowej.

    Callback uaktualniający dane 'update’ jest nieco obszerniejszy, ale niewiele bardziej skomplikowany. Po ominięciu domyślnego zdarzenia i otrzymaniu bieżącego posta, otrzymujemy nowe dane formularza ze strony i zapisujemy je w obiekcie postProperties.

    Następnie przekazujemy ten obiekt to metody Meteora Collection.update() i używamy callbacka który wyświetla błąd gdy uaktualnienie się nie powiedzie lub wysyła użytkownika z powrotem na stronę posta, gdy uaktualnienie przebiegnie pomyślnie.

    Dodawanie linków

    Powinniśmy również edytować linki do naszych postów, aby użytkownicy mieli dostęp do strony edytowania posta:

    <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

    Oczywiście nie chcemy pokazywać linku do edytowania posta osobom postronnym. Pomaga w tym 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
    Formularz edytowania posta.
    Formularz edytowania posta.

    Zatwierdź 8-1

    Dodany formularz edytowania posta.

    Nasz formularz edytowania posta wygląda dobrze, ale właściwie nie możemy niczego teraz edytować. Co się dzieje?

    Ustawianie praw dostępu

    Ponieważ poprzednio usunęliśmy pakiet insecure, wszystkie zmiany po stronie klienta są obecnie odrzucane.

    Aby to naprawić, ustawimy zasady praw dostępu. Najpierw utwórz nowy plik permissions.js w folderze lib. Załaduje to logikę ustawiania praw dostępu jako pierwszą (i będzie dostępna w obu środowiskach, po stronie klienta i serwera).

    // sprawdz czy userId jest wlascicielem dokumentu
    ownsDocument = function(userId, doc) {
      return doc && doc.userId === userId;
    }
    
    lib/permissions.js

    W rozdziale Tworzenie postów pozbyliśmy się metod allow(), ponieważ wstawialiśmy nowe posty wyłącznie za pomocą metody serwerowej (która i tak omija allow()).

    Teraz jednak edytujemy i usuwamy posty po stronie klienta, wróćmy zatem do pliku posts.js i dodajmy blok allow():

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

    Zatwierdź 8-2

    Dodanie podstawowych praw dostępu do sprawdzenia właścici…

    Ograniczanie edycji

    Fakt, że możesz edytować własne posty nie oznacza, że powinieneś edytować każde pole. Na przykład, nie chcemy aby użytkownicy mogli tworzyć posta i następnie przypisać go innej osobie.

    Używamy callbacka Meteora deny() aby upewnić się, że użytkownicy mogę edytować wyłącznie niektóre pola:

    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

    Zatwierdź 8-3

    Pozwól na zmianę tylko niektórych pól posta.

    Używamy tablicy fieldNames, która zawiera listę zmienianych pól i używamy metody without() Underscore, aby zwrócić tablicę, która zawiera częściową tablicę zawierającą pola, te które nie są polami url czy title.

    Jeżeli wszystko przebiegnie prawidłowo, ta tablica powinna być pusta i jej długość powinna być równa 0. Jeżeli ktoś stara się dokonać czegoś nieoczekiwanego, długość tablicy będzie równa 1 lub więcej i callback zwrócie true (odrzucając w ten sposób uaktualnienie).

    Zawołania metod vs manipulacja danych po stronie klienta

    Używamy metody post aby utworzyć nowe posty, natomiast do edycji i usuwania ich wołamy update i remove bezpośrednio po stronie klienta i ograniczamy dostęp przez allow i deny.

    Kiedy odpowiednie jest używanie każdego z nich?

    Gdy sytuacja jest relatywnie jasna i można konkretnie wyrazić zasady za pomocą allow i deny, zwykle prościej jest wykonywać je po stronie klienta.

    Bezpośrednia manipulacja bazy danych ze strony klienta tworzy wyobrażenie natychmiastowej zmiany i służy lepszemu postrzeganiu aplikacji przez użytkownika, tak długo, jak pamiętasz o prawidłowym obchodzeniu się z przypadkami błędów (tj. wtedy gdy serwer zwraca informację, że zmiana się nie powiodła).

    Jednakże, gdy masz potrzebę wykonania czynności, które powinny być poza kontrolą użytkownika (tak jak ustawianie znacznika czasu posta czy przypisywanie go do konkretnego użytkownika), lepiej jest użyć Metodę po stronie serwera.

    Metody serwerowe są także odpowiednie w kliku innych przypadkach:

    • Gdy musisz znać lub zwracać wartości za pomocą callbacka raczej niż czekać na synchronizację i propagację danych przez serwer.
    • Dla funkcji obciążających bazę danych, dla których jest zbyt kosztowne przesyłanie całej kolekcji do klienta.
    • Do sumowania lub zbierania danych (np. zliczanie, obliczanie średniej lub sumy).