Статьи

Использование Backbone в WordPress Admin: интерфейс

Добро пожаловать во вторую часть использования Backbone внутри администратора WordPress. В первой части мы настроили «бэкэнд» нашего плагина, а теперь во второй части мы закончим добавлением функциональности «на стороне клиента» или «клиентской части». Для обзора того, что мы создаем в этом руководстве, а также структуры папок и файлов, просмотрите первую часть.


В папке src создайте еще один файл с именем Templates и файл внутри этого файла с именем metabox.templ.php . Здесь мы будем помещать HTML-код, необходимый для нашего мета-блока. Это также отличная возможность вывести данные JSON, необходимые для наших ответов.

Ваши папки и файлы теперь должны выглядеть следующим образом.

files02

Давайте еще раз посмотрим на то, что мы создаем. Вы можете думать о каждом ответе как о модели данных, и поскольку мы будем использовать шаблоны на стороне клиента для создания представления для каждого из них, это представление может реагировать на изменения в модели. Это позволяет нам быть очень конкретными при привязывании событий к пользовательскому интерфейсу и, естественно, приводит к упрощению рабочего процесса — как только вы разберетесь в этом, то есть.

модель-представление

Внутри нашего недавно созданного metabox.templ.php это шаблон, который мы будем использовать для каждой из наших моделей. Вы можете видеть, что мы в основном оборачиваем некоторый HTML в тег скрипта. Мы даем тегу script атрибут type="text/template" чтобы браузер не отображал его на странице. Этот небольшой фрагмент HTML-кода будет использоваться позже для создания разметки, необходимой для каждого представления. Мы будем использовать встроенные в Underscore возможности шаблонов, поэтому значения обернуты так: будут заменены данными в наших моделях позже.

1
2
3
4
5
6
7
8
9
<!— src/templates/metabox.templ.php —>
 
<!— Template —>
<script type=»text/template» id=»inputTemplate»>
    <label for=»<%= answer_id %>»><%= index %>:</label>
    <input id=»<%= answer_id %>» class=»answers» size=»30″ type=»text» name=»<%= answer_id %>» value=»<%= answer %>» placeholder=»Answer for Question <%= index %> Here»>
    <button disabled=»true»>Save</button>
</script>
<!— End template —>

Все еще внутри src / templates / metabox.templ.php — здесь мы просто устанавливаем контейнеры, которые будут заполнены входными данными из шаблона выше. Это происходит после того, как Backbone проанализировал данные JSON, необходимые для модели, поэтому пока это все, что нам нужно сделать здесь.

1
2
3
4
5
6
7
8
9
<!— src/templates/metabox.templ.php —>
<p>Enter the Answers below</p>
<div id=»answerInputs»></div>
<div id=»answerSelect»>
    <span>Correct Answer:
</div>
<p>
    <input name=»save» type=»submit» class=»button button-primary button-small» value=»Save all»>
</p>

Последнее, что нужно в файле src / templates / metabox.templ.php , — это данные JSON, представляющие каждый ответ. Здесь мы создаем объект в глобальном пространстве имен, а затем присваиваем значения, которые мы отправили, с $viewData массива $viewData . Мне также нравится сохранять ссылки на контейнеры, которые мы будем использовать позже, чтобы у меня не было идентификаторов в двух отдельных файлах.

01
02
03
04
05
06
07
08
09
10
11
<!— src/templates/metabox.templ.php —>
<script>
    window.wpQuiz = {};
    var wpq = window.wpQuiz;
    wpq.answers = <?= $answers ?>;
    wpq.answers.correct = <?= $correct ?>;
    wpq.answerSelect = ‘#answerSelect’;
    wpq.answerInput = ‘#answerInputs’;
    wpq.inputTempl = ‘#inputTemplate’;
    wpq.post_id = <?= $post->ID ?>;
</script>

Хорошо, если вы зашли так далеко, вы успешно настроили свой плагин, чтобы разрешить использование Backbone.js, а ваш мета-блок выводит необходимую разметку и данные JSON. Теперь пришло время собрать все вместе и использовать Backbone.js для организации нашего клиентского кода. Пришло время покрыть:

  1. Создание коллекции моделей из данных JSON
  2. Использование клиентских шаблонов для создания представления для каждого
  3. Наблюдение за кликом, ключом и размытием событий в каждом представлении
  4. Сохранение модели обратно в базу данных

Ваша окончательная структура каталогов и файлов должны выглядеть следующим образом.

files03

