Редактирование постов

8

На данный момент переведено

В этой главе вы:

  • Добавите форму редактирования постов.
  • Создадите права редактирования.
  • Ограничите поля для редактирования.
  • Теперь, когда мы можем создавать посты, следующим шагом будет их редактирование и удаление. Хотя UI код для этого довольно прост, сейчас самое время будет поговорить о том, как Meteor работает с правами доступа.

    Давайте взглянем на роутер. Мы добавим путь для страницы редактирования поста и зададим контекст:

    Router.configure({
      layoutTemplate: 'layout',
      loadingTemplate: 'loading',
      waitOn: function() { return Meteor.subscribe('posts'); }
    });
    
    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(pause) {
      if (! Meteor.user()) {
        if (Meteor.loggingIn())
          this.render('loading')
        else
          this.render('accessDenied');
    
        pause();
      }
    }
    
    Router.onBeforeAction(requireLogin, {only: 'postSubmit'});
    
    lib/router.js

    Шаблон страницы редактирования поста

    Теперь можем сосредоточится на шаблоне. Наш postEdit шаблон приобретёт стандартный вид:

    <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

    А вот и 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

    К этому моменту большая часть этого кода должна быть вам уже знакома.

    У нас есть две функции-обработчика событий - одна для события submit на форме, и одна для клика на линке delete.

    Обработчик события delete предельно прост - перехватить событие клика, спросить подтверждение на удаление поста. Если пользователь согласен продолжить, получить ID текущего поста из контекста this нашего шаблона, и, наконец, перенаправить пользователя на главную страницу.

    Обработчик события редактирования поста слегка длиннее, но не намного сложнее. Мы получаем ID текущего поста из контекста шаблона через this._id. Затем мы считываем обновленный пост из полей формы, создаем новый объект postProperties и сохраняем обновленный пост в нем.

    Затем мы передаем этот объект методу Meteor'a Collection.update(). Вторым параметром вызова этого метода идет функция-коллбек, которую метод вызовет под конец обработки данных. В случае ошибки мы покажем диалог с причиной, а в случае успеха пользователь будет перенаправлен на страницу с обновленным постом.

    Добавляем ссылки

    Чтобы пользователи могли легко отредактировать пост, добавим соответствующие линки:

    <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

    Имеет смысл показывать линк редактирования поста только в том случае, если этот пост принадлежит вам. Для этого воспользуемся хелпером 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
    Форма редактирования поста
    Форма редактирования поста

    Коммит 8-1

    Добавлена форма редактирования поста

    Форма для редактирования поста готова и выглядит отлично. Но вы все еще не сможете отредактировать пост. Что же происходит?

    Создаем права доступа

    Так как мы недавно удалили пакет insecure, все модификации со стороны клиента отвергаются сервером.

    Чтобы это починить, создадим правила доступа. Для начала создайте новый permissions.js файл в папке lib. Это позволит Meteor'у загрузить логику прав доступа с самого начала (и сделать ее доступной на клиенте и сервере):

    // проверяем что userId на самом деле автор данного документа
    ownsDocument = function(userId, doc) {
      return doc && doc.userId === userId;
    }
    
    lib/permissions.js

    В главе Создание постов мы избавились от методов allow(), так как мы добавляли новые посты через Метод на сервере (который, в свою очередь, игнорирует allow()).

    Но теперь, когда мы редактируем и удаляем посты на клиенте, давайте вернемся к файлу posts.js и добавим блок allow():

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

    Коммит 8-2

    Добавлены простые права доступа с проверкой автора поста

    Ограничиваем редактирование

    Только потому, что вам разрешено редактировать собственные посты еще не означает что вам можно редактировать все поля. Например, нам бы не хотелось дать пользователям возможность создать пост и затем сменить ID автора на кого-то другого.

    Мы воспользуемся функцией deny() Meteor'a чтобы ограничить пользователей в редактировании полей:

    Posts = new Meteor.Collection('posts');
    
    Posts.allow({
      update: ownsDocument,
      remove: ownsDocument
    });
    
    Posts.deny({
      update: function(userId, post, fieldNames) {
        // разрешаем редактировать только следующие два поля:
        return (_.without(fieldNames, 'url', 'title').length > 0);
      }
    });
    
    collections/posts.js

    Коммит 8-3

    Разрешаем редактировать только определенные поля постов.

    Мы берем массив fieldNames со списком редактируемых полей и используем метод библиотеки Underscore without() чтобы пропустить через него наш массив и вернуть новый, уже без полей url или title.

    Если все в порядке, новый массив будет пуст и его длина должна быть 0. Если же кто-то пытается отредактировать недопустимые поля, длина этого массива будет 1 или больше, и функция вернет true (и редактирование поста будет отвергнуто).

    Вызовы Методов или редактирования данных на клиенте?

    Для создания постов мы используем Метод post. Для редактирования и удаления постов мы вызываем update и remove прямо на клиенте, ограничивая доступ через allow и deny.

    Когда же стоит использовать каждую из этих техник?

    Когда вещи довольно просты и вы можете адекватно выразить правила редактирования данных через allow и deny, обычно все проще делать через клиента.

    Прямой доступ к базе данных через клиента создает ощущение непосредственности, и может лучше порадовать пользователя отзывчивым интерфейсом, до тех пор пока вы помните правильно реагировать на ошибки сервера (то есть, когда сервер ответит что редактирование базы данных не удалось).

    Однако как только вам понадобится совершать действия выходящие за рамки контроля пользователя (например, добавление времени создания поста или его авторства), стоит использовать Метод.

    Вызов Метода также лучше подходит в следующих случаях:

    • Когда вам нужно узнать или вернуть данные через функцию-коллбек вместо того, чтобы ждать пока сработает реактивность и синхронизация данных.
    • Для массивных функций обработки базы данных
    • Когда требуется просуммировать или агрегировать данные (например, вызов методов count, average, sum для массивов данных).