Пользователь
0,0
рейтинг
31 января 2011 в 06:58

Разработка → Работаем с jQuery Templates

Введение


Плагин jQuery Templates – это «движок шаблонов», работающий на стороне клиента как расширение jQuery.

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

Разумеется, jQuery Templates – не единственный и не первый «движок шаблонов», но у него есть большое преимущество перед альтернативными вариантами – поддержка со стороны jQuery Team. Это позволяет нам не бояться того, что этот плагин окажется заброшенным, и различные проблемы, возникающие при выходе новых версий браузеров, придется решать своими силами.

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


Немного истории


Этот плагин был разработан в компании Microsoft на основе JavaScript Micro-templating library (автор – John Resig), некоторые подробности о разработке можно узнать из блог-поста Stephen Walther «An Introduction to jQuery Templates» и из поста John Resig «Templating Syntax» на форуме jQuery. С октября прошлого года плагины jQuery Templates, jQuery DataLink и jQuery Globalization стали частью проекта jQuery.

Часть первая, теоретическая


Приступаем к работе


Давайте начнем. Приведенный ниже пример показывает список фильмов, заданный в массиве (полный код примера – в файле BasicSample1.htm):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Простой пример (1)</title>
    <link href="Styles/Default.css" rel="Stylesheet" type="text/css" />
    <script src="Scripts/jquery-1.5rc1.js" type="text/javascript"></script>
    <script src="Scripts/jquery.tmpl.js" type="text/javascript"></script>
    <script src="DataItems.js" type="text/javascript"></script>
    <script id="movieTmpl" type="text/x-jquery-tmpl">
        <div class="movie-bag">
            <img src="Content/Thumbnails/${thumbnail}" class="thumbnail" />
            <div class="base-info">
                <h2>
                    ${title}
                </h2>
                <p>
                    Режиссер: ${director}<br />
                    В ролях: ${actors}<br />
                    Год: ${year}
                </p>
            </div>
        </div>
    </script>
    <script type="text/javascript">

        $(function () {
            $('#movieTmpl').tmpl(dataItems).appendTo('#movieListBag');
        });

    </script>
</head>
<body>
    <h1>Простой пример (1)</h1>
    <div id="movieListBag">
    </div>
</body>
</html>

А вот что вы увидите в браузере:



Давайте разберем этот пример подробно.

Итак, первое, что я делаю, это подключаю jQuery Core Library и jQuery Templates:

<script src="Scripts/jquery-1.5rc1.js" type="text/javascript"></script>
<script src="Scripts/jquery.tmpl.js" type="text/javascript"></script>

Ранее неоднократно говорилось о том, что jQuery Templates будут включены в jQuery Core Library – но в jQuery 1.5 RC1, вышедшем 24 января, шаблоны по прежнему отсутствуют.

Затем я загружаю список фильмов:

<script src="DataItems.js" type="text/javascript"></script>

Разумеется, вы можете подготовить исходные данные любым удобным вам способом – получить их с помощью AJAX-запроса, создать на основе пользовательского ввода и т.п., статический скрипт я использую только для примера.

Внутри файл DataItems.js выглядит следующим образом:

var dataItems = [
    {
        title: 'Бандиты',
        thumbnail: 'Bandits.jpg',
        director: 'Барри Левинсон',
        actors: ['Брюс Уиллис', 'Билли Боб Торнтон', 'Кейт Бланшетт'],
        year: 2001,
        budget: 95000000,
        grossRevenue: 67631903,
        rating: 0,
        frames: ['Bandits-1.jpg', 'Bandits-2.jpg', 'Bandits-3.jpg', 'Bandits-4.jpg', 'Bandits-5.jpg']
    },
    ...

Следующим шагом я создаю шаблон:

<script id="movieTmpl" type="text/x-jquery-tmpl">
    <div class="movie-bag">
        <img src="Content/Thumbnails/${thumbnail}" class="thumbnail" />
        <div class="base-info">
            <h2>
                ${title}
            </h2>
            <p>
                Режиссер: ${director}<br />
                В ролях: ${actors}<br />
                Год: ${year}
            </p>
        </div>
    </div>
</script>

Обратите внимание, что шаблон размещается в теге SCRIPT, а в качестве MIME-типа я указываю text/x-jquery-tmpl. Встретив при разборе документа незнакомый MIME-тип, браузер не пытается интерпретировать содержимое тега SCRIPT, что мне и требуется.

Вообще говоря, шаблон можно разместить в любом теге, например, в теге DIV:

<div id="movieTmpl" style="display: none">
    <div class="movie-bag">
        <img src="Content/Thumbnails/${thumbnail}" class="thumbnail" />
        <div class="base-info">
            <h2>
                ${title}
            </h2>
            <p>
                Режиссер: ${director}<br />
                В ролях: ${actors}<br />
                Год: ${year}
            </p>
        </div>
    </div>
</div>

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

Например, для приведенного выше примера будет сделана попытка загрузить несуществующую картинку:



А вот случае с таблицей все может быть гораздо интереснее (большое спасибо TEHEK за этот пример!):

<div id="movieTmpl" style="display: none">
    <table>
        <tbody>
            {{each dataItems}}
            <tr>
                <td>${title}</td>
                <td>${director}</td>
                <td>${year}</td>
            </tr>
            {{/each}}
        </tbody>
    </table>
</div>

Internet Explorer и Opera обработают этот код корректно:



А вот Chrome и Fire Fox «вытолкнут» лишний код за пределы таблицы, в результате чего таблица окажется пустой… Happy debugging! ;-)



Для тега SELECT будет наблюдаться аналогичная картина.

Я рекомендую при разработке размещать шаблоны в тегах DIV, чтобы воспользоваться всеми прелестями IntelliSence, а затем перемещать их в тег SCRIPT.

И, наконец, я инстанцирую шаблон с помощью следующего вызова:

$('#movieTmpl').tmpl(dataItems).appendTo('#movieListBag');

Что при этом происходит, я изобразил на приведенной ниже диаграмме:



