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);
@sdesai
Copy link
Author

sdesai commented Nov 28, 2011

@rgrove:

I see that conceptual value if Widget is broken up into separate M, V, C components as discussed in the App/Widget conversation. If I'm just mixing in support for string based rendering to the current Widget API, I don't see any value in pulling the View API along if we won't be using any of it, especially since it'll probably lead to confusing overlap with similar areas of Widget which are already defined (bounding box/content box templates vs. template, widget destruction vs. view destruction, model vs. widgets attrs, container vs boundingBox etc).

If we do end up breaking Widget into a separate M, V and C down the road (not currently in the 3.5.0 timeline, maybe 3.6.0) then I think it makes complete sense to see what the most basic M, V and C could be which could be used in the Widget world as well as the App world.

I'm leaning towards having WidgetStringRenderer be something that maybe Tree owns (I'll do the work, but it'll be an internal TreeViewNode impl) for the 3.5.0 release (or at least for the pr1 release), due to the shortness of time, and we can visit the Widget MVC breakup in more depth for 3.6.0 - which I believe is the real answer to the above design pros/cons.

I'll think about it a little further from the angle of delegating to a View string renderer, but I think there's too much backwards compatibility baggage to carry along and I'm likely to go with the above.

@lsmith, @rgrove:

I agree - when we don't have an existing API we need to reconcile (as in the base Widget case) doing whatever we can to break the view pieces of a widget out into encapsulated reusable chunks makes sense. I would argue though that View needs to be more basic, if we have more than a couple of use cases which don't end up being bound to a Model (that was the basis of my DT review question). Having for example a DT header View instance, which is not bound to a model, but has a model/modelList attribute, leads to some confusion, especially in the DT world. The most basic View could be purely a renderer (with associated utility methods/attributes), without any Model/Data binding overhead.

@lsmith:

Re: WidgetBase, while playing around with the Widget MVC breakout during the week of NodeJS, that's actually what I had - WidgetBase + WidgetLifecycle + WidgetView + WidgetModel + WidgetController extensions [ didn't get as far as putting it all back together ]

@lsmith
Copy link

lsmith commented Nov 28, 2011

@sdesai, Agreed that the Widget MVC breakup is probably the real answer to the above pros and cons.

While I can see the value in having a class extension that can be mixed into the existing arch while we revisit a larger project like breaking Widget into MVC, it seems like a gallery module more so than a core module. As a core module, it should be there because there is at least one driving use case.

You're suggesting TreeView is that use case (are there others?), but given I am taking a different approach for DT and @derek is exploring a markup factory approach for Button, I suspect we need to come together around a single (or fewer than three) solution, or 3.5.0 will represent a loss of cohesion to our story for how to build Widgets.

I'm willing to accept (though not happy about) the idea that DT and TreeView (and Button if needs be) will have separate stop gap solutions in 3.5.0 for quasi-MVC implementations until the real thing comes along in 3.6.0. But if it's avoidable, that would be preferable. I wonder which approach closer approximates your vision of the future.

@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