Статьи

Создайте менеджер контактов с помощью Backbone.js: часть 4

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


Начнем с добавления еще одной простой кнопки в шаблон, которая позволит редактировать его данные:

1
<button class=»edit»>Edit</button>

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

01
02
03
04
05
06
07
08
09
10
11
12
<script id=»contactEditTemplate» type=»text/template»>
<form action=»#»>
    <input type=»file» value=»<%= photo %>» />
    <input class=»name» value=»<%= name %>» />
    <input id=»type» type=»hidden» value=»<%= type %>» />
    <input class=»address» value=»<%= address %>» />
    <input class=»tel» value=»<%= tel %>» />
    <input class=»email» value=»<%= email %>» />
    <button class=»save»>Save</button>
    <button class=»cancel»>Cancel</button>
</form>
</script>

Новый шаблон состоит в основном из элементов <input> которые предоставляют редактируемые данные. Нам не нужно беспокоиться о метках для элементов, а вместо этого использовать данные из модели в качестве значений по умолчанию для каждого входа. Обратите внимание, что мы используем скрытое поле формы для хранения атрибута типа модели, мы будем использовать его для установки значения <select> которое нам нужно добавить, используя наш скрипт вместо того, чтобы шаблон отображал его.

Затем мы можем связать некоторые обработчики событий для новых кнопок, которые мы добавили; обновите объект events в классе ContactView чтобы он содержал следующие новые привязки:

1
2
3
4
«click button.edit»: «editContact»,
«change select.type»: «addType»,
«click button.save»: «saveEdits»,
«click button.cancel»: «cancelEdit»

Не забудьте добавить запятую в конце существующей привязки! Они очень похожи на привязки, которые мы использовали раньше; каждая пара ключ: значение просто указывает событие для прослушивания и селектор для соответствия элементу, который запускает событие в качестве ключа, и обработчик события для выполнения при обнаружении события в качестве значения.


Таким же образом, как мы сохранили ссылку на функцию шаблона в свойстве template нашего класса ContactView, мы также должны сохранить ссылку на функцию шаблона, которую мы будем использовать для переключения контакта в режим редактирования. Добавьте editTemplate сразу после свойства шаблона:

