Статьи

В ринг с нокаутом

В красном углу весом всего 29 Кб (без сжатия) находится knockout.js ; чистая библиотека JavaScript, которая упрощает создание динамических пользовательских интерфейсов. Knockout не зависит от библиотеки, поэтому его можно легко использовать с любой из самых популярных библиотек JavaScript, уже доступных, но он особенно хорошо работает с jQuery и использует jQuery.tmpl в качестве движка шаблонов по умолчанию.

Knockout не является заменой jQuery.

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

Вполне возможно создать сложные и высокодинамичные пользовательские интерфейсы только с помощью jQuery, но есть ли в бюджете вашего проекта время, необходимое для написания и отладки более 800 строк кода? Как быть через 6 месяцев, когда что-то нужно изменить или добавить? Это где нокаут приходит.

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


Knockout использует архитектуру модели View-model-view. Видимый список контактов, которые мы используем в этом примере, и элементы на странице, из которой они состоят, можно рассматривать как представление. Данные, которые отображаются на странице, являются моделью. Модель представления представляет собой представление текущего состояния пользовательского интерфейса, комбинацию данных и представление, которое также содержит поведение, используемое для взаимодействия с моделью и обновления представления.

Давайте начнем с создания структуры папок, которая нам понадобится, и базовой страницы, с которой мы будем работать. Создайте новую папку с именем knockout где-нибудь в вашей системе, затем в этой папке создайте три новые папки с css , img и js . Папка css будет использоваться для хранения простой таблицы стилей, которую мы будем использовать, а папка img — отдельное изображение. Папка js будет содержать файл скрипта, который мы создаем, а также библиотеки, от которых мы зависим. Изначально эта папка должна содержать следующие файлы:

  • jquery.tmpl.js
  • JQuery-1.6.2.js
  • нокаут-1.2.1.js

Теперь в вашем текстовом редакторе создайте следующую базовую страницу:

01
02
03
04
05
06
07
08
09
10
11
12
13
<!DOCTYPE html>
<html>
    <head>
        <title>Knockout</title>
        <link rel=»stylesheet» href=»css/styles.css» />
    </head>
    <body>
        <script src=»js/jquery-1.6.2.min.js»></script>
        <script src=»js/jquery.tmpl.js»></script>
        <script src=»js/knockout-1.2.1.js»></script>
        <script src=»js/behavior.js»></script>
    </body>
</html>

Сохраните эту страницу как index.html в корневой папке knockout . Пока что здесь нет ничего заслуживающего внимания, кроме использования HTML5. Хотя knockout.js совместим с более ранними версиями HTML, атрибуты, которые мы будем добавлять к нашим элементам, не являются частью стандартного стандарта HTML 4.01, и поэтому страница будет недействительной. Это не относится к HTML5, который определяет атрибуты data-* для встраивания пользовательских данных.

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

Далее мы можем создать наш файл поведения; на новой странице в вашем текстовом редакторе добавьте следующий код:

