Skip to content

Instantly share code, notes, and snippets.

@qfox
Last active July 16, 2021 17:26
Show Gist options
  • Save qfox/165f0ef6a976034d071a to your computer and use it in GitHub Desktop.
Save qfox/165f0ef6a976034d071a to your computer and use it in GitHub Desktop.
Templates

Сравнение синтаксиса

Для сравнения была поставлена задача преобразовать входные данные вида:

var data = {
  caption: "Cap",
  people: [ 'John', 'Malkovich', 'Doe' ]
}

в строку Cap: John, Malkovich, Doe

См. также Сравнение возможностей веб-шаблонизаторов, EN

Динозавры

Препроцессор

#ifdef( CAPTION )
CAPTION:
#endif
# no vm, no loops

m4

m4_ifdef( `CAPTION', `CAPTION: ')', )
m4_dnl no loops?

Классические шаблонизаторы

Apache Velocity

#if( $caption )
  $caption:
#endif
#foreach( $person in $people )
  $person
#end

PHP

<?= $caption ?>
<? if ($caption): ?>: <? endif ?>
<? foreach ($people as $i => $person): ?>
  <?= $person ?>
  <? if ($i < count($people) - 1): ?>, <? endif ?>
<? endforeach ?>

ejs & resig

<%= caption %>
<% if (caption) { %>: <% } %>
<% for (var i = 0; i < people.length; i++) { %>
  <%= people[i] %>
  <% if (i < people.length - 1) { %>, <% } %>
<% } %>

erb

<%= caption %>
<% if (caption) %>: <% end %>
<% @people.each do |person, i| -%>
  <%= person %>
  <% if (i < people.length - 1) %>, <% end %>
<% end -%>

Histone

{{caption}}
{{if caption}}: {{/if}}
{{for i:person in people}}
  {{person}}
  {{if i < people.length}}, {{/if}}
{{/for}}

smarty/fenom

{$caption}
{if !$caption}: {/if}
{foreach from=$people item=person}
  {$person}
  {if !$smarty.foreach.last}, {/if}
{/if}

tt2

[% caption %]
[% IF caption %]: [% END %]
[% FOREACH person IN people %]
  [% person %]
  [% IF !loop.last %], [%END%]
[% END %]

jinja

{{caption}}
{% if caption %}: {% endif %}
{% for person in people %}
  {{ person }}
  {% if not loop.last %}, {% endif %}
{% endfor %}

mustache/handlebars

