среда, 18 мая 2011 г.

Связывание данных в jQuery

Если вы будете искать data-binding для jQuery, то, естественно, что вы ничего не найдете :) В силу существования метода bind() для привязки событий элементам. На самом деле механизм связывания у них называется data-linking ;) И если рассказывать про jQuery Data Link, то грешно не затронуть механизм шаблонов jQuery Templates, который, надо сказать, вышел одновременно с первым – и оба они были последствием сотрудничества с Майкрософт ;) Подробнее об этом можно почитать здесь.

Шаблоны в jQuery - jQuery Templates

Итак, рассмотрим примитивное использование шаблонов. Качаем плагин отсюда.
Удобнее всего текст шаблона брать в объятия тега <script>, ибо a)контейнеру надо присвоить идентификатор для обращения; б)содержимое этого тега не отображается на странице. Рассмотрим на примере массива из объектов с двумя полями: числовым id и текстовым title.
var places = [{ id: 0, title: "home" }, { id: 1, title: "work"}];
Я хочу, чтобы элементы выводились так:
[0] home
[1] work

Шаблон для этого случая будет следующим:
<script type="text/html" id="listTmpl">
    <li>[{{= id}}] {{= title}}</li>
</script>
В виду того, что шаблоны используются для отображения элементов массива, которыми являются объекты, то в коде шаблона переменные для форматирования вывода – имена полей объекта. В нашем случае объект имеет два поля, которые мы хотим отобразить: id и title. Указать их можно двумя способами: a){{= имя_поля}} или б)${имя_поля}. Разница в том, что вторая запись вводилась позже, и её подстановка займет немножко больше времени.
А теперь, чтобы шаблон сработал, надо у элемента его содержащего (в нашем случае это тег <script> с id=listTmpl) вызвать метод tmpl(), передав ему параметром массив с объектами, а также добавить в цепочку вывода метод appendTo(), указывая ему в какой элемент поместить сгенерированное по шаблону содержание:
$("#listTmpl").tmpl(places).appendTo("#list");
Полностью код и разметка страницы представлены ниже. По нему видно, что в appendTo был передан элемент списка <ul>:
<html>
    <head>
        <title>Test</title>
        <script type="text/javascript" src="js/jquery.js"></script>
        <script type="text/javascript" src="js/jquery.tmpl.js"></script>
        <script type="text/javascript"> 
            $().ready(function () {
                var places = [{ id: 0, title: "home" }, { id: 1, title: "work"}];
                $("#listTmpl").tmpl(places).appendTo("#list");
            });
 
            </script>
    </head>
    <body>
        <ul id="list">
            <script type="text/html" id="listTmpl"> 
                <li>[{{= id}}] {{= title}}</li>
            </script>
        </ul>
    </body>
</html>

Связывание данных в jQuery data- bindinglinking

Для связывания данных понадобится указать три параметра: 1) имя поля у объекта; 2) идентификатор элемента вывода; 3) метод получения данных. Я буду рассматривать здесь и далее привязку в оба конца через метод linkBoth. Допустим, у нас объект с двумя полями: title и description. Тогда код для привязки поля title к содержимому текстового поля <input type="text" id="title" /> будет выглядеть так:
var obj = { title: "someTitle", description: "Foo" };
$(obj).linkBoth("title", "#title", "val");
Первый параметр – имя связываемого поля в объекте. Второй – идентификатор текстового поля ввода. Третий – метод jQuery, с помощью которого можно получить значение этого текстового поля.

Комбинация шаблонов с привязкой данных

