AmpersandJS / ampersandjs.com

The documentation site for ampersand.js
http://ampersandjs.com
MIT License
51 stars 40 forks source link

Lets discuss templating... #116

Open ivan-kleshnin opened 9 years ago

ivan-kleshnin commented 9 years ago

AmpersandJS declares itself to be template agnostic. But it turns out not to be exactly true. I tried my best to combine popular (and well written) Nunjucks template engine with Ampersand views and come to frustrating conclusions.

1) Most template engines want to operate on native JS data. Arrays, Objects you know. Ampersand works with wrapped data. There mismatch begins. I tried to convert data with toJSON() but it seems to target mostly backend stuff as it misses derived and session by default. Override or add some method? I wanted to be conventional, not reinvent a wheel right from the start. But passing Ampersand objects fails completely. Nunjucks, for example, can't even iterate Ampersand collection with its {% for ... in %} loop construction. And custom JS code is forbidden or hard to implement. I wonder why I should reimplement loops, are we solving puzzles instead of work? Logic-less templaters fail by the same reason. Embedded JS template engines could help but there are none decent at this moment. Don't advice me EJS, please :) So all this agnostic declarations look more like advertisements after all.

It perfectly shows, but the way, how much flawed is OOP. But I still think that docs or "Human JavaScript" book had to mention this ubiquitous issue.

2) Major part of Ampersand documentation and code propagates view bindings. But that bindings are an alternative template solution on their own, aren't they? They require pure HTML and call it "template". How confusing! To implement loop then I should fall back to helper. To implement if... I don't even know... I suppose that template engines and Ampersand view bindings are mutually exclusive. At least in some sense. And documentation is silent about this again.

Most of this are not exactly Ampersand issues. My point on this is that documentation should provide guidance, should show real world examples and pitfalls. If you guys use that view bindings heavily and they prove themselves – let us know. Agnostic mottos just for a sake of it are useless, we all know that being agnostic is good but bitter truth is better. If not – why bother?

prust commented 9 years ago

@ivan-kleshnin: For reference, this issue (or part of it, anyway) was discussed in https://github.com/AmpersandJS/ampersand-state/pull/76.

To echo @latentflip's question:

given that ampersand-state/models have direct property accessors, without having to use .get('foo') like in backbone, is there any actual need/benefit to serializing to json before shipping to your templates?

Do you know what exactly the Nunjucks template engine is doing something (iterating with a for/in or checking hasOwnProperty or something else?) that doesn't work with getters/setters? If it's not too crazy, there's a chance other templating engines are doing it as well, in which case we may want to consider converting to raw objects/arrays before sending to the templating engine, as you propose...

ivan-kleshnin commented 9 years ago

I don't propose convertion to raw data. It would be definitely bad in a current state of things. Those wrappers were added for a reason, as JS notoriously lacks builtin functionality. I look for a solution, just as you, guys, and the only bullet-proof approach I see at this moment is JSX (ReactJS).

prust commented 9 years ago

@ivan-kleshnin: Regarding the purpose of view bindings, my understanding (and I could totally be off) is that they are intended as a simple stop-gap supplement to templating for a performance boost when you need to update tiny bits of the DOM but don't want to re-render the entire template. The ampersand community has been investigating a more complex, comprehensive solution, like JSX (ReactJS), that infers the bindings from a template, called domthing.

There has been a little work on integrating React with Ampersand in https://github.com/AmpersandJS/ampersand-react-mixin/issues/1 and https://github.com/JaapRood/react-backbone-events-mixin

wraithgar commented 9 years ago

How much of this would be solved if collections were made properly enumerable? This seems like it could potentially be a small oversight rather than the far-reaching issues you're deriving from a missing 'for ... in' support.

Most templating languages out there are functions that receive a context, and return html. It appears even nunjucks operates this way. The goal of being template agnostic means ampersand aims to work with any language that follows this concept, and since getters/setters are already properly set up in ampersand-state (for derived/session) having enumerables in collections would probably suffice.

wraithgar commented 9 years ago

https://github.com/AmpersandJS/ampersand-collection/issues/35

wraithgar commented 9 years ago

For now, can you try iterating through the 'models' property of the collection instead of the collection? It's less than ideal but at least that could have you up and working right now, until that other issue is addressed.

ivan-kleshnin commented 9 years ago

@wraithgar, maybe... I won't argue.

Nunjucks renders its for loop like this:

<ul>
  {% for model in models %}
    <li>{{ model }}</li>
  {% endfor %}
