Статьи

Погружение в CanJS: часть 2

Это вторая часть серии из трех частей, которая научит вас создавать приложение диспетчера контактов в JavaScript с использованием CanJS и jQuery. Когда вы закончите с этим руководством, у вас будет все, что вам нужно, чтобы создавать свои собственные приложения JavaScript с использованием CanJS!

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

В этой части вы будете:

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

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


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

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

can.route — это специальная наблюдаемая, которая обновляет и реагирует на изменения в window.location.hash . Используйте can.route для сопоставления URL-адресов со свойствами, в результате can.route красивые URL-адреса, такие как #!filter/all . Если маршруты не определены, хеш-значение просто сериализуется в кодированную URL-нотацию, например #!category=all .

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

1
2
can.route( ‘filter/:category’ )
can.route(», {category: ‘all’ })

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


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

Contact.List потребуется две вспомогательные функции для фильтрации списка контактов и отчета о количестве контактов в каждой категории. Добавьте это к contacts.js сразу после модели Contact :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
Contact.List = can.Model.List({
  filter: function(category){
    this.attr(‘length’);
    var contacts = new Contact.List([]);
    this.each(function(contact, i){
      if(category === ‘all’ || category === contact.attr(‘category’)) {
        contacts.push(contact)
      }
    })
    return contacts;
  },
  count: function(category) {
    return this.filter(category).length;
  }
});

Вот две вспомогательные функции:

  • filter() просматривает каждый контакт в списке и возвращает новый Contact.List контактов в категории. this.attr('length') включен здесь, поэтому EJS установит динамическое связывание, когда мы используем этот помощник в представлении.
  • count() возвращает количество контактов в категории, используя вспомогательную функцию filter() . Из-за this.attr('length') в filter() EJS установит динамическое связывание, когда мы используем этот помощник в представлении.

Если вы будете использовать помощника в EJS, используйте attr() в списке или свойстве экземпляра, чтобы установить динамическое связывание.


Далее вы измените представление contactsList.ejs чтобы отфильтровать контакты на основе свойства категории в хэше. В представлении contactsList.ejs измените параметр, передаваемый помощнику list() на contacts.filter(can.route.attr('category')) . Ваш EJS-файл должен выглядеть следующим образом:

1
2
3
4
5
6
7
8
9
<ul class=»unstyled clearfix»>
  <% list(contacts.filter(can.route.attr(‘category’)), function(contact){ %>
    <li class=»contact span8″ <%= (el)-> el.data(‘contact’, contact) %>>
      <div class=»»>
        <%== can.view.render(‘contactView’, {contact: contact, categories: categories}) %>
      </div>
    </li>
  <% }) %>
</ul>

Во второй строке filter() вызывается с текущей категорией из can.route . Поскольку вы использовали attr() в filter() и can.route , EJS установит динамическое связывание для повторного рендеринга вашего пользовательского интерфейса при любом из этих изменений.

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


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

Сначала создайте новый вид для отображения списка категорий. Сохраните этот код как filterView.ejs в вашей папке представлений:

01
02
03
04
05
06
07
08
09
10
11
<ul class=»nav nav-list»>
  <li class=»nav-header»>Categories</li>
  <li>
    <a href=»javascript://» data-category=»all»>All (<%= contacts.count(‘all’) %>)</a>
  </li>
  <% $.each(categories, function(i, category){ %>
    <li>
      <a href=»javascript://» data-category=»<%= category.data %>»><%= category.name %> (<%= contacts.count(category.data) %>)</a>
    </li>
  <% }) %>
</ul>

Давайте пройдемся по нескольким строкам этого кода и посмотрим, что они делают:

1
<% $.each(categories, function(i, category){ %>

$.each перебирает категории и выполняет обратный вызов для каждой из них.

1
<a href=»javascript://» data-category=»<%= category.data %>»><%= category.name %> (<%= contacts.count(category.data) %>

Каждая ссылка имеет атрибут data-category который будет добавлен в объект данных jQuery. Позже к этому значению можно получить доступ, используя .data('category') в теге <a> . Название категории и количество контактов будут использоваться в качестве теста ссылки. Активное связывание настраивается на количество контактов, потому что count() вызывает filter() который содержит this.attr('length') .


Элемент управления автоматически связывает методы, которые выглядят как обработчики событий, когда создается экземпляр. Первая часть обработчика событий — это селектор, а вторая часть — событие, которое вы хотите прослушать. Селектор может быть любым допустимым селектором CSS, а событие может быть любым событием DOM или пользовательским событием. Таким образом, функция типа 'a click' будет прослушивать щелчок по любому тегу <a> в элементе элемента управления.

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


Создайте элемент управления, который будет управлять категориями, добавив этот код в contacts.js сразу после Contacts управления « Contacts :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
Filter = can.Control({
  init: function(){
    var category = can.route.attr(‘category’) ||
    this.element.html(can.view(‘filterView’, {
      contacts: this.options.contacts,
      categories: this.options.categories
    }));
    this.element.find(‘[data-category=»‘ + category + ‘»]’).parent().addClass(‘active’);
  },
  ‘[data-category] click’: function(el, ev) {
    this.element.find(‘[data-category]’).parent().removeClass(‘active’);
    el.parent().addClass(‘active’);
    can.route.attr(‘category’, el.data(‘category’));
  }
});

Давайте рассмотрим код из элемента управления `Filter`, который вы только что создали:

1
2
3
4
this.element.html(can.view(‘filterView’, {
  contacts: this.options.contacts,
  categories: this.options.categories
}));

Как и в can.view() управления Contacts , init() использует can.view() для отображения категорий и html() для вставки его в элемент Control.

1
this.element.find(‘[data-category=»‘ + category + ‘»]’).parent().addClass(‘active’);

Находит ссылку, соответствующую текущей категории, и добавляет класс ‘active’ к своему родительскому элементу.

1
‘[data-category] click’: function(el, ev) {

Прослушивает событие click на любом элементе, соответствующем селектору [data-category] .

1
2
this.element.find(‘[data-category]’).parent().removeClass(‘active’);
el.parent().addClass(‘active’);

Удаляет «активный» класс из всех ссылок, затем добавляет класс «активных» к ссылке, по которой щелкнули.

1
can.route.attr(‘category’, el.data(‘category’));

can.route свойство категории в can.route используя значение из объекта данных jQuery для <a> которому был выполнен щелчок.


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
$(document).ready(function(){
  $.when(Category.findAll(), Contact.findAll()).then(function(categoryResponse, contactResponse){
    var categories = categoryResponse[0],
      contacts = contactResponse[0];
 
    new Contacts(‘#contacts’, {
      contacts: contacts,
      categories: categories
    });
    new Filter(‘#filter’, {
      contacts: contacts,
      categories: categories
    });
  });
})

С этим изменением в элементе #filter будет создан #filter элемента управления #filter . Будет передан список контактов и категорий.

Теперь, запустив приложение в браузере, вы сможете отфильтровать контакты, нажав на категории справа:

Часть 2

Это все для второй части! Вот что мы сделали:

  • Создан элемент управления, который прослушивает события и управляет категориями
  • Настройка маршрутизации для фильтрации контактов по категориям
  • Изменены ваши представления, чтобы привязка в реальном времени синхронизировала весь пользовательский интерфейс с уровнем данных

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

Не могу дождаться, чтобы узнать больше? Третья часть серии была размещена здесь !