Javascript.RU

Шаблонизация с javascript

Update: Более новый материал по этой теме находится по адресу https://learn.javascript.ru/template-lodash.
http://ejohn.org/blog/javascript-micro-templating/

Есть одна утилитка, которой я уже пользуюсь некоторое время, довольно полезная при построении javascript-приложений. Это - супер-простая и очень быстрая функция для шаблонизации на клиенте. Она предложена Джоном Ресигом.

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

Вот исходный код функции шаблонизатора:

(function(){
  var cache = {};
 
  this.tmpl = function tmpl(str, data){
    // Выяснить, мы получаем шаблон или нам нужно его загрузить
    // обязательно закешировать результат
    var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :
     
      // Сгенерировать (и закешировать) функцию, 
      // которая будет служить генератором шаблонов
      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
       
        // Сделать данные доступными локально при помощи with(){}
        "with(obj){p.push('" +
       
        // Превратить шаблон в чистый JavaScript
        str
          .replace(/[\r\t\n]/g, " ")
          .split("<%").join("\t")
          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
          .replace(/\t=(.*?)%>/g, "',$1,'")
          .split("\t").join("');")
          .split("%>").join("p.push('")
          .split("\r").join("\\'")
      + "');}return p.join('');");
   
    // простейший карринг(термин функ. прог. - прим. пер.)
    // для пользователя
    return data ? fn( data ) : fn;
  };
})();

Её можно использовать с шаблонами, написанными в таком виде (не обязательно точно в таком, но этот стиль мне нравится):

<script type="text/html" id="item_tmpl">
  <div id="<%=id%>" class="<%=(i % 2 == 1 ? " even" : "")%>">
    <div class="grid_1 alpha right">
      <img class="righted" src="<%=profile_image_url%>"/>
    </div>
    <div class="grid_6 omega contents">
      <p><b><a href="/<%=from_user%>"><%=from_user%></a>:</b> <%=text%></p>
    </div>
  </div>
</script>

Вы также можете сделать скриптовую вставку:

<script type="text/html" id="user_tmpl">
  <% for ( var i = 0; i < users.length; i++ ) { %>
    <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li>
  <% } %>
</script>
<script type="text/html">

Скрипты с неизвестным типом содержания type (как в примере выше - браузер не знает что делать с text/html-скриптом) просто игнорируются браузерами и поисковиками.

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

А вызов шаблонки из скрипта - примерно такой:

var results = document.getElementById("results");
results.innerHTML = tmpl("item_tmpl", dataObject);

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

var show_user = tmpl("item_tmpl"), html = "";
for ( var i = 0; i < users.length; i++ ) {
  html += show_user( users[i] );
}

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

Однако, он использует одну технику, которая мне очень нравится - а именно: если Вы делаете поиск-и-замену фиксированных подстрок, то быстрее всех с этим справляется .split("match").join("replace").

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

Играйте с этим кодом, как захотите - мне даже любопытно, во что он может превратиться. Он очень прост - и по идее, с ним можно сделать многое.


Автор: Илья Кантор, дата: 19 августа, 2008 - 10:42
#permalink

Чуть не забыл - когда шаблоны пишете - обязательно везде ставить точки с запятой и не использовать комментов //

Т.к весь шаблон функция превращает в одну строку в итоге - будут синтаксические ошибки без точек с запятой.

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

function tmpl(str){
    var fn = new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +

        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" + document.getElementById(str).innerHTML
          .replace(/[\r\t\n]/g, " ")
          .split("<%").join("\t")
          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
          .replace(/\t=(.*?)%>/g, "',$1,'")
          .split("\t").join("');")
          .split("%>").join("p.push('")
          .split("\r").join("\\'") + "');} return p.join('');");


    return fn
}

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

tmpl('template-id')(data)

Автор: bdiang, дата: 27 октября, 2010 - 10:51
#permalink

А вот так не скушает

<%=var;%>

Надо без точки с запятой в таких конструкциях.


Автор: Гость (не зарегистрирован), дата: 20 июня, 2011 - 10:40
#permalink

Я бы сделал даже так:

return function(o){
        try {
            fn(o);
        } catch(e) {
            e.message = "(Tpl-error) " + e.message;
            throw e;
        }
    }

Мы ведь говорим о RIA...


Автор: Вирь (не зарегистрирован), дата: 25 сентября, 2008 - 12:14
#permalink

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

/* comment */

Мне кажется, что после сборки в одну строку комментарий такого вида не должен мешать выполнению скрипта, к тому же, можно удалять его через всё тот же RegExp... (с чем у меня, правда говоря, проблемы - пока не возникало большой необходимости его изучать)