1
2
3
4
5
6
7
8
9
(function ($) { var model = [{ name: «John», address: «1, a road, a town, a county, a postcode», tel: «1234567890», site: «www.aurl.com», pic: «/img/john.jpg», deleteMe: function () { viewModel.people.remove(this); }
    }, { name: «Jane», address: «2, a street, a city, a county, a postcode», tel: «1234567890», site: «www.aurl.com», pic: «/img/jane.jpg», deleteMe: function () { viewModel.people.remove(this);
    }, { name: «Fred», address: «3, an avenue, a village, a county, a postcode», tel: «1234567890», site: «www.aurl.com», pic: «/img/fred.jpg», deleteMe: function () { viewModel.people.remove(this);
    }, { name: «Freda», address: «4, a street, a suburb, a county, a postcode», tel: «1234567890», site: «www.aurl.com», pic: «/img/jane.jpg», deleteMe: function () { viewModel.people.remove(this);
    }], viewModel = { people: ko.observableArray(model),
    }
  };
     
})(jQuery);

Сохраните этот файл под именем behavior.js в папке js . Мы начнем с определения функции, вызывающей себя, которую мы передаем jQuery, чтобы присвоить псевдониму символ $ .

Затем мы определяем модель, которую будем использовать. В этом примере это локальный массив, но мы можем достаточно легко получить точно такой же формат данных из веб-службы. Наш array содержит серию объектов людей, которые соответствуют отдельным записям в базе данных contacts . В основном наши данные состоят из простых строк, но каждый object также содержит method deleteMe , который используется для удаления object из viewModel .

Помните, что viewModel ссылается на текущее состояние пользовательского интерфейса. Это объект, и первым элементом, который мы добавляем к нему, является наш array содержащий объекты людей. Мы используем метод knockout ko.observableArray() чтобы добавить наш array в object viewModel . Наблюдаемые объекты являются фундаментальным аспектом knockout.js; мы инструктируем нокаут, чтобы другие лица могли наблюдать за этими предметами и реагировать на их изменения.

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

После object object мы используем метод ko.applyBindings() чтобы применить любые созданные нами привязки и начать управление viewModel . На данный момент в примере мы еще не добавили привязки. Чтобы создать привязки между нашим view и viewModel , нам нужно добавить еще немного HTML.


Knockout прекрасно работает с шаблонизацией jQuery.

Теперь у нас есть наша model и простая viewModel . Следующее, что мы должны сделать, это отобразить данные из viewModel на странице. Knockout прекрасно работает с шаблонизацией jQuery. Это позволяет нам использовать плагин tmpl для создания необходимого HTML. Добавьте следующий код в элемент <body> страницы непосредственно перед элементами <script> :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<div id=»people» data-bind=»template: { name: ‘personTemplate’, foreach: people }»>
</div>
<script id=»personTemplate» type=»text/x-jquery-tmpl»>
    <section class=»person»>
        <img src=»../img/person.png» alt=»${ name }» />
        <h1>${ name }</h1>
        <address>${ address }</address>
        <span class=»tel»>${ tel }
        <a href=»http://${ site }» title=»Visit site»>${ site }</a>
        <div class=»tools»>
            <button data-bind=»click: deleteMe»>Delete</button>
        </div>
    </section>
</script>

Сначала мы добавляем пустой элемент <div> с id — в основном для стилизации. Этот элемент также имеет специальный атрибут — data-bind . Этот атрибут сообщает нокауту, что элемент хранит свои данные в viewModel . Когда мы вызываем ko.applyBindings() в нашем JS, это связывание применяется. В этом случае мы используем привязку шаблона, которая позволяет нам указать имя шаблона, которое мы хотели бы использовать в объекте конфигурации, передаваемом привязке.

Мы также используем свойство foreach в этом объекте конфигурации и указываем имя наших сотрудников observableArray в качестве источника наших данных. Мы могли бы использовать стандартный синтаксис tmpl {{each}} для перебора наших данных о людях, но вместо этого более эффективно использовать синтаксис knockout. Поскольку наши данные о людях содержатся в наблюдаемом array , нокаут отслеживает изменения в array и, когда они происходят, автоматически обновляет любые шаблоны, отображающие данные. Если мы используем синтаксис tmpl, весь наш шаблон будет перерисовываться каждый раз при изменении данных, но когда мы используем свойство foreach нокаута, только один экземпляр, соответствующий измененному элементу, перерисовывается.

После контейнера <div> мы определяем наш шаблон. Это делается так же, как обычный шаблон tmpl. В шаблоне мы указываем элементы, которые мы хотели бы повторить для каждого объекта в нашем источнике данных. У нас есть элемент <section> в качестве контейнера, за которым следует соответствующий элемент для каждого элемента в object person . Стоит отметить, что мы можем предоставить привязки в нашем коде шаблона. Мы добавляем атрибут data-bind к кнопке удаления; на этот раз мы используем привязку click и указываем имя person найденного в каждом object person .

Когда мы запускаем страницу в браузере, мы должны обнаружить, что наша страница содержит данные из нашей viewModel , красиво отрисованные с использованием нашего шаблона:

Так что это круто, верно? Но это не так уж отличается от использования плагина tmpl.

Действительно здорово, что представление не только обновляется соответствующим образом при изменении viewModel , но и viewModel обновляется при изменении представления. Поэтому, если мы нажмем одну из кнопок удаления на нашей странице, из array people также будет удален соответствующий object person !

Исходный array который мы передали в метод ko.observable() фактически не обновляется, но обычно мы, вероятно, получаем наши данные из запроса AJAX, а не жестко кодируем их на странице, поэтому все, что нам нужно чтобы сделать это повторно отправить данные, с удаленным person .


У нас есть возможность удалить object person ; затем мы можем добавить возможность добавить нового человека в нашу dataModel ; Обновите контейнер <div> мы добавили на страницу ранее, чтобы он содержал следующие новые элементы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<a href=»#» title=»Add new person» data-bind=»click: showForm, visible: displayButton»>Add person</a>
<fieldset data-bind=»visible: displayForm»>
    <div class=»details»>
        <label>Name: <input id=»name» /></label>
        <label>Address: <input id=»address» /></label>
        <label>Tel: <input id=»tel» /></label>
        <label>Site: <input id=»site» /></label>
    <div>
    <div class=»img»>
        <label>Picture: <input id=»pic» type=»file» /></label>
    </div>
    <div class=»tools»>
        <button data-bind=»click: addPerson»>Add</button>
        <button data-bind=»click: hideForm»>Cancel</button>
    </div>
</fieldset>

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

Как видите, мы можем указать несколько привязок для элемента. Наш элемент <a> также использует видимую привязку. Опять же, мы указываем свойство нашего viewModel , за исключением того, что на этот раз это не функция, а простая переменная, содержащая boolean ; вы увидите, как это работает, когда мы вскоре добавим JS для нашей новой функциональности.

После ссылки мы также добавляем <fieldset> содержащий метки и входные данные, которые мы можем использовать для добавления соответствующих данных для создания нового object в нашем array . В конце нашего нового HTML мы добавляем два новых элемента <button> ; к обоим из них добавлены привязки кликов. Первая ссылка на method addPerson , вторая на method hideForm . Загрузка изображения на самом деле не работает в этом примере, она только для демонстрации.

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

1
2
3
4
5
displayButton: ko.observable(true), displayForm: ko.observable(false), showForm: function () { viewModel.displayForm(true).displayButton(false);
}, hideForm: function () { viewModel.displayForm(false).displayButton(true);
}, addPerson: function () { viewModel.displayForm(false).displayButton(true).people.push({ name: $(«#name»).val(), address: $(«#address»).val(), tel: $(«#tel»).val(), site: $(«#site»).val(), pic: «», deleteMe: function () { viewModel.people.remove(this); }
    });
}

Первое свойство — displayButton , которое является наблюдаемым свойством (его значение может наблюдаться) другими объектами. Объект, который наблюдает за его значением, является нашим элементом <a> в представлении. Сначала мы установили для него значение true , поэтому при загрузке страницы (точнее, при applyBindings() метода applyBindings() ) ссылка будет видна.

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

Затем мы добавляем два метода: showForm() и hideForm() . Эти два простых метода, очевидно, используются, чтобы, соответственно, отображать или скрывать форму, и для этого все, что им нужно, это установить для displayForm observable значение true или false . Поскольку значение соблюдается, при каждом изменении его значения наше представление будет обновляться автоматически.

Мы также настраиваем свойство showButton всякий раз, когда изменяется состояние формы. Если fieldset видим, мы скрываем ссылку, и если мы скрываем fieldset , кнопка снова становится видимой. Как вы можете видеть, knockout поддерживает связывание, что делает обновление нескольких свойств в нашей viewModel чрезвычайно простым. Вид должен выглядеть следующим образом, когда форма видима:

Последний метод, который мы добавляем, — это addPerson() , который используется для обновления нашей viewModel информацией о новом человеке. Все, что мы делаем в этом методе — это скрываем форму и показываем кнопку, а также создаем литерал объекта, содержащий значения, введенные в текстовые поля, а затем помещаем этот object в наш array people .

Чтобы извлечь обновленный people array из нашей viewModel , мы можем использовать встроенный в JSON сериализатор JSON для записи наблюдаемого array в object JSON. Обычно мы делаем это для того, чтобы передать данные обратно на сервер, но, чтобы проверить это, мы могли бы добавить эту строку кода в конец метода addPerson() :

1
console.log(ko.toJSON(viewModel.people));

Метод ko.toJSON() сгенерировать object JSON, содержащий текущее содержимое array people , который мы видим в Firebug (доступны другие исследователи DOM):


В этом уроке мы рассмотрели два основных аспекта knockout.js — декларативные привязки и наблюдаемые.

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

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

Knockout.js — чрезвычайно полезный уровень, который находится между интерфейсом нашего пользовательского интерфейса и его основными данными и управляет взаимодействиями и изменениями состояния для нас. Это делает для нас очень много работы, хотя в этом базовом примере мы на самом деле только поверхностно продемонстрировали, на что он способен. Что вы думаете о knockout.js?