UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

1,252 lines (1,128 loc) 64 kB
/* Copyright The Infusion copyright holders See the AUTHORS.md file at the top-level directory of this distribution and at https://github.com/fluid-project/infusion/raw/main/AUTHORS.md. Licensed under the Educational Community License (ECL), Version 2.0 or the New BSD license. You may not use this file except in compliance with one these Licenses. You may obtain a copy of the ECL 2.0 License and BSD License at https://github.com/fluid-project/infusion/raw/main/Infusion-LICENSE.txt */ "use strict"; (function () { function debugPosition(component) { return "as child of " + (component.parent.fullID ? "component with full ID " + component.parent.fullID : "root"); } function computeFullID(component) { var togo = ""; var move = component; if (component.children === undefined) { // not a container // unusual case on the client-side, since a repetitive leaf may have localID blasted onto it. togo = component.ID + (component.localID !== undefined ? component.localID : ""); move = component.parent; } while (move.parent) { var parent = move.parent; if (move.fullID !== undefined) { togo = move.fullID + togo; return togo; } if (move.noID === undefined) { var ID = move.ID; if (ID === undefined) { fluid.fail("Error in component tree - component found with no ID " + debugPosition(parent) + ": please check structure"); } var colpos = ID.indexOf(":"); var prefix = colpos === -1 ? ID : ID.substring(0, colpos); togo = prefix + ":" + (move.localID === undefined ? "" : move.localID) + ":" + togo; } move = parent; } return togo; } var renderer = {}; renderer.isBoundPrimitive = function (value) { return fluid.isPrimitive(value) || fluid.isArrayable(value) && (value.length === 0 || typeof(value[0]) === "string"); }; var unzipComponent; function processChild(value, key) { if (renderer.isBoundPrimitive(value)) { return {componentType: "UIBound", value: value, ID: key}; } else { var unzip = unzipComponent(value); if (unzip.ID) { return {ID: key, componentType: "UIContainer", children: [unzip]}; } else { unzip.ID = key; return unzip; } } } function fixChildren(children) { if (!fluid.isArrayable(children)) { var togo = []; for (var key in children) { var value = children[key]; if (fluid.isArrayable(value)) { for (var i = 0; i < value.length; ++i) { var processed = processChild(value[i], key); // if (processed.componentType === "UIContainer" && // processed.localID === undefined) { // processed.localID = i; // } togo[togo.length] = processed; } } else { togo[togo.length] = processChild(value, key); } } return togo; } else {return children; } } function fixupValue(uibound, model, resolverGetConfig) { if (uibound.value === undefined && uibound.valuebinding !== undefined) { uibound.value = fluid.get(model, uibound.valuebinding, resolverGetConfig); } } function upgradeBound(holder, property, model, resolverGetConfig) { if (holder[property] !== undefined) { if (renderer.isBoundPrimitive(holder[property])) { holder[property] = {value: holder[property]}; } else if (holder[property].messagekey) { holder[property].componentType = "UIMessage"; } } else { holder[property] = {value: null}; } fixupValue(holder[property], model, resolverGetConfig); } renderer.duckMap = { children: "UIContainer", value: "UIBound", valuebinding: "UIBound", messagekey: "UIMessage", markup: "UIVerbatim", selection: "UISelect", target: "UILink", choiceindex: "UISelectChoice", functionname: "UIInitBlock" }; var boundMap = { UISelect: ["selection", "optionlist", "optionnames"], UILink: ["target", "linktext"], UIVerbatim: ["markup"], UIMessage: ["messagekey"] }; renderer.boundMap = fluid.transform(boundMap, fluid.arrayToHash); renderer.inferComponentType = function (component) { for (var key in renderer.duckMap) { if (component[key] !== undefined) { return renderer.duckMap[key]; } } }; renderer.applyComponentType = function (component) { component.componentType = renderer.inferComponentType(component); if (component.componentType === undefined && component.ID !== undefined) { component.componentType = "UIBound"; } }; unzipComponent = function (component, model, resolverGetConfig) { if (component) { renderer.applyComponentType(component); } if (!component || component.componentType === undefined) { var decorators = component.decorators; if (decorators) {delete component.decorators;} component = {componentType: "UIContainer", children: component}; component.decorators = decorators; } var cType = component.componentType; if (cType === "UIContainer") { component.children = fixChildren(component.children); } else { var map = renderer.boundMap[cType]; if (map) { fluid.each(map, function (value, key) { upgradeBound(component, key, model, resolverGetConfig); }); } } return component; }; function fixupTree(tree, model, resolverGetConfig) { if (tree.componentType === undefined) { tree = unzipComponent(tree, model, resolverGetConfig); } if (tree.componentType !== "UIContainer" && !tree.parent) { tree = {children: [tree]}; } if (tree.children) { tree.childmap = {}; for (var i = 0; i < tree.children.length; ++i) { var child = tree.children[i]; if (child.componentType === undefined) { child = unzipComponent(child, model, resolverGetConfig); tree.children[i] = child; } child.parent = tree; if (child.ID === undefined) { fluid.fail("Error in component tree: component found with no ID " + debugPosition(child)); } tree.childmap[child.ID] = child; var colpos = child.ID.indexOf(":"); if (colpos === -1) { // tree.childmap[child.ID] = child; // moved out of branch to allow // "relative id expressions" to be easily parsed } else { var prefix = child.ID.substring(0, colpos); var childlist = tree.childmap[prefix]; if (!childlist) { childlist = []; tree.childmap[prefix] = childlist; } if (child.localID === undefined && childlist.length !== 0) { child.localID = childlist.length; } childlist[childlist.length] = child; } child.fullID = computeFullID(child); var componentType = child.componentType; if (componentType === "UISelect") { child.selection.fullID = child.fullID; } else if (componentType === "UIInitBlock") { var call = child.functionname + "("; var childArgs = child.arguments; for (var j = 0; j < childArgs.length; ++j) { if (childArgs[j] instanceof fluid.ComponentReference) { // TODO: support more forms of id reference childArgs[j] = child.parent.fullID + childArgs[j].reference; } call += JSON.stringify(childArgs[j]); if (j < childArgs.length - 1) { call += ", "; } } child.markup = {value: call + ")\n"}; child.componentType = "UIVerbatim"; } else if (componentType === "UIBound") { fixupValue(child, model, resolverGetConfig); } fixupTree(child, model, resolverGetConfig); } } return tree; } fluid.NULL_STRING = "\u25a9null\u25a9"; var LINK_ATTRIBUTES = { a: "href", link: "href", img: "src", frame: "src", script: "src", style: "src", input: "src", embed: "src", form: "action", applet: "codebase", object: "codebase" }; renderer.decoratorComponentPrefix = "**-renderer-"; renderer.IDtoComponentName = function (ID, num) { return renderer.decoratorComponentPrefix + ID.replace(/\./g, "") + "-" + num; }; renderer.invokeFluidDecorator = function (func, args, ID, num, options) { var that; if (options.parentComponent) { var name = renderer.IDtoComponentName(ID, num); that = fluid.constructChild(options.parentComponent, name, $.extend({ type: func, container: args[0] }, args[1])); } else { that = fluid.invokeGlobalFunction(func, args); } return that; }; fluid.oldRenderer = function (templates, tree, options, fossilsIn) { options = options || {}; tree = tree || {}; var debugMode = options.debugMode; if (!options.messageLocator && options.messageSource) { options.messageLocator = fluid.resolveMessageSource(options.messageSource); } options.document = options.document || document; options.jQuery = options.jQuery || $; options.fossils = options.fossils || fossilsIn || {}; // map of submittingname to {EL, submittingname, oldvalue} var globalmap = {}; var branchmap = {}; var rewritemap = {}; // map of rewritekey (for original id in template) to full ID var seenset = {}; var collected = {}; var out = ""; var renderOptions = options; var decoratorQueue = []; var renderedbindings = {}; // map of fullID to true for UISelects which have already had bindings written var usedIDs = {}; var that = {options: options}; function getRewriteKey(template, parent, id) { return template.resourceKey + parent.fullID + id; } // returns: lump function resolveInScope(searchID, defprefix, scope) { var deflump; var scopelook = scope ? scope[searchID] : null; if (scopelook) { for (var i = 0; i < scopelook.length; ++i) { var scopelump = scopelook[i]; if (!deflump && scopelump.rsfID === defprefix) { deflump = scopelump; } if (scopelump.rsfID === searchID) { return scopelump; } } } return deflump; } // returns: lump function resolveCall(sourcescope, child) { var searchID = child.jointID ? child.jointID : child.ID; var split = fluid.SplitID(searchID); var defprefix = split.prefix + ":"; var match = resolveInScope(searchID, defprefix, sourcescope.downmap, child); if (match) {return match;} if (child.children) { match = resolveInScope(searchID, defprefix, globalmap, child); if (match) {return match;} } return null; } function noteCollected(template) { if (!seenset[template.href]) { fluid.aggregateMMap(collected, template.collectmap); seenset[template.href] = true; } } var fetchComponent; function resolveRecurse(basecontainer, parentlump) { var i; var id; var resolved; for (i = 0; i < basecontainer.children.length; ++i) { var branch = basecontainer.children[i]; if (branch.children) { // it is a branch resolved = resolveCall(parentlump, branch); if (resolved) { branchmap[branch.fullID] = resolved; id = resolved.attributemap.id; if (id !== undefined) { rewritemap[getRewriteKey(parentlump.parent, basecontainer, id)] = branch.fullID; } // on server-side this is done separately noteCollected(resolved.parent); resolveRecurse(branch, resolved); } } } // collect any rewritten ids for the purpose of later rewriting if (parentlump.downmap) { for (id in parentlump.downmap) { //if (id.indexOf(":") === -1) { var lumps = parentlump.downmap[id]; for (i = 0; i < lumps.length; ++i) { var lump = lumps[i]; var lumpid = lump.attributemap.id; if (lumpid !== undefined && lump.rsfID !== undefined) { resolved = fetchComponent(basecontainer, lump.rsfID); if (resolved !== null) { var resolveID = resolved.fullID; rewritemap[getRewriteKey(parentlump.parent, basecontainer, lumpid)] = resolveID; } } } // } } } } function resolveBranches(globalmapp, basecontainer, parentlump) { branchmap = {}; rewritemap = {}; seenset = {}; collected = {}; globalmap = globalmapp; branchmap[basecontainer.fullID] = parentlump; resolveRecurse(basecontainer, parentlump); } function dumpTillLump(lumps, start, limit) { for (; start < limit; ++start) { var text = lumps[start].text; if (text) { // guard against "undefined" lumps from "justended" out += lumps[start].text; } } } function dumpScan(lumps, renderindex, basedepth, closeparent, insideleaf) { var start = renderindex; while (true) { if (renderindex === lumps.length) { break; } var lump = lumps[renderindex]; if (lump.nestingdepth < basedepth) { break; } if (lump.rsfID !== undefined) { if (!insideleaf) {break;} if (insideleaf && lump.nestingdepth > basedepth + (closeparent ? 0 : 1)) { fluid.log("Error in component tree - leaf component found to contain further components - at " + lump.toString()); } else {break;} } // target.print(lump.text); ++renderindex; } // ASSUMPTIONS: close tags are ONE LUMP if (!closeparent && (renderindex === lumps.length || !lumps[renderindex].rsfID)) { --renderindex; } dumpTillLump(lumps, start, renderindex); //target.write(buffer, start, limit - start); return renderindex; } function isPlaceholder() { // TODO: equivalent of server-side "placeholder" system return false; } function isValue(value) { return value !== null && value !== undefined && !isPlaceholder(value); } // In RSF Client, this is a "flyweight" "global" object that is reused for every tag, // to avoid generating garbage. In RSF Server, it is an argument to the following rendering // methods of type "TagRenderContext". var trc = {}; /*** TRC METHODS ***/ function openTag() { if (!trc.iselide) { out += "<" + trc.uselump.tagname; } } function closeTag() { if (!trc.iselide) { out += "</" + trc.uselump.tagname + ">"; } } function renderUnchanged() { // TODO needs work since we don't keep attributes in text dumpTillLump(trc.uselump.parent.lumps, trc.uselump.lumpindex + 1, trc.close.lumpindex + (trc.iselide ? 0 : 1)); } function isSelfClose() { return trc.endopen.lumpindex === trc.close.lumpindex && fluid.XMLP.closedTags[trc.uselump.tagname]; } function dumpTemplateBody() { if (isSelfClose()) { if (!trc.iselide) { out += "/>"; } } else { if (!trc.iselide) { out += ">"; } dumpTillLump(trc.uselump.parent.lumps, trc.endopen.lumpindex, trc.close.lumpindex + (trc.iselide ? 0 : 1)); } } function replaceAttributes() { if (!trc.iselide) { out += fluid.dumpAttributes(trc.attrcopy); } dumpTemplateBody(); } function replaceAttributesOpen() { if (trc.iselide) { replaceAttributes(); } else { out += fluid.dumpAttributes(trc.attrcopy); var selfClose = isSelfClose(); // TODO: the parser does not ever produce empty tags out += selfClose ? "/>" : ">"; trc.nextpos = selfClose ? trc.close.lumpindex + 1 : trc.endopen.lumpindex; } } function replaceBody(value) { out += fluid.dumpAttributes(trc.attrcopy); if (!trc.iselide) { out += ">"; } out += fluid.XMLEncode(value.toString()); closeTag(); } function rewriteLeaf(value) { if (isValue(value)) { replaceBody(value); } else { replaceAttributes(); } } function rewriteLeafOpen(value) { if (trc.iselide) { rewriteLeaf(trc.value); } else { if (isValue(value)) { replaceBody(value); } else { replaceAttributesOpen(); } } } /*** END TRC METHODS**/ function rewriteUrl(template, url) { if (renderOptions.urlRewriter) { var rewritten = renderOptions.urlRewriter(url); if (rewritten) { return rewritten; } } if (!renderOptions.rebaseURLs) { return url; } var protpos = url.indexOf(":/"); if (url.charAt(0) === "/" || protpos !== -1 && protpos < 7) { return url; } else { return renderOptions.baseURL + url; } } function dumpHiddenField(/** UIParameter **/ todump) { out += "<input type=\"hidden\" "; var isvirtual = todump.virtual; var outattrs = {}; outattrs[isvirtual ? "id" : "name"] = todump.name; outattrs.value = todump.value; out += fluid.dumpAttributes(outattrs); out += " />\n"; } var outDecoratorsImpl; function applyAutoBind(torender, finalID) { if (!finalID) { // if no id is assigned so far, this is a signal that this is a "virtual" component such as // a non-HTML UISelect which will not have physical markup. return; } var tagname = trc.uselump.tagname; var applier = renderOptions.applier; function applyFunc() { fluid.applyBoundChange(fluid.byId(finalID, renderOptions.document), undefined, applier); } if (renderOptions.autoBind && /input|select|textarea/.test(tagname) && !renderedbindings[finalID]) { var decorators = [{jQuery: ["on", "change", applyFunc]}]; outDecoratorsImpl(torender, decorators, trc.attrcopy, finalID); } } function dumpBoundFields(/** UIBound**/ torender, parent) { if (torender) { var holder = parent ? parent : torender; if (renderOptions.fossils && holder.valuebinding !== undefined) { var fossilKey = holder.submittingname || torender.finalID; // TODO: this will store multiple times for each member of a UISelect renderOptions.fossils[fossilKey] = { name: fossilKey, EL: holder.valuebinding, oldvalue: holder.value }; // But this has to happen multiple times applyAutoBind(torender, torender.finalID); } if (torender.fossilizedbinding) { dumpHiddenField(torender.fossilizedbinding); } if (torender.fossilizedshaper) { dumpHiddenField(torender.fossilizedshaper); } } } function dumpSelectionBindings(uiselect) { if (!renderedbindings[uiselect.selection.fullID]) { renderedbindings[uiselect.selection.fullID] = true; // set this true early so that selection does not autobind twice dumpBoundFields(uiselect.selection); dumpBoundFields(uiselect.optionlist); dumpBoundFields(uiselect.optionnames); } } function isSelectedValue(torender, value) { var selection = torender.selection; return fluid.isArrayable(selection.value) ? selection.value.indexOf(value) !== -1 : selection.value === value; } function getRelativeComponent(component, relativeID) { component = component.parent; while (relativeID.indexOf("..::") === 0) { relativeID = relativeID.substring(4); component = component.parent; } return component.childmap[relativeID]; } // TODO: This mechanism inefficiently handles the rare case of a target document // id collision requiring a rewrite for FLUID-5048. In case it needs improving, we // could hold an inverted index - however, these cases will become even rarer with FLUID-5047 function rewriteRewriteMap(from, to) { fluid.each(rewritemap, function (value, key) { if (value === from) { rewritemap[key] = to; } }); } function adjustForID(attrcopy, component, late, forceID) { if (!late) { delete attrcopy["rsf:id"]; } if (component.finalID !== undefined) { attrcopy.id = component.finalID; } else if (forceID !== undefined) { attrcopy.id = forceID; } else { if (attrcopy.id || late) { attrcopy.id = component.fullID; } } var count = 1; var baseid = attrcopy.id; while (renderOptions.document.getElementById(attrcopy.id) || usedIDs[attrcopy.id]) { attrcopy.id = baseid + "-" + (count++); } if (count !== 1) { rewriteRewriteMap(baseid, attrcopy.id); } component.finalID = attrcopy.id; return attrcopy.id; } function assignSubmittingName(attrcopy, component, parent) { var submitting = parent || component; // if a submittingName is required, we must already go out to the document to // uniquify the id that it will be derived from adjustForID(attrcopy, component, true, component.fullID); if (submitting.submittingname === undefined && submitting.willinput !== false) { submitting.submittingname = submitting.finalID || submitting.fullID; } return submitting.submittingname; } function explodeDecorators(decorators) { var togo = []; if (decorators.type) { togo[0] = decorators; } else { for (var key in decorators) { if (key === "$") {key = "jQuery";} var value = decorators[key]; var decorator = { type: key }; if (key === "jQuery") { decorator.func = value[0]; decorator.args = value.slice(1); } else if (key === "addClass" || key === "removeClass") { decorator.classes = value; } else if (key === "attrs") { decorator.attributes = value; } else if (key === "identify") { decorator.key = value; } togo[togo.length] = decorator; } } return togo; } outDecoratorsImpl = function (torender, decorators, attrcopy, finalID) { var id; var sanitizeAttrs = function (value, key) { if (value === null || value === undefined) { delete attrcopy[key]; } else { attrcopy[key] = fluid.XMLEncode(value); } }; renderOptions.idMap = renderOptions.idMap || {}; for (var i = 0; i < decorators.length; ++i) { var decorator = decorators[i]; var type = decorator.type; if (!type) { var explodedDecorators = explodeDecorators(decorator); outDecoratorsImpl(torender, explodedDecorators, attrcopy, finalID); continue; } if (type === "$") {type = decorator.type = "jQuery";} if (type === "jQuery" || type === "event" || type === "fluid") { id = adjustForID(attrcopy, torender, true, finalID); if (decorator.ids === undefined) { decorator.ids = []; decoratorQueue[decoratorQueue.length] = decorator; } decorator.ids.push(id); } // honour these remaining types immediately else if (type === "attrs") { fluid.each(decorator.attributes, sanitizeAttrs); } else if (type === "addClass" || type === "removeClass") { // Using an unattached DOM node because jQuery will use the // node's setAttribute method to add the class. var fakeNode = $("<div>", {"class": attrcopy["class"]})[0]; renderOptions.jQuery(fakeNode)[type](decorator.classes); attrcopy["class"] = fakeNode.className; } else if (type === "identify") { id = adjustForID(attrcopy, torender, true, finalID); renderOptions.idMap[decorator.key] = id; } else if (type !== "null") { fluid.log("Unrecognised decorator of type " + type + " found at component of ID " + finalID); } } }; function outDecorators(torender, attrcopy) { if (!torender.decorators) {return;} if (torender.decorators.length === undefined) { torender.decorators = explodeDecorators(torender.decorators); } outDecoratorsImpl(torender, torender.decorators, attrcopy); } function dumpBranchHead(branch, targetlump) { if (targetlump.elide) { return; } var attrcopy = {}; $.extend(true, attrcopy, targetlump.attributemap); adjustForID(attrcopy, branch); outDecorators(branch, attrcopy); out += "<" + targetlump.tagname + " "; out += fluid.dumpAttributes(attrcopy); out += ">"; } function resolveArgs(args) { if (!args) {return args;} args = fluid.copy(args); // FLUID-4737: Avoid corrupting material which may have been fetched from the model return fluid.transform(args, function (arg, index) { upgradeBound(args, index, renderOptions.model, renderOptions.resolverGetConfig); return args[index].value; }); } function degradeMessage(torender) { if (torender.componentType === "UIMessage") { // degrade UIMessage to UIBound by resolving the message torender.componentType = "UIBound"; if (!renderOptions.messageLocator) { torender.value = "[No messageLocator is configured in options - please consult documentation on options.messageSource]"; } else { upgradeBound(torender, "messagekey", renderOptions.model, renderOptions.resolverGetConfig); var resArgs = resolveArgs(torender.args); torender.value = renderOptions.messageLocator(torender.messagekey.value, resArgs); } } } function renderComponent(torender) { var value; var attrcopy = trc.attrcopy; degradeMessage(torender); var componentType = torender.componentType; var tagname = trc.uselump.tagname; outDecorators(torender, attrcopy); function makeFail(torender, end) { fluid.fail("Error in component tree - UISelectChoice with id " + torender.fullID + end); } if (componentType === "UIBound" || componentType === "UISelectChoice") { var parent; if (torender.choiceindex !== undefined) { if (torender.parentRelativeID !== undefined) { parent = getRelativeComponent(torender, torender.parentRelativeID); if (!parent) { makeFail(torender, " has parentRelativeID of " + torender.parentRelativeID + " which cannot be resolved"); } } else { makeFail(torender, " does not have parentRelativeID set"); } assignSubmittingName(attrcopy, torender, parent.selection); dumpSelectionBindings(parent); } var submittingname = parent ? parent.selection.submittingname : torender.submittingname; if (!parent && torender.valuebinding) { // Do this for all bound fields even if non submitting so that finalID is set in order to track fossils (FLUID-3387) submittingname = assignSubmittingName(attrcopy, torender); } if (tagname === "input" || tagname === "textarea") { if (submittingname !== undefined) { attrcopy.name = submittingname; } } // this needs to happen early on the client, since it may cause the allocation of the // id in the case of a "deferred decorator". However, for server-side bindings, this // will be an inappropriate time, unless we shift the timing of emitting the opening tag. dumpBoundFields(torender, parent ? parent.selection : null); if (typeof(torender.value) === "boolean" || attrcopy.type === "radio" || attrcopy.type === "checkbox") { var underlyingValue; var directValue = torender.value; if (torender.choiceindex !== undefined) { if (!parent.optionlist.value) { fluid.fail("Error in component tree - selection control with full ID " + parent.fullID + " has no values"); } underlyingValue = parent.optionlist.value[torender.choiceindex]; directValue = isSelectedValue(parent, underlyingValue); } if (isValue(directValue)) { if (directValue) { attrcopy.checked = "checked"; } else { delete attrcopy.checked; } } attrcopy.value = fluid.XMLEncode(underlyingValue ? underlyingValue : "true"); rewriteLeaf(null); } else if (fluid.isArrayable(torender.value)) { // Cannot be rendered directly, must be fake renderUnchanged(); } else { // String value value = parent ? parent[tagname === "textarea" || tagname === "input" ? "optionlist" : "optionnames"].value[torender.choiceindex] : torender.value; if (tagname === "textarea") { if (isPlaceholder(value) && torender.willinput) { // FORCE a blank value for input components if nothing from // model, if input was intended. value = ""; } rewriteLeaf(value); } else if (tagname === "input") { if (torender.willinput || isValue(value)) { attrcopy.value = fluid.XMLEncode(String(value)); } rewriteLeaf(null); } else { delete attrcopy.name; rewriteLeafOpen(value); } } } else if (componentType === "UISelect") { var ishtmlselect = tagname === "select"; var ismultiple = false; // eslint-disable-line no-unused-vars if (fluid.isArrayable(torender.selection.value)) { ismultiple = true; // eslint-disable-line no-unused-vars if (ishtmlselect) { attrcopy.multiple = "multiple"; } } // assignSubmittingName is now the definitive trigger point for uniquifying output IDs // However, if id is already assigned it is probably through attempt to decorate root select. // in this case restore it. assignSubmittingName(attrcopy, torender.selection); if (ishtmlselect) { // The HTML submitted value from a <select> actually corresponds // with the selection member, not the top-level component. if (torender.selection.willinput !== false) { attrcopy.name = torender.selection.submittingname; } applyAutoBind(torender, attrcopy.id); } out += fluid.dumpAttributes(attrcopy); if (ishtmlselect) { out += ">"; var values = torender.optionlist.value; var names = torender.optionnames === null || torender.optionnames === undefined || !torender.optionnames.value ? values : torender.optionnames.value; if (!names || !names.length) { fluid.fail("Error in component tree - UISelect component with fullID " + torender.fullID + " does not have optionnames set"); } for (var i = 0; i < names.length; ++i) { out += "<option value=\""; value = values[i]; if (value === null) { value = fluid.NULL_STRING; } out += fluid.XMLEncode(value); if (isSelectedValue(torender, value)) { out += "\" selected=\"selected"; } out += "\">"; out += fluid.XMLEncode(names[i]); out += "</option>\n"; } closeTag(); } else { dumpTemplateBody(); } dumpSelectionBindings(torender); } else if (componentType === "UILink") { var attrname = LINK_ATTRIBUTES[tagname]; if (attrname) { degradeMessage(torender.target); var target = torender.target.value; if (!isValue(target)) { target = attrcopy[attrname]; } target = rewriteUrl(trc.uselump.parent, target); // Note that all real browsers succeed in recovering the URL here even if it is presented in violation of XML // seemingly due to the purest accident, the text &amp; cannot occur in a properly encoded URL :P attrcopy[attrname] = fluid.XMLEncode(target); } value = undefined; if (torender.linktext) { degradeMessage(torender.linktext); value = torender.linktext.value; } if (!isValue(value)) { replaceAttributesOpen(); } else { rewriteLeaf(value); } } else if (torender.markup !== undefined) { // detect UIVerbatim degradeMessage(torender.markup); var rendered = torender.markup.value; if (rendered === null) { // TODO, doesn't quite work due to attr folding cf Java code out += fluid.dumpAttributes(attrcopy); out += ">"; renderUnchanged(); } else { if (!trc.iselide) { out += fluid.dumpAttributes(attrcopy); out += ">"; } out += rendered; closeTag(); } } if (attrcopy.id !== undefined) { usedIDs[attrcopy.id] = true; } } function rewriteIDRelation(context) { var attrname; var attrval = trc.attrcopy["for"]; if (attrval !== undefined) { attrname = "for"; } else { attrval = trc.attrcopy.headers; if (attrval !== undefined) { attrname = "headers"; } } if (!attrname) {return;} var tagname = trc.uselump.tagname; if (attrname === "for" && tagname !== "label") {return;} if (attrname === "headers" && tagname !== "td" && tagname !== "th") {return;} var rewritten = rewritemap[getRewriteKey(trc.uselump.parent, context, attrval)]; if (rewritten !== undefined) { trc.attrcopy[attrname] = rewritten; } } function renderComment(message) { out += ("<!-- " + fluid.XMLEncode(message) + "-->"); } function renderDebugMessage(message) { out += "<span style=\"background-color:#FF466B;color:white;padding:1px;\">"; out += message; out += "</span><br/>"; } function reportPath(/*UIComponent*/ branch) { var path = branch.fullID; return !path ? "component tree root" : "full path " + path; } function renderComponentSystem(context, torendero, lump) { var lumpindex = lump.lumpindex; var lumps = lump.parent.lumps; var nextpos = -1; var outerendopen = lumps[lumpindex + 1]; var outerclose = lump.close_tag; nextpos = outerclose.lumpindex + 1; var payloadlist = lump.downmap ? lump.downmap["payload-component"] : null; var payload = payloadlist ? payloadlist[0] : null; var iselide = lump.rsfID.charCodeAt(0) === 126; // "~" var endopen = outerendopen; var close = outerclose; var uselump = lump; var attrcopy = {}; $.extend(true, attrcopy, (payload === null ? lump : payload).attributemap); trc.attrcopy = attrcopy; trc.uselump = uselump; trc.endopen = endopen; trc.close = close; trc.nextpos = nextpos; trc.iselide = iselide; rewriteIDRelation(context); if (torendero === null) { if (lump.rsfID.indexOf("scr=") === (iselide ? 1 : 0)) { var scrname = lump.rsfID.substring(4 + (iselide ? 1 : 0)); if (scrname === "ignore") { nextpos = trc.close.lumpindex + 1; } else if (scrname === "rewrite-url") { torendero = {componentType: "UILink", target: {}}; } else { openTag(); replaceAttributesOpen(); nextpos = trc.endopen.lumpindex; } } } if (torendero !== null) { // else there IS a component and we are going to render it. First make // sure we render any preamble. if (payload) { trc.endopen = lumps[payload.lumpindex + 1]; trc.close = payload.close_tag; trc.uselump = payload; dumpTillLump(lumps, lumpindex, payload.lumpindex); lumpindex = payload.lumpindex; } adjustForID(attrcopy, torendero); //decoratormanager.decorate(torendero.decorators, uselump.getTag(), attrcopy); // ALWAYS dump the tag name, this can never be rewritten. (probably?!) openTag(); renderComponent(torendero); // if there is a payload, dump the postamble. if (payload !== null) { // the default case is initialised to tag close if (trc.nextpos === nextpos) { dumpTillLump(lumps, trc.close.lumpindex + 1, outerclose.lumpindex + 1); } } nextpos = trc.nextpos; } return nextpos; } var renderRecurse; function renderContainer(child, targetlump) { var t2 = targetlump.parent; var firstchild = t2.lumps[targetlump.lumpindex + 1]; if (child.children !== undefined) { dumpBranchHead(child, targetlump); } else { renderComponentSystem(child.parent, child, targetlump); } renderRecurse(child, targetlump, firstchild); } fetchComponent = function (basecontainer, id) { if (id.indexOf("msg=") === 0) { var key = id.substring(4); return {componentType: "UIMessage", messagekey: key}; } while (basecontainer) { var togo = basecontainer.childmap[id]; if (togo) { return togo; } basecontainer = basecontainer.parent; } return null; }; function fetchComponents(basecontainer, id) { var togo; while (basecontainer) { togo = basecontainer.childmap[id]; if (togo) { break; } basecontainer = basecontainer.parent; } return togo; } function findChild(sourcescope, child) { var split = fluid.SplitID(child.ID); var headlumps = sourcescope.downmap[child.ID]; if (!headlumps) { headlumps = sourcescope.downmap[split.prefix + ":"]; } return headlumps ? headlumps[0] : null; } renderRecurse = function (basecontainer, parentlump, baselump) { var children; var targetlump; var child; var renderindex = baselump.lumpindex; var basedepth = parentlump.nestingdepth; var t1 = parentlump.parent; var rendered; if (debugMode) { rendered = {}; } while (true) { renderindex = dumpScan(t1.lumps, renderindex, basedepth, !parentlump.elide, false); if (renderindex === t1.lumps.length) { break; } var lump = t1.lumps[renderindex]; var id = lump.rsfID; // new stopping rule - we may have been inside an elided tag if (lump.nestingdepth < basedepth || id === undefined) { break; } if (id.charCodeAt(0) === 126) { // "~" id = id.substring(1); } //var ismessagefor = id.indexOf("message-for:") === 0; if (id.indexOf(":") !== -1) { var prefix = fluid.getPrefix(id); children = fetchComponents(basecontainer, prefix); var finallump = lump.uplump.finallump[prefix]; var closefinal = finallump.close_tag; if (children) { for (var i = 0; i < children.length; ++i) { child = children[i]; if (child.children) { // it is a branch if (debugMode) { rendered[child.fullID] = true; } targetlump = branchmap[child.fullID]; if (targetlump) { if (debugMode) { renderComment("Branching for " + child.fullID + " from " + fluid.debugLump(lump) + " to " + fluid.debugLump(targetlump)); } renderContainer(child, targetlump); if (debugMode) { renderComment("Branch returned for " + child.fullID + fluid.debugLump(lump) + " to " + fluid.debugLump(targetlump)); } } else if (debugMode) { renderDebugMessage( "No matching template branch found for branch container with full ID " + child.fullID + " rendering from parent template branch " + fluid.debugLump(baselump)); }