cnCt — клиентский js шаблонизатор

от автора

Рано или поздно шаблонизация перемещается на клиент. На данный момент существует несколько общепринятых клиентских шаблонизаторов ими являются mustache и underscore.template или что-нибудь подобноедоклад(ах) Сергея Бережного можно найти ещё). Несмотря на огромное количество клиентских шабнизаторов большинство, за редким исключением, являются строчными.

Проблемы строчной шаблонизации — работа со строками

Это действительно ужасно т.к.
Во-первых, шаблоны избыточны (про отношение к html будет раскрыто ниже) и кашеобразны. Даже в рекламных примерах

var template = '<div class="entry"><h1>{{title}}</h1><div class="body">{{body}}</div></div>'; 

В шаблонах мы наблюдаем неформатированный html, и некий метаязык, поддержку которого нужно как-то внедрить в нашу среду (спасибо и не спасибо JetBrains за своевременную поддержку хипстерских метаязыков). А так же после прогона через регулярочки всё это вставляется с помощью innerHTML, причём, если вы писали более менее большое приложение, вы понимаете что вставки шаблонов происходят постоянно в разных местах дерева, и свести это к одной вставке всех нагенерёных шаблонов через documentFragment попросту невозможно. А частые вставки через innerHTML это ад (в будущем планирую выложить интересные тесты по этому поводу).

Хотя стоит признаться, что проблема форматирования отчасти решена вставкой шаблона в неопределённый тег script, что в свою очередь создаёт новую проблему: все шаблоны вашего приложения оказываются на index.html(php?), и когда их набирается много приходится писать хитросборщики, ибо phpStorm’у печально даже с 32Гб оперативки.

Во-вторых, проблемы работы с логикой

где-то так

