Fork me on GitHub

basis-templates.js

basis.js template and l10n modules as standalone library

Development 180 KB Production 63.2 KB min

Что это такое?

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);

Документация





Backbone plugin

basis.js templates plugin for backbone.js

Development 183 KB Production 64.2 KB min

Как использовать

При подключении, плагин добавляет в глобальную область видимости единственное имя 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

Примеренение плагина на примере реализации 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>

todo-view.js

Было

/*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);