Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement HTMLBars #15

Open
jamesplease opened this issue Dec 24, 2014 · 4 comments
Open

Implement HTMLBars #15

jamesplease opened this issue Dec 24, 2014 · 4 comments

Comments

@jamesplease
Copy link
Owner

The default templating engine should be htmlbars

@jridgewell
Copy link

👍. I'm super interested in it, though I think it's still beta?

@jamesplease
Copy link
Owner Author

I think it's still beta?

Yup, I think so too. But it's now also included in Ember beta, which gives me some confidence that the API is nearing stability. I also asked some friends who keep up with this stuff, and they said that it 'definitely is.' I can keep an eye on it, and it will grow with puppets!

@jamesplease
Copy link
Owner Author

An example Backbone framework-thing that uses htmlbars is Rebound. That library does a whole bunch of things – far more than I want to do with Puppets –  but it's useful as a resource.

The tl;dr of HTMLBars is that it doesn't provide data-binding out of the box. Rather, it seems to function exactly, or very similarly, to Handlebars out-of-the-box.

What it does is provide 'hooks' that you can override to add features such as data-binding. The default hooks are what make it act similarly to Handlebars. The source of the hooks is here. Documentation for the hooks can be found here.

It's difficult to find a bare-bones example of data-binding out-of-the-box. It's either nonexistent (as in the HTMLBars source itself) or under a huge load of abstractions (as in Ember and Rebound). And given that the docs for HTMLBars is still in the works, it can be tough to figure out what to do.

But I think I've figured it out, and here would be a barebones data-binding example:

// I'm loading the cjs version of HTMLBars using ES6 because...
// that's what my copy+pasted webpack config did for me. DON'T JUDGE.
// The important thing is that we'll need the runtime. Or, to be more specific,
// the default hooks, which are included in the runtime
import * as htmlbarsRuntime from 'htmlbars/dist/cjs/htmlbars-runtime';

// This is copy+pasted from the hooks file. It's used
// internally, but not exposed.
function lookupHelper(env, context, helperName) {
  return env.helpers[helperName];
}

// Create our own `content` hook. This is nearly exactly the same as the original...
// the last few lines are what has changed (and are what give us data binding)
export default {
  content: function(env, morph, context, path) {
    var helper = lookupHelper(env, context, path);

    var value;
    if (helper) {
      value = helper.call(context, [], {}, { morph: morph }, env);
    } else {
      value = htmlbarsRuntime.hooks.get(env, context, path);
    }

    morph.update(value);

    // Expose this method on the window that we can call at will to
    // update the 'morph,' which is the DOM node that this content is bound to
    // This is the most important part of this entire example
    window.setVal = function(val) {
      morph.update(val);
    }
  }
};

Later, you can set up the data-bound template like so:

var template = htmlbars.compile('<span>{{num}} <input type="text"></span>');
var allHooks = _.extend(htmlbarsRuntime.hooks, hooks);
var context = {num: 0};
var env = {hooks: allHooks, helpers: htmlbarsRuntime.helpers, dom: new htmlbars.DOMHelper()};

var domFragment = template.render(context, env);
$('body').append(domFragment);

window.setInterval(function() {z
  window.setVal(context.num++);
}, 1000);

It will update the prefix to the input field every second with the new number, but won't re-render the whole template. Neato.

In Ember, Streams are used instead of that hacky window.setVal. When the content fn is run, if you pass a stream it registers a callback that updates the morph. In Rebound, a LazyValue thing is used that effectively accomplishes the same thing.

I'm cool with streams, but I'm curious as to the benefits of using them over a simple pub-sub strategy. I'm sure there is one, or Ember wouldn't have implemented them. But what exactly are those benefits? I'll have to do more research. In a first draft implementation, I'll just link it up to a model's change event and see what happens.

Question: How does Ember send streams sometimes to the hooks, and not streams other times? It doesn't support bound attributes right now, so does that mean that you simply can't do attr={{ myVal }}, or is there a way to write that without it becoming a stream?

@jridgewell
Copy link

I'm also looking into another templating language: Paperclip.js

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

No branches or pull requests

2 participants