Прежде всего, мы обернем все, что мы делаем, в функцию с немедленным вызовом и передадим jQuery для использования со знаком $ , я не буду показывать эту обертку в каких-либо других фрагментах, поэтому убедитесь, что вы поместили все ниже в нее.

1
2
3
4
5
6
7
/* js/admin.js */
 
(function($) {
 
    /** Our code here **/
 
}(jQuery));

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

1
2
3
4
/* js/admin.js */
 
    var Quiz = { Views:{} };
    var wpq = window.wpQuiz;

Модель представляет собой один ответ. В его конструкторе мы делаем несколько вещей.

  1. Установка значения по умолчанию для правильного как false
  2. Установка URL, который необходим Backbone для сохранения модели обратно в базу данных. Мы можем получить доступ к правильному URL-адресу благодаря WordPress, который доказывает ajaxurl переменной ajaxurl , доступной на каждой странице администратора. Мы также добавляем имя нашего метода, который обрабатывает запрос ajax
  3. Затем мы перезаписываем метод toJSON чтобы добавить идентификатор текущей записи к каждой модели. Это можно было бы сделать на стороне сервера, но я привел это здесь в качестве примера того, как вы можете переопределить то, что сохраняется на сервере (это может оказаться очень удобным, поэтому я включил его здесь)
  4. Наконец, в методе initialize мы проверяем, является ли текущая модель правильным ответом, сравнивая ее идентификатор с идентификатором правильного ответа. Мы делаем это так, чтобы позже мы знали, какой ответ должен быть выбран по умолчанию
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/* js/admin.js */
 
    Quiz.Model = Backbone.Model.extend({
        defaults : {
            ‘correct’ : false
        },
        url : ajaxurl+’?action=save_answer’,
        toJSON : function() {
            var attrs = _.clone( this.attributes );
            attrs.post_id = wpq.post_id;
            return attrs;
        },
        initialize : function() {
            if ( this.get( ‘answer_id’ ) === wpq.answers.correct ) {
                this.set( ‘correct’, true );
            }
        }
    });

Коллекция, по сути, является просто оберткой для множества моделей и делает работу с этими моделями проще простого. Для нашего небольшого примера мы не будем изменять коллекцию, кроме указания, какую модель она должна использовать.

1
2
3
4
5
/* js/admin.js */
 
    Quiz.Collection = Backbone.Collection.extend({
        model: Quiz.Model
    });

Наше первое представление может рассматриваться как оболочка для отдельных полей ввода. Нам не нужно объявлять шаблон или какой HTML-элемент мы хотим, чтобы Backbone создавал для нас в этом случае, потому что позже, когда мы создадим это представление, мы передадим ему идентификатор div который мы создали в мета-боксе. файл. Затем Backbone просто использует этот элемент в качестве своего контейнера. Это представление будет принимать коллекцию, и для каждой модели в этой коллекции он будет создавать новый элемент input и добавлять его к себе.

01
02
03
04
05
06
07
08
09
10
11
/* js/admin.js */
 
    Quiz.Views.Inputs = Backbone.View.extend({
        initialize:function () {
            this.collection.each( this.addInput, this );
        },
        addInput : function( model, index ) {
            var input = new Quiz.Views.Input({ model:model });
            this.$el.append( input.render().el );
        }
    });

Этот следующий вид представляет одну модель. В целях демонстрации типов вещей, которые вы можете делать при кодировании JavaScript таким образом, я попытался предоставить несколько различных методов взаимодействия и показать, как реагировать на них с помощью Backbone.

Обратите внимание, что мы указываем здесь « tagName » вместе с шаблоном. В нашем случае это будет захватить тот шаблон, который мы рассматривали ранее, проанализировать его, используя данные из модели, а затем обернуть все в тег p (что даст нам небольшой запас по каждому из них).

Также обратите внимание, как события связаны с элементами в представлении. Гораздо чище, чем ваш обычный обратный вызов jQuery, и что еще лучше, это возможность использовать селектор jQuery, подобный this.$('input') в наших представлениях, зная, что они автоматически ограничиваются областью представления. Это означает, что jQuery не смотрит на весь DOM при попытке сопоставить селектор.

