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