{{#repo}}   <b>{{name}}</b> {{/repo}} {{^repo}}   No repos :({ {/repo}} 

где-то в строках (надеюсь среды уже умеют работать с таким, иначе мне искрение жаль тех, кто с этим работает)

var list = "<% _.each(people, function(name) { %> <li><%= name %></li> <% }); %>"; _.template(list, {people: ['moe', 'curly', 'larry']}); // "<li>moe</li><li>curly</li><li>larry</li>" 

где-то в отдельных функциях, собирающих строки

{{#list people}}{{firstName}} {{lastName}}{{/list}}  {   people: [     {firstName: "Yehuda", lastName: "Katz"},     {firstName: "Carl", lastName: "Lerche"},     {firstName: "Alan", lastName: "Johnson"}   ] }  Handlebars.registerHelper('list', function(items, options) {   var out = "<ul>";    for(var i=0, l=items.length; i<l; i++) {     out = out + "<li>" + options.fn(items[i]) + "</li>";   }    return out + "</ul>"; }); 

(невольно вспоминается мой первый опыт клиентского шаблонизирования по методу от Степана Резникова)

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

Обычно это выглядит так

var templateWrapper = document.getElementById('templateWrapper'); something.render(template, templateWrapper); var templateElemet = templateWrapper.getElementsByClassName('templateElement'); //особо не приятно в старых ие 

_small_(Отчасти расстраивает отсутсвие возможности нормально использовать шаблон в шаблоне, хотя возможно недоизучил эту тему по причине глубокого расстройства, вызванного изложенным выше.)_/small_

cnCt

Основным плюсам cnCt являются построение DOM методами DOM и описание шаблона в виде json.

пример на jsfiddle

//описание шаблона var template = function(data){     //если 'e' не указан, будет сделан div     return {c: 'user', C: [         {e: 'a', h: 'http://facebook.com/' + data.facebookId, c: 'user-avatar-wrapper', C:               {e: 'img', c: 'user-avatar', S: 'http://graph.facebook.com/' + data.facebookId + '/picture?width=200&height=200'}         },         {c: 'user-name', t: data.fristName + ' ' + data.lastName, n: 'userName'},     ]}; };  //собираем шаблон. метод tp возвращает нам результат сбоки  var build = cnCt.tp(template, {fristName: 'swf', lastName: 'dev', facebookId: '100005155868851'}, document.body);  //работаем и навешиваем события на найденные элементы build.r.addEventListener('click', function(){console.log('hello')}); //build.r (r сокращённо от root) это div.user build.userName.addEventListener('click', function(){console.log(this.textContent)}); // элемент найденный по n: 'userName' 

При разработке сложных веб интейрфейсов мы стараемся отойти от тегов в принципе, т.к. понимаем html как структуру блоков вьюшки, поэтому используем только теги a/input/textarea/(…) как основу необходимого функционала для блока вьюшки, в следствии чего был выработан такой подход.

В бою это выглядит так (jsfiddle)

//для работы с датами function dataToStr(_date){     var date = _date instanceof Date ? _date : new Date(_date);     return date.getDate() + '/' + date.getMonth() + '/' + date.getFullYear(); } //псевдолокализация function l10n(key){     return l10Keys[key] || key; } var l10Keys = {         youFriendsList: 'список ваших друзей',         sendMessage: 'отправить сообщение'     },     //описываем шаблоны     templates = {         //основа         basis: function(){             return [                 //локализация                 {c: 'header', n: 'header', t: l10n('youFriendsList')},                 {c: 'content-view', n: 'contentView'},                 {c: 'footer', t: '© SoftWear LLC'}             ];         },          //статический микрошаблон используемый другими шаблонами         icon: {c: 'icon'},          //кнопка         button: function(data){             return {c: 'button', t: data.t, n: data.n, C: templates.icon, a: data.a};         },          //список друзей         friendsList: function(friends){             var items = [],                 i = 0,                 iMax = friends.length;             //чистый js в шаблонах (можно и посортировать, но но не будем усложнять)             for(; i < iMax; i += 1){                 items[i] = {c: 'friend', C: [                     {e: 'a', h: 'http://facebook.com/' + friends[i].facebookId, c: 'friend-avatar-wrapper', C:                         //максимум результата из минимум данных                         {e: 'img', c: 'friend-avatar', S: 'http://graph.facebook.com/' + friends[i].facebookId + '/picture?width=200&height=200'}                     },                     {c: 'friend-name', t: friends[i].firstName + ' ' + friends[i].lastName},                     //работа с датами                     {c: 'friend-birthday', t: dataToStr(friends[i].birthday)},                     //шаблоны в шаблонах                     templates.button({n: 'sendMessage', t: l10n('sendMessage'), a: {'data-friend-id': friends[i].facebookId}})                 ]};             }             return {c: 'friend-list', C: items};         }     }, //    модель данных     friends = [         {             facebookId: 1,             firstName: 'Иван',             lastName: 'Иванов',             birthday: 1         },         {             facebookId: 2,             firstName: 'Василий',             lastName: 'Васин',             birthday: 111111111         },         {             facebookId: 3,             firstName: 'Пётр',             lastName: 'Петров',             birthday: 123123123         }     ],     build,     $contentView,     $buttons,     i; //строим объект шаблонов для более удобной работы (они же описаны на уровне вьюшек эппа) cnCt.bindTemplates(templates); //строим основу $contentView = cnCt.tp('basis', document.body).contentView; function getByFacebookId(id){     var i = friends.length;     id = +id;     for (; i-- ;){         if (friends[i].facebookId === id){             return friends[i];         }     }     return null; } $buttons = cnCt.tp('friendsList', friends, $contentView).sendMessage; // а тут массив т.к. элементов много  function sendMessage(){     var friend = getByFacebookId(this.getAttribute('data-friend-id'));     console.log('hello ' + friend.firstName); }  for (i = friends.length; i-- ;){     $buttons[i].addEventListener('click', sendMessage);     } 

Плюсы:

  1. Чистый js
  2. Абстрагирование от html
  3. Намного быстрее строчных на мобильных, быстрее везде кроме оперы и ие на десктопах (визуализацию тестов делаю), и на телике и киосках быстрее 🙂
  4. Шаблоны в шаблонах
  5. Поиск внутри и не завязан на имена классов или id
  6. Самодостаточна
  7. Автоподсветка и cc во всём что понимает js без плагинов
  8. поддерживает inline SVG и VML, т.к. поддерживает .createElementNS()
  9. XML покидает вашу жизнь
  10. Работает в жизни(доступ к последнему), правда не в выделенном виде, соответсвенно будем поддерживать

Минусы:

  1. Медленнее в ие (нам важнее мобилки, киоски (мы переводим их на Chrome) и телики)
  2. Кажется непонятной (через неделю использования, html кажется непонятным)
  3. Слабо документирована (скоро всё будет)
  4. Бесполезна на ноде (потому что не для него / хотя можно написать ветку cnCt json to str, но зачем?)

Будем документировать и развивать.

PS: Решили открыть т.к. коллеги взвыли от underscore при работе с проектом, который делался не у нас.

ссылка на оригинал статьи http://habrahabr.ru/post/202432/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *