22.01.2010

Простое шаблонирование в JavaScript

Задача: в JS на основе имеющихся данных сгенерить блок (кусок хтмл), заполнить его этими данными и вставить в DOM.

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

var data = {
    url: '/test/',
    thumb_src: 'test.gif',
    thumb_width: 60,
    thumb_height: 30,
    caption: 'Трам-парам!'
};

В итоге для этих данных хотим получить вот такой хтмл:

<div class="preview"><p class="image"><a href="/test/"><img src="test.gif" width="60" height="30"/></a></p><p class="caption">Трам-парам!</p></div>

Встают вопросы: в каком виде хранить шаблон и как впердолить в этот шаблон наши данные?

Вручную создавать элементы (createElement) и аппендить их (appendChild) довольно муторно. Получается громоздко и шаблон размазывается тонким слоем по JS-коду.

Часто делают конкатенацию кусочков шаблона вперемешку с данными:

var result = '<div class="preview"><p class="image"><a href="' + data.url + '"><img src="' + data.thumb_src + '" width="' + data.thumb_width + '" height="' + data.thumb_height + '"/></a></p><p class="caption">' + data.caption + '</p></div>';

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

Я использую способ, который подсмотрел в лекциях Дугласа Крокфорда. Добавляем в прототипы объекта String метод supplant, который в строке ищет выражения заключенные в фигурные скобки {}. Каждое найденное выражение используется как ключ к переданному объекту, и, если по этому ключу лежит строковое или числовое значение, то выражение в фигурных скобках заменяется этим значением.

/**
 * supplant() does variable substitution on the string. It scans
 * through the string looking for expressions enclosed in {} braces.
 * If an expression is found, use it as a key on the object,
 * and if the key has a string value or number value, it is
 * substituted for the bracket expression and it repeats. 
 */
String.prototype.supplant = function(o) {
    return this.replace(/{([^{}]*)}/g,
        function(a, b) {
            var r = o[b];
            return typeof r === 'string' || typeof r === 'number' ? r : a;
        }
    );
};

Теперь мы можем сделать так:

var template = '<div class="preview"><p class="image"><a href="{url}"><img src="{thumb_src}" width="{thumb_width}" height="{thumb_height}"/></a></p><p class="caption">{caption}</p></div>';

var result = template.supplant(data);

Еще один пример (подстановка данных в сообщение пользователю):

var template = 'Take train {number} from {from} to {to}.';

var data = {
    from: 'Madrid',
    to: 'Barcelona',
    number: '78A'
};

var result = template.supplant(data);

13 комментариев:

Serge Bezborodov комментирует...

огромное Вам спасибо!
Сам часто работаю с JSON строками, и призодится делать обычным сложением строк
Ваш метод куда более красив

kulakowka комментирует...

ЭТО ОЧЕНЬ КРУТО!!! Думаю было бы здорово встроить это в jQuery по умолчанию)

Unknown комментирует...

kulakowka, ничто не мешает вам оформить это как плагин к jQuery!

Regent комментирует...

Сергей, да, это довольно удобно для распечатывания JSON данных :)

Но для других применений пока не додумался :(

Заходите к нам ;) http://vl.vg/

Анонимный комментирует...

Заслуживает внимания способ, предложенный Джоном Ресигом: компилирование шаблона в функцию.

Анонимный комментирует...

Спасибо. Как раз думал как бы мне прототипчик типа подобного добавить в свой dotPlant CMS

Regent комментирует...

Привет ещё раз!)
Вышел codeEvaluator 0.0.3rc1 Прошу протестировать до выхода релиза по этой ссылочки http://vl.vg/24.01.2010/method-introduced-by-setting/#ce-0-0-3rc1

cr0t комментирует...

Спасибо! Отличный способ!

Georgy комментирует...

В Dojo Toolkit в пакете dojo.string
есть функция dojo.string.substitute

Пример использования:
dojo.string.substitute(
"File '${name}' is not found in directory '${info.dir}'.",
{ name: "foo.html", info: { dir: "/temp" } }
);

Unknown комментирует...

таже мысль но на пару дней раньше

http://mabp.kiev.ua/2010/01/16/javascript-template-engine/

Kigorw комментирует...

Вообще советую полазить по коду разных джс-фреймворков. Насколько я помню, этот прием несколько лет назад в коде экст.джс подсмотрел.
Для джквери использую http://plugins.jquery.com/project/jquerytemplate

У прототайпа сразу есть класс Template.

Еще замечу, что самая быстрая конкатенация делается с помощью массива, а рендер с помощью иннерШТМЛ:
var template = new Template('option value="#{id}">#{name} option');
var options = []
for(var i=0;i<cities.length;i++)
{
var city = cities[i];
options.push(template.evaluate(city));
}
this.cbCity.innerHTML = options.join("");

Unknown комментирует...

Шаблонизатор на javascript, прикольно :)

Артём Курапов комментирует...

Советую глянуть на шаблонизатор из underscore.js

http://documentcloud.github.com/underscore/#template

По сути - создаётся спрятанный HTML внутри script-тэга, который заполняется передаваемыми туда данными. Есть условные операторы, циклов не видел.