Hm.js

where model and view should live in

Template

Hm.js does not have a template engine, instead it uses template adapters to call other template engine. It comes with two template adapters, one for jsrender, one for handlebars.js. If your template favorite engine is not one of them, you can implement an adapter for your favorite one. The follow example renders a view using jsRender adapter.

<body>
  <table>
    <tr>
      <td>First Name</td>
      <td>Last Name</td>
	</tr>
	<tbody data-sub="include:contacts|contactRow">
	</tbody>
  </table>
  <script type="jsrender" id="contactRow">
	<tr>
		<td>{{:firstName}}</td>
		<td>{{:lastName}}</td>
	</tr>
  </script>
</body>

If objects react but not act, how a view can be rendered? It turns out the view render itself. The "for" subscription group registers a subscription for a special event init with a template workflow called "tmpl". The "init" event will be triggered when the subscription is registered, so the workflow will be executed. In above example, the workflow render some mark-up using data in "contacts" and template "contactRow", and the view replace its content with the markup.

How template workflow works

A template workflow consists of five activities. In the case here, the "tmpl" activit is as follow:

tmpl = {
 initialize: "*templateOptions",
 get: "get", //extensible
 convert: "*template",
 set: "html", //extensible
 finalize: "*parseSubs"
};

When a subscription is registered with a template workflow, the "initialize" activity "*templateOptions" fetch an workflow instance with a template id. When event triggered,the workflow instance uses "get" activity "get" to get data in model repository, then uses "convert" activity "*template" to convert the data into markup with the template, then use set activity "html" to replace view's content with mark-up, and finally uses "finalize" activity to "*parseSubs" to register the declarative subscriptions embedded in the markup.

In the above example, the output content of the workflow is static. In the following example, the output content contains declarative subscriptions, which will be automatically registered by the finalize activity "*parseSubs", so the output content is responsive to view event and model event. This "*parseSubs" activity is one of the most important reason to use declarative subscription. Without this, it will be cumbersome to register subscriptions programmatically again and again. With this, it is possible that child view can render its descendant views, and so far and so on.

<tbody data-sub="ns:contacts;
                  listView:.|contactRow;
                  movableRow:.;
                  $delete:.|*removeItem;"></tbody>
</table>

<script type="jsrender" id="contactRow">
  <tr data-sub="{{keyToNs /}}">
    <td><input type="text" data-sub="val:firstName" /></td>
    <td><input type="text" data-sub="val:lastName" /></td>
    <td>
      <input type="button" value="delete" data-sub="del:." />
      <input type="button" value="move up" class="moveUp" />
      <input type="button" value="move down" class="moveDown" />
	</td>
  </tr>
</script>

Render a <body /> with template

Thanks to the "*parseSubs" activity of template workflow, root view <body /> can build its child views using template workflow, and recursively the child views can build their own child views. Eventually, whole view can be built. The following is refactory of the above example. The difference is that, the body is empty initially.


<body data-sub="include:/|rootView"></body>

Embedded template, external template and inline template.

In previous examples, the templates are called embedded template because they are embedded in the page script tags, they can be reused in the page because they can be reference by Ids.

If you have templates which can be reused across the pages or you don't want to embedded into the page like previous example, you can move your templates in external files. But you don't need to change a single lines of code because of this. The library can dynamically load the external template before rendering. The following is refactory of previous example. The external template file is here


<body data-sub="include:/|rootView"></body>

If your template is simple and not meant to be reused, you can put them in inside of the view container, like the following. This kind of template is called inline template.

<table>
  <tr>
    <td>First Name</td>
    <td>Last Name</td>
  </tr>
  <tbody data-sub="include:app.contacts">
    <tr>
      <td>{{:firstName}}</td>
      <td>{{:lastName}}</td>
    </tr>
  </tbody>
</table>

What is next?

This is wrap for an overview of the library. For more information about the library and the bundled plugins, please refer to documentation. There are lots example in the documentation, and they are hosted in jsbin.com. Because of that, they are fully hackable. You can change the code directly, the original code will not be effected. Great thanks to jsbin.com. You can report bugs and discuss features on the GitHub issues page.