Итак:
  1. Метод .tmpl() получает текст шаблона – т.е. inner text элемента, полученного с помощью вызова $('#movieTmpl').
  2. Текст шаблона компилируется – на его основе создается функция JavaScript.
  3. Создается «экземпляр шаблона» — объект, который содержит ссылку на элемент данных (поле data), переданный как аргумент метода .tmpl(). Методу .tmpl() можно передать массив, объект, null или вызвать его без аргументов. Если передать массив, то для каждого элемента массива будет создан свой экземпляр шаблона, ссылающийся на этот элемент, во всех остальных случаях будет создан только один экземпляр.
  4. Вызывается скомпилированная функция-шаблон, которой передается объект-экземпляр. Функция возвращает текст шаблона, в котором сделаны все подстановки.
  5. Полученный на предыдущем шаге текст преобразуется в коллекцию HTML-элементов. Ссылки на эти элементы также сохраняются в объекте-экземпляре (поле nodes), что позволяет в дальнейшем легко обновить «выход» шаблона при изменении исходных данных (см. раздел «Динамическое обновление»).
  6. И, наконец, метод .tmpl() возвращает jQuery-коллекцию HTML-элементов, которые добавляются в документ с помощью вызова appendTo('#movieListBag').


Выражения


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

Использование свойств объекта (элемента массива):

<h2>
    ${title}
</h2>

Использование выражений JavaScript:

<p>
    Бюджет: $${(budget / 1000000).toFixed(0)} млн.<br />
    Сборы: $${(grossRevenue / 1000000).toFixed(1)} млн.
</p>


Поля и методы экземпляра шаблона


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

Каждый экземпляр шаблона содержит следующие поля:
  1. data – содержит ссылку на элемент данных, связанный с экземпляром шаблона;
  2. tmpl – содержит ссылку на скомпилированный шаблон, используемый для рендеринга;
  3. parent – если шаблон был вызван из другого шаблона с помощью тега {{tmpl}}, содержит ссылку на «родительcкий» экземпляр шаблона;
  4. nodes – после рендеринга содержит ссылки на HTML-элементы, порожденные в результате применения шаблона.

Кроме того, метод .tmpl() принимает два аргумента – data и options. С аргументом data вы уже познакомились, через него передается ссылка на элемент данных. А используя аргумент options, можно передать ссылку на объект, все поля и методы которого будут перенесены в каждый экземпляр шаблона, созданный в методе .tmpl().

Ниже приведен пример использования этого параметра:

<script id="movieTmpl" type="text/x-jquery-tmpl">
    <div class="movie-bag">
        <img src="Content/Thumbnails/${thumbnail}" class="thumbnail" />
        <div class="base-info">
            <h2>
                ${title}
            </h2>
            <p>
                Режиссер: ${director}<br />
                В ролях: ${actors}<br />
                Год: ${year}<br />
                Бюджет: $${$item.formatBudget(budget)} млн.<br />
                Сборы: $${$item.formatGrossRevenue(grossRevenue)} млн.
            </p>
        </div>
    </div>
</script>


$(function () {
    $('#movieTmpl')
        .tmpl(
            dataItems,
            {
                formatBudget: function (value) {
                    return (value / 1000000).toFixed(0);
                },
                formatGrossRevenue: function (value) {
                    return (value / 1000000).toFixed(1);
                }
            })
        .appendTo('#movieListBag');
});

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

И, наконец, экземпляр шаблона содержит методы update() и html(), использование которых я покажу далее.

Как выглядит скомпилированный шаблон?


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

$('#compiledTemplateBag').text('' + $('#movieTmpl').template());

Шаблон, использованный в примере выше, после компиляции выглядит следующим образом (текст отформатирован для лучшей читаемости):

function anonymous(jQuery, $item) {
    var $ = jQuery, call, _ = [], $data = $item.data;

    with ($data) {
        _.push('<div class="movie-bag"> <img src="Content/Thumbnails/');

        if (typeof (thumbnail) !== 'undefined' && (thumbnail) != null) {
            _.push($.encode((typeof (thumbnail) === 'function' ? (thumbnail).call($item) : (thumbnail))));
        }

        _.push('" class="thumbnail" /> <div class="base-info"> <h2> ');

        if (typeof (title) !== 'undefined' && (title) != null) {
            _.push($.encode((typeof (title) === 'function' ? (title).call($item) : (title))));
        }

        _.push(' </h2> <p> Режиссер: ');

        if (typeof (director) !== 'undefined' && (director) != null) {
            _.push($.encode((typeof (director) === 'function' ? (director).call($item) : (director))));
        }

        _.push('<br /> В ролях: ');

        if (typeof (actors) !== 'undefined' && (actors) != null) {
            _.push($.encode((typeof (actors) === 'function' ? (actors).call($item) : (actors))));
        }

        _.push('<br /> Год: ');

        if (typeof (year) !== 'undefined' && (year) != null) {
            _.push($.encode((typeof (year) === 'function' ? (year).call($item) : (year))));
        }
        _.push(' </p> </div> </div>');
    }

    return _;
}

Думаю, что теперь вам должно быть понятно, как обрабатываются выражения, указанные в теге ${...} – и это понимание может существенно помочь вам при отладке! Дело в том, что jQuery Templates выполняет относительно простое преобразование текста шаблона, поэтому если вы допустите ошибку в выражении, то сообщение об ошибке будет относиться к тексту полученной в результате преобразования функции и часто может быть крайне невразумительным.



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

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

Часть вторая, практическая


Условия


Для того чтобы применять части шаблона в зависимости от некоторых условий, в jQuery Templates используются теги {{if}}...{{else}}...{{/if}}.

Приведенный ниже пример показывает использование этих тегов (полный код примера – в файле IfElseTag.htm):

<p>
    Носитель:
    {{if $item.data.media == 'dvd'}}
        <img src="Images/media-dvd.png" />
    {{else $item.data.media == 'blue-ray'}}
        <img src="Images/media-blueray.png" />
    {{else}}
        <img src="Images/media-unspecified.png" />
    {{/if}}
</p>

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



В качестве условия в тегах {{if}} и {{else}} можно использовать любое корректное JavaScript-выражение.

Обработка коллекций


