Komentarze

10

procent przetłumaczone

W tym rozdziale dowiesz się:

  • Wyświetlaniu istniejących komentarzy.
  • Dodaniu formularza do wprowadzania nowych postów.
  • Wczytywaniu komentarzy wyłącznie do bieżącego posta.
  • Dodaniu licznika komentarzy do postów.
  • Głównym celem serwisu wiadomości społecznościowych jest utworzenie społeczności użytkowników i będzie ciężko to osiągnąć bez dostarczenia sposobu komunikacji między ludźmi. Zatem dodajmy komentarze w tym rozdziale!

    Zaczniemy od utworzenia nowej kolekcji do przechowywania komentarzy i dodanie podstawowych danych początkowych (fixture) do tej kolekcji.

    Comments = new Meteor.Collection('comments');
    
    collections/comments.js
    // Dane początkowe
    if (Posts.find().count() === 0) {
      var now = new Date().getTime();
    
      // utwórz dwóch użytkowników
      var tomId = Meteor.users.insert({
        profile: { name: 'Tom Coleman' }
      });
      var tom = Meteor.users.findOne(tomId);
      var sachaId = Meteor.users.insert({
        profile: { name: 'Sacha Greif' }
      });
      var sacha = Meteor.users.findOne(sachaId);
    
      var telescopeId = Posts.insert({
        title: 'Introducing Telescope',
        userId: sacha._id,
        author: sacha.profile.name,
        url: 'http://sachagreif.com/introducing-telescope/',
        submitted: now - 7 * 3600 * 1000
      });
    
      Comments.insert({
        postId: telescopeId,
        userId: tom._id,
        author: tom.profile.name,
        submitted: now - 5 * 3600 * 1000,
        body: 'Interesting project Sacha, can I get involved?'
      });
    
      Comments.insert({
        postId: telescopeId,
        userId: sacha._id,
        author: sacha.profile.name,
        submitted: now - 3 * 3600 * 1000,
        body: 'You sure can Tom!'
      });
    
      Posts.insert({
        title: 'Meteor',
        userId: tom._id,
        author: tom.profile.name,
        url: 'http://meteor.com',
        submitted: now - 10 * 3600 * 1000
      });
    
      Posts.insert({
        title: 'The Meteor Book',
        userId: tom._id,
        author: tom.profile.name,
        url: 'http://themeteorbook.com',
        submitted: now - 12 * 3600 * 1000
      });
    }
    
    server/fixtures.js

    Nie zapomnijmy opublikować i subskrybować nowo utworzoną kolekcję:

    Meteor.publish('posts', function() {
      return Posts.find();
    });
    
    Meteor.publish('comments', function() {
      return Comments.find();
    });
    
    server/publications.js
    Router.configure({
      layoutTemplate: 'layout',
      loadingTemplate: 'loading',
      waitOn: function() { 
        return [Meteor.subscribe('posts'), Meteor.subscribe('comments')];
      }
    });
    
    lib/router.js

    Zatwierdź 10-1

    Dodana kolekcja komentarzy, publikacja/subskrypcja i dane…

    Zauważ, że aby uruchomić powyższy kod ustawiający dane początkowe, będziesz musiał wykonać meteor reset aby wyczyścić bazę danych. Po resecie nie zapomnij utworzyć nowego konta i się do niego zalogować!

    Na początek stworzyliśmy kilku (całkowicie wymyślonych) użytowników, wstawiliśmy ich dane do bazy danych i użyliśmy ich id aby później pobrać ich z bazy. Następnie dodaliśmy komentarz każdemu użytkownikowi do pierwszego posta, łącząc komentarz do posta (z postId) i użytkownika (z userId). Dodaliśmy także datę dodania posta i jego treść, jak również zdenormalizowane pole author wskazujące na autora komentarza.

    Również zmieniliśmy router, aby obsługiwał zarówno posty jak i komentarze.

    Wyświetlanie komentarzy

    Dobrze, że teraz wszystkie komentarze mamy w bazie danych, ale również potrzebujemy pokazać je na stronie zawierającą dyskusję. Mam nadzieję, że ten proces jest już tobie znajomy i masz pojęcie o kolejnych krokach:

    <template name="postPage">
      {{> postItem}}
    
      <ul class="comments">
        {{#each comments}}
          {{> comment}}
        {{/each}}
      </ul>
    </template>
    
    client/views/posts/post_page.html
    Template.postPage.helpers({
      comments: function() {
        return Comments.find({postId: this._id});
      }
    });
    
    client/views/posts/post_page.js

    Wstawiamy blok {{#each comments}} w szablonie posta, zatem this jest postem w helperze comments. Aby znaleźć odpowiednie komentarze, sprawdzamy te, które są połączone z danym postem przez atrybut postId.

    Biorąc po uwagę, że poznaliśmy już Handlebars i funkcje pomocnicze szablonu (helper), renderowanie komentarza nie powinno nastręczać większych kłopotów. Utworzymy nowy katalog comments w views aby zapisać całą informację o komentarzu:

    <template name="comment">
      <li>
        <h4>
          <span class="author">{{author}}</span>
          <span class="date">on {{submittedText}}</span>
        </h4>
        <p>{{body}}</p>
      </li>
    </template>
    
    client/views/comments/comment.html

    Zaimplementujmy szybko helper szablonu, aby wyświetlić naszą datę publikacji submitted w czytelnym formacie (chyba że jesteś jednym z ludzi, którzy płynnie odczytują timestamp UNIX i kody hexadecymalne?)

    Template.comment.helpers({
      submittedText: function() {
        return new Date(this.submitted).toString();
      }
    });
    
    client/views/comments/comment.js

    Następnie, wyświetlimy liczbę komentarzy do każdego 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}},
            <a href="{{pathFor 'postPage'}}">{{commentsCount}} comments</a>
            {{#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

    I dodamy helper commentsCount do naszego managera postItem:

    Template.postItem.helpers({
      ownPost: function() {
        return this.userId == Meteor.userId();
      },
      domain: function() {
        var a = document.createElement('a');
        a.href = this.url;
        return a.hostname;
      },
      commentsCount: function() {
        return Comments.find({postId: this._id}).count();
      }
    });
    
    client/views/posts/post_item.js

    Zatwierdź 10-2

    Wyświetlanie komentarzy na `postPage`.

    Powinniśmy teraz być w stanie wyświetlić komentarze z danych początkowych i zobaczyć coś podobnego do:

    Wyświetlanie komentarzy
    Wyświetlanie komentarzy

    Wysyłanie komentarzy

    Dodajmy teraz możliwość dodawania komentarzy dla użytkowników. Proces, przez który przejdziemy będzie bardzo podobny do tego, w jakim pozwoliliśmy użytkownikom tworzyć nowe posty.

    Zaczniemy od dodania przycisku wstawiania pod każdym postem:

    <template name="postPage">
      {{> postItem}}
    
      <ul class="comments">
        {{#each comments}}
          {{> comment}}
        {{/each}}
      </ul>
    
      {{#if currentUser}}
        {{> commentSubmit}}
      {{else}}
        <p>Komentować mogą wyłącznie zalogowani użytkownicy. Proszę się zalogować.</p>
      {{/if}}
    </template>
    
    client/views/posts/post_page.html

    I następnie utworzenia formularza szablonu komentarza:

    <template name="commentSubmit">
      <form name="comment" class="comment-form">
        <div class="control-group">
            <div class="controls">
                <label for="body">Skomentuj</label>
                <textarea name="body"></textarea>
            </div>
        </div>
        <div class="control-group">
            <div class="controls">
                <button type="submit" class="btn">Dodaj komentarz</button>
            </div>
        </div>
      </form>
    </template>
    
    client/views/comments/comment_submit.html
    Formularz do dodawania komentarza
    Formularz do dodawania komentarza

    Aby opublikować komentarz, wołamy metodę comment w managerze commentSubmit, który działa podobnie do managera postSubmit:

    Template.commentSubmit.events({
      'submit form': function(e, template) {
        e.preventDefault();
    
        var $body = $(e.target).find('[name=body]');
        var comment = {
          body: $body.val(),
          postId: template.data._id
        };
    
        Meteor.call('comment', comment, function(error, commentId) {
          if (error){
            throwError(error.reason);
          } else {
            $body.val('');
          }
        });
      }
    });
    
    client/views/comments/comment_submit.js

    Zupełnie tak samo, jak zaimplementowaliśmy metodę post po stronie serwera, zaimplementujemy metodę comment, która utworzy komentarz, sprawdzi, że wszystko przebiegło prawidłowo i wstawi nowy komentarz do kolekcji comments.

    Comments = new Meteor.Collection('comments');
    
    Meteor.methods({
      comment: function(commentAttributes) {
        var user = Meteor.user();
        var post = Posts.findOne(commentAttributes.postId);
        // ensure the user is logged in
        if (!user)
          throw new Meteor.Error(401, "Musisz się zalogować aby dodawać komentarze");
    
        if (!commentAttributes.body)
          throw new Meteor.Error(422, 'Proszę wpisać treść komentarza. ');
    
        if (!post)
          throw new Meteor.Error(422, 'Musisz komentować określony post');
    
        comment = _.extend(_.pick(commentAttributes, 'postId', 'body'), {
          userId: user._id,
          author: user.username,
          submitted: new Date().getTime()
        });
    
        return Comments.insert(comment);
      }
    });
    
    collections/comments.js

    Zatwierdź 10-3

    Utworzony formularz do dodawania koentarzy.

    Nie dzieje się tutaj nic szczególnego, jest tu proste sprawdzenie, że użytkownik jest zalogowany, komentarz ma treść i że jest połączony z postem.

    Kontrola subskrypcji komentarzy

    Obecna sytuacja jest taka, że publikujemy wszystkie komentarze wszystkich postów do wszystkich podłączonych klientów. Wydaję się to trochę nieekonomiczne. Używamy w końcu tylko małego podzbioru danych w określonym momencie. Polepszmy zatem publikacje i subskrypcje aby dokładnie kontrolować, które komentarze są publikowane.

    Jeżeli pomyślimy o tym więcej, jedynym momentem, w którym potrzebujemy subskrybować publikację comments, jest chwila w której użytkownik wchodzi na stronę konkretnego posta i potrzebujemy wczytać jedynie podzbiór komentarzy do niego.

    Pierwszym krokiem będzie zmiana sposobu subskrypcji komentarzy. Aż do teraz, subskrypcje były tworzone na poziomie routera, co oznaczało, że wczytywaliśmy wszystkie dane raz podczas inicjalizacji routera.

    Chcemy teraz, żeby nasza subskrypcja zależała od parametru ścieżki i że parametr może się oczywiście zmienić w dowolnym momencie. Z tego względu musimy przenieść kod subskrypcji z poziomu routera do poziomu scieżki.

    Ma to jeszcze inne konsekwencje: zamiast wczytywać dane gdy inicjalizujemy aplikację, od teraz będziemy je wczytywali za każdym razem po wejściu na konkretną ścieżkę (inaczej URL). Oznacza to, że będziesz teraz doświadczał doczytywania danych przy przeglądaniu aplikacji. Jest to niemożliwe do uniknięcia, chyba że zamierzasz początkowo wczytać wszystkie dostępne dane.

    Oto jak wygląda nowa funkcja waitOn na poziomie ścieżki:

    Router.map(function() {
    
      //...
    
      this.route('postPage', {
        path: '/posts/:_id',
        waitOn: function() {
          return Meteor.subscribe('comments', this.params._id);
        },
        data: function() { return Posts.findOne(this.params._id); }
      });
    
      //...
    
    });
    
    lib/router.js

    Zauważysz, że przekazujemy this.params._id jako argument do subskrypcji. Użyjmy teraz tej nowej informacji do upewnienia się, że ograniczyliśmy zbiór danych do komentarzy należących do bieżącego posta:

    Meteor.publish('posts', function() {
      return Posts.find();
    });
    
    Meteor.publish('comments', function(postId) {
      return Comments.find({postId: postId});
    });
    
    server/publications.js

    Zatwierdź 10-4

    Utworzona prosta publikacja/subskrypcja komentarzy.

    Jest tylko jeden problem: gdy powrócimy do strony domowej, twierdzi że wszystkie posty mają 0 komentarzy:

    Wszystkie komentarze zniknęły!
    Wszystkie komentarze zniknęły!

    Liczenie komentarzy

    Przyczyna takiego stanu rzeczy wyda się wkrótce jasna: w jednym momencie możemy mieć tylko wczytane komentarze jednego posta, zatem gdy wołamy Comments.find({postId: this._id}) w helperze commentsCount w managerze postItem, Meteor nie odnajduje potrzebnych danych do obliczenia liczby komentarzy po stronie klienta.

    Najlepszym sposobem na poradzenie sobie z tym, jest zdenormalizowanie liczby komentarzy do posta (nie przejmuj się, jeżeli nie wiesz o co chodzi, następny rozdział tłumaczy to szczegółowo). Zobaczysz, że powoduje to w małym stopniu zwiększenie złożoności kodu, ale wzrost wydajności jaki uzyskujemy przez uniknięcie publikowania wszystkich komentarzy jest tego warty.

    Osiągniemy to przez dodanie zmiennej commentsCount do struktury danych post. Aby rozpocząć, uaktualnimy nasze dane początkowe (ang. fixtures) (i użyjemy meteor reset aby je przeładować – nie zapomnij ponownie utworzyć konta użytkownika po dokonaniu tej operacji):

    var telescopeId = Posts.insert({
      title: 'Introducing Telescope',
      ..
      commentsCount: 2
    });
    
    Posts.insert({
      title: 'Meteor',
      ...
      commentsCount: 0
    });
    
    Posts.insert({
      title: 'The Meteor Book',
      ...
      commentsCount: 0
    });
    
    server/fixtures.js

    Następnie upewnimy się, że wszystkie nowe posty mają 0 komentarzy:

    // pick out the whitelisted keys
    var post = _.extend(_.pick(postAttributes, 'url', 'title', 'message'), {
      userId: user._id, 
      author: user.username, 
      submitted: new Date().getTime(),
      commentsCount: 0
    });
    
    var postId = Posts.insert(post);
    
    collections/posts.js

    I następnie uaktualniamy odpowiedni licznik commentsCount gdy tworzymy nowy komentarz za pomocą operatora $inc Mongo (który zwiększa pole numeryczne o 1):

    // uaktualnienie posta o liczbę komentarzy
    Posts.update(comment.postId, {$inc: {commentsCount: 1}});
    
    return Comments.insert(comment);
    
    collections/comments.js

    Na koniec, możemy usunąć helper commentsCount z client/views/posts/post_item.js, skoro to pole jest dostępne teraz bezpośrednio w poście.

    Zatwierdź 10-5

    Zdenormalizowana liczba komentarzy do posta.

    Teraz skoro użytkownicy mogą ze sobą rozmawiać, byłoby szkoda, gdyby przegapili nowe komentarze na ich posty. I proszę, następny rozdział właśnie pokaże, jak zaimplementować powiadomienia, aby tego uniknąć!