</ul>
var t_3 = runtime.contextOrFrameLookup(context, frame, "models"); // this is collection object
...
for(var t_1=0; t_1 < t_3.length; t_1++) {
  var t_4 = t_3[t_1];      // here t_4 is always undefined as Ampersand models don't support index access.
  frame.set("model", t_4); // model is always undefined
  ...
}

Is that what you called "unproperly enumerable"?

ivan-kleshnin commented 9 years ago

@wraithgar I will appreciate your answer to the part about view bindings too. How those data hooks are supposed to work along with template variable placeholders?

Confirm that collection.models access seems to work.

ivan-kleshnin commented 9 years ago

@prust but then you have concept duplication: filters, coercions, i18n, etc. should be declared in template for full render and filters, coercions, i18n, etc. should be declared in ampersand view code for partial rerenders. That is even worse than code duplication because can't be resolved.

<div data-hook="foo">{{ foo | someFilter }}</div>

domthing looks promising, I agree on that.

prust commented 9 years ago

@ivan-kleshnin:

then you have concept duplication

Agreed, that's why I consider view bindings a stop-gap, not a final solution. It's tricky to balance all the concerns (performance, modularity/extensibility, ease of use, maintainable implementation, etc); React is focused squarely on this issue and is 128k minified.

wraithgar commented 9 years ago

If you want the state of the data to track into your view you will want to use bindings (or domthing which listens to events from the data to know when to update). If you are simply doing a single templating of data that isn't going to need to update if the data changes, use template variable placeholders. It's not about using them all, just the ones that suit your needs.

latentflip commented 9 years ago

This is a fundamental challenge of being agnostic to templating engines, while also wanting to data-bind model properties/collection changes to the template itself.

Templating engines are predominantly just string interpolators, so can't bind to future changes in properties. So if you want to interpolate {{ foo | somefilter }} with the template engine, then if foo changes the property + filter won't be updated. In backbone that means if you have model changes you have to rerender the full view and any sub views to have the change be reflected. That's conceptually simple, but also slow and can be a frustrating user experience as things like selections, cursor positions etc, will be lost.

To deal with this ampersand gives you the bindings hash, which bind properties in various ways to the dom, based on CSS selectors, like data-hooks. When a prop on a model changes, the binding finds the property to the element in the existing dom. Now, you're absolutely right, that means duplication of concepts with your templating engine, which is why in general we (at andyet) just don't bother using {{ }} interpolations in the template engine, and just generate html with data-hooks and use bindings to set the model props in the dom. Yes, this means throwing away half the power of your templating engine of choice, as frequently it's just generating boring html.

Collections present similar challenges. Iterating with for ... In in nunchucks or whatever won't add/remove elements from the dom as the collection changes, unless you trigger a rerender on everything. Here, ampersand provides renderCollection which will handle updating views in the dom as models are added/removed from the collection. Again, that means not using a bunch of your templating engine.

So, what to do? It's kind of up to you. Bindings and simple templates works well for us, but makes things like i18n and complex filters become trickier. Rerendering all your views allows you to use the full power of nunchucks, but sucks performance wise. Domthing is a project of mine that's trying to allow you to interpolate in your template (like nunchucks) but which sets up bindings for you automatically so you don't need duplication of bindings in the view. It's a powerful idea but still a bit beta. You could use react, which kind of replaces views and templates with it's own single concept, which conceptually "Rerenders all the time, but performantly", obviously you won't get to use nunchucks there either.

I know there's no easy answers here, but hopefully it at least gives some clarity to the discussion :)

<3 phil

Philip Roberts &yet

On 4 Dec 2014, at 20:18, Ivan Kleshnin notifications@github.com wrote:

@prust but then you have concept duplication: filters / coercions / i18n should be in template for full render and filters / coercions / i18n should be ampersand view code for partial rerenders. That is even worse than code duplication.

{{ foo | someFilter }}

— Reply to this email directly or view it on GitHub.

ivan-kleshnin commented 9 years ago

Thank you guys. @latentflip, now that was a answer!

If templaters in Ampersand ecospace are just "for boring HTML" and their power is kinda unasable that definitely influence our choice of them. I don't want to send to browser kilobytes of code I won't use. Now even pet underscore templater seems to be good fit with Ampersand as it: 1) allows custom js code 2) is tiny That is exactly what I wanted to extract from this discussion.

prust commented 9 years ago

Great overview, @latentflip, thank you!