Для обработки коллекций в шаблонах используется тег {{each}}...{{/each}}. Приведенный ниже пример показывает использование тега {{each}} для вывода списка актеров (полный код примера – в файле EachTag1.htm):

В ролях:
{{each actors}}
    ${$value}
    {{if $index < $data.actors.length - 1}}
        ,
    {{/if}}
{{/each}}

В браузере этот пример выглядит следующим образом:



В качестве аргумента тегу {{each}} можно передать массив, объект, или jQuery-коллекцию. Внутри тег {{each}} использует вызов jQuery.each(), поэтому все, что сказано в документации о поведении jQuery.each() справедливо и для тега {{each}}. Пример ниже демонстрирует использование тега {{each}} для показа всех свойств объекта (полный код примера – в файле EachTag2.htm):

<script id="objTmpl" type="text/x-jquery-tmpl">
    <div>
        <dl>
            {{each $data}}
                <dt>
                    ${$index}
                </dt>
                <dd>
                    ${$value}
                </dd>
            {{/each}}
        </dl>
    </div>
</script>

Внутри тега {{each}} доступно две переменные: $value, которая содержит ссылку на текущий элемент массива, и $index, которая содержит индекс текущего элемента массива или имя свойства.

Разумеется, внутри тега {{each}} можно использовать другие теги, а кроме того, вам по-прежнему будут доступны переменные $item и $data. В приведенном примере переменные $index и $data вместе с тегом {{if}} используются, чтобы вывести запятую между именами актеров – к сожалению, переменная $last не предусмотрена, хотя она была бы очень полезна!

Наконец, если у вас вдруг возникнет такая необходимость, вы можете изменить имена переменных по умолчанию. В примере, приведенном ниже, эти имена изменены на myIndex и myValue (полный код примера – в файле EachTag3.htm):

В ролях:
{{each(myIndex, myValue) actors}}
    ${myValue}
    {{if myIndex < $data.actors.length - 1}}
        ,
    {{/if}}
{{/each}}

Кстати, попытка изменить имя только для переменной $index ни к чему хорошему не приведет – ошибки не будет, но и доступа к текущему значению вы тоже не сможете получить!

Вложенные шаблоны


Шаблоны могут быть очень большими – и тогда имеет смысл разделить их на несколько частей меньшего объема или включать повторяющиеся части, которые логично выделить в отдельный шаблон. В jQuery Templates это осуществляется с помощью вложенных шаблонов, для вызова которых используется тег {{tmpl}}.

Пример ниже иллюстрирует, как вынести часть кода шаблона в другой шаблон (полный код примера – в файле NestedTemplates1.htm):

<script id="movieTmpl" type="text/x-jquery-tmpl">
    ...
    <p>
        Режиссер: ${director}<br />
        В ролях: {{tmpl '#actorsTmpl'}}<br />
        Год: ${year}
    </p>
    ...
</script>
<script id="actorsTmpl" type="text/x-jquery-tmpl">
    {{each actors}}
        ${$value}
        {{if $index < $data.actors.length - 1}}
            ,
        {{/if}}
    {{/each}}
</script>

В теге {{tmpl}} обязательно указывается jQuery-селектор вызываемого шаблона или имя шаблона, ранее сохраненного в кэше. Т.к. в этом примере других аргументов у тега {{tmpl}} нет, вложенный шаблон получит тот же самый элемент данных, что и родительский – но экземпляр шаблона у него будет свой, причем поле parent в нем будет ссылаться на родительский экземпляр шаблона.

Следующий пример демонстрирует передачу во вложенный шаблон нового элемента данных и использование ссылки на родительский экземпляр шаблона. Как и в случае использования метода .tmpl(), если при вызове вложенного шаблона указать массив, то шаблон будет применен для каждого элемента массива (полный код примера – в файле NestedTemplates2.htm):

<script id="movieTmpl" type="text/x-jquery-tmpl">
    ...
    <p>
        Режиссер: ${director}<br />
        В ролях: {{tmpl(actors) '#actors_template'}}<br />
        Год: ${year}
    </p>
    ...
</script>
<script id="actors_template" type="text/x-jquery-tmpl">
    ${$data}
    {{if $data !== $item.parent.data.actors[$item.parent.data.actors.length - 1]}}
        ,
    {{/if}}
</script>

И, наконец, последний пример в этом разделе показывает, как передать во вложенный шаблон аргумент options, а заодно демонстрирует, как аргумент options можно использовать для определения последнего элемента в обрабатываемом массиве (полный код примера – в файле NestedTemplates3.htm):

<script id="movieTmpl" type="text/x-jquery-tmpl">
    ...
    <p>
        Режиссер: ${director}<br />
        В ролях: {{tmpl(actors, { last: actors[actors.length - 1] }) '#actors_template'}}<br />
        Год: ${year}
    </p>
    ...
</script>
<script id="actors_template" type="text/x-jquery-tmpl">
    ${$data}
    {{if $data !== $item.last}}
        ,
    {{/if}}
</script>


Трансформация


Еще одна интересная возможность jQuery Templates связана с трансформацией HTML-разметки, для чего используется тег {{wrap}} (вообще говоря, wrap – это «обертывание», но мне кажется что термин «трансформация» лучше отражает суть происходящего).

Классический пример использования тега {{wrap}} – это создание закладок:



Вот как это выглядит внутри (полный код примера – в файле Transformation1.htm):

<script id="tabsContent" type="text/x-jquery-tmpl">
    {{wrap(null, { viewState: $item.viewState }) '#tabsTmpl'}}
        <h1>English</h1>
        <div>
            <h2>The Ballad of East and West</h2>
            <h3>Rudyard Kipling</h3>
            <p>OH, East is East, and West is West...</p>
        </div>
    {{/wrap}}
</script>

Исходные данные для трансформации размещаются в шаблоне tabContent – именно этот шаблон я дальше будут инстанцировать.

HTML-разметка, которую я буду трансформировать, помещается в теге {{wrap}}. Для тега {{wrap}} используется точно такая же нотация вызова, что и для тега {[tmpl}} – т.е., вы обязательно должны указать селектор или имя шаблона и дополнительно можете передать ссылку на элемент данных и options. В данном случае в параметре options я передаю ссылку на объект viewState, который содержит индекс выбранной закладки.