1
editTemplate: _.template($(«#contactEditTemplate»).html()),

Теперь мы можем добавить сами обработчики событий, которые также должны перейти в класс ContactView после существующего deleteContact() . Сначала мы добавим метод editContact() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
editContact: function () {
    this.$el.html(this.editTemplate(this.model.toJSON()));
 
    var newOpt = $(«<option/>», {
        html: «<em>Add new…</em>»,
        value: «addType»
    }),
 
    this.select = directory.createSelect().addClass(«type»)
        .val(this.$el.find(«#type»).val()).append(newOpt)
        .insertAfter(this.$el.find(«.name»));
 
    this.$el.find(«input[type=’hidden’]»).remove();
},

Мы начинаем с рендеринга нашего нового editTemplate который мы добавили на страницу, используя метод template() Underscore так же, как мы добавляли каждый контакт, используя стандартный шаблон отображения.

Чтобы упростить редактирование типа контакта, мы можем отобразить поле выбора, которое позволяет пользователю легко переключаться между существующими типами, но мы также хотим учитывать возможность того, что пользователь может захотеть добавить новый тип. Чтобы учесть это, мы создадим специальную опцию для поля выбора с текстом Add new... и значением addType .

Затем мы создаем новый элемент <select> используя метод createSelect() нашего основного представления, который, если вы помните из последней части этого урока, вернет элемент <select> содержащий <option> для каждого уникального типа в коллекции , Мы даем ему имя класса, и чтобы элемент <select> отображал существующий тип редактируемого контакта, мы устанавливаем его значение равным значению скрытого <input> мы добавили в наш шаблон. Затем мы вставляем новый <select> после <input> для имени контакта. Новый элемент select добавляется как свойство экземпляра представления, чтобы мы могли легко взаимодействовать с ним.

После того как мы добавили элемент <select> для типа контакта, мы можем затем удалить скрытое поле, чтобы оно не мешало сохранению редактирования, что мы рассмотрим в ближайшее время.

Теперь мы должны нажать кнопку « edit в любом из наших контактов и преобразовать содержимое этого контакта в форму:


Одна из привязок событий, которые мы добавили, была для события change поля выбора типа, поэтому мы можем добавить обработчик, который заменяет поле <select> стандартным элементом <input> :

1
2
3
4
5
6
7
if (this.select.val() === «addType») {
    this.select.remove();
 
    $(«<input />», {
        «class»: «type»
    }).insertAfter(this.$el.find(«.name»)).focus();
}

Когда значение элемента <select> изменяется, мы сначала проверяем, является ли его значение addType и если это так, мы удаляем элемент со страницы и создаем новый элемент <input> для его замены. Затем мы вставляем новый элемент, используя метод insertAfter() jQuery, и фокусируем его на готовности к вводу текста.


Затем мы можем добавить обработчик, который будет принимать изменения, внесенные в форму редактирования, и обновлять данные в модели. Добавьте метод saveEdits() сразу после метода editContact() который мы только что добавили:

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
saveEdits: function (e) {
    e.preventDefault();
 
    var formData = {},
        prev = this.model.previousAttributes();
 
    $(e.target).closest(«form»).find(«:input»).add(«.photo»).each(function () {
 
        var el = $(this);
        formData[el.attr(«class»)] = el.val();
    });
 
    if (formData.photo === «») {
        delete formData.photo;
    }
 
    this.model.set(formData);
 
    this.render();
 
    if (prev.photo === «/img/placeholder.png») {
        delete prev.photo;
    }
 
    _.each(contacts, function (contact) {
        if (_.isEqual(contact, prev)) {
            contacts.splice(_.indexOf(contacts, contact), 1, formData);
        }
    });
},

Прежде всего, мы создаем пустой элемент для хранения данных, которые были введены в форму, а также сохраняем копию previousAttributes атрибутов модели, которая принадлежит представлению, с которым мы работаем. Свойство previousAttributes моделей — это хранилище данных, которое Backbone поддерживает для нас, чтобы мы могли легко увидеть, какими были данные предыдущего атрибута атрибута.

Затем мы получаем каждый элемент ввода из формы, используя комбинацию метода find() jQuery и фильтра :input , который дает нам все поля формы. Мы не хотим отменять или сохранять элементы <button> , поэтому мы удаляем их из выделения, используя метод not() jQuery.

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

Когда мы преобразуем редактируемый контакт обратно в обычный контакт, мы не хотим потерять фотографию по умолчанию, если новая фотография не была выбрана. Чтобы мы не потеряли фотографию по умолчанию, мы можем удалить свойство photo из нашего объекта formData если его значение пустое.

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

Базовые модели имеют метод установки, который можно использовать для установки любого атрибута. Чтобы обновить данные модели, мы просто вызываем метод set() передавая объект formData который мы подготовили. Как только это будет сделано, мы вызываем метод render() и наша обновленная модель будет возвращена обратно на страницу с любой обновленной информацией из формы.

Как мы делали ранее, нам нужно обновить данные, хранящиеся в нашем исходном массиве contacts чтобы фильтрация представления не теряла сделанные нами изменения. Мы делаем это так же, как и раньше, сначала проверяя, имеет ли свойство photo значение по умолчанию, и удаляя его, если это так, а затем используя комбинацию методов Underscore each() и isEqaul() чтобы найти элемент в массиве contacts что изменилось Здесь мы используем previousAttributes которые мы сохранили ранее; мы больше не можем использовать текущую модель, потому что ее атрибуты были только что обновлены.

Мы используем встроенную функцию JavaScript splice() для обновления массива contacts . Как и прежде, мы получаем индекс элемента для обновления, используя метод indexOf() Underscore в качестве первого аргумента splice() и устанавливаем функцию для обновления одного элемента, используя второй аргумент. На этот раз мы предоставляем наш объект formData в качестве третьего аргумента. Когда splice() получает три (или более) аргумента, третий аргумент — это данные, которые заменяют только что удаленные данные.


У нас осталась одна кнопка, для которой нам нужно добавить обработчик — кнопка отмены. Этот метод будет очень простым и просто переключит контакт обратно в режим без редактирования, используя исходные данные из модели. Добавьте этот метод после метода saveEdits() :

1
2
3
cancelEdit: function () {
    this.render();
},

Это все, что нам нужно сделать! У нас уже есть метод, который берет модель и отображает ее как представление на странице, поэтому мы просто вызываем этот метод, и исходные данные модели будут использоваться для воссоздания исходного контакта. Это полезно, потому что даже если кто-то изменит данные в полях формы, когда контакт находится в режиме редактирования, при нажатии кнопки отмены эти изменения будут потеряны.


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

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

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

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

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

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