Skip to content

Instantly share code, notes, and snippets.

@sdesai
Created November 22, 2011 20:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sdesai/1386836 to your computer and use it in GitHub Desktop.
Save sdesai/1386836 to your computer and use it in GitHub Desktop.
WidgetStringRenderer

USE CASES

  • Render widgets on NodeJS, where DOM is absent.
  • Optimize rendering of Widgets at scale (1000s of TreeViewNodes).

GOAL

  • 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).

DESIGN

Opt-in Widget Extension, to maintain backwards compatibility.

A boundingBox/contentBox Node instance won't be present for initializer/attr setters/attr getters/HTML_PARSER, so needs to be opt-in.

  1. 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.

  2. 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]);

Handlebars Default

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?).

Two Phase Render

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

Options

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()

Will require re-establishing boundingBox/contentBox Node references, post-renderUI().

Widget will generate node instance from rendered template content for boundingBox, contentBox

Usage

TreeViewNode - Always 100% String rendered

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("");

Only on NodeJS (conditionally loaded extension mix)

// 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);
@natecavanaugh
Copy link

Hey guys,
I don't know how far you got with this (either the WidgetStringRenderer, or the MVC breakup of Widget), but in @sdesai's description of the new Widget (Base+Lifecycle+View+Model+Controller), how would the string renderer fit in there?

Would it be a plugin, an extension of the view that you compose in, or something else altogether?

We've run into the need for this in multiple components (trees, toolbar w/buttons, our textboxlist, and also other components custom to Liferay).
The other use case I can see, besides tree, would be building an entire UI from the JS. ExtJS recently did a kind of similar change in that they modified their rendering engine to do bulk updates so that each child component can contribute it's rendered state to it's owner (or parent) component, then at the end do one big render.

Basically, all components can have an owner, and the children all sit inside of the owner's (boudning|content)Box, and during some part of the lifecycle (probably render), the owner gathers up the children DOM (string or Nodes)*, and if it has an owner, pass it up, if it doesn't, flush the buffer of HTML into the render location.

I wonder for the P.E. scenario, I wonder if the same idea could be leveraged, where it contributes HTML_PARSER attributes, and right after render this is resolved.

*Resolving the contribution of either strings or nodes might be a bit hairy, though I can think of some ways to handle that, for instance, using stamped placeholder divs that are immediately swapped out with the component nodes on render)

Of course, you guys may have already solved all of this, and if so, ignore these as the ramblings of someone 6months behind :)

@sdesai
Copy link
Author

sdesai commented May 17, 2012

Hey Nate,

The breakup of Widget into these separate pieces doesn't appear to be on the near term horizon, as far as the base Widget goes. It's mainly because I haven't had a chance to think through how we'd get there, while maintaining backwards compatibility. But I'd imagine if it was broken out it would be:

StringRendering Based Widget : Widget = Base + Lifecycle + StringBasedView + Model + Controller 

The basic idea being - I should be able to switch out Views with the same Model, and one of those views could be purely string/template based.

Whether these are extensions or plugins doesn't make that much difference at the high level.

The idea is to be able to re-use/extend/switch out each piece independently.

That said, at the lower level, "extend" is where it does make a difference - since we'd need full "multiple inheritance" support, if you wanted to mix in an extension with it's own prototype chain.

If they are "plugins", they'd be separate objects which interact with each other. The downside there being the potential performance overhead of a single object now being three separate objects - the M, V and C instances. Not a big deal for a handful of instances, but for Tree etc, it may bubble up. If they were "extensions", they'd go through Y.Base.create, to create a single object (with the multiple inheritance drawback).

If I could start from scratch on a Widget2 base class, it would be broken out, with separately extendable M, V and C pieces. That doesn't mean folks extending Widget, can't begin to explore breaking out the M, V and C responsibilities which currently get rolled into a single class, as Luke is doing for the refactored DataTable.


As for the WidgetStringRenderer, I'd be interested in your feedback on this initial approach, which we're trying to iron out the gaps for:

https://github.com/sdesai/yui3/blob/widget-htmlrenderer/src/widget/tests/manual/widget-htmlrenderer.html
https://github.com/sdesai/yui3/blob/widget-htmlrenderer/src/widget/tests/manual/tree-htmlrenderer.html

Which is essentially the implementation based on the discussion above.

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