Автор: Илья Кантор, дата: 26 сентября, 2008 - 16:41
#permalink

Угу, такой комментарий вполне подойдет.


Автор: И.Тынгылчав (не зарегистрирован), дата: 9 октября, 2008 - 09:34
#permalink

Извините, конечно, но это семиколесный велосипед. Его много раз изобретали до вас. И он конечно ездит. Но недалеко.
Есть куча js-фреймворков, превращающих маниуляции с DOM в детскую игру. Есть XSLT выполняемый непосредственно броузеорм или через JS. И в конце концов есть куча бесплатных или очень дешевых хостингов с php/.NET/phyton...


Автор: Илья Кантор, дата: 10 октября, 2008 - 14:10
#permalink

Вещь хорошая ездит замечательно. Пользуюсь нравится.
Про php/.NET/python - это Вы в сторону далеко ушли. Тут именно JS-шаблонка. Для своих задач. Возможно, их у Вас пока не возникало, возникнут - вспомните, придете на эту страничку

P.S А XSLT на уровне браузера - мало того что куча несовместимостей, так оно еще и убогое, без EXSLT.


Автор: vicont (не зарегистрирован), дата: 21 ноября, 2008 - 10:17
#permalink

для чего вообще это нужно?


Автор: Марианна (не зарегистрирован), дата: 24 декабря, 2008 - 12:25
#permalink

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


Автор: Алик Кириллович, дата: 15 февраля, 2009 - 19:35
#permalink

Эта функция шаблонизации не будет работать в ASP-страницах.

Дело в том, что дескрипторы начала (<%=) и конца (%>) шаблона уже зарезервированы ASP-движком.


Автор: Илья Кантор, дата: 12 апреля, 2009 - 18:29
#permalink

Точно. На таких страницах лучше бы поправить <%= .. %> на что-нибудь другое.. Например, на <?= ... ?>


Автор: Гость (не зарегистрирован), дата: 25 мая, 2011 - 15:23
#permalink

мсье шутить изволит))


Автор: iyntx, дата: 31 января, 2012 - 09:12
#permalink

Зачем в серверном языке джаваскрипт-шаблоны ? :\


Автор: Антоннн (не зарегистрирован), дата: 12 апреля, 2009 - 17:58
#permalink

А как сделать, чтоб при нажатии на кнопку появлялось окошко с текстом?(что надо в скрипте написать?)
(что надо здесь написать?)("действие")


Автор: GE1serf, дата: 22 января, 2010 - 14:33
#permalink

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


Автор: MODist, дата: 22 января, 2010 - 16:54
#permalink

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


Автор: Илья Кантор, дата: 22 января, 2010 - 22:09
#permalink

Это уже вопросы к вашему движку и фреймворку. Javascript-шаблон - такой же шаблон как и остальные.


Автор: cooli0, дата: 29 января, 2010 - 06:39
#permalink

Удивился по поводу сказанного, что split.join работает быстрее replace.

Поэтому провел тесты.

Делал замену 10 различных значений {blahblah} в длинной строке.
Запускал в IE6 и FF3.5. Оба браузера показали преимущество функции replace. Если в FF (на маленьких итерациях) значения еще хоть куда не шли, - похожие, то в IE6 split.join работает в разы медленнее replace. На больших итерациях split.join проигрывает очень сильно и в FF, и в IE6.


Автор: rmaksim (не зарегистрирован), дата: 8 марта, 2010 - 11:49
#permalink

а еще можно выкинуть лишнее - функция print нигде не используется, видимо ресиг юзал её для своих целей, потом забыл выкинуть её, а все остальные тупо содрали
print=function(){p.push.apply(p,arguments);}


Автор: Shoorf (не зарегистрирован), дата: 16 июня, 2010 - 16:17
#permalink

Функция print далеко не лишняя! Вставка переменной в шаблон осуществляется либо с помощью конструкции <%=variable%>, либо <% print (variable) %>. Второй вариант удобен в случае, например, такого шаблона:

<script type="text/html" id="my_tpl">
  <% if (something == true) print(variable) %>
</script>
var data = new Object();
data.something = true;
data.variable = "Привет!";
var dest_container = document.getElementById("dest_container");
dest_container.innerHTML = tmpl("my_tpl", data);

Автор: Shoorf, дата: 16 июня, 2010 - 16:26
#permalink

Неплохо было бы указать назначение функции print в статье.


