Шаблоны данных

88

В этой статье вы познакомитесь с подключаемым модулем (плагином) jQuery — jQuery Templates. Он обеспечивает возможность использования шаблонов, упрощающих генерацию HTML-элементов из объектов данных JavaScript.

Чтобы не возникало недопонимания, хочу предупредить, что данный модуль не относится к числу активно разрабатываемых или поддерживаемых в настоящее время, и команда разработчиков jQuery не рекомендует его применять. Это не означает, что вы не должны его использовать, однако я счел своим долгом сказать вам об этом, прежде чем вы будете включать его в свои проекты. Я был бы рад порекомендовать вам какой-нибудь другой активно разрабатываемый вариант, однако найти хотя бы близкую по своим возможностям замену jQuery Templates мне пока что не удалось. Но даже при упомянутом отношении к нему разработчиков этот модуль все еще остается наилучшим.

История модуля jQuery Templates довольно необычна. В свое время Microsoft и команда разработчиков jQuery объявили, что трем подключаемым модулям, разработанным компанией Microsoft, присвоен статус "официальных", чего до того не удостаивался ни один из подключаемых модулей.

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

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

Настройка библиотеки jQuery Templates

Прежде чем использовать шаблоны jQuery, нужно загрузить библиотеку jQuery Templates и подключить ее к своему документу.

Распакуйте архив и скопируйте файл jQuery.tmpl.js (версия для разработки) или jQuery.tmpl.min.js (версия для развертывания) на свой веб-сервер. Следующее, что необходимо сделать — добавить в образец документа элемент script, подключающий библиотеку шаблонов, как показано в примере ниже:

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="robots" content="none"/>
<title>Библиотека jQuery</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="jquery.tmpl.min.js"></script>
<script>	
$(function() {
    
    // Сюда будет помещаться код примеров

});
</script>
<style>
h1 {
    min-width: 70px; border: thick double black; margin-left: auto;
    margin-right: auto; text-align: center; font-size: x-large; padding: .5em;
    color: darkgreen; background-image: url("http://professorweb.ru/downloads/jquery/border.png");
    background-size: contain; margin-top: 0;
}
.dtable {display: table;}
.drow {display: table-row;}       
.dcell {display: table-cell; padding: 10px;}
.dcell > * {vertical-align: middle}
input {width: 2em; text-align: right; border: thin solid black; padding: 2px;}
label {width: 6em;  padding-left: .5em; display: inline-block;}
#buttonDiv {text-align: center;}
button {padding: 12px;}
#oblock {display: block; margin-left: auto; margin-right: auto; min-width: 700px; }
</style>
</head>
<body>
    <h1>Цветочный магазин</h1>
    <form method="post">
        <div id="oblock">        
            <div class="dtable">
                <div id="row1" class="drow"></div>
                <div id="row2" class="drow"></div>        
            </div>
        </div>
        <div id="buttonDiv">
            <button type="submit">Заказать</button>
        </div>    
    </form>
</body>
</html>

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

Внешний вид исходного документа в окне браузера на данном этапе представлен на рисунке:

Исходный документ для примеров

Простой пример шаблона данных

Наилучший способ изучения шаблонов данных — сразу же взяться за дело. Для демонстрации основных возможностей шаблонов мы используем код из примера ниже:

...
   
<script>	
$(function() {
    
    var data = [
        { name: "Астра", product: "astor", stocklevel: "10", price: 2.99},
        { name: "Нарцисс", product: "daffodil", stocklevel: "12", price: 1.99},
        { name: "Роза", product: "rose", stocklevel: "2", price: 4.99},
        { name: "Пион", product: "peony", stocklevel: "0", price: 1.50},
        { name: "Примула", product: "primula", stocklevel: "1", price: 3.12},
        { name: "Подснежник", product: "snowdrop", stocklevel: "15", price: 0.99},
    ];
    
    $('#flowerTmpl').tmpl(data).appendTo('#row1');

});
</script>
<script id="flowerTmpl" type="text/x-jquery-tmpl">
        <div class="dcell">
            <img src="http://professorweb.ru/downloads/jquery/${product}.png"/>
            <label for="${product}">${name}:</label>
            <input name="${product}" data-price="${price}" data-stock="${stocklevel}"
                value="0" required />
        </div>
</script>

...

В последующих разделах мы разобьем пример на отдельные части и проанализируем код каждой из них по отдельности. Когда данные являются частью документа, они называются встроенными данными (inline data). Альтернативой им являются дистанционные данные (remote data), хранящиеся на сервере отдельно от документа. Мы рассмотрим дистанционные данные несколько позже, а пока что можно заметить, что этот вопрос тесно связан с поддержкой Ajax, которую предоставляет библиотека jQuery.

