Статьи

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

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

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


Если вы вернетесь к первой части, вы вспомните, как мы добавили все наши модели в коллекцию, когда коллекция была инициализирована. Но как мы можем добавить отдельные модели в коллекцию после того, как коллекция уже инициализирована? Это на самом деле очень просто.

Мы добавим возможность добавления новых контактов, что повлечет за собой обновление основного HTML и нашего основного представления. Во-первых, HTML; добавьте следующую разметку в контейнер контактов:

1
2
3
4
5
6
7
8
9
<form id=»addContact» action=»#»>
    <label for=»photo»>photo:</label><input id=»photo» type=»file» />
    <label for=»type»>Type:</label><input id=»type» />
    <label for=»name»>Name:</label><input id=»name» />
    <label for=»address»>Address:</label><input id=»address» />
    <label for=»tel»>Tel:</label><input id=»tel» />
    <label for=»email»>Email:</label><input id=»email» />
    <button id=»add»>Add</button>
</form>

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

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

1
«click #add»: «addContact»

Не забудьте добавить запятую в конце существующей привязки! На этот раз мы указываем событие click инициируемое элементом с id add , который является кнопкой в ​​нашей форме. Обработчик, который мы привязываем к этому событию, это addContact , который мы можем добавить следующим. Добавьте следующий код после filterByType() из второй части:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
addContact: function (e) {
    e.preventDefault();
 
    var newModel = {};
    $(«#addContact»).children(«input»).each(function (i, el) {
        if ($(el).val() !== «») {
            newModel[el.id] = $(el).val();
        }
    });
 
    contacts.push(formData);
 
    if (_.indexOf(this.getTypes(), formData.type) === -1) {
        this.collection.add(new Contact(formData));
        this.$el.find(«#filter»).find(«select»).remove().end().append(this.createSelect());
    } else {
        this.collection.add(new Contact(formData));
    }
}

Так как это обработчик событий, он автоматически получит объект event , который мы можем использовать для предотвращения поведения по умолчанию элемента <button> при его нажатии (то есть для отправки формы и перезагрузки страницы — не то, что мы хочу). Затем мы создаем новый пустой объект и используем метод each() jQuery для итерации по каждому элементу <input> в нашей форме addContact .

В функции обратного вызова, предоставленной функции each() , мы сначала проверяем, что в поле был введен текст, и, если это так, мы добавляем новое свойство к объекту с ключом, равным id текущего элемента, и значением, равным к его текущей value . Если поле пустое, свойство не будет установлено, и новая модель унаследует все значения по умолчанию, которые могли быть указаны.

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

Наконец, нам нужно обновить элемент <select> чтобы, если новый контакт имел другой тип, этот тип был доступен для фильтрации. Однако мы хотим перерисовать <select> если был добавлен новый тип. Мы можем использовать метод Underscore indexOf() для поиска в массиве определенного значения. Как и собственный метод JavaScript indexOf() для строк, этот метод вернет -1 если значение не найдено. Мы передаем массив для поиска в качестве первого аргумента в indexOf() , а значение для поиска в качестве второго.

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


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

1
this.collection.on(«add», this.renderContact, this);

Мы снова используем метод on() чтобы присоединить прослушиватель событий, и, поскольку у нас уже есть метод, который создает и отображает отдельные представления, мы просто указываем эту функцию в качестве обработчика. Мы также устанавливаем главное представление как объект this в обработчике, как мы это делали с предыдущими обработчиками. К этому моменту мы теперь сможем заполнить форму, и новый контакт будет отображен на странице:

Следует отметить, что если addContact формы addContact оставить полностью пустыми, полученная модель будет почти полностью лишена атрибутов, что вызовет проблемы, когда мы попытаемся позже манипулировать моделью. Одним из способов избежать этого является предоставление значений по умолчанию для большинства атрибутов модели, так же как мы предоставили атрибут photo по умолчанию. Если нет разумных значений по умолчанию, которые мы можем использовать, например, для имени контакта, мы можем просто указать пустую строку. Обновите объект по defaults в классе Contact чтобы включить значения по умолчанию для других наших атрибутов:

1
2
3
4
5
name: «»,
address: «»,
tel: «»,
email: «»,
type: «»

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

1
<button class=»delete»>Delete</button>

Это все, что нам нужно для этого примера. Логика удаления отдельной модели может быть добавлена ​​в класс представления, который представляет отдельный контакт, поскольку экземпляр представления будет связан с конкретным экземпляром модели. Нам нужно добавить привязку события и обработчик события, чтобы удалить модель при нажатии кнопки; добавьте следующий код в конец класса ContactView :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
events: {
    «click button.delete»: «deleteContact»
},
 
deleteContact: function () {
    var removedType = this.model.get(«type»).toLowerCase();
 
    this.model.destroy();
 
    this.remove();
 
    if (_.indexOf(directory.getTypes(), removedType) === -1) {
        directory.$el.find(«#filter select»).children(«[value='» + removedType + «‘]»).remove();
    }
}

Мы используем объект events чтобы указать нашу привязку к событию, как мы делали это раньше с нашим главным представлением. На этот раз мы прослушиваем события click вызванные <button> , имя класса которой delete . Обработчиком, связанным с этим событием, является deleteContact , который мы добавляем после объекта events .

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

Затем мы вызываем метод destroy() для модели, связанной с this , экземпляром представления. Мы также можем удалить HTML-представление представления со страницы, вызвав метод remove() jQuery, который имеет дополнительный бонус в очистке любых обработчиков событий, прикрепленных к представлению.

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

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


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

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

1
this.collection.on(«remove», this.removeContact, this);

Вы должны быть хорошо знакомы с этим утверждением к настоящему времени, но как напоминание, первый аргумент метода on() — это событие, которое мы слушаем, второй — это обработчик, который выполняется, когда происходит событие, а третий — контекст, чтобы использовать как это, когда обработчик выполняется. Далее мы можем добавить метод removeContact() ; после addContact() добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
removeContact: function (removedModel) {
    var removed = removedModel.attributes;
 
    if (removed.photo === «/img/placeholder.png») {
        delete removed.photo;
    }
 
    _.each(contacts, function (contact) {
        if (_.isEqual(contact, removed)) {
            contacts.splice(_.indexOf(contacts, contact), 1);
        }
    });
}

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

В этом примере нам нужно проверить, совпадает ли свойство photo модели со значением по умолчанию, и если это так, мы удаляем свойство photo .

Как только это будет сделано, мы можем перебрать каждый элемент массива contacts и протестировать его, чтобы увидеть, совпадает ли он с моделью, которая была удалена из коллекции. Мы можем сравнить каждый элемент с объектом, который мы храним в удаленной переменной, с помощью метода Underscore isEqual() .

Если метод isEqual() возвращает true, мы вызываем собственный метод JavaScript splice() в массиве contacts , передавая индекс удаляемого элемента и количество удаляемых элементов. Индекс получается с помощью метода indexOf() Underscore, который мы использовали ранее.

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


Итак, мы просто addContact форму addContact на страницу, не так ли? Чтобы закрыть эту часть руководства, мы можем что-то сделать, чтобы скрыть его, пока не будет нажата ссылка. Мы можем добавить следующую ссылку в элемент <header> :

1
<a id=»showForm» href=»#»>Add new contact</a>

Чтобы ссылка отображала форму, сначала нам нужно ее скрыть, а затем использовать обработчик событий пользовательского интерфейса для ее отображения. Привязка может быть добавлена ​​к объекту events в классе DirectoryView :

1
«click #showForm»: «showForm»

Наш showForm() может быть простым, как showForm() ниже (хотя вы, вероятно, захотите сделать с ним немного больше, чем мы здесь!):

1
2
3
showForm: function () {
    this.$el.find(«#addContact»).slideToggle();
}

В этом уроке мы рассмотрели исключительно то, как новые модели можно добавлять в коллекцию и как модели можно удалять из коллекции. Мы видели, что методы Backbone, используемые для добавления и удаления моделей, неудивительно, что методы add() и remove() .

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

Мы также рассмотрели некоторые полезные вспомогательные функции Underscore, которые можно использовать для работы с нашими данными, в том числе _indexOf() которая возвращает этот индекс элемента в массиве, и isEqual() которые можно использовать для глубокого сравнения двух объектов с посмотрим, идентичны ли они.

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

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