basis.js template and l10n modules as standalone library
basis-template.js - это быстрый низкоуровневый шаблонизатор. В то время, как большинство шаблонизаторов лишь производят некоторый HTML, данный шаблонизатор оперирует в терминах DOM, и не только производит DOM фрагмент из описания, но и занимается дальнейшим его обслуживанием, в чем и заключается его уникальность.
Этот шаблонизатор не расчитан на использование в исходном виде, а нацелен на встраивание в другие библиотеки и фреймворки, в виде модулей или плагинов, которые предоставляют более удобное API.
На данный момент есть плагин для backbone.js.
Подключение библиотеки.
<script src="basis-templates.js"></script>
Описываем шаблон
<script type="text/basis-template" id="example"> <div class="block {foo}"> <h1>Hello {name}!</h1> <button event-click="sayHello">Say hello!</button> </div> </script>
Создаем и используем экземпляр шаблона
// определяем шаблон var template = bt('id:example'); // создаем var example = template.createInstance(null, function(name, event){ if (name == 'sayHello') alert('Hello' + event.name + '!'); }); // установить значение для биндинга example.set('foo', true); example.set('name', 'world'); // вставка в документ document.body.appendChild(example.element);
Разрушение экземпляра
bt.dispose(example);
При подключении, плагин добавляет в глобальную область видимости единственное имя bbt. Это значение является функцией, которая создает шаблон. В качестве значения можно задавать строку содержающую описание, либо строку с префиксом "id:" после которого следует идентификатор. В последнем случае будет использовано текстовое содержимое тега <script> c таким идентификатором, при этом необходимо чтобы его type был равен "text/basis-template".
Шаблон задается как значение свойства el в конфиге View.
var Foo = Backbone.View.extend({ el: bbt('<h1>Hello world</h1>') }); var Bar = Backbone.View.extend({ el: bbt('id:templateId') });
Для определения значений для шаблона, задается свойство binding.
var MyView = Backbone.View.extend({ el: bbt('id:templateId'), binding: { value: function(view){ return view.property; }, eventValue: { events: 'myEvent', getter: function(view){ return view.propertyChangingOnMyEvent; } } } });
Для обращения к свойствам внутренних объектов View, можно использовать сокращенную запись.
var MyView = Backbone.View.extend({ el: bbt('id:templateId'), binding: { name: 'model:name', age: 'model:' // если имя биндинга и имя поля в модели совпадают, // то можно не указывать имя поля } });
Для перевычисления значения биндинга, в зависимости от события вложенного объекта, такие события предваряются префиксом с названием свойства и ":".
var MyView = Backbone.View.extend({ el: bbt('id:templateId'), binding: { updateOnModelChange: { events: 'model:change', getter: function(view){ return view.model.firstName + ' ' + view.model.lastName; } } } });
Для обработки событий достаточно указать в описании шаблона атрибут с префиксом "event-", и чтобы у View был метод указанный в значении атрибута.
var MyView = Backbone.View.extend({ el: bbt('<button event-click="sayHello">hello</button>'), sayHello: function(event){ alert('Hello world!') } });
Примеренение плагина на примере реализации TodoMVC. Использование basis-templates.js позволяет уменьшить количество исходного кода и ускорить решение в 2,5 раза.
Ниже приведены изменения, которые были внесены в оригинальное решение.
<script type="text/template" id="item-template"> <div class="view"> <input class="toggle" type="checkbox" <%= completed ? 'checked' : '' %> <label><%- title %></label> <button class="destroy"></button> </div> <input class="edit" value="<%- title %>"> </script>
<script type="text/basis-template" id="item-template"> <li class="{completed} {hidden} {editing}"> <div class="view"> <input class="toggle" type="checkbox" checked="{completed}" event-click="toggleCompleted" /> <label event-dblclick="edit">{title}</label> <button class="destroy" event-click="clear"></button> </div> <input{input} class="edit" value="{title}" event-keyup="updateOnEnter" event-blur="close" /> </li> </script>
/*global Backbone, jQuery, _, ENTER_KEY */ var app = app || {}; (function ($) { 'use strict'; // Todo Item View // -------------- // The DOM element for a todo item... app.TodoView = Backbone.View.extend({ //... is a list tag. tagName: 'li', // Cache the template function for a single item. template: _.template($('#item-template').html()), // The DOM events specific to an item. events: { 'click .toggle': 'toggleCompleted', 'dblclick label': 'edit', 'click .destroy': 'clear', 'keypress .edit': 'updateOnEnter', 'blur .edit': 'close' }, // The TodoView listens for changes to its model, re-rendering. Since there's // a one-to-one correspondence between a **Todo** and a **TodoView** in this // app, we set a direct reference on the model for convenience. initialize: function () { this.listenTo(this.model, 'change', this.render); this.listenTo(this.model, 'destroy', this.remove); this.listenTo(this.model, 'visible', this.toggleVisible); }, // Re-render the titles of the todo item. render: function () { this.$el.html(this.template(this.model.toJSON())); this.$el.toggleClass('completed', this.model.get('completed')); this.toggleVisible(); this.$input = this.$('.edit'); return this; }, toggleVisible: function () { this.$el.toggleClass('hidden', this.isHidden()); }, isHidden: function () { var isCompleted = this.model.get('completed'); return (// hidden cases only (!isCompleted && app.TodoFilter === 'completed') || (isCompleted && app.TodoFilter === 'active') ); }, // Toggle the `"completed"` state of the model. toggleCompleted: function () { this.model.toggle(); }, // Switch this view into `"editing"` mode, displaying the input field. edit: function () { this.$el.addClass('editing'); this.$input.focus(); }, // Close the `"editing"` mode, saving changes to the todo. close: function () { var trimmedValue = this.$input.val().trim(); this.$input.val(trimmedValue); if (trimmedValue) { this.model.save({ title: trimmedValue }); } else { this.clear(); } this.$el.removeClass('editing'); }, // If you hit `enter`, we're through editing the item. updateOnEnter: function (e) { if (e.which === ENTER_KEY) { this.close(); } }, // Remove the item, destroy the model from *localStorage* and delete its view. clear: function () { this.model.destroy(); } }); })(jQuery);
/*global Backbone, jQuery, _, ENTER_KEY */ var app = app || {}; (function ($) { 'use strict'; // Todo Item View // -------------- // The DOM element for a todo item... app.TodoView = Backbone.View.extend({ el: bbt('id:item-template'), binding: { completed: 'model:', title: 'model:', hidden: { events: 'model:visible', getter: function(view){ var isCompleted = view.model.get('completed'); return ( // hidden cases only (!isCompleted && app.TodoFilter === 'completed') || (isCompleted && app.TodoFilter === 'active') ); } } }, // The TodoView listens for changes to its model, re-rendering. Since there's // a one-to-one correspondence between a **Todo** and a **TodoView** in this // app, we set a direct reference on the model for convenience. initialize: function () { this.listenTo(this.model, 'destroy', this.remove); }, // Toggle the `"completed"` state of the model. toggleCompleted: function () { this.model.toggle(); }, // Switch this view into `"editing"` mode, displaying the input field. edit: function () { this.tmpl.set('editing', true); this.tmpl.input.focus(); }, // Close the `"editing"` mode, saving changes to the todo. close: function () { var trimmedValue = this.tmpl.input.value.trim(); if (trimmedValue) { this.model.save({ title: trimmedValue }); } else { this.clear(); } this.tmpl.set('editing', false); }, // If you hit `enter`, we're through editing the item. updateOnEnter: function (e) { if (e.which === ENTER_KEY) { this.close(); } }, // Remove the item, destroy the model from *localStorage* and delete its view. clear: function () { this.model.destroy(); } }); })(jQuery);