Автор: Beck, дата: 29 ноября, 2010 - 00:07
#permalink

Вот вот, меня эта функция запутала, когда ковырял.
Собственно хотелось бы понять её смысл.


Автор: pixmaster (не зарегистрирован), дата: 15 апреля, 2010 - 19:10
#permalink

пример с шаблонизатором сделанный на основе выше предложенного ссылка на архив


Автор: ixth, дата: 15 октября, 2010 - 14:50
#permalink

У меня почему-то не работает, просто возвращает id темплейта - и все.

UPD. id шаблона должен подходить под регулярку \w+, иначе шаблонизатор принимает строку, переданную первым параметром в tmpl за шаблон, переданный текстом. Т.о. нельзя использовать дефисы в идентификаторах и такая запись не будет воспринята шаблонизатором как надо:

<script type="text/html" id="tpl-template">
...
var tpl = tmpl('tpl-template', obj);

Автор: rmaksim (не зарегистрирован), дата: 6 января, 2011 - 19:00
#permalink

для использования "-" в id-шках можно переделать

!/\W/ в !/[^\w-]/


Автор: Гость (не зарегистрирован), дата: 4 февраля, 2011 - 21:08
#permalink

Вот если кто-то любит жквери, а еще любит шаблоны держать отдельно от страниц, то тут показано, как можно загружать внешние шаблоны http://www.clearboth.ru/article/using-external-templates-with-jquery-tem...


Автор: Гость (не зарегистрирован), дата: 26 сентября, 2011 - 00:53
#permalink

поясните значение $1 при реплейсинге.


Автор: ViruSkin, дата: 18 октября, 2011 - 19:51
#permalink

Для шаблонизации я бы использовал связку js+XSLT


Автор: Гость (не зарегистрирован), дата: 28 сентября, 2012 - 18:00
#permalink

Самый лёгкий шаблонизатор (jQuery):

var template = '<div class="pop-up-overlay"></div>' +
                    '<div class="pop-up">' +
                    '  <div class="heading">{title}</div>' +
                    '  <div class="notify">{notify}</div>' +
                    '  <div class="content">{content}</div>' +
                    '</div>';

data = {
  title: 'Добавление товара в корзину', 
  notify: 'Прошло успешно!', 
  content: 'Товар "Погремушка" успешно добавлен в корзину покупок.'
}

function render(template, data) {
  var html = '';

  $.each(data, function(k, v){
    html = template.split('{' + k + '}').join(v);

    template = html;
  });

  return html;
}

$('body').prepend(render(template, data));

Автор: netkoatl (не зарегистрирован), дата: 22 октября, 2012 - 00:13
#permalink

У меня тупошаблонизатор похож на предложенный в комментариях, только синтаксис ещё проще: %varname подставляет значение.

А вот реализация - чуть сложнее; при инициализации он ищет все script[type="text/x-tpl"] и складывает в массив по id. Ну и вызов - Tpl.render(myTemplateId, dataObject).

Думал над раскатыванием шаблонов в кэш js-функций, как в основном посте; на таких тупых шаблонах особого выигрыша не даёт. А совать в шаблоны логику я не стану.


Автор: Гость (не зарегистрирован), дата: 17 декабря, 2015 - 11:59
#permalink

Замечательно!!! Спасибо.
Можно конечно использовать существующие библиотеки, но мне нравится этот вариант.
Можно использовать несколько шаблонов, разместив их в объекте.
Это наверное пригодилось бы при работе с содержимым балунов в гугл картах.

Я заменил открывающий и закрывающий тэги в шаблоне на $[[ и ]].

var obj_templ = {
	str_templ_1 : '\
<div id="$[[id]]" class="$[[(i % 2 == 1 ? " even" : "")]]">\
	<div class="grid_1 alpha right">\
		<img class="righted" src="$[[profile_image_url]]"/>\
	</div>\
	<div class="grid_6 omega contents">\
		<p><b><a href="/$[[from_user]]">$[[from_user]]</a>:</b> $[[text]]</p>\
	</div>\
</div>',
	str_templ_2 : '\
<div id="$[[id]]" >\
	<a href="/$[[from_user]]">\
		<img class="righted" src="$[[profile_image_url]]" style="width:90px;"/>\
		$[[from_user]]\
	</a>\
	$[[text]]\
</div>'
}
results_1.innerHTML = tmpl(obj_templ.str_templ_1)(dataObject);
results_2.innerHTML = tmpl(obj_templ.str_templ_2)(dataObject);

 
Текущий раздел
Поиск по сайту
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Последние темы на форуме
Forum