- Render widgets on NodeJS, where DOM is absent.
- Optimize rendering of Widgets at scale (1000s of TreeViewNodes).
- Avoid all Node references from Widget through the end of renderUI().
- bindUI()/syncUI() will still have Node references (to bind events and incrementally update DOM).
A boundingBox/contentBox Node instance won't be present for initializer/attr setters/attr getters/HTML_PARSER, so needs to be opt-in.
-
Component Developer can opt-in, if they have a 100% string rendered component (e.g. TreeViewNode)
In this case, get("boundingBox") etc. needs to be documented accordingly for the end user.
-
Environment can opt-in (e.g. NodeJS - conditional loading)
Example:
Y.WidgetStringRenderer = function() {...};
Y.WidgetStringRenderer.prototype = {
// override what needs to be overridden in Y.Widget to support string template based rendering.
}
Y.Foo = Y.Base.create("foo", Y.Widget, [Y.WidgetStringRenderer]);
It'll use Handlebar style templates as opposed to substitute, for forward compatibility.
However we should maybe look into a "handlebars-core" to satisfy the kweight nitpickers. We've been asked to break out less than 3KB chunks before which is where handlebars-base-min.js currently is.
"handlebars-core" could provide basic {{ }} and {{{ }}} support, and also maybe provide substitute compatibility (how to identify single { tokens, from content in a handlebars template?).
We'll need to break up the render()
phase, into the renderUI() portion and the bind/syncUI() portion
render()
renderUI() : No Node references
bindUI() : Node references
syncUI() : Node references
Not sure what the method split should be yet. Options are below, first one is my leading candidate
TreeView use case:
// While iterating 1000s treeview nodes ...
treeviewNode.renderHTML(buffer); // only renderUI() - is it OK that render event is not fired?
// I think so. Nothing is in the DOM yet.
// Once injected into the DOM ...
treeviewNode.render(); // renderUI() [if not invoked before], bindUI(), syncUI()
NodeJS use case:
// On Server
calendar.renderHTML();
// On Client
calendar.render();
Or,
treeviewNode.render();
treeviewNode.bind(); // How about if they want to do it all at once in render(); bind() and Y.bind() confusion
Or,
treeviewNode.renderHTML();
treeviewNode.bind();
Or,
treeviewNode.renderUI() // When do we fire/set render state?
treeviewNode.bindUI()
treeviewNode.syncUI()
Widget will generate node instance from rendered template content for boundingBox, contentBox
Y.TreeViewNode = Y.Base.create("treeViewNode", Y.Widget, [Y.Parent, Y.Child, Y.WidgetStringRenderer]);
// In Parent.render() ...
var buffer = [];
for (i = 0; i < children.length; i++) {
child.renderHTML(buffer);
}
var allChildrenHTML = buffer.join("");
// calendar-nodejs, or maybe just widget-base-nodejs
Y.Calendar = Y.Base.create("calendar", Y.Widget, [Y.WidgetStringRenderer]);
var buffer = []
calendar.renderHTML(buffer);
var calendarHTML = buffer.join("");
We can add sugar in the future (render straight into a template for example). Not enough time for Sprint 1.
calendar.renderHTML(template, token);
I haven't read through entirely, and will likely have more feedback (meeting, perhaps?).
IMO, markup string composition is a good baseline approach and is useful in 2 of what I see as the 3 core "render"ing scenarios:
The distinction as you noted in the gist is whether it's appropriate to do any work beyond creating the markup. #1 gets no benefit from DOM or markup string composition at all unless mutation is supported, but needs the binding/syncing(optional?) step. #2 needs markup generation, DOM creation, and binding. #3 needs only markup generation.
I entertained the idea of having a markup factory for DataTable a while back, which DT would leverage, but would exist as a separate utility. I liked the separation of DOM generation from feature enhancement because it would allow implementers the ability to render a static table from a set of data without the overhead of Widget instance creation. The resulting table could then be enhanced with Node plugins to add features. In the end, I opted to go with View instances hung off the DT instance serving as renderers of the various table sections. This would offload the choice of #1, #2, or #3 to the given View assigned at instance creation, while preserving the class API. Because DT has a wide range of possible features that impact the markup/DOM, I suspect a class extension like StringRenderer would run into conflicts fairly quickly.
The result is similar given the DT's View/Renderer case, though. The default renderer (be it baked in, mixed in with extension, or delegated to a View) would need to be replaced with an alternate that supports the conflicting feature. Having the renderer be configurable, though, hopefully obviates the need for Base.create calls to add features or resolve rendering conflicts.
The resulting arch for DT is that the Views will be treated like markup factories, receiving the DT instance as the data origin. But theoretically, these Views would be able to live separately and serve as markup/DOM factories for static data.
I think this harkens back to your original thought of Widgets as MVC triplets with various renderers.
After this long stream-of-consciousness novella, I wonder if it would be preferable to have the DOM rendering implementation broken out into a class extension and WidgetBase would be an abstract class.
Gotta run--babies crying.