Отображение шаблонов представлений
В отличие от Backbone.View, представления Marionette предоставляют настраиваемое решение для рендеринга шаблона с данными и размещения результатов в DOM.
import _ from 'underscore';
import { View } from 'backbone.marionette';
const MyView = View.extend({
tagName: 'h1',
template: _.template('Contents')
});
const myView = new MyView();
myView.render();
В приведенном выше примере содержимое атрибута template будет отображено внутри тега <h1>, доступного по адресу myView.el.
Индекс документации
- Что такое шаблон
- Настройка шаблона представления
- Рендеринг шаблона
- Сериализация данных
- Добавление контекстных данных
Что такое шаблон?
Шаблон — это функция, которая, получив данные, возвращает либо строку HTML, либо DOM. По умолчанию рендерер в Marionette ожидает, что шаблон вернет строку HTML. Зависимость Marionette — Underscore — поставляется со компилятором шаблонов для строк HTML.
import _ from 'underscore';
import { View } from 'backbone.marionette';
const MyView = View.extend({
template: _.template('<h1>Hello, world</h1>')
});
Это не обязательно должен быть шаблон underscore, вы можете передать свою собственную функцию рендеринга:
import Handlebars from 'handlebars';
import { View } from 'backbone.marionette';
const MyView = View.extend({
template: Handlebars.compile('<h1>Hello, {{ name }}')
});
Настройка шаблона представления
Представления Marionette используют метод getTemplate для определения шаблона, который будет использоваться для рендеринга в el. По умолчанию getTemplate предварительно определен в представлении как:
getTemplate() {
return this.template
}
В большинстве случаев, используя стандартный getTemplate, вы можете просто установить template в представлении, чтобы определить шаблон представления, но в некоторых случаях вам может потребоваться условное задание шаблона.
import { View } from 'backbone.marionette';
const MyView = View.extend({
template: _.template('Hello World!'),
getTemplate() {
if (this.model.has('user')) {
return _.template('Hello User!');
}
return this.template;
}
});
Использование представления без шаблона
По умолчанию CollectionView не имеет определенного template и будет пытаться рендерить template только в том случае, если он определен. Для View могут быть ситуации, когда вы не хотите использовать шаблон.
Возможно, вам нужен только el или вы используете предобработанное содержимое.
В этом случае установка template в false предотвратит рендеринг шаблона. В случае с View это также предотвратит render события.
import { View } from 'backbone.marionette';
const MyIconButtonView = View.extend({
template: false,
tagName: 'button',
className: '.icon-button',
triggers: {
'click': 'click'
},
onRender() {
console.log('You will never see me!');
}
});
Рендеринг шаблона
Каждый класс представления имеет рендерер, который по умолчанию передает данные представления данные представления в функцию шаблона и возвращает сгенерированную строку html.
Текущий рендерер по умолчанию по существу выглядит следующим образом:
import { View, CollectionView } from 'backbone.marionette';
function renderer(template, data) {
return template(data);
}
View.setRenderer(renderer);
CollectionView.setRenderer(renderer);
До версии Marionette v4, рендерер по умолчанию был TemplateCache. Этот рендерер был выделен в отдельную библиотеку: https://github.com/marionettejs/marionette.templatecache и может быть использован с v4.
Использование пользовательского рендерера
Вы можете установить рендерер для класса представления, используя метод класса setRenderer. Рендерер принимает два аргумента. Первый — это шаблон, переданный представлению, а второй — данные, которые необходимо отобразить в шаблоне.
Вот пример, который позволяет template представления быть строкой шаблона underscore.
import _ from 'underscore';
import { View } from 'backbone.marionette';
View.setRenderer(function(template, data) {
return _.template(template)(data);
});
const myView = new View({
template: 'Hello <%- name %>!',
model: new Backbone.Model({ name: 'World' })
});
myView.render();
// myView.el is <div>Hello World!</div>
Рендерер также можно настроить отдельно в любом расширенном представлении.
const MyHBSView = View.extend();
// Similar example as above but for handlebars
MyHBSView.setRenderer(function(template, data) {
return Handlebars.compile(template)(data);
});
const myHBSView = new MyHBSView({
template: 'Hello {{ name }}!',
model: new Backbone.Model({ name: 'World' })
});
myHBSView.render();
// myView.el is <div>Hello World!</div>
Примечание. Эти примеры, хотя и функциональны, могут быть не оптимальными. По возможности рекомендуется предварительно скомпилировать шаблоны, что можно сделать для ряда шаблонизаторов с помощью различных плагинов для инструментов сборки, таких как Browserify или Webpack.
Рендеринг в HTML
По умолчанию рендеры Marionette возвращают HTML как строку. Эта строка передается методу attachElContents представления, который в свою очередь использует API DOM setContents для установки содержимого el представления с помощью DOM из строки.
Настройка attachElContents
Вы можете изменить способ привязки скомпилированного шаблона к el конкретного представления, переопределив attachElContents. Этот метод получает только результаты рендеринга представления и вызывается только в том случае, если рендерер возвращает значение.
Например, может потребоваться пропустить API DOM и установить html непосредственно:
attachElContent(html) {
this.el.innerHTML = html;
}
Рендеринг в DOM
Marionette также поддерживает шаблоны, которые рендерятся в DOM вместо строк html, используя пользовательский рендерер.
В следующем примере метод template, переданный рендереру, вернет элемент DOM, а затем, если представление уже отображено, использует morphdom для обновления DOM, или в противном случае установит el представления в результат шаблона. (В этом случае el представления, созданного при инициализации, будет перезаписан).
import morphdom from 'morphdom';
import { View } from 'backbone.marionette';
const VDomView = View.extend();
VDomView.setRenderer(function(template, data) {
const el = template(data);
if (this.isRendered()) {
// Patch the view's el contents in the DOM
morphdom(this.el, el, { childrenOnly: true });
return;
}
this.setElement(el.cloneNode(true));
});
В этом случае, поскольку рендерер изменяет el напрямую, нет необходимости возвращать результат рендеринга шаблона для обработки представлением в attachElContents. Конечно, можно возвращать скомпилированный DOM и изменять attachElContents для обработки объекта DOM вместо строковой константы, но во многих случаях это может быть излишне сложно.
Существует множество возможностей для рендеринга с Marionette. Если вы ищете альтернативы по умолчанию, этот ресурс может быть полезным: https://github.com/blikblum/marionette.renderers#renderers
Сериализация данных
Marionette автоматически сериализует данные из model или collection для использования шаблоном во время рендеринга. Вы можете переопределить эту логику и предоставить сериализацию других данных с помощью метода serializeData. Метод вызывается без аргументов, но имеет контекст представления и должен возвращать объект JavaScript для использования шаблоном. Если serializeData не возвращает данные, шаблон все равно может получить дополнительный контекст или пустой объект для рендеринга.
import _ from 'underscore';
import { View } from 'backbone.marionette';
const MyView = View.extend({
template: _.template(`
<div><% user.name %></div>
<ul>
<% _.each(groups, function(group) { %>
<li><%- group.name %></li>
<% }) %>
</ul>
`),
serializeData() {
// For this view I need both the
// model and collection serialized
return {
user: this.serializeModel(),
groups: this.serializeCollection(),
};
}
});
Примечание. Не следует использовать этот метод для добавления произвольных дополнительных данных в шаблон. Вместо этого используйте templateContext для добавления контекстных данных в шаблон.
Сериализация модели
Если представление имеет model, оно передаст атрибуты этой модели в шаблон.
import _ from 'underscore';
import Backbone from 'backbone';
import { View } from 'backbone.marionette';
const MyModel = Backbone.Model.extend({
defaults: {
name: 'world'
}
});
const MyView = View.extend({
template: _.template('<h1>Hello, <%- name %></h1>')
});
const myView = new MyView({ model: new MyModel() });
Способы сериализации model также могут быть настраиваемыми в зависимости от представления.
import _ from 'underscore';
import { View } from 'backbone.marionette';
const MyView = View.extend({
serializeModel() {
const data = _.clone(this.model.attributes);
// serialize nested model data
data.sub_model = data.sub_model.attributes;
return data;
}
});
Сериализация коллекции
Если представление не имеет model, но имеет collection, модели коллекции будут сериализованы в массив, предоставленный как атрибут items шаблону.
import _ from 'underscore';
import Backbone from 'backbone';
import { View } from 'backbone.marionette';
const MyView = View.extend({
template: _.template(`
<ul>
<% _.each(items, function(item) { %>
<li><%- item.name %></li>
<% }) %>
</ul>
`)
});
const collection = new Backbone.Collection([
{name: 'Steve'}, {name: 'Helen'}
]);
const myView = new MyView({ collection });
Способы сериализации collection также могут быть настраиваемыми в зависимости от представления.
import _ from 'underscore';
import { View } from 'backbone.marionette';
const MyView = View.extend({
serializeCollection() {
return _.map(this.collection.models, model => {
const data = _.clone(model.attributes);
// serialize nested model data
data.sub_model = data.sub_model.attributes;
return data;
});
}
});
Сериализация с CollectionView
Если вы используете template с CollectionView, который также не получает model, ваше CollectionView сериализует коллекцию для шаблона. Это может быть затратно и не нужно. Если ваше CollectionView имеет template, рекомендуется либо использовать пустое model, либо переопределить метод serializeData.
Добавление контекстных данных
Представления Marionette предоставляют атрибут templateContext, который используется для добавления дополнительной информации в ваши шаблоны. Это может быть объект или функция, возвращающая объект. Ключи возвращенного объекта будут добавлены к ключам модели или коллекции и будут доступны шаблону.
import _ from 'underscore';
import { View } from 'backbone.marionette';
const MyView = View.extend({
template: _.template('<h1>Hello, <%- name %></h1>'),
templateContext: {
name: 'World'
}
});
Кроме того, контекстные данные переопределяют сериализованные данные.
import _ from 'underscore';
import { View } from 'backbone.marionette';
const MyView = View.extend({
template: _.template('<h1>Hello, <%- name %></h1>'),
templateContext() {
return {
name: this.model.get('name').toUpperCase()
};
}
});
Вы также можете определить значение контекста шаблона как метод. Способ вызова этого метода определяется вашей системой шаблонизации. Например, с handlebars метод вызывается с контекстом данных, переданных шаблону.
import Handlebars from 'handlebars';
import Backbone from 'backbone';
import { View } from 'backbone.marionette';
const MyView = View.extend({
template: Handlebars.compile(`
<h1{{#if isDr}} class="dr"{{/if}}>Hello {{ fullName }}</h1>,
`),
templateContext: {
isDr() {
return (this.degree) === 'phd';
},
fullName() {
// Because of Handlebars `this` here is the data object
// passed to the template which is the result of the
// templateContext mixed with the serialized data of the view
return this.isDr() ? `Dr. { this.name }` : this.name;
}
}
});
const myView = new MyView({
model: new Backbone.Model({ degree: 'masters', name: 'Joe' });
});
Примечание. Объект данных, передаваемый шаблону, не клонируется глубоко и в некоторых случаях вообще не клонируется. Будьте внимательны при изменении данных, передаваемых шаблону, чтобы косвенно не изменять данные вашей модели.
Что такое контекстные данные?
В то время как сериализация данных больше связана с получением данных представления в шаблон, контекст шаблона добавляет другие необходимые данные или, в некоторых случаях, выполняет дополнительные вычисления, которые выходят за рамки простого «сериализации» данных model или collection
import _ from 'underscore'
import { CollectionView } from 'backbone.marionette';
import GroupView from './group-view';
const MyCollectionView = CollectionView.extend({
tagName: 'div',
childViewContainer: 'ul',
childView: GroupView,
template: _.template(`
<h1>Hello <% name %> of <% orgName %></h1>
<div>You have <% stats.public %> group(s).</div>
<div>You have <% stats.private %> group(s).</div>
<h3>Groups:</h3>
<ul></ul>
`),
templateContext() {
const user = this.model;
const organization = user.getOrganization();
const groups = this.collection;
return {
orgName: organization.get('name'),
name: user.getFullName(),
stats: groups.countBy('type')
};
}
})
© 2017 Muted Solutions, LLC
Licensed under the MIT License.
https://marionettejs.com/docs/v4.0.0/view.rendering.html