{{caption}}
{{#caption}}: {{/caption}}
{{#people}}
  {{.}}
  {{^last}}, {{/last}}
{{/people}}

dust.js

{caption}
{#caption}: {/caption}
{#people}
  {.}
  {@sep}, {/sep}
{/people}

ASP (?)

<%# DataBinder.Eval(Container, "caption") %>
<asp:datalist id="people"><%# DataBinder.Eval(Container, "person") %></asp:repeater>

Fest

<fest:template xmlns:fest="http://fest.mail.ru" context_name="json">
  <fest:value>json.caption</fest:value>
  <fest:if test="json.caption">:<fest:space /></fest:if>
  <fest:for iterate="json.people" index="i" value="person">
    <fest:value>person</fest:value>
    <fest:if test="i < json.people.length - 1">,<fest:space /></fest:if>
  </fest:for>
</fest:template>

Ближе к HTML

Jade

- if (caption)
  = caption + ': '
- each person, i in people
  = person
  - if (i < people.length - 1)
    = ', '

Haml

- if (caption)
  = caption + ': '
= @people.join(', ')

XSLT

<?xml version="1.0"?>
<data>
  <caption>Cap</caption>
  <people>
    <person>John</person>
    <person>Doe</person>
  </people>
</data>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/data/caption">
    <xsl:value-of select="." />:
  </xsl:template>
  <xsl:template match="/data/people/person">
    <xsl:value-of select="." />,
  </xsl:template>
  <xsl:template match="/data/people/person[position() = last()]">
    <xsl:value-of select="." />
  </xsl:template>
</xsl:stylesheet>

json2html

http://jsfiddle.net/qfox/4ye3fLgc/1/

var transform = [
    { tag : 'h1', html : function (v) {
        return v.caption ? v.caption + ': ' : '';
    } },
    { tag : 'section', children : function (ctx) {
        return json2html.transform(ctx.people, {
            tag : 'span', html : function (person, i) {
                return person + (i < ctx.people.length - 1 ? ', ' : '');
            }
        } );
    } }
];

document.body.innerHTML = json2html.transform(data, transform);
<h1>Cap: </h1>
<section>
  <span>John, </span>
  <span>Malkovich, </span>
  <span>Doe</span>
</section>

Transparency

div.root
  h1.caption
  section.people
    span.person
$('.root').render({
  "caption": "Cap",
  "people": [
    { "person": "John" }, // unfair
    { "person": "Malkovich" },
    { "person": "Doe" }
  ]
});
<div class="root">
  <h1 class="caption">Cap</h1>
  <section class="people">
    <span class="person">John</span>
    <span class="person">Malkovich</span>
    <span class="person">Doe</span>
  </section>
</div>

Plates (flatiron.js)

http://www.javascriptoo.com/Plates

var tmpl = '<h1 class="caption"></h1><span class="person"></span>';

var map = Plates.Map();
map.class('caption').to(function(v, k) { return v[k] ? v[k] + ': ' : ''; });
map.class('person').to(function(v, k) {
  if (!v[k]) { // hacky ;-(
    if (!v.people) return null;
    this._left = v.people.length;
    return v.people;
  }
  return v[k] + (--this._left ? ', ' : '');
});

var res = Plates.bind(tmpl, data, map);
<h1 class="caption">Cap: </h1><span class="person">John, </span><span class="person">Malkovich, </span><span class="person">Doe</span>

PURE

http://jsfiddle.net/qfox/t78k9wL5/

var tmpl = '<div><h1 class="caption"></h1><span class="person"></span></div>';

var directive = {
  '.caption': function () {
    var v = this.caption;
    return v ? v + ': ' : '';
  },
  '.person': {
    'person<-people': {
      '.': function (ctx) {
        return ctx.item + (ctx.pos < ctx.length - 1 ? ', ' : '');
      }
    }
  }
};

document.body.innerHTML = tmpl;
$p('div').render(data, directive);
<div>
  <h1 class="caption">Cap: </h1>
  <span class="person">John, </span>
  <span class="person">Malkovich, </span>
  <span class="person">Doe</span>
</div>

Внутри шаблонов html с биндингами

angular.js

http://jsfiddle.net/qfox/suv4d99r/1/

<div ng-app="root">
  <div ng-controller="scope">
    <h1 ng-if="caption">{{caption}}: </h1>
    <span ng-repeat="person in people" ng-class="{'with-comma': !$last}">{{person}}</span>
  </div>
</div>
function scope($scope) {
  $scope.caption = "Cap";
  $scope.people = [ 'John', 'Malkovich', 'Doe' ];
}
angular.module('root', []);
.with-comma:after { content: ", "; } /* tricky ;-( */

Result:

<div ng-app="root" class="ng-scope">
  <div ng-controller="scope" class="ng-scope">
    <h1 ng-if="caption" class="ng-binding">Cap: </h1>
    <!-- ngRepeat: person in people -->
    <span ng-repeat="person in people" ng-class="{'with-comma':!$last}" class="ng-scope ng-binding with-comma">foo</span>
    <span ng-repeat="person in people" ng-class="{'with-comma':!$last}" class="ng-scope ng-binding">bar</span>
  </div>
</div>

Knockout.js

<h1 data-bind="if: caption"><!-- ko text: caption --><!-- /ko -->: </h1>
<section data-bind="foreach: people">
  <!-- ko text: $data --><!-- /ko -->
  <!-- ko if: ($index() < ($parent.people().length - 1)) -->, <!-- /ko -->
</section>
function PeopleViewModel(data) {
  this.caption = ko.observable(data.caption);
  this.people = ko.observableArray(data.people);
}

ko.applyBindings(new PeopleViewModel(data));
<h1 data-bind="if: caption"><!-- ko text: caption -->Cap<!-- /ko -->: </h1>
<section data-bind="foreach: people">
  <!-- ko text: $data -->John<!-- /ko -->
  <!-- ko if: ($index() < ($parent.people().length - 1)) -->, <!-- /ko -->
  <!-- ko text: $data -->Malkovich<!-- /ko -->
  <!-- ko if: ($index() < ($parent.people().length - 1)) -->, <!-- /ko -->
  <!-- ko text: $data -->Doe<!-- /ko -->
  <!-- ko if: ($index() < ($parent.people().length - 1)) --><!-- /ko -->
</section>

React Templates

http://wix.github.io/react-templates/fiddle.html#88d931fc

var template = React.createClass({
  render: function () {
    return templateRT.apply(this);
  }
});
<div>
  <h1 rt-if="data.caption">{data.caption}: </h1>
  <span rt-repeat="person in data.people">
    {person}
    <span rt-if="personIndex < data.people.length - 1">, </span>
  </span>
</div>
<div data-reactid=".2"><h1 data-reactid=".2.0"><span data-reactid=".2.0.0">Cap</span><span data-reactid=".2.0.1">: </span></h1><span data-reactid=".2.1:0"><span data-reactid=".2.1:0.0">John</span><span data-reactid=".2.1:0.1">, </span></span><span data-reactid=".2.1:1"><span data-reactid=".2.1:1.0">Malkovich</span><span data-reactid=".2.1:1.1">, </span></span><span data-reactid=".2.1:2"><span data-reactid=".2.1:2.0">Doe</span></span></div>

view-ориентированные структуры

React JSX

https://jsfiddle.net/qfox/4ymugqw2/

jsx:

var TemplateRT = React.createClass({
  render: function() {
    var data = this.props.data;
    return <div>
      {data.caption ? data.caption + ': ' : ''}
      {data.people.map(function(person, personIndex, people) {
        return person +
          (personIndex < people.length - 1 ? ', ' : '');
      })}
    </div>;
  }
});

React.render(<TemplateRT data={data} />, document.body);

js:

var TemplateRT = React.createClass({displayName: "TemplateRT",
  render: function() {
    var data = this.props;
    return React.createElement("div", null,
      data.caption ? data.caption + ': ' : '',
      data.people.map(function(person, personIndex, people) {
        return person +
          (personIndex < people.length - 1 ? ', ' : '');
      })
    );
  }
});

React.render(React.createElement(TemplateRT, {data: data}), document.body);
<div data-reactid=".0"><span data-reactid=".0.0">Cap: </span><span data-reactid=".0.1:0">John, </span><span data-reactid=".0.1:1">Malkovich, </span><span data-reactid=".0.1:2">Doe</span></div>

BH

([
  { block: 'caption', content: 'Cap' },
  { block: 'people', content: [
    { elem: 'person', content: "John" },
    { elem: 'person', content: "Doe" }
  ] }
])
bh.beforeEach(function (ctx, json) {
  // we need a string in result
  ctx.tag(false);
});
bh.match('caption', function (ctx) {
  return ctx.content() + ': ';
});
bh.match('people__person', function (ctx) {
  return ctx.content() + (ctx.isLast() ? '' : ', ');
});

NB: BH is a BEM specific template engine aimed to generate HTML. But it's possible to convert raw data right in place into structured json:

bh.beforeEach(function (ctx, json) {
  // code below needed to make bemjson for raw data only
  if (json.content) return;
  ctx.content([
    json.caption && { block: 'caption', content: json.caption },
    json.people && { block: 'people', content: json.people.map(function (person) {
        return { elem : 'person', content: person };
      })}
  ]);
});

BEMHTML (xjst based)

([
  { block: 'caption', content: 'Cap' },
  { block: 'people', content: [
    { elem: 'person', content: "John" },
    { elem: 'person', content: "Doe" }
  ] }
])
block('caption')(
    tag()(false),
    content()(this.ctx.content + ': ')
)
block('people')(
    tag()(false),
    content()(function () {
        return this.ctx.content
            .map(function(el) { return el.content })
            .join(', ')
    })
)

Другие

XJST

template(this.caption)(function () {
  return this.caption + ': ';
});
template(this.people)(function () {
  return this.people.join(', '); // unfair
});

Ссылки

@voischev
Copy link

Круто!

@AVStarikovich
Copy link

Теперь BH кажется не таким уж и странным, а очень даже удобным)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment