Проблемы строчной шаблонизации — работа со строками
Это действительно ужасно т.к.
Во-первых, шаблоны избыточны (про отношение к 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.
//описание шаблона 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); }
Плюсы:
- Чистый js
- Абстрагирование от html
- Намного быстрее строчных на мобильных, быстрее везде кроме оперы и ие на десктопах (визуализацию тестов делаю), и на телике и киосках быстрее 🙂
- Шаблоны в шаблонах
- Поиск внутри и не завязан на имена классов или id
- Самодостаточна
- Автоподсветка и cc во всём что понимает js без плагинов
- поддерживает inline SVG и VML, т.к. поддерживает .createElementNS()
- XML покидает вашу жизнь
- Работает в жизни(доступ к последнему), правда не в выделенном виде, соответсвенно будем поддерживать
Минусы:
- Медленнее в ие (нам важнее мобилки, киоски (мы переводим их на Chrome) и телики)
- Кажется непонятной (через неделю использования, html кажется непонятным)
- Слабо документирована (скоро всё будет)
- Бесполезна на ноде (потому что не для него / хотя можно написать ветку cnCt json to str, но зачем?)
Будем документировать и развивать.
PS: Решили открыть т.к. коллеги взвыли от underscore при работе с проектом, который делался не у нас.
ссылка на оригинал статьи http://habrahabr.ru/post/202432/
Добавить комментарий