Определение данных

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

    var data = [
        { name: "Астра", product: "astor", stocklevel: "10", price: 2.99},
        { name: "Нарцисс", product: "daffodil", stocklevel: "12", price: 1.99},
        { name: "Роза", product: "rose", stocklevel: "2", price: 4.99},
        { name: "Пион", product: "peony", stocklevel: "0", price: 1.50},
        { name: "Примула", product: "primula", stocklevel: "1", price: 3.12},
        { name: "Подснежник", product: "snowdrop", stocklevel: "15", price: 0.99},
];

Данные выражаются в виде одного или нескольких объектов JavaScript. Библиотека шаблонов jQuery предоставляет значительную гибкость в выборе объектов, которые могут быть использованы в качестве данных, но представленный выше формат, соответствующий формату данных JSON, является наиболее распространенным. Формат JS0N играет очень важную роль, поскольку его часто используют при работе с Ajax.

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

Определение шаблона

Как вы, наверное, и сами догадываетесь, центральным элементом библиотеки шаблонов является шаблон данных (data template). Он представляет собой набор HTML-элементов, содержащих заполнители, которые соответствуют различным свойствам объектов данных. Шаблон для этого примера показан ниже:

<script id="flowerTmpl" type="text/x-jquery-tmpl">
        <div class="dcell">
            <img src="http://professorweb.ru/downloads/jquery/${product}.png"/>
            <label for="${product}">${name}:</label>
            <input name="${product}" data-price="${price}" data-stock="${stocklevel}"
                value="0" required />
        </div>
</script>

Первое, на что следует обратить внимание, — это то, что шаблон помещается в элемент script, атрибуту type которого присваивается значение несуществующего типа — text/x-jquery-tmpl. Это сделано для того, чтобы браузер не пытался интерпретировать содержимое шаблона как обычную HTML-разметку. Хотя это и несущественно, но такой практики следует придерживаться, поскольку она чрезвычайно полезна и позволит вам избежать множества потенциальных проблем в будущем.

Второй момент, на котором я хочу заострить ваше внимание, — это то, что для присвоения имени шаблону, определенному в элементе script, используется атрибут id. В данном случае именем шаблона служит flowerTmpl. Чтобы применить к данным шаблон, необходимо знать его имя.

Содержимое шаблона будет применено ко всем объектам в массиве данных, что приведет к созданию набора HTML-элементов для каждого объекта. Вы видите, что структура шаблона в целом соответствует набору элементов, которые использовались в предыдущих статьях для представления различных видов цветочной продукции. Главное, чем они отличаются — это элементы кода выполняющие функции заполнителей (data placeholders).

В процессе обработки шаблона вместо каждого заполнителя подставляется значение свойства, взятое из текущего объекта. Например, для первого объекта массива вместо заполнителя ${product} будет подставлено значение свойства product, т.е. "astor". Таким образом, часть шаблона

<label for="${product}">${name}:</label>

преобразуется в следующий HTML-фрагмент:

<label for="astor">Астра:</label>

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

Применение шаблона

Для объединения шаблона с данными используется метод tmpl(). При этом вы указываете данные, которые должны использоваться, и применяемый к ним шаблон. Пример использования этого метода приведен ниже:

$('#flowerTmpl').tmpl(data).appendTo('#row1');

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

Метод tmpl() возвращает стандартный объект jQuery, который содержит элементы, полученные из шаблона. В данном случае это приводит к набору элементов div, каждый из которых содержит элементы img, label и input, сконфигурированные для одного из объектов, содержащихся в массиве данных. Для вставки всего набора в качестве дочернего элемента в элемент row1 используется метод appendTo(). Результат представлен на рисунке:

Использование шаблонов данных

Модификация результата

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

...
   
    $('#flowerTmpl').tmpl(data)
            .slice(0, 3).appendTo('#row1').end().end()
			.slice(3).appendTo("#row2");

В этом примере методы slice() и end() используются для сужения и расширения набора выбранных элементов, а метод appendTo() — для добавления поднаборов элементов, сгенерированных с помощью шаблона, в различные ряды.

Обратите внимание: для возврата набора в исходное состояние, в котором он находился до применения методов slice() и appendTo(), метод end() пришлось вызывать два раза подряд. Ничего противозаконного в этом нет, и я охотно использую метод end(), чтобы выполнить необходимые действия в рамках одной инструкции, но последовательность end().end() не вызывает у меня восторга. В подобных случаях я предпочитаю разбивать всю последовательность действий на ряд отдельных операций, как показано в примере ниже:

...
   
    var templResult = $('#flowerTmpl').tmpl(data);
    templResult.slice(0, 3).appendTo('#row1');
    templResult.slice(3).appendTo("#row2");

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

Корректировка результата для получения требуемого макета страницы

Изменение способа предоставления входных данных

Другой возможный подход заключается в изменении способа передачи данных методу tmpl(). Соответствующий пример приведен ниже:

...
   
    var template = $('#flowerTmpl');
    template.tmpl(data.slice(0, 3)).appendTo("#row1");
    template.tmpl(data.slice(3)).appendTo("#row2");

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

Вычисление выражений

Объекты данных можно использовать не только для получения значений свойств. Если поместить между двумя фигурными скобками выражение JavaScript, то движок шаблонов вычислит его и вставит в сгенерированную шаблоном HTML-разметку. Соответствующий пример приведен ниже:

<script id="flowerTmpl" type="text/x-jquery-tmpl">
        <div class="dcell">
            <img src="${product}.png"/>
            <label for="${product}">${name}:</label>
            <input name="${product}" data-price="${price}" data-stock="${stocklevel}"
                value="${stocklevel > 0 ? 1: 0}" required />
        </div>
</script>

В этом шаблоне значение атрибута value элемента input устанавливается на основании значения свойства stocklevel с помощью тернарного условного оператора. Выражение, заключенное в фигурные скобки, играет ту же роль, какую играло бы записанное вместо него непосредственное значение свойства. Если значение свойства stocklevel больше нуля, то значение value устанавливается равным 1, в противном случае — 0.

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

Вычисление выражений в шаблоне

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

Использование переменных шаблона

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

Контекстные переменные шаблона
Переменная Описание
$data Возвращает текущий элемент данных
$item Возвращает текущий экземпляр шаблона
$ Функция $() библиотеки jQuery

Использование переменной $data

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

Я всегда стараюсь уменьшить объем кода шаблона до необходимого минимума и поэтому предпочитаю использовать переменную $data внутри функций JavaScript, которые затем вызываю из шаблона. Соответствующий демонстрационный пример приведен ниже:

<script>	
$(function() {
    
    var data = [
        { name: "Астра", product: "astor", stocklevel: "10", price: 2.99},
        { name: "Нарцисс", product: "daffodil", stocklevel: "12", price: 1.99},
        { name: "Роза", product: "rose", stocklevel: "2", price: 4.99},
        { name: "Пион", product: "peony", stocklevel: "0", price: 1.50},
        { name: "Примула", product: "primula", stocklevel: "1", price: 3.12},
        { name: "Подснежник", product: "snowdrop", stocklevel: "15", price: 0.99},
    ];
    
    var template = $('#flowerTmpl');
    template.tmpl(data.slice(0, 3)).appendTo("#row1");
    template.tmpl(data.slice(3)).appendTo("#row2");
});

function stockDisplay(product) {
            return product.stocklevel > 0 ? 1 : 0;
}
</script>
<script id="flowerTmpl" type="text/x-jquery-tmpl">
        <div class="dcell">
            <img src="http://professorweb.ru/downloads/jquery/${product}.png"/>
            <label for="${product}">${name}:</label>
            <input name="${product}" data-price="${price}" data-stock="${stocklevel}"
                value="${stockDisplay($data)}" required />
        </div>
</script>

В этом примере определяется функция stockDisplay(), возвращающая значение, которое должно отображаться в элементе input. Аргументом этой функции является объект данных, который мы получаем внутри шаблона с использованием переменной $data. Учитывая, что речь идет всего лишь о простом тернарном операторе, разница в удобочитаемости кода по сравнению с предыдущим вариантом не очень значительна, но в случае более сложных выражений или в случае многократного использования выражения в пределах одного шаблона она будет гораздо более ощутимой.

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

Использование функции $() внутри шаблона

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

Использование переменной $item

Объект, возвращаемый переменной $item, решает несколько задач. Первая из них — обеспечение возможности обмена дополнительными данными между сценарием JavaScript и шаблоном. Соответствующий пример приведен ниже:

<script>	
$(function() {
    
    var data = [
        { name: "Астра", product: "astor", stocklevel: "10", price: 2.99},
        { name: "Нарцисс", product: "daffodil", stocklevel: "12", price: 1.99},
        { name: "Роза", product: "rose", stocklevel: "2", price: 4.99},
        { name: "Пион", product: "peony", stocklevel: "0", price: 1.50},
        { name: "Примула", product: "primula", stocklevel: "1", price: 3.12},
        { name: "Подснежник", product: "snowdrop", stocklevel: "15", price: 0.99},
    ];
	
	$("<h2>Специальное предложение на сегодняшний день: " +
	            "<span id=offer data-discount='0.50'>скидка 50 центов</span></h2>")
                .insertAfter('h1')
                .css({ color: "red", fontSize: "14pt", textAlign: "center" });
    
    var options = {
         discount: $('#offer').data('discount'),
         stockDisplay: function(product) {
              return product.stocklevel > 0 ? 1 : 0;        
         }
    };
	
    var template = $('#flowerTmpl');
    template.tmpl(data.slice(0, 3), options).appendTo("#row1");
    template.tmpl(data.slice(3), options).appendTo("#row2");
});
</script>
<script id="flowerTmpl" type="text/x-jquery-tmpl">
        <div class="dcell">
            <img src="http://professorweb.ru/downloads/jquery/${product}.png"/>
            <label for="${product}">${name}:</label>
            <input name="${product}" 
			       data-price="${price - $item.discount}"
				   data-stock="${stocklevel}"
				   value="${$item.stockDisplay($data)}"
				   required />
        </div>