Конечно, хочется использовать связывание данных совместно с шаблонами. Но как это сделать, если data-linking берет на вход объект, а шаблоны хотят массив объектов? Лично я не нашла статей о том, что разработчики сделали этот уже давно планируемый апдэйт с комбинацией.
Поэтому я у каждого элемента массива вызываю linkBoth, а в шаблоне атрибуту id элемента вывода приписываю id объекта, к примеру: у объекта есть поле id:11, тогда в шаблоне блок данных одного объекта можно взять в объятия тега <div id="obj_{{= id}}"></div> и больше в шаблоне не использовать обращение к другим полям {{= другие_поля_объекта}}, а контейнерам полей прописать классы, чтобы значения передавались только один раз - через data-linking (если указать значения контейнеров еще и через шаблон, то дважды отработает присвание значения: первый раз при генерации шаблона, и второй раз при первом вызове link), указанием идентификатора вида #obj_11 .Obj_field.
Давайте рассмотрим конкретный пример. Есть у меня класс с относительно большим набором полей. event: id, title, description, duration. И я хочу выводить массив объектов этого класса в редактируемом виде. Притом для демонстрации дополнительных возможностей связывания поле duration выводится с односторонней связкой в видоизмененном виде относительно того, как оно представлено в объекте, т.е. используя вместо метода linkBoth методы linkFrom и linkTo, можно указать 4ый параметр – конвертер передаваемого значения. Как выше было сказано, я использую див-контейнер для вывода полей объекта, прописывая id объекта только ему. До вызова шаблона массив заполняется тремя объектами, вызывается шаблон, а потом к каждому элементу массива вызывается цепочка методов связывания данных. Каждое новое событие создается конструктором, в котором есть метод linkEvents, где идет вызов функций связывания. Чтобы вывести новый объект на страницу, к контейнеру шаблона вызывается метод tmpl с новым объектом в качестве параметра, а не массивом всех объектов. И уже после того, как шаблоном были добавлены новые ui-элементы на страницу, у объекта вызывается linkEvents, в котором отрабатывается привязка всех полей объекта к только что созданным элементам их представления.
<html>
    <head>
        <title>Test</title>
        <script type="text/javascript" src="js/jquery.js"></script>
        <script type="text/javascript" src="js/jquery.tmpl.js"></script>
        <script type="text/javascript" src="js/jquery.datalink.js"></script>
        <script type="text/javascript"> 
            var events;
            var curId = 100;
            function event(id, title, desc, dur) {
                this.id = id;
                this.title = title;
                this.description = desc;
                this.duration = dur;
                this.linkEvents = linkEvents;
            }
            $().ready(function () {
                events = [new event(curId++, "sleep", "go to see pink-sweety dreams", 480),
                          new event(curId++, "morning", "time to read a book", 30),
                          new event(curId++, "breakfast", "time to eat!", 70)];
                $("#evTmpl").tmpl(events).appendTo("#events");
                $(events).each(linkEvents);
            });
            function showData() {
                var message = "";
                for (var i in events) {
                    $(events[i]).attr('duration', Math.floor(Math.random() * 111) );
                    message += events[i].title + " <font color=Gray>" + events[i].description + " </font><i> " + events[i].duration + "minutes</i> <br />";
                }
                $("#outputArea").html(message);
            }
            function addNewEvent() {
                var newEv = new event(curId++, "event_" + curId, "description " + curId, 100 );
                $("#evTmpl").tmpl(newEv).appendTo("#events");
                events[events.length] = newEv;
                newEv.linkEvents();
            }
            function linkEvents () {
                    var id = "#ev_" + $(this).attr("id") + " .";
                    $(this)
                        .linkBoth("title", id + "Title", "val")
                        .linkBoth("description", id + "Description", "val")
                        .linkTo("duration", id + "Time", "text", function (value) {
                            value = parseInt(value);
                            var m = value % 60;
                            var h = (value - m) / 60;
                            m = (m == 0) ? "" : m + "min";
                            h = (h == 0) ? "" : h + "h ";
                            return h + m;
                        });
             }
 </script>
    </head>
    <body>
        <input type="submit" value="output" onclick="showData();" />
        <input type="submit" value="generate new event" onclick="addNewEvent();" />
        <ul id="events" style="list-style:none;">
            <script type="text/tmpl" id="evTmpl"> 
                <li id="ev_{{= id}}">
                    title: <input type="text" class="Title" />
                    description: <input type="text" class="Description" />
                    <span class="Time"></span>
                </li>
 </script>
        </ul>
        <div style="margin-top:20px;;" id="outputArea"></div>
    </body>
</html>
В примере две кнопки: отобразить содержимое массива в диве и сгенерировать новое событие. При отображении в див также происходит генерация новых значений duration для каждого уже имеющегося объекта. Пример того, что делает скрипт, представлен ниже:
Не надо забывать, что связывание не будете работать, пока шаблон не создаст содержимое – те контейнеры, к которым будет осуществляться привязка данных.
Живая демонстрация скрипта и его исходник.

Комментариев нет:

Отправить комментарий