Код для трансформации выглядит следующим образом:

<script id="tabsTmpl" type="text/x-jquery-tmpl">
    <div class="tab-head">
        {{each $item.html("h1", true)}}
            <div class="tab {{if $index == $item.viewState.index}}active{{/if}}">
                ${$value}
            </div>
        {{/each}}
    </div>
    <div class="tab-body">
        {{html $item.html("div")[$item.viewState.index]}}
    </div>
</script>

Здесь в DIV.tab-head содержатся ярлыки закладок, а в DIV.tab-body – содержимое выбранной закладки.

Вызов $item.html(«h1», true)} производит выборку данных. Первый параметр (filter) задает фильтр для элементов первого уровня, а второй параметр (textOnly) указывает, что я хочу получить только inner text каждого выбранного элемента – т.е. в данном случае я получу коллекцию строк, каждая из которых будет содержать текст из соответствующего тега H1. Из этой коллекции строк я и создаю ярлыки закладок.

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

Еще один забавный момент связан со способом задания селектора – при попытке задать селектор в моих любимых одинарных кавычках при компиляции шаблона возникает ошибка.

Следующим шагом я выбираю из исходных данных DIV, соответствующий активной закладке, и помещаю его в DIV.tab-body. Обратите внимание, что в данном случае я использую тег {{html}}, а не ${...} – дело в том, что при использовании тега ${...} выполняется экранирование специальных символов, а мне это в данном случае не нужно.

Наконец, я инстанцирую шаблон:

var viewState = { index: 0 };
$('#tabsContent').tmpl(null, { viewState: viewState }).appendTo('#tabsBag');

И устанавливаю обработчики нажатий на закладки:

$('#tabsBag').delegate('.tab', 'click', function () {
    var item = $.tmplItem(this);
    item.viewState.index = $(this).index();
    item.update();
});

Что же происходит в обработчике нажатия? Во-первых, с помощью вызова $.tmplItem(this) я получаю ссылку на экземпляр шаблона, связанный с текущим элементом. Затем, я меняю индекс выбранной закладки – помните, что у нас в каждом экземпляре шаблона содержится ссылка на объект viewState? И в финале я вызываю метод update() экземпляра шаблона, что вызывает повторный рендеринг шаблона, но уже с другим значением во viewState.

Следующий пример более сложный, но зато и более полезный.

Первое его отличие от предыдущего примера заключается в том, что исходные данные для трансформации я создаю динамически (полный код примера – в файле Transformation2.htm):

<script id="movieListTmpl" type="text/x-jquery-tmpl">
    {{wrap(null, {viewState: $item.viewState}) '#tabsTmpl'}}
        {{tmpl(dataItems) '#movieTmpl'}}
    {{/wrap}}
        </script>
<script id="movieTmpl" type="text/x-jquery-tmpl">
    <div class="movie-bag">
        <img src="Content/Thumbnails/${thumbnail}" class="thumbnail" />
        ...
    </div>
</script>

var viewState = { index: 0 };
$('#movieListTmpl').tmpl({ dataItems: dataItems }, { viewState: viewState }).appendTo('#tabsBag');

Второе отличие – т.к. теги с названием закладок теперь находятся не на первом уровне, при создании ярлыков мне приходится использовать селекторы jQuery для получения этой информации:

<script id="tabsTmpl" type="text/x-jquery-tmpl">
    <div class="tab-head">
        {{each $item.html("div")}}
            <div class="tab {{if $index == $item.viewState.index}}active{{/if}}">
                ${$('h2', $value).text()}
            </div>
        {{/each}}
    </div>
    <div class="tab-body">
        {{html $item.html("div")[$item.viewState.index]}}
    </div>
</script>

В браузере этот пример будет выглядеть так:



Кэширование шаблонов


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

Компиляция и кэширование шаблона производится следующим образом:

$('#movieTmpl').template('movieTmpl');

Скомпилированный шаблон сохраняется во внутреннем кэше jQuery Templates под именем movieTmpl. Для обращения к кэшированному шаблону используется метод jQuery.tmpl(), и первым параметром ему передается имя кэшированного шаблона:

$.tmpl('movieTmpl', dataItems).appendTo('#movieListBag');

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

Код шаблона практически не отличается от тех, что я использовал ранее, единственное его отличие – под описанием фильма дополнительно размещаются ссылки для навигации (полный код примера – в файле CachedTemplates.htm):

<script id="movieTmpl" type="text/x-jquery-tmpl">
    <div class="movie-bag">
        <img src="Content/Thumbnails/${thumbnail}" class="thumbnail" />
        <div class="base-info">
            <h2>
                ${title}
            </h2>
            <p>
                Режиссер: ${director}<br />
                В ролях: ${actors}<br />
                Год: ${year}
            </p>
        </div>
    </div>
    <div>
        {{if $item.canMoveBack}}
            <a href="javascript:" class="nav-link" x-inc="-1">[Назад]</a>
        {{/if}}
        {{if $item.canMoveFwd}}
            <a href="javascript:" class="nav-link" x-inc="1">[Вперед]</a>
        {{/if}}
    </div>
</script>

Сопутствующий скрипт так же несложен:

var movieIndex = 0;

$(function () {
    $('#movieTmpl').template('movieTmpl');

    updateMovie();

    $('#movieBag').delegate('.nav-link', 'click', function () {
        movieIndex += parseInt($(this).attr('x-inc'));
        updateMovie();
    });
});

function updateMovie() {
    $('#movieBag').empty();

    $('#movieBag').append(
        $.tmpl('movieTmpl',
        dataItems[movieIndex],
        {
            canMoveBack: movieIndex > 0,
            canMoveFwd: movieIndex < dataItems.length - 1
        }));
}

Обработчик нажатия на навигационную ссылку меняет индекс выбранного фильма, а затем вызывает функцию updateMovie(), которая сначала очищает контейнер с описанием фильма, а затем заполняет его новыми данными.

Вот как этот пример выглядит в браузере:



Динамическая загрузка шаблонов


К сожалению, код, показанный ниже, работать не будет:

<script id="movieTmpl" src="Templates/DynamicLoading.htm" type="text/x-jquery-tmpl"></script>

Браузер, конечно, загрузит соответствующий файл – но вот получить доступ к его содержимому у вас все равно не получится.

Но шаблон все-таки можно разместить в отдельном файле, и для этого потребуется буквально одна дополнительная строчка кода (полный код примера – в файле DynamicLoading.htm):

$(function () {
    $.get('Templates/DynamicLoading.htm', {}, function (templateBody) {
        $.tmpl(templateBody, dataItems).appendTo('#movieListBag');
    });
});

Т.к. в данном случае мы получаем шаблон в виде текста, для его инстанцирования применяется метод jQuery.tmpl(), первым аргументом которому передается полученный текст шаблона.

Да, метод jQuery.tmpl() используется для инстанцирования как кэшированных шаблонов по имени, так и шаблонов, заданных в виде текста (традиция!..) – впрочем, он достаточно «умен», чтобы отличить их друг от друга.

Если вам нужно загрузить несколько связанных шаблонов, вы можете воспользоваться библиотекой WaitSync (см. «Синхронизация асинхронных вызовов. WaitSync») или написать свой синхронизатор (полный код примера – в файле DynamicLoading2.htm):

$(function () {
    var ws = new WaitSync(function () {
        $.tmpl('movieTmpl', dataItems).appendTo('#movieListBag');
    });

    $.ajax({
        cache: false,
        url: 'Templates/MovieTmpl.htm',
        success: ws.wrap('MovieTmpl', function (templateBody) {
            $.template('movieTmpl', templateBody);
        }),
        error: ws.wrap('MovieTmpl', function () {
            alert('Error loading MovieTmpl.htm!');
        })
    });

    $.ajax({
        cache: false,
        url: 'Templates/ActorsTmpl.htm',
        success: ws.wrap('ActorsTmpl', function (templateBody) {
            $.template('actorsTmpl', templateBody);
        }),
        error: ws.wrap('ActorsTmpl', function () {
            alert('Error loading ActorsTmpl.htm!');
        })
    });
});

Обратите внимание, что в данном случае шаблон actorsTmpl вызывается по имени (файл Templates\MovieTmpl.htm):

<p>
    Режиссер: ${director}<br />
    В ролях: {{tmpl(actors, { last: actors[actors.length - 1] }) 'actorsTmpl'}}<br />
    Год: ${year}
</p>


Динамическое обновление


В последнем разделе практической части я покажу еще два сценария работы jQuery Templates – изменение связанных данных и подмену связанного шаблона.

В приведенном ниже примере для каждого фильма добавлена возможность изменить его рейтинг (полный код примера – в файле DynamicUpdate1.htm):

<script id="movieTmpl" type="text/x-jquery-tmpl">
    <div class="movie-bag">
        <img src="Content/Thumbnails/${thumbnail}" class="thumbnail" />
        <div class="base-info">
            ...
            <p>
                Рейтинг:
                <img src="Images/rating-down.png" alt="" title="-1" class="rating-button" x-inc="-1" />
                ${rating}
                <img src="Images/rating-up.png" alt="" title="+1" class="rating-button" x-inc="1" />
            </p>
        </div>
    </div>
</script>

Сопутствующий код:

$(function () {
    $('#movieTmpl').tmpl(dataItems).appendTo('#movieListBag');

    $('#movieListBag').delegate('.rating-button', 'click', function () {
        var item = $.tmplItem(this);
        item.data.rating += parseInt($(this).attr('x-inc'));
        item.update();
    });
});

Как видно, этот код очень похож на код закладок из раздела «Трансформация», только при работе с закладками я обращался к разделяемому объекту viewState, а здесь я работаю с данными, связанными с экземпляром шаблона.

В браузере этот пример выглядит так:



Следующий пример демонстрирует подмену связанного шаблона (полный код примера – в файле DynamicUpdate2.htm):

<script id="movieShortTmpl" type="text/x-jquery-tmpl">
    <div class="movie-bag">
        {{tmpl '#movieMainTmpl'}}
        <p style="clear: both"><a href="javascript:" class="more-details">[Больше...]</a></p>
    </div>
</script>
<script id="movieFullTmpl" type="text/x-jquery-tmpl">
    <div class="movie-bag">
        {{tmpl '#movieMainTmpl'}}
        <p style="clear: both">Кадры из фильма:</p>
        <div>
            {{each frames}}
                <img src="Content/Frames/${$value}" />
            {{/each}}
        </div>
        <p><a href="javascript:" class="more-details">[Меньше...]</a></p>
    </div>
</script>

Здесь я использую два шаблона, movieShortTmpl и movieFullTmpl, общая часть которых вынесена в шаблон movieMainTmpl.

Сопутствующий код:

$(function () {
    var shortTemplate = $('#movieShortTmpl').template('shortTemplate');
    var fullTemplate = $('#movieFullTmpl').template();

    $.tmpl('shortTemplate', dataItems).appendTo('#movieListBag');

    $('#movieListBag').delegate('.more-details', 'click', function () {
        var item = $.tmplItem(this);
        item.tmpl = item.tmpl === shortTemplate ? fullTemplate : shortTemplate;
        item.update();
    });
});

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

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

Код же обработчика нажатия на ссылки «Больше/меньше» очень похож на код из предыдущего примера, в нем я присваиваю полю tmpl ссылку на один из скомпилированных ранее шаблонов и вызываю метод update() для рендеринга.

В браузере этот пример выглядит следующим образом:



Заключение


Код примеров, использованных в статье, вы можете загрузить по этой ссылке.

Загрузить jQuery Templates можно с web-сайта ASP.NET CDN или напрямую из репозитария GitHub:

Документация по jQuery Templates доступна на сайте документации jQuery.

В примерах я использовал jQuery 1.5 RC1, но работать jQuery Templates будут, начиная с версии jQuery 1.4.2 – в моем последнем проекте была использована именно такая связка.

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

Чтобы получить представление о производительности jQuery Templates по сравнению с другими «движками шаблонов», взгляните на статью Brian Landau «Benchmarking Javascript Templating Libraries».