С этой точки зрения мы сможем:

  1. Знать, когда поле ввода было изменено
  2. Обновите модель, связанную с ней автоматически (которая будет использоваться для автоматического обновления поля выбора под ним)
  3. Включить кнопку сохранения на стороне входа, который был изменен
  4. Выполните сохранение обратно в базу данных
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* js/admin.js */
 
    Quiz.Views.Input = Backbone.View.extend({
        tagName: ‘p’,
        // Get the template from the DOM
        template :_.template( $(wpq.inputTempl).html() ),
 
        // When a model is saved, return the button to the disabled state
        initialize:function () {
            var _this = this;
            this.model.on( ‘sync’, function() {
                _this.$(‘button’).text( ‘Save’ ).attr( ‘disabled’, true );
            });
        },
 
        // Attach events
        events : {
            ‘keyup input’ : ‘blur’,
            ‘blur input’ : ‘blur’,
            ‘click button’ : ‘save’
        },
 
        // Perform the Save
        save : function( e ) {
            e.preventDefault();
            $(e.target).text( ‘wait’ );
            this.model.save();
        },
 
        // Update the model attributes with data from the input field
        blur : function() {
            var input = this.$(‘input’).val();
            if ( input !== this.model.get( ‘answer’ ) ) {
                this.model.set(‘answer’, input);
                this.$(‘button’).attr( ‘disabled’, false );
            }
        },
 
        // Render the single input — include an index.
        render:function () {
            this.model.set( ‘index’, this.model.collection.indexOf( this.model ) + 1 );
            this.$el.html( this.template( this.model.toJSON() ) );
            return this;
        }
    });

Этот элемент выбора — то, где пользователь может выбрать правильный ответ. Когда это представление создается, оно получит ту же коллекцию моделей, что и оболочка ввода. Это пригодится позже, потому что мы сможем прослушивать изменения модели в полях ввода и автоматически обновлять соответствующие значения в этом элементе select.

01
02
03
04
05
06
07
08
09
10
11
/* js/admin.js */
 
    Quiz.Views.Select = Backbone.View.extend({
        initialize:function () {
            this.collection.each( this.addOption, this );
        },
        addOption:function ( model ) {
            var option = new Quiz.Views.Option({ model:model });
            this.$el.append( option.render().el );
        }
    });

Наш окончательный вид создаст элемент option для каждой модели и будет добавлен к элементу select выше. На этот раз я показал, как вы можете динамически устанавливать атрибуты элемента, возвращая хэш из функции обратного вызова, назначенной свойству атрибутов. Также обратите внимание, что в методе initialize() мы «подписались» на изменение событий в модели (в частности, атрибута answer ). Это в основном просто означает: каждый раз, когда атрибут ответа этой модели изменяется, вызывается метод render() (который в этом случае просто обновит текст). Эта концепция «подписки» или «прослушивания» событий, происходящих внутри модели, действительно делает Backbone.js и многие другие библиотеки такими мощными, полезными и приятными для работы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
/* js/admin.js */
 
    Quiz.Views.Option = Backbone.View.extend({
        tagName:’option’,
 
        // returning a hash allows us to set attributes dynamically
        attributes:function () {
            return {
                ‘value’:this.model.get( ‘answer_id’ ),
                ‘selected’:this.model.get( ‘correct’ )
            }
        },
 
        // Watch for changes to each model (that happen in the input fields and re-render when there is a change
        initialize:function () {
            this.model.on( ‘change:answer’, this.render, this );
        },
        render:function () {
            this.$el.text( this.model.get( ‘answer’ ) );
            return this;
        }
    });

Теперь мы так близки, все, что нам нужно сделать, — это создать новую коллекцию и передать ей необходимый JSON, а затем создать оба представления «оболочки» для элемента select и для входных данных. Обратите внимание, что мы также передаем свойство el нашим представлениям. Это ссылки на элемент div и select, который мы оставили пустым ранее в мета-поле.

1
2
3
4
5
/* js/admin.js */
 
    var answers = new Quiz.Collection( wpq.answers );
    var selectElem = new Quiz.Views.Select({ collection:answers, el :wpq.answerSelect });
    var inputs = new Quiz.Views.Inputs({ collection:answers, el:wpq.answerInput });

Если вы сделали это до конца, у вас должен быть полностью рабочий пример того, как включить Backbone JS в плагин WordPress. Если вы пойдете дальше и посмотрите на исходные файлы, вы заметите, что фактический объем кода, необходимый для включения Backbone, относительно невелик. Большая часть кода, который мы здесь рассмотрели, была PHP, необходимой для плагина. Ежедневная работа с Backbone в течение последних 6 недель действительно дала мне новое уважение к организации внешнего кода, и я надеюсь, что вы сможете оценить преимущества, которые наверняка получат от такой работы.

В сообществе WordPress я могу представить некоторые из более сложных и высококачественных плагинов, которые действительно выигрывают от использования Backbone, и для меня большая честь поделиться с вами техникой, позволяющей сделать именно это.