Post during the week ^ W month JavaScript on Habré.
After an
article on the development of a one-page web application, I bookmarked
ICanHaz with the aim of tearing it in and slightly finishing it up with how they would reach it. And, as usual, put it in the back box.
And so, having caught a cold right before the last weekend and, accordingly, unexpectedly having received two free days at the computer, I returned to this idea. As a result, instead of cosmetic finishing, it turned out its modest bicycle with a motor.
')
Recall that ICanHaz is an easy way to slightly organize Mustache templates used by javascript in the browser. Rendering templates using this library is reduced to a simple function call. It also eliminates the need to screen half of the template, because its text can be written directly in the HTML <script> tag
What for?
Actually, ICanHaz is simple: there is a template engine (mustache.js), there are templates (full-fledged and partial), written directly in HTML code in <script> tags with names in ID attributes and there is a global ich object, which when the page loads methods with template names are added. Next, passing the object to the appropriate method, we get the rendered text at the output:
$('#myDiv').html(ich.myTemplateName(objModel));
I also wanted a somewhat more subtle mental ^ W organization of templates. For example, in the simplest online store there can be several types of goods (let it be books, magazines, films and music). For each of them there should be a list template (list), for each list an item, for item, a price with assigned currency (price). And then we add a column on the right with discounts and we want to show the price in a slightly different way ...
In general, it is possible, of course, to work with a flat collection of templates, but it would be nice to somehow organize them, for example, into a hierarchy, and even with the inheritance of partial'ov (for example, for all types of goods to determine the price format, for all templates with books - a way to format the names of the authors). It would also be great to be able to add one template to several places without having to copy the <script> block (for example, make an exit date template for magazines and newspapers containing the week number of the year, and only a year for books and films).
From these ideas and something was born.
Description HotMilk
The library is based on the
Milk template engine, which is a
CoffeeScript implementation of
Mustache . Milk was chosen a long time ago and I don’t remember for what reasons, it seems like because of the best support for the Mustache specification. The name, respectively, from Milk'a and went (plus a hint of the templates that are embedded in HTML).
HotMilk versions for jQuery, MooTools and a browser without a framework, as well as the basic version without loading templates from the DOM (they can be used on the server if desired) can be assembled.
Before use, just like in ICanHaz, you need to fill out the template library. This is done using the $ addTemplate method:
HotMilk.$addTemplate('path/to/template', 'template {{text}}'); HotMilk.$addTemplate('path/to#partial', '...');
Templates can be added and removed in any order, so in which case they can be asynchronously pull up from the server. Templates can also be automatically compiled from <script> tags when the page finishes loading. In order for the template to be loaded, you need to set the “text / x-mustache-template” type to it and provide it with the data-hotmilk-path attribute (by the way, the HTML5 specification does not prohibit the use of data- * attributes for scripts, so that even validity is observed ):
<script type="text/x-mustache-template" data-hotmilk-path="books/list#item"> <a href="books/{{id}}"><b>{{title}}</b> by {{#author}}{{>author}}{{/author}}</a> </script>
The sample template is partial, i.e. it is used as part of the drawing of the books / list template.
In the data-hotmilk-path attribute, you can add several paths separated by a colon (several independent copies will be created).
All the principles of the organization of templates can be put into a few simple theses:
A small demo is in
the HotMilk repository on github .
If someone suddenly wants to use the library in business, pay attention that at least some sane test suite is not available yet and it may not be for a long time, so there may be bugs.
Under the hood: implementation features
I'll run through some source code fragments with a few comments.
Partial collection class. It is due to this construction that the parental template subtext patterns are inherited.
PartialsCollection = function(parentPartialsCollection) { var ctor = function() {}; ctor.prototype = parentPartialsCollection || PartialsCollection.prototype; return new ctor(); };
It works simply: with each call, it creates a new class with the previous set of partial's as a prototype. When you first start takes your prototype. Accordingly, each object created in this way will be quite an instance of the PartialsCollection class, possessing, moreover, the properties of all collections in the parent chain:
var a = new PartialsCollection(); a.t1 = "template 1"
Factory template functions. Virtually the heart of the library. Gets a template and an instance of the PartialCollection class and returns a function that takes a model and returns a rendered string.
var createTemplatingFunction = function(template, partialsCollection) { return function(data) { return Milk.render(template, data, function(partialName) { if(partialsCollection[partialName] && partialsCollection[partialName].$value != null) { return partialsCollection[partialName].$value; } else { throw new Error("Unknown partial: " + partialName); } }); }; };
The functions Milk.render are passed to the template, model, and subtemplate search function by name, which simply looks to see if the collection has such a field and whether it has $ value.
And then, using this function factory, a full-fledged node of our tree is created:
var createTemplateNode = function(template, partialsCollection) { partialsCollection = partialsCollection || new PartialsCollection(); var templatingFunction = createTemplatingFunction(template, partialsCollection); templatingFunction.$ = partialsCollection;
To implement the addition of templates in random order, I had to implement another trick. For example, it is possible that a partial “path / to # partial” is added but no template exists yet and it is not clear if “to” is a template or another group. To resolve this situation, the path is always built from groups, and then, if it turned out that it was necessary to attach a template, the node is replaced with partial partial saving:
var addNormalTemplate = function(root, path, template) { if(path.length === 0) { throw new Error("Couldn't create template: name must not be empty"); } var node = nodeBuildPath(root, path.slice(0,-1)), name = path[path.length - 1]; if(hasOwnProperty(node, name)) {
With subpatterns easier:
var addPartialTemplate = function(root, path, partialName, template) {
If a template is created by creating a group and then replacing it, then deletion is the opposite: first, the template is replaced with a group, preserving the collection of subpatterns, and then the path is cleaned from the end (groups that are left without templates and partial'es are deleted).
Attentive readers have noticed the curve way to call hasOwnProperty. This is done again for reinsurance so as not to break from the template named hasOwnProperty. The situation, of course, is delusional, but oh well, at the same time it should have a positive effect on compression.
var hasOwnProperty = function(obj, propName) { return Object.prototype.hasOwnProperty.call(obj, propName); };
In general, the main points considered, anyone can explore the remainder on github. Download the finished assembly can be there.
I hope someone will come in handy. Thanks for attention!
Links
UPD: It was enough to write an article, exposes that nakosyachil in the strongest way when trying to make inheritance from Function (is it even possible ???).
In general, fixed. After editing, the functionality was not affected, only the insides redid and updated the post in the affected places.