</script>

В этом примере мы создаем объект options, для которого определяются свойство (discount) и метод (stockDisplay()). Затем этот объект передается методу tmpl() в качестве второго аргумента. Доступ к свойствам и методам объекта из шаблона обеспечивает переменная $item. Как видите, для обработки скидки в цене, здесь используется свойство discount объекта options.

Хочу обратить ваше внимание на необходимость включения префикса $ в имена контекстных переменных: $item и $data. Такой же префикс обязателен и в конструкции дескриптора шаблона ${...}, используемой для подстановки значений в шаблон. Пропуск любого из этих префиксов является одной из наиболее распространенных ошибок.

О других применениях этого объекта мы поговорим далее.

Использование вложенных шаблонов

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

...
<script id="flowerTmpl" type="text/x-jquery-tmpl">
        <div class="dcell">
            <img src="http://professorweb.ru/downloads/jquery/${product}.png"/>
            <label for="${product}">${name}:</label>
            {{tmpl($data, $item) "#inputTmpl"}}
        </div>
</script>
<script id="inputTmpl" type="text/x-jquery-tmpl">
       <input name="${product}" 
			       data-price="${price - $item.discount}"
				   data-stock="${stocklevel}"
				   value="${$item.stockDisplay($data)}"
				   required />
</script>

В этом примере шаблон разбит на две части. Первая из них, шаблон flowerTmpl, вызывается для каждого элемента массива данных. В свою очередь, этот шаблон вызывает шаблон inputTmpl для создания элементов input. Вызов второго шаблона осуществляется с помощью дескриптора {{tmpl}}. В этом вызове используются три аргумента. Первые два — это текущий элемент данных и объект options; эти аргументы заключаются в круглые скобки. Третий аргумент — это вызываемый шаблон. Его можно задавать либо jQuery-селектором (что и сделано выше), либо переменной или функцией, определенной в сценарии.

Использование условных шаблонов

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

...
   
    {{if stocklevel > 0}}
		<div class="dcell">
            <img src="http://professorweb.ru/downloads/jquery/${product}.png"/>
            <label for="${product}">${name}:</label>
            <input name="${product}" 
			       data-price="${price - $item.discount}"
				   data-stock="${stocklevel}"
				   value="${$item.stockDisplay($data)}"
				   required />

        </div>
    {{/if}}

Условие указывается в дескрипторе {{if}}, и часть шаблона, заключенная между этим дескриптором и дескриптором {{/if}}, будет использоваться, только если результат вычисления условного выражения окажется равным true. Если же этот результат равен false, то указанная часть шаблона игнорируется. В данном случае проверяется значение свойства stocklevel, и если оно равно нулю, то игнорируется весь шаблон flowerTmpl. Это означает, что отображаться будут лишь те продукты, которые имеются в наличии на складе, как показано на рисунке:

Рендеринг шаблона лишь для тех видов цветочной продукции, которые имеются в наличии

Более сложные условия можно задавать с помощью дескриптора {{else}}, позволяющего определить часть шаблона, которая должна использоваться в тех случаях, когда результатом вычисления выражения в дескрипторе {{if}} является false. Соответствующий пример приведен ниже:

...

    {{if stocklevel > 5}}
		<div class="dcell">
            <img src="http://professorweb.ru/downloads/jquery/${product}.png"/>
            <label for="${product}">${name}:</label>
            <input name="${product}" 
			       data-price="${price - $item.discount}"
				   data-stock="${stocklevel}"
				   value="${$item.stockDisplay($data)}"
				   required />

        </div>
    {{else stocklevel > 0}}
	    <div class="dcell">
            <img src="http://professorweb.ru/downloads/jquery/${product}.png"/>
            <label style="color:red" for="${product}">${name}: (Небольшое количество)</label>
            <input name="${product}" 
			       data-price="${price - $item.discount}"
				   data-stock="${stocklevel}"
				   value="${$item.stockDisplay($data)}"
				   required />

        </div>
	{{else}}
	    <div class="dcell">
            <img src="http://professorweb.ru/downloads/jquery/${product}.png" style="opacity:0.5"/>
            <label style="color:grey" for="${product}">${name} (Нет в наличии)</label>
        </div>
	{{/if}}

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

Результат работы приведенного выше сценария представлен на рисунке:

Использование условных операторов в шаблоне
Пройди тесты
Лучший чат для C# программистов