Proposal for building component trees - "protoComponents", and I18N chat tomorrow (Monday 25th 11am EST)

Antranig Basman antranig.basman at colorado.edu
Mon Jan 25 05:21:15 UTC 2010


Although far-reaching renderer improvements are a long-term aim for the 
coming year, we are already feeling some pain with the immediate Engage 
work with respect to the way we build component trees. This message 
explains a proposal for simplified building of the simpler kinds of 
trees that we face, as well as providing a foundation to ease in the 
more ambitious work we will want to tackle for Engage 2.

Component trees as they stand are verbose and deeply nested, which 
inhibits readability and promotes error. "protoComponents" (I am quite 
sure this name will end up being changed - for some reason the community 
doesn't take kindly to many of my names and "I can't think why!" - 
whatever happened to fluid.contund() :P) are a new slimline way of 
expressing component trees in a form which is not only condensed but 
unambiguous. It is a little similar in outline to the historical formats 
which were called "dehydrated" trees, but is different in a number of 
respects:

i) The input to the new "expander" (which is created by a call to 
fluid.makeProtoExpander) is unambiguous in that it does not consist of a 
mixture of full components (with explicit IDs) and condensed ones 
(protoComponents with the ID written as the key) but ONLY accepts 
protoComponents.
ii) The exact operations performed during expansion are CONFIGURABLE by 
means of the options structure supplied to makeProtoExpander. This 
currently accepts two options, "ELstyle" and "IDescape".
iii) The set of expansion strategies supported is already more powerful 
than "dehydrated components" and is set to grow even more powerful. At 
the moment, the most useful form of expansion involves automatic 
selection between strings interpreted as literals (the "value" member of 
a UIBound component) and as EL paths (the "valuebinding" member). 
Depending on the "ELstyle" option, different strategies are available 
which suit different contexts of use for the renderer (whether building 
a primarily forms-based interface such as the "import" view, or a 
primarily output-only interface such as "exhibition view".

fluid.makeProtoExpander is currently housed within engageClientUtils.js 
which is housing an increasing number of utilities that have been found 
useful to speed up our work on Engage - please consult the comments 
there for more technical details.

I am proposing fluid.makeProtoExpander as an alternative to the "forest 
family" of customised creator functions which are beginning to appear at 
the end of engageClientUtils such as fluid.engage.renderUtils.uiBound 
etc. - we should talk over the pros and cons of these approaches at the 
dev meeting on Thursday. I am arguing this format represents an 
improvement since it

i) allows component trees to take up even less code/space in creation 
than with the utilities (and a very clear win over "hydrated" trees)
ii) represents a fully declarative strategy which allows 
trees/treebuilders to be introspected by other tools, of the sort we 
expect to find ourselves creating when starting on authoring work for 
Engage 2
iii) provides an architectural entry point for the more powerful 
renderer-oriented features coming in "Renderer 2.0", including the 
ability to specify repetition structures and decision points declaratively.

To demonstrate the new format, I have reworked the tree building code in 
ExhibitionView.js which used to look as follows:


     var generateCatalogSubtree = function (model, strings) {
         return [
             {
                 ID: "catalogue"
             },
             {
                 ID: "catalogueTitle",
                 value: fluid.stringTemplate(strings.catalogueTitle, {
                     size: model.catalogueSize
                 })
             }
         ];
     };

     var generateComponentTree = function (model, strings) {
         var utils = fluid.engage.renderUtils;
         var children = [
             utils.uiBound("about", "About:"), // TODO: Does this need 
ot be localized?
             utils.uiBound("navBarTitle", model.title),
             utils.uiBound("displayDate", model.displayDate),
             utils.uiBound("shortDescription", model.shortDescription),
             utils.uiBound("description", model.introduction ? 
model.introduction : model.content),
             utils.uiBound("guestbook", 
fluid.stringTemplate(strings.guestbook, {
                 size: model.guestbookSize || 0
             })),
             utils.uiBound("guestbookLinkText", strings.guestbookLinkText),
             utils.attrDecoratedUIBound("guestbookLink", "href", 
model.guestbookLink),
             utils.attrDecoratedUIBound("image", "src", model.image),
             utils.attrDecoratedUIBound("catalogueLink", "href", 
model.catalogueLink),
             utils.uiBound("catalogueLinkText", strings.catalogueLinkText),
             utils.attrDecoratedUIBound("aboutLink", "href", 
model.aboutLink),
             utils.uiBound("aboutLinkText", strings.aboutLink),
             utils.uiBound("title", model.title),
             utils.uiBound("guestbookInvitation", model.comments || 
strings.guestBookInvitationString)
         ];

         // Only render the catalogue section if there are artifacts in 
the catalogue.
         return {
             children: model.catalogueSize > 0 ?
                       children.concat(generateCatalogSubtree(model, 
strings)) : children
         };
     };





So that it now looks like this:


     function makeProtoComponents(model) {
         var proto = {
             about: "About:",
             navBarTitle: "%title",
             displayDate: "%displayDate",
             shortDescription: "%shortDescription",
             description: {markup: model.introduction ? 
model.introduction : model.content},
             guestBook: {messagekey: "guestbook", args: {size: 
"%guestbookSize"}},
             guestbookLink: {target: "%guestbookLink"},
             guestbookLinkText: {messagekey: "guestbookLinkText"},
             image: {target: "%image"},
             catalogueLink: {target: "%catalogueLink"},
             catalogueLinkText: {messagekey: "catalogueLinkText"},
             aboutLink: {target: "%aboutLink"},
             aboutLinkText: {messagekey: "aboutLink"},
             title: "%title",
             guestbookInvitation: model.comments || {messagekey: 
"guestBookInvitationString"}
         };
         if (model.catalogueSize > 0) {
             fluid.renderer.mergeComponents(proto, {
                 catalogue: null,
                 catalogueTitle: {messagekey: "catalogueTitle", args: 
{size: "%catalogueSize"}}
             });
         }
         return proto;
     };

1207 characters to 2104 - savings of over 40%! As well as cutting down 
explicit references to "model" to only 3 sites.

The use of the character "%" can be adjusted to any other single 
character, or else the bracketing sequence "$()" which there are 
arguments is superior.

I believe the component behaves the same as it did before the change, 
but I may have missed something.


This new form also demonstrates the route we will use for our I18N work 
over the next week - in my most recent commit to Infusion I have 
upgraded various pathways to allow exactly the same format we currently 
apply as component "strings" to form as a message bundle, and to make 
use of the UIMessage component to resolve strings from these. This is 
also shown in ExhibitionView.js, and requires the line of code:

var messageLocator = fluid.messageLocator(that.options.strings, 
fluid.stringTemplate);

The messageLocator then needs to be supplied as an option to the 
renderer under the name "messageLocator".
Let's have a meeting at 11am EST (9am MST, 5pm EEST) to talk over the 
plans for I18N at least, and maybe a few other issues before standup.

Cheers,
Antranig.



More information about the fluid-work mailing list