И в заключение я хочу выразить благодарность Виталию Дильмухаметову и Денису Гладких за ценные замечания, сделанные в процессе работы над статьей.
Сергей Попов @psg1234
карма
66,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (67)

  • +2
    Содержательно. Спасибо.
  • –5
    Отличная либа! Уже предвижу, в каких проектах и где ее можно применить
    • +3
      О, предвидцы на хабре! Подскажите пожалуйста мне где можно эту либу использовать, в каких проектах?
      • 0
        в персональных, а вы с какой целью интересуетесь?
        • +1
          Омг, извините, не знал что чувство юмора это так сложно
          • +7
            Не обижайтесь, но в этот раз шутка так себе =)
  • +1
    Удобно, но сеошники это не одобрят…
    • +8
      Не SEO единым… Есть еще и бизнес web apps.
      • +1
        А зачем это в бизнес web-apps? такие системы не являются настолько нагруженными что бы оптимизировать их выносом обработки фронтента на компьютер пользователя. Написание бекенда и фронтенда на разных системах шаблонизации такой же бред, усложняющий поддержку.
        То есть остаются исключительно специфические web системы, которые опять же не настолько нагружены что бы тратить время программистов на отлавливание багов в шаблонах созданных таким способом.

        Итог:
        + перенос нагрузки по формированию шаблона на сторону пользователя
        — дополнительные навыки верстальщика и программиста
        — сеошники это точно не одобрят
        — дополнительные затраты времени отлавливание ошибок
        • 0
          — дополнительные навыки верстальщика и программиста
          4 часа на чтение и усвоение 6 листов документации
          — сеошники это точно не одобрят
          никто не обязывает, что вообще за глупость такая — использовать это публичной части. что надо курить, чтобы додуматься до этого?..
          — дополнительные затраты времени отлавливание ошибок
          что за глупость? чем отличается время поиска ошибок с использованием одного или другого шаблонизатора. здесь же нет сложной логики, просто вывод переменных. если ошибся с названием переменной, то в консоль выведется ошибка, что такой переменной нет, остается только найти ее с помощью ctrl+f. так же, если понимать во что компилируется этот шаблон, то есть возможность использовать и вывод в консоль, и трассировку и прочее.

          просто признайтесь, что вам плагин просто не понравился. пора бы открыть глаза, на дворе 21 век.
          • 0
            — 4 часа на чтение и усвоение 6 листов документации
            Это вы ответите тогда когда вам скажут через 2 часа все должно быть готово, какой смысл тратить это время, если этого можно не делать, да и потом еще получить проблемы с совместимостью.

            — никто не обязывает, что вообще за глупость такая — использовать это публичной части. что надо курить, чтобы додуматься до этого?..
            Покажите мне смысл писать для фронтенда и бекенда используя разные шаблонизаторы? меня вполне устраивает ajax подгрузка на бекенде.

            — что за глупость? чем отличается время поиска ошибок с использованием одного или другого шаблонизатора…
            Цитата с текста статьи: Дело в том, что jQuery Templates выполняет относительно простое преобразование текста шаблона, поэтому если вы допустите ошибку в выражении, то сообщение об ошибке будет относиться к тексту полученной в результате преобразования функции и часто может быть крайне невразумительным.

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

            Статья интересная, технология интересная, я хотел сказать только то что это узкоспециализированное направление, и применять бездумно налево и на право не стоит.
            • 0
              «Это вы ответите тогда когда вам скажут через 2 часа все должно быть готово»
              подобные предположения вообще не имеют смысла, потому что если в 2012… в работе у вас все время так? рубите-рубите своим незаточенным топором…

              «используя разные шаблонизаторы»
              можно вообще не использовать новые технологии. а смысл вообще аякс использовать? иногда так проще получить какие-то данные. но иногда если есть зависимости, то приходится писать больше кода.

              «сообщение об ошибке будет относиться к тексту полученной в результате преобразования функции»
              а в других шаблонизаторах не так? сраный смарти например.

              «Это не означает что все велосипедисты должны пересесть на газонокосилки»
              никто и не заставляет. ваши выводы — не верны

              «это узкоспециализированное направление, и применять бездумно налево и на право не стоит.»
              это очевидно =)
    • +2
      Отлично подойдет для неиндексируемого контента типа бекэндов! Спасибо за обзор.
    • 0
      Что мешает иметь статику для роботов и недобраузеров, а для людей перехватывать события (клики и тп), подгружать только данные и отрисовывать их темплейтами?
      Вроде ж, стандартная схема.
      • +1
        Двойная работа?..
        • 0
          Во-первых, это практично. Упрощаем жизнь хорошим пользователям с правильными браузерами/опциями и уменьшаем нагрузку на сервер.

          Во-вторых, если считать грубо, то работа не двойная, а много меньшая, так как у нас уже готова модель (при правильной организации приложения), уже есть статический вью, и контроллер для их связки. Осталось сделать, грубо говоря, еще один вью (jquery templates) и немного подтюнить контроллер для анализа типа запроса (ajax/не ajax). Так что процентов до 30 перетрудимся (но точно не еще на 100).
          • 0
            Идеально было бы иметь такой же парсер темплейтов на сервере.
            Если это Microsoft сделал, то может уже такой на .net, может кто знает?
            • 0
              • +1
                Вообще я имею ввиду под «таким же парсером» — с таким же синтаксисом шаблона.

                Чтобы html-темплейт и входные данные не переписывать, а использовать одни и те же на серверной и клиентской части.
                • 0
                  Хм… Принципиально это осуществимо, но на практике на стороне сервера или будет масса ограничений, или все будет работать невыносимо медленно.
                  • 0
                    Что значит невыносимо медленно. Медленнее, чем парсинг .aspx страниц?
                    Ну да, они компилируются, но ведь и темплейты компилируются, даже клиентские.

                    А что Razor? У него какая-то особая обработка? Почему остальные должны работать невыносимо медленнее?
                    • 0
                      Вы забыли, что в jQuery Templates вы можете в том числе работать с DOM страницы, а для сервера разбирать каждую отдаваемую им страницу будет очень накладно.

                      Ну а если от этого отказаться, то все вполне осуществимо, можно даже попробовать обойтись без JS на стороне сервера ;-)

                      Script#: C# to Javascript Converter
                      michaelsync.net/2007/10/29/script-c-to-javascript-converter

                      Что же касается Web Forms и Razor, то они после разбора и компиляции кэшируются. Кстати, в отличие от Razor ASPX даже не парсится, а просто разбивается на части по ключевым меткам (<@...> и <%...%>).
                      • 0
                        Вы наверное, не очень понимаете зачем мне этот парсер.
                        Если почитать текущую ветку, то становится понятно, что нужен механизм создания страницы для роботов и юзеров без JS.

                        Чтобы сформированный HTML сразу передался на клиента. Тут не нужны никакие DOM и обновление при изменении данных и прочие плюшки.
                        • 0
                          Зря вы так, я все прекрасно понимаю, поэтому и сказал о куче ограничений :)
  • +11
    отличная статья, пожалуй, самая полная, если не единственная, на русском по теме
    • +4
      вас в гугле штоли забанили? чем вы, хомячки хабра, лучше хомячков из контакта, если у вам интернет заканчивается на хабре?
      www.linkexchanger.su/2010/644.html
      • +1
        А если колонку сделать в 150 пикселей, указанная статья будет почти бесконечная)
  • +1
    Уже применил в генераторе смет моей студии разработки сайтов.
    • 0
      раньше использовал sprintf
  • 0
    Спасибо, обязательно попробую на одном из своих проектов.
  • 0
    Огромное спасибо за статью! Уже успели обсудить с коллегами, сегодня пробуем внедрять.
  • НЛО прилетело и опубликовало эту надпись здесь
  • –2
    В избранное!
  • +1
    Коллеги, объясните мне пожалуйста практическую ценность данной статьи? Чем она отличается от официально документации и от статей на русском, которые были написаны несколько месяцев назад? Если вы собираетесь это использовать то проще же просто поискать в гугле. А если не собираетесь, то зачем вам сейчас эта информация? Почему вы считаете этот информационный мусор полезным? Конечно, я уже задавал этот вопрос в других подобных статьях, что отразилось сами знаете на чем, но я до сих пор не понимаю как можно быть довольным отрывочными сведениями? Ведь статья в любом случае хуже документации охватывает объем возможностей предмета обзора. Иногда, конечно, статьи помогают разобраться и начать использовать что-то сложное, но в данном случае плагин настолько прост, что рассказывать не о чем.
    У меня есть несколько знакомых, которые тоже занимаются «сайтостроительством», только их знания ограничены подобными статьями, и они меня постоянно задалбывают тупейшими вопросами, а причина — одна: они не читают книг, где описаны все основы. А заказчики потом получают говносайты с проваленными сроками. Давайте мы все таки будем стараться быть профессионалами, и будем читать книги. А статьи будем требовать только на те темы, которые не освещены в других местах, которые содержат уникальные исследования авторов.
    А если у этой статьи основная цель — рассказать о том, что такой плагин есть, то достаточно воспользоваться поиском, об этом новость уже была.
    У меня и без этой статьи почему-то не было сложностей с использованием этого плагина в одном из приложений для Моего Мира. Данные я получаю в json, и генерирую представление уже загруженными шаблонами. Визуально работает очень быстро.
    • 0
      объясните мне пожалуйста практическую ценность вашего комментария?
      • –3
        я взываю к разуму хомячков, чтобы в интернете было меньше бесполезной информации.
        все мы не любим, когда в поисковой выдаче мы получаем кучу сеошного мусора, который генерируют ради увеличения посещаемости и может быть продаж с сайта. а в данном случае цель — увеличение сами-знаете-чего.
        • +2
          это крайне полезная статья, а бесполезное тут — только ваш собственный комментарий.
          воспользуйтесь своим собственным советом и перестаньте засорять интернеты
          • –4
            ты не прав. эта статья — лишь повторение того, что уже было где-то написано. но хомячки такие хомячки и их тысячи…
            • 0
              Да, тема уже освещалась, даже выше была дана ссылка на линкэксчейнджер, но там слишком сухо все дано, да и приравнивать подобные статьи к SEO-мусору совсем неправильно. Статья как основа для ознакомления с плагином очень даже хороша: дана вся информация, видны авторские исследования и советы по использованию. Получилось довольно полно, к тому ж автор честно дал ссылки на другие источники для более глубоко погружения в тему.
              • 0
                я не вижу смысла в «исследованиях» доволно простых и очевидных вещей. единственное принципальное отличие этой статьи от документации и прочих материалов — это место публикации.
                но сообщество не согласно с моей точкой зрения. ок, я не обижаюсь на младших «братьев» =)
  • +1
    Есть тесты производительности? И если можно, где в реальных проектах это может быть удобным? Мне видится удобным для каких-то небольших списков данных, как в приведенном примере, а в качестве модели для построения всего сайта может быть громоздко.
    • 0
      Производительность можно оценить, прочитав вот эту статью (ссылка на нее есть в конце статьи):

      www.viget.com/extend/benchmarking-javascript-templating-libraries/

      Правда, в ней речь идет других jQuery Templates… :) Но тестовый код доступен на GitHub, и вы можете добавить туда свой тест — и если вы это сделаете, я буду вам очень признателен. Я же постараюсь в ближайшие дни найти время и протестировать производительность jQuery Templates + последние версии других template engines.

      На мой взгляд производительность jQuery Templates «достаточна для любых практических целей», IMHO тут важно соблюсти баланс (ну, например, использовать {{each}} для вывода массива строк через запятую не лучший вариант, метод join() справится с этим лучше — но зато это хороший демонстрационный пример).

      В реальных проектах я использую jQuery Templates для показа различных списков, для кастомизации диалогов, использовал его также в достаточно нетривиальном редакторе workflow – и сэкономил при этом много времени. Каких-то проблем с громоздкостью я не заметил, но это индивидуальное восприятие.

      Использовать jQuery Templates в своих проектах или нет решать вам, исходя из ваших индивидуальных предпочтений и требований к вашим проектам. Основную причину, по которой я выбор именно jQuery я уже озвучил – я не большой любитель решать проблемы в чужом коде, а этому плагину гарантирована поддержка как части проекта jQuery.
    • +1
      Производительность не очень: habrahabr.ru/blogs/jquery/108067/
  • 0
    Продолжая идею, переносим на js контроллер и модели, и посылаем только запросы к бд и системе.
    • 0
      Мне кажется, что это не очень хорошая идея ;-) А вот three-tier app вполне можно построить.
  • 0
    А шаблоны можно как-то закэшировать на клиенте? Что-то типа
    • 0
      Парсер скушал
      <script type="text/x-jquery-tmpl" src="template.html"></script>
      • 0
        Так – нет, вы не сможете получить доступ к тексту шаблона. Но при динамической загрузке шаблоны будут кэшироваться by default.
        • 0
          Сейчас проверил — браузер не загружает автоматически script src с неизвестным mime.
          Как вариант, модифицировать либу и делать XMLHTTPRequest запросы на src таких элементов. Либо на ONDOMLOADED повесить коллбэк, который загрузит все скрипты с MIME == text/x-jquery-tmpl в какой-то список и работать уже оттуда с ними.
          Как считаете, хорошая идея?
          • +1
            1) По крайней мере IE запрашивает соответствующий файл с сервера, но доступ к его содержимому все равно не дает.

            2) Загрузка шаблонов через AJAX — это не секрет:

            $.get('Templates/DynamicLoading.htm', {}, function (templateBody) {
            $.tmpl(templateBody, dataItems).appendTo('#movieListBag');
            });

            Если хотите задавать URLs таких шаблонов в теге SCRIPT — я не возражаю… ;-)
  • +1
    Не верю своим глазам: веб-программист, работающий в IE :)
    • +1
      IMHO нужно использовать тот же софт, что и твои пользователи.
      • +1
        Для тестирования — конечно, но для разработки есть инструменты и поудобнее :) Тоже IMHO, конечно :)
      • 0
        Представил себе дизайнера ретуширующего фото в Пайнт.нете :-)))
        • 0
          Бедный, но честный так и будет работать. Хотя не знаю, может, gimp покруче pdn будет.
          А вот браузеры со всеми их плагинами для разработчиков все одинаково доступны.
  • +1
    Внимание, господа!

    В статье Сергей отметил, что использование <script type=«xxxx»> предпочтительней, чем div. Хотел бы добавить, что, например, вот это:

    	<div id="tpl" style="display:none">
    		
    		<ul>
    			{{each users}}
    			<li>${$index + 1}. ${$value}</li>
    			{{/each}}
    		</ul>
    
    		<select>
    		{{each users}}
    			<option value="${$index + 1}">${$index + 1} . ${$value}</option>
    		{{/each}}
    		</select>
    		
    		<table>
    		{{each(ind, value) users}}
    			<tr>
    				<td>${ind + 1}</td>
    				<td>${value}</td>
    			</tr>
    		{{/each}}
    		</table>
    
    	</div>
    


    Будет работать неверно. Будет выведен лишь первый список (ul). Выпадающий список и таблица окажутся пустыми. Причина столь странного поведения (из-за чего я некогда бросил jQuery template, т.к. проект уже поджимал) заключается в том, что по факту, движок возьмет $("#tpl")[0].innerHTML . При парсинге браузером удалятся все неправильные элементы: внутри table — все, кроме tr, внутри ul — все, кроме li. В результате, innerHTML выдаст:

    <ul>
    	{{each users}} 
    	<li>
    		${$index + 1}. ${$value}
    	</li>
    	{{/each}} 
    </ul>
    <select>
    	<option value="${$index + 1}">${$index + 1} . ${$value}</option>
    </select>
    {{each(ind, value) users}} {{/each}} 
    <table>
    	<tbody>
    		<tr>
    			<td>
    				${ind + 1}
    			</td>
    			<td>
    				${value}
    			</td>
    		</tr>
    	</tbody>
    </table>
    
    

    небольшая поправочка: FF «вытолкнул» побочные элементы из тела таблицы наверх, из-за этого, движок выкинет невразумительную ошибку о том, что $index не существует. Как результат, получаем совершенно невменяемые вещи.

    Использование
    • 0
      … <script> решает проблему.

      Кстати, нафига они делают $("#tpl")[0] немного неясно… если элемент не найден, шаблон вываливается с ошибкой… имхо, правильнее было бы проверку делать или уж как все jQuery плагины, обрабатывать и возвращать нодлист.
      • 0
        Думаю, что это просто баг :)
    • 0
      Хорошее уточнение, с вашего разрешения добавлю его в текст :)
    • +1
      Добавил в текст ваш пример + пример динамической загрузки связанных шаблонов на основе вашей библиотеки WaitSync.
      • 0
        o_O приятно ошарашен =))))
    • 0
      е-мое… опять опечатался… внутри select — все, кроме option.
  • 0
    Да это же рай для всяких контент-грабберов :)
    • 0
      Только если вы хотите грабить свой контент, контент чужого сайта вам никто не отдаст :)
      • 0
        Ну как это не отдадите. А это:
        var dataItems = [
            {
                title: 'Бандиты',
                thumbnail: 'Bandits.jpg',
                director: 'Барри Левинсон',
                actors: ['Брюс Уиллис', 'Билли Боб Торнтон', 'Кейт Бланшетт'],
                year: 2001,
                budget: 95000000,
                grossRevenue: 67631903,
                rating: 0,
                frames: ['Bandits-1.jpg', 'Bandits-2.jpg', 'Bandits-3.jpg', 'Bandits-4.jpg', 'Bandits-5.jpg']
            },
        

        • 0
          Ясно :) Ну, вы же понимаете, что все, что вы видите в браузере, может быть сграблено ;-)
  • 0
    Обновление:

    1. Добавил пример побочных эффектов при размещении текста шаблона вне тега SCRIPT, способный вызвать серьезную головную боль (большое спасибо TEHEK за этот пример!).

    2. Добавил пример динамической загрузки связанных шаблонов (опять спасибо TEHEK за полезную библиотеку :).

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