Created
July 2, 2015 11:58
-
-
Save amb26/42c108ee5c46566ed1bc to your computer and use it in GitHub Desktop.
FluidIoC.js diff from FLUID-5249 to master
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/src/framework/core/js/FluidIoC.js b/src/framework/core/js/FluidIoC.js | |
| index e1c13fa..70fb7f7 100644 | |
| --- a/src/framework/core/js/FluidIoC.js | |
| +++ b/src/framework/core/js/FluidIoC.js | |
| @@ -1,6 +1,6 @@ | |
| /* | |
| Copyright 2011-2013 OCAD University | |
| -Copyright 2010-2011 Lucendo Development Ltd. | |
| +Copyright 2010-2015 Lucendo Development Ltd. | |
| 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 | |
| @@ -14,45 +14,83 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| (function ($, fluid) { | |
| "use strict"; | |
| + | |
| + /** NOTE: The contents of this file are by default NOT PART OF THE PUBLIC FLUID API unless explicitly annotated before the function **/ | |
| /** The Fluid "IoC System proper" - resolution of references and | |
| * completely automated instantiation of declaratively defined | |
| * component trees */ | |
| - // unsupported, non-API function | |
| // Currently still uses manual traversal - once we ban manually instantiated components, | |
| // it will use the instantiator's records instead. | |
| - fluid.visitComponentChildren = function (that, visitor, options, path, i) { | |
| - var instantiator = fluid.getInstantiator(that); | |
| + fluid.visitComponentChildren = function (that, visitor, options, segs) { | |
| + segs = segs || []; | |
| for (var name in that) { | |
| - var newPath = instantiator.composePath(path, name); | |
| var component = that[name]; | |
| // This entire algorithm is primitive and expensive and will be removed once we can abolish manual init components | |
| - if (!fluid.isComponent(component) || (options.visited && options.visited[component.id])) {continue; } | |
| - if (options.visited) { | |
| + if (!fluid.isComponent(component) || (options.visited && options.visited[component.id])) { | |
| + continue; | |
| + } | |
| + segs.push(name); | |
| + if (options.visited) { // recall that this is here because we may run into a component that has been cross-injected which might otherwise cause cyclicity | |
| options.visited[component.id] = true; | |
| } | |
| - if (visitor(component, name, newPath, path, i)) { | |
| + if (visitor(component, name, segs, segs.length - 1)) { | |
| return true; | |
| } | |
| if (!options.flat) { | |
| - fluid.visitComponentChildren(component, visitor, options, newPath); | |
| + fluid.visitComponentChildren(component, visitor, options, segs); | |
| } | |
| + segs.pop(); | |
| } | |
| }; | |
| + | |
| + fluid.getContextHash = function (instantiator, that) { | |
| + var shadow = instantiator.idToShadow[that.id]; | |
| + return shadow && shadow.contextHash; | |
| + }; | |
| + | |
| + fluid.componentHasGrade = function (that, gradeName) { | |
| + var contextHash = fluid.getContextHash(fluid.globalInstantiator, that); | |
| + return !!(contextHash && contextHash[gradeName]); | |
| + }; | |
| + | |
| + // A variant of fluid.visitComponentChildren that supplies the signature expected for fluid.matchIoCSelector | |
| + // this is: thatStack, contextHashes, memberNames, i - note, the supplied arrays are NOT writeable and shared through the iteration | |
| + fluid.visitComponentsForMatching = function (that, options, visitor) { | |
| + var instantiator = fluid.getInstantiator(that); | |
| + options = $.extend({ | |
| + visited: {}, | |
| + instantiator: instantiator | |
| + }, options); | |
| + var thatStack = [that]; | |
| + var contextHashes = [fluid.getContextHash(instantiator, that)]; | |
| + var visitorWrapper = function (component, name, segs) { | |
| + thatStack.length = 1; | |
| + contextHashes.length = 1; | |
| + for (var i = 0; i < segs.length; ++ i) { | |
| + var child = thatStack[i][segs[i]]; | |
| + thatStack[i + 1] = child; | |
| + contextHashes[i + 1] = fluid.getContextHash(instantiator, child) || {}; | |
| + } | |
| + return visitor(component, thatStack, contextHashes, segs, segs.length); | |
| + }; | |
| + fluid.visitComponentChildren(that, visitorWrapper, options, []); | |
| + }; | |
| - // unsupported, non-API function | |
| fluid.getMemberNames = function (instantiator, thatStack) { | |
| var path = instantiator.idToPath(thatStack[thatStack.length - 1].id); | |
| - var segs = fluid.model.parseEL(path); | |
| + var segs = instantiator.parseEL(path); | |
| + // TODO: we should now have no longer shortness in the stack | |
| segs.unshift.apply(segs, fluid.generate(thatStack.length - segs.length, "")); | |
| + | |
| return segs; | |
| }; | |
| // thatStack contains an increasing list of MORE SPECIFIC thats. | |
| // this visits all components starting from the current location (end of stack) | |
| - // in visibility order up the tree. | |
| - var visitComponents = function (instantiator, thatStack, visitor, options) { | |
| + // in visibility order UP the tree. | |
| + fluid.visitComponentsForVisibility = function (instantiator, thatStack, visitor, options) { | |
| options = options || { | |
| visited: {}, | |
| flat: true, | |
| @@ -60,17 +98,18 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| }; | |
| var memberNames = fluid.getMemberNames(instantiator, thatStack); | |
| for (var i = thatStack.length - 1; i >= 0; --i) { | |
| - var that = thatStack[i], path; | |
| - if (that.typeName) { | |
| - options.visited[that.id] = true; | |
| - path = instantiator.idToPath[that.id]; | |
| - if (visitor(that, memberNames[i], path, path, i)) { | |
| - return; | |
| - } | |
| + var that = thatStack[i]; | |
| + | |
| + // explicitly visit the direct parent first | |
| + options.visited[that.id] = true; | |
| + if (visitor(that, memberNames[i], memberNames, i)) { | |
| + return; | |
| } | |
| - if (fluid.visitComponentChildren(that, visitor, options, path, i)) { | |
| + | |
| + if (fluid.visitComponentChildren(that, visitor, options, memberNames)) { | |
| return; | |
| } | |
| + memberNames.pop(); | |
| } | |
| }; | |
| @@ -89,23 +128,28 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| }; | |
| }; | |
| - // unsupported, NON-API function | |
| fluid.invokerFromRecord = function (invokerec, name, that) { | |
| fluid.pushActivity("makeInvoker", "beginning instantiation of invoker with name %name and record %record as child of %that", | |
| {name: name, record: invokerec, that: that}); | |
| - var invoker = fluid.makeInvoker(that, invokerec, name); | |
| + var invoker = invokerec ? fluid.makeInvoker(that, invokerec, name) : undefined; | |
| fluid.popActivity(); | |
| return invoker; | |
| }; | |
| - // unsupported, NON-API function | |
| - fluid.memberFromRecord = function (memberrec, name, that) { | |
| - var value = fluid.expandOptions(memberrec, that, null, null, {freeRoot: true}); | |
| - return value; | |
| + fluid.memberFromRecord = function (memberrecs, name, that) { | |
| + var togo; | |
| + for (var i = 0; i < memberrecs.length; ++ i) { // memberrecs is the special "fluid.mergingArray" type which is not Arrayable | |
| + var expanded = fluid.expandImmediate(memberrecs[i], that); | |
| + if (!fluid.isPlainObject(togo)) { // poor man's "merge" algorithm to hack FLUID-5668 for now | |
| + togo = expanded; | |
| + } else { | |
| + togo = $.extend(true, togo, expanded); | |
| + } | |
| + } | |
| + return togo; | |
| }; | |
| - // unsupported, NON-API function | |
| - fluid.recordStrategy = function (that, options, optionsStrategy, recordPath, recordMaker, prefix) { | |
| + fluid.recordStrategy = function (that, options, optionsStrategy, recordPath, recordMaker, prefix, exceptions) { | |
| prefix = prefix || []; | |
| return { | |
| strategy: function (target, name, i) { | |
| @@ -124,105 +168,56 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| initter: function () { | |
| var records = fluid.driveStrategy(options, recordPath, optionsStrategy) || {}; | |
| for (var name in records) { | |
| - fluid.getForComponent(that, prefix.concat([name])); | |
| + if (!exceptions || !exceptions[name]) { | |
| + fluid.getForComponent(that, prefix.concat([name])); | |
| + } | |
| } | |
| } | |
| }; | |
| }; | |
| // patch Fluid.js version for timing | |
| - // unsupported, NON-API function | |
| fluid.instantiateFirers = function (that) { | |
| var shadow = fluid.shadowForComponent(that); | |
| var initter = fluid.get(shadow, ["eventStrategyBlock", "initter"]) || fluid.identity; | |
| initter(); | |
| }; | |
| - // unsupported, NON-API function | |
| - fluid.makeDistributionRecord = function (contextThat, sourceRecord, sourcePath, targetSegs, exclusions, offset, sourceType) { | |
| - offset = offset || 0; | |
| + fluid.makeDistributionRecord = function (contextThat, sourceRecord, sourcePath, targetSegs, exclusions, sourceType) { | |
| sourceType = sourceType || "distribution"; | |
| var source = fluid.copy(fluid.get(sourceRecord, sourcePath)); | |
| fluid.each(exclusions, function (exclusion) { | |
| - fluid.model.applyChangeRequest(source, {path: exclusion, type: "DELETE"}); | |
| + fluid.model.applyChangeRequest(source, {segs: exclusion, type: "DELETE"}); | |
| }); | |
| var record = {options: {}}; | |
| - var primitiveSource = fluid.isPrimitive(source); | |
| - fluid.model.applyChangeRequest(record, {path: targetSegs, type: primitiveSource? "ADD": "MERGE", value: source}); | |
| - return $.extend(record, {contextThat: contextThat, recordType: sourceType, priority: fluid.mergeRecordTypes.distribution + offset}); | |
| + fluid.model.applyChangeRequest(record, {segs: targetSegs, type: "ADD", value: source}); | |
| + return $.extend(record, {contextThat: contextThat, recordType: sourceType}); | |
| }; | |
| - // unsupported, NON-API function | |
| // Part of the early "distributeOptions" workflow. Given the description of the blocks to be distributed, assembles "canned" records | |
| // suitable to be either registered into the shadow record for later or directly pushed to an existing component, as well as honouring | |
| // any "removeSource" annotations by removing these options from the source block. | |
| - fluid.filterBlocks = function (contextThat, sourceBlocks, sourcePath, targetSegs, exclusions, removeSource) { | |
| - var togo = [], offset = 0; | |
| + fluid.filterBlocks = function (contextThat, sourceBlocks, sourceSegs, targetSegs, exclusions, removeSource) { | |
| + var togo = []; | |
| fluid.each(sourceBlocks, function (block) { | |
| - var source = fluid.get(block.source, sourcePath); | |
| + var source = fluid.get(block.source, sourceSegs); | |
| if (source) { | |
| - togo.push(fluid.makeDistributionRecord(contextThat, block.source, sourcePath, targetSegs, exclusions, offset++, block.recordType)); | |
| + togo.push(fluid.makeDistributionRecord(contextThat, block.source, sourceSegs, targetSegs, exclusions, block.recordType)); | |
| var rescued = $.extend({}, source); | |
| if (removeSource) { | |
| - fluid.model.applyChangeRequest(block.source, {path: sourcePath, type: "DELETE"}); | |
| + fluid.model.applyChangeRequest(block.source, {segs: sourceSegs, type: "DELETE"}); | |
| } | |
| fluid.each(exclusions, function (exclusion) { | |
| var orig = fluid.get(rescued, exclusion); | |
| - fluid.set(block.source, sourcePath.concat(exclusion), orig); | |
| + fluid.set(block.source, sourceSegs.concat(exclusion), orig); | |
| }); | |
| } | |
| }); | |
| return togo; | |
| }; | |
| - // unsupported, NON-API function | |
| - // TODO: This implementation is obviously poor and has numerous flaws | |
| - fluid.matchIoCSelector = function (selector, thatStack, contextHashes, memberNames, i) { | |
| - var thatpos = thatStack.length - 1; | |
| - var selpos = selector.length - 1; | |
| - while (true) { | |
| - var mustMatchHere = thatpos === thatStack.length - 1 || selector[selpos].child; | |
| - | |
| - var that = thatStack[thatpos]; | |
| - var selel = selector[selpos]; | |
| - var match = true; | |
| - for (var j = 0; j < selel.predList.length; ++j) { | |
| - var pred = selel.predList[j]; | |
| - if (pred.context && !(contextHashes[thatpos][pred.context] || memberNames[thatpos] === pred.context)) { | |
| - match = false; | |
| - break; | |
| - } | |
| - if (pred.id && that.id !== pred.id) { | |
| - match = false; | |
| - break; | |
| - } | |
| - } | |
| - if (selpos === 0 && thatpos > i && mustMatchHere) { | |
| - match = false; // child selector must exhaust stack completely - FLUID-5029 | |
| - } | |
| - if (match) { | |
| - if (selpos === 0) { | |
| - return true; | |
| - } | |
| - --thatpos; | |
| - --selpos; | |
| - } | |
| - else { | |
| - if (mustMatchHere) { | |
| - return false; | |
| - } | |
| - else { | |
| - --thatpos; | |
| - } | |
| - } | |
| - if (thatpos < i) { | |
| - return false; | |
| - } | |
| - } | |
| - }; | |
| - | |
| // Use this peculiar signature since the actual component and shadow itself may not exist yet. Perhaps clean up with FLUID-4925 | |
| fluid.noteCollectedDistribution = function (parentShadow, memberName, distribution) { | |
| fluid.model.setSimple(parentShadow, ["collectedDistributions", memberName, distribution.id], true); | |
| @@ -233,10 +228,9 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| }; | |
| fluid.clearCollectedDistributions = function (parentShadow, memberName) { | |
| - fluid.model.applyChangeRequest(parentShadow, {path: ["collectedDistributions", memberName], type: "DELETE"}); | |
| + fluid.model.applyChangeRequest(parentShadow, {segs: ["collectedDistributions", memberName], type: "DELETE"}); | |
| }; | |
| - // unsupported, NON-API function | |
| fluid.collectDistributions = function (distributedBlocks, parentShadow, distribution, thatStack, contextHashes, memberNames, i) { | |
| var lastMember = memberNames[memberNames.length - 1]; | |
| if (!fluid.isCollectedDistribution(parentShadow, lastMember, distribution) && | |
| @@ -258,26 +252,24 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| } | |
| }; | |
| - // unsupported, NON-API function | |
| fluid.receiveDistributions = function (parentThat, gradeNames, memberName, that) { | |
| var instantiator = fluid.getInstantiator(parentThat || that); | |
| var thatStack = instantiator.getThatStack(parentThat || that); // most specific is at end | |
| + thatStack.unshift(fluid.rootComponent); | |
| var memberNames = fluid.getMemberNames(instantiator, thatStack); | |
| - var distributedBlocks = []; | |
| var shadows = fluid.transform(thatStack, function (thisThat) { | |
| return instantiator.idToShadow[thisThat.id]; | |
| }); | |
| var parentShadow = shadows[shadows.length - (parentThat ? 1 : 2)]; | |
| var contextHashes = fluid.getMembers(shadows, "contextHash"); | |
| - if (parentThat) { // if called before construction of component from embodyDemands - NB this path will be abolished/amalgamated | |
| + if (parentThat) { // if called before construction of component from assembleCreatorArguments - NB this path will be abolished/amalgamated | |
| memberNames.push(memberName); | |
| contextHashes.push(fluid.gradeNamesToHash(gradeNames)); | |
| thatStack.push(that); | |
| } else { | |
| fluid.registerCollectedClearer(shadows[shadows.length - 1], parentShadow, memberNames[memberNames.length - 1]); | |
| } | |
| - // This use of function creation within a loop is acceptable since | |
| - // the function does not attempt to close directly over the loop counter | |
| + var distributedBlocks = []; | |
| for (var i = 0; i < thatStack.length - 1; ++ i) { | |
| fluid.each(shadows[i].distributions, function (distribution) { | |
| fluid.collectDistributions(distributedBlocks, parentShadow, distribution, thatStack, contextHashes, memberNames, i); | |
| @@ -285,110 +277,216 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| } | |
| return distributedBlocks; | |
| }; | |
| + | |
| + fluid.computeTreeDistance = function (path1, path2) { | |
| + var i = 0; | |
| + while (i < path1.length && i < path2.length && path1[i] === path2[i]) { | |
| + ++i; | |
| + } | |
| + return path1.length + path2.length - 2*i; | |
| + }; | |
| + | |
| + // Called from applyDistributions (immediate application route) as well as mergeRecordsToList (pre-instantiation route) | |
| + fluid.computeDistributionPriority = function (targetThat, distributedBlock) { | |
| + if (!distributedBlock.priority) { | |
| + var instantiator = fluid.getInstantiator(targetThat); | |
| + var targetStack = instantiator.getThatStack(targetThat); | |
| + var targetPath = fluid.getMemberNames(instantiator, targetStack); | |
| + var sourceStack = instantiator.getThatStack(distributedBlock.contextThat); | |
| + var sourcePath = fluid.getMemberNames(instantiator, sourceStack); | |
| + var distance = fluid.computeTreeDistance(targetPath, sourcePath); | |
| + distributedBlock.priority = fluid.mergeRecordTypes.distribution + distance; | |
| + } | |
| + return distributedBlock; | |
| + }; | |
| - // unsupported, NON-API function | |
| // convert "preBlocks" as produced from fluid.filterBlocks into "real blocks" suitable to be used by the expansion machinery. | |
| fluid.applyDistributions = function (that, preBlocks, targetShadow) { | |
| var distributedBlocks = fluid.transform(preBlocks, function (preBlock) { | |
| return fluid.generateExpandBlock(preBlock, that, targetShadow.mergePolicy); | |
| + }, function (distributedBlock) { | |
| + return fluid.computeDistributionPriority(that, distributedBlock); | |
| }); | |
| var mergeOptions = targetShadow.mergeOptions; | |
| mergeOptions.mergeBlocks.push.apply(mergeOptions.mergeBlocks, distributedBlocks); | |
| mergeOptions.updateBlocks(); | |
| return distributedBlocks; | |
| }; | |
| + | |
| + // TODO: This implementation is obviously poor and has numerous flaws - in particular it does no backtracking as well as matching backwards through the selector | |
| + fluid.matchIoCSelector = function (selector, thatStack, contextHashes, memberNames, i) { | |
| + var thatpos = thatStack.length - 1; | |
| + var selpos = selector.length - 1; | |
| + while (true) { | |
| + var mustMatchHere = thatpos === thatStack.length - 1 || selector[selpos].child; | |
| - // unsupported, NON-API function | |
| - fluid.parseExpectedOptionsPath = function (path, role) { | |
| - var segs = fluid.model.parseEL(path); | |
| - if (segs.length > 1 && segs[0] !== "options") { | |
| - fluid.fail("Error in options distribution path ", path, " - only " + role + " paths beginning with \"options\" are supported"); | |
| + var that = thatStack[thatpos]; | |
| + var selel = selector[selpos]; | |
| + var match = true; | |
| + for (var j = 0; j < selel.predList.length; ++j) { | |
| + var pred = selel.predList[j]; | |
| + if (pred.context && !(contextHashes[thatpos][pred.context] || memberNames[thatpos] === pred.context)) { | |
| + match = false; | |
| + break; | |
| + } | |
| + if (pred.id && that.id !== pred.id) { | |
| + match = false; | |
| + break; | |
| + } | |
| + } | |
| + if (selpos === 0 && thatpos > i && mustMatchHere) { | |
| + match = false; // child selector must exhaust stack completely - FLUID-5029 | |
| + } | |
| + if (match) { | |
| + if (selpos === 0) { | |
| + return true; | |
| + } | |
| + --thatpos; | |
| + --selpos; | |
| + } | |
| + else { | |
| + if (mustMatchHere) { | |
| + return false; | |
| + } | |
| + else { | |
| + --thatpos; | |
| + } | |
| + } | |
| + if (thatpos < i) { | |
| + return false; | |
| + } | |
| } | |
| - return segs.slice(1); | |
| + }; | |
| + | |
| + /** Query for all components matching a selector in a particular tree | |
| + * @param root {Component} The root component at which to start the search | |
| + * @param selector {String} An IoCSS selector, in form of a string. Note that since selectors supplied to this function implicitly | |
| + * match downwards, they need not contain the "head context" followed by whitespace required in the distributeOptions form. E.g. | |
| + * simply <code>"fluid.viewComponent"</code> will match all viewComponents below the root. | |
| + * @param flat {Boolean} [Optional] <code>true</code> if the search should just be performed at top level of the component tree | |
| + * Note that with <code>flat=true</code> this search will scan every component in the tree and may well be very slow. | |
| + */ | |
| + // supported, PUBLIC API function | |
| + fluid.queryIoCSelector = function (root, selector, flat) { | |
| + var parsed = fluid.parseSelector(selector, fluid.IoCSSMatcher); | |
| + var togo = []; | |
| + | |
| + fluid.visitComponentsForMatching(root, {flat: flat}, function (that, thatStack, contextHashes, memberNames, i) { | |
| + if (fluid.matchIoCSelector(parsed, thatStack, contextHashes, memberNames, i)) { | |
| + togo.push(that); | |
| + } | |
| + }); | |
| + return togo; | |
| }; | |
| - // unsupported, NON-API function | |
| fluid.isIoCSSSelector = function (context) { | |
| return context.indexOf(" ") !== -1; // simple-minded check for an IoCSS reference | |
| }; | |
| - // unsupported, NON-API function | |
| fluid.pushDistributions = function (targetHead, selector, blocks) { | |
| var targetShadow = fluid.shadowForComponent(targetHead); | |
| var id = fluid.allocateGuid(); | |
| var distributions = (targetShadow.distributions = targetShadow.distributions || []); | |
| distributions.push({ | |
| - id: id, // This id is used in clearDistributions - which itself currently only seems to appear in IoCTestUtils | |
| + id: id, // This id is used in clearDistributions | |
| selector: selector, | |
| blocks: blocks | |
| }); | |
| return id; | |
| }; | |
| - // unsupported, NON-API function | |
| - fluid.clearDistributions = function (targetHead, id) { | |
| + fluid.clearDistribution = function (targetHead, id) { | |
| var targetShadow = fluid.shadowForComponent(targetHead); | |
| fluid.remove_if(targetShadow.distributions, function (distribution) { | |
| return distribution.id === id; | |
| }); | |
| }; | |
| + | |
| + fluid.clearDistributions = function (shadow) { | |
| + fluid.each(shadow.outDistributions, function (outDist) { | |
| + fluid.clearDistribution(outDist.targetComponent, outDist.distributionId); | |
| + }); | |
| + }; | |
| - // unsupported, NON-API function | |
| - // Modifies a parsed selector to extra its head context which will be matched upwards | |
| + // Modifies a parsed selector to extract and remove its head context which will be matched upwards | |
| fluid.extractSelectorHead = function (parsedSelector) { | |
| var predList = parsedSelector[0].predList; | |
| var context = predList[0].context; | |
| predList.length = 0; | |
| return context; | |
| }; | |
| + | |
| + fluid.parseExpectedOptionsPath = function (path, role) { | |
| + var segs = fluid.model.parseEL(path); | |
| + if (segs.length > 1 && segs[0] !== "options") { | |
| + fluid.fail("Error in options distribution path ", path, " - only " + role + " paths beginning with \"options\" are supported"); | |
| + } | |
| + return segs.slice(1); | |
| + }; | |
| + | |
| + fluid.replicateProperty = function (source, property, targets) { | |
| + if (source[property] !== undefined) { | |
| + fluid.each(targets, function (target) { | |
| + target[property] = source[property]; | |
| + }); | |
| + } | |
| + }; | |
| - fluid.undistributableOptions = ["gradeNames", "distributeOptions", "returnedPath", "argumentMap", "initFunction", "mergePolicy", "progressiveCheckerOptions"]; // automatically added to "exclusions" of every distribution | |
| + fluid.undistributableOptions = ["gradeNames", "distributeOptions", "argumentMap", "initFunction", "mergePolicy", "progressiveCheckerOptions"]; // automatically added to "exclusions" of every distribution | |
| - // unsupported, NON-API function | |
| fluid.distributeOptions = function (that, optionsStrategy) { | |
| - var records = fluid.makeArray(fluid.driveStrategy(that.options, "distributeOptions", optionsStrategy)); | |
| + var thatShadow = fluid.shadowForComponent(that); | |
| + var records = fluid.driveStrategy(that.options, "distributeOptions", optionsStrategy); | |
| fluid.each(records, function (record) { | |
| var targetRef = fluid.parseContextReference(record.target); | |
| - var targetComp, selector; | |
| + var targetComp, selector, context; | |
| if (fluid.isIoCSSSelector(targetRef.context)) { | |
| selector = fluid.parseSelector(targetRef.context, fluid.IoCSSMatcher); | |
| var headContext = fluid.extractSelectorHead(selector); | |
| - if (headContext !== "that") { | |
| - fluid.fail("Downwards options distribution not supported from component other than \"that\""); | |
| + if (headContext === "/") { | |
| + targetComp = fluid.rootComponent; | |
| + } else { | |
| + context = headContext; | |
| } | |
| - targetComp = that; | |
| } | |
| else { | |
| - targetComp = fluid.resolveContext(targetRef.context, that); | |
| - if (!targetComp) { | |
| - fluid.fail("Error in options distribution record ", record, " - could not resolve context selector {"+targetRef.context+"} to a root component"); | |
| - } | |
| + context = targetRef.context; | |
| + } | |
| + targetComp = targetComp || fluid.resolveContext(context, that); | |
| + if (!targetComp) { | |
| + fluid.fail("Error in options distribution record ", record, " - could not resolve context {"+context+"} to a root component"); | |
| } | |
| var targetSegs = fluid.model.parseEL(targetRef.path); | |
| var preBlocks; | |
| if (record.record !== undefined) { | |
| - preBlocks = [(fluid.makeDistributionRecord(that, record.record, [], targetSegs, [], 0))]; | |
| + preBlocks = [(fluid.makeDistributionRecord(that, record.record, [], targetSegs, []))]; | |
| } | |
| else { | |
| - var thatShadow = fluid.shadowForComponent(that); | |
| var source = fluid.parseContextReference(record.source || "{that}.options"); // TODO: This is probably not a sensible default | |
| if (source.context !== "that") { | |
| fluid.fail("Error in options distribution record ", record, " only a context of {that} is supported"); | |
| } | |
| - var sourcePath = fluid.parseExpectedOptionsPath(source.path, "source"); | |
| - var fullExclusions = fluid.makeArray(record.exclusions).concat(sourcePath.length === 0 ? fluid.undistributableOptions : []); | |
| + var sourceSegs = fluid.parseExpectedOptionsPath(source.path, "source"); | |
| + var fullExclusions = fluid.makeArray(record.exclusions).concat(sourceSegs.length === 0 ? fluid.undistributableOptions : []); | |
| var exclusions = fluid.transform(fullExclusions, function (exclusion) { | |
| return fluid.model.parseEL(exclusion); | |
| }); | |
| - preBlocks = fluid.filterBlocks(that, thatShadow.mergeOptions.mergeBlocks, sourcePath, targetSegs, exclusions, record.removeSource); | |
| + preBlocks = fluid.filterBlocks(that, thatShadow.mergeOptions.mergeBlocks, sourceSegs, targetSegs, exclusions, record.removeSource); | |
| thatShadow.mergeOptions.updateBlocks(); // perhaps unnecessary | |
| } | |
| + fluid.replicateProperty(record, "priority", preBlocks); | |
| + fluid.replicateProperty(record, "namespace", preBlocks); | |
| // TODO: inline material has to be expanded in its original context! | |
| if (selector) { | |
| - fluid.pushDistributions(targetComp, selector, preBlocks); | |
| + var distributionId = fluid.pushDistributions(targetComp, selector, preBlocks); | |
| + thatShadow.outDistributions = thatShadow.outDistributions || []; | |
| + thatShadow.outDistributions.push({ | |
| + targetComponent: targetComp, | |
| + distributionId: distributionId | |
| + }); | |
| } | |
| else { // The component exists now, we must rebalance it | |
| var targetShadow = fluid.shadowForComponent(targetComp); | |
| @@ -397,7 +495,6 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| }); | |
| }; | |
| - // unsupported, NON-API function | |
| fluid.gradeNamesToHash = function (gradeNames) { | |
| var contextHash = {}; | |
| fluid.each(gradeNames, function (gradeName) { | |
| @@ -407,68 +504,41 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| return contextHash; | |
| }; | |
| - // unsupported, NON-API function | |
| fluid.cacheShadowGrades = function (that, shadow) { | |
| var contextHash = fluid.gradeNamesToHash(that.options.gradeNames); | |
| - contextHash[that.nickName] = true; | |
| + if (!contextHash[shadow.memberName]) { | |
| + contextHash[shadow.memberName] = "memberName"; // This is filtered out again in recordComponent - TODO: Ensure that ALL resolution uses the scope chain eventually | |
| + } | |
| shadow.contextHash = contextHash; | |
| + fluid.each(contextHash, function (troo, context) { | |
| + shadow.ownScope[context] = that; | |
| + if (shadow.parentShadow && shadow.parentShadow.that.type !== "fluid.rootComponent") { | |
| + shadow.parentShadow.childrenScope[context] = that; | |
| + } | |
| + }); | |
| }; | |
| // First sequence point where the mergeOptions strategy is delivered from Fluid.js - here we take care | |
| // of both receiving and transmitting options distributions | |
| - // unsupported, NON-API function | |
| fluid.deliverOptionsStrategy = function (that, target, mergeOptions) { | |
| var shadow = fluid.shadowForComponent(that, shadow); | |
| fluid.cacheShadowGrades(that, shadow); | |
| shadow.mergeOptions = mergeOptions; | |
| }; | |
| - // unsupported, NON-API function | |
| - fluid.resolveReturnedPath = function (returnedPath, that) { | |
| - var shadow = fluid.shadowForComponent(that); | |
| - // This prevents corruption of instantiator records by defeating effect of "returnedPath" for non-roots | |
| - return shadow && shadow.path !== "" ? null : returnedPath; | |
| - }; | |
| - | |
| - fluid.defaults("fluid.gradeLinkageRecord", { | |
| - gradeNames: ["fluid.littleComponent"] | |
| - }); | |
| - | |
| - /** A "tag component" to opt in to the grade linkage system (FLUID-5212) which is currently very expensive - | |
| - * this will become the default once we have a better implementation and have stabilised requirements | |
| - */ | |
| - fluid.defaults("fluid.applyGradeLinkage", { }); | |
| - | |
| - fluid.gradeLinkageIndexer = function (defaults) { | |
| - if (defaults.contextGrades && defaults.resultGrades) { | |
| - return ["*"]; | |
| - } | |
| - }; | |
| - | |
| - fluid.getLinkedGrades = function (gradeNames) { | |
| - var togo = []; | |
| - var gradeLinkages = fluid.indexDefaults("gradeLinkages", { | |
| - gradeNames: "fluid.gradeLinkageRecord", | |
| - indexFunc: fluid.gradeLinkageIndexer | |
| - }); | |
| - fluid.each(gradeLinkages["*"], function (defaultsName) { | |
| - var defaults = fluid.defaults(defaultsName); | |
| - var exclude = fluid.find(fluid.makeArray(defaults.contextGrades), | |
| - function (grade) { | |
| - if (!fluid.contains(gradeNames, grade)) { | |
| - return true; | |
| - } | |
| - } | |
| - ); | |
| - if (!exclude) { | |
| - togo.push.apply(togo, fluid.makeArray(defaults.resultGrades)); | |
| - } | |
| - }); | |
| - return togo; | |
| - }; | |
| - | |
| fluid.expandDynamicGrades = function (that, shadow, gradeNames, dynamicGrades) { | |
| var resolved = []; | |
| + // Receive distributions first since these may cause arrival of more contextAwareness blocks. | |
| + // TODO: this closure algorithm is not reliable since we only get one shot at a "function" grade source when | |
| + // really we should perform complete closure over all other sources of options before we try it at the very end - particularly important for contextAwareness | |
| + var distributedBlocks = fluid.receiveDistributions(null, null, null, that); | |
| + if (distributedBlocks.length > 0) { | |
| + var readyBlocks = fluid.applyDistributions(that, distributedBlocks, shadow); | |
| + // rely on the fact that "dirty tricks are not permitted" wrt. resolving gradeNames - each element must be a literal entry or array | |
| + // holding primitive or EL values - otherwise we would have to go all round the houses and reenter the top of fluid.computeDynamicGrades | |
| + var gradeNamesList = fluid.transform(fluid.getMembers(readyBlocks, ["source", "gradeNames"]), fluid.makeArray); | |
| + resolved = resolved.concat.apply(resolved, gradeNamesList); | |
| + } | |
| fluid.each(dynamicGrades, function (dynamicGrade) { | |
| var expanded = fluid.expandOptions(dynamicGrade, that); | |
| if (typeof(expanded) === "function") { | |
| @@ -478,27 +548,11 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| resolved = resolved.concat(expanded); | |
| } | |
| }); | |
| - var allGrades = fluid.makeArray(gradeNames).concat(resolved); | |
| - if (fluid.contains(allGrades, "fluid.applyGradeLinkage")) { | |
| - var linkedGrades = fluid.getLinkedGrades(allGrades); | |
| - fluid.remove_if(linkedGrades, function (gradeName) { | |
| - return fluid.contains(allGrades, gradeName); | |
| - }); | |
| - resolved = resolved.concat(linkedGrades); | |
| - } | |
| - var distributedBlocks = fluid.receiveDistributions(null, null, null, that); | |
| - if (distributedBlocks.length > 0) { | |
| - var readyBlocks = fluid.applyDistributions(that, distributedBlocks, shadow); | |
| - // rely on the fact that "dirty tricks are not permitted" wrt. resolving gradeNames - each element must be a literal entry or array | |
| - // holding primitive or EL values - otherwise we would have to go all round the houses and reenter the top of fluid.computeDynamicGrades | |
| - var gradeNamesList = fluid.transform(fluid.getMembers(readyBlocks, ["source", "gradeNames"]), fluid.makeArray); | |
| - resolved = resolved.concat.apply(resolved, gradeNamesList); | |
| - } | |
| return resolved; | |
| }; | |
| // Discover further grades that are entailed by the given base typeName and the current total "dynamic grades list" held in the argument "resolved". | |
| - // These are looked up conjointly in the grade registry, and then any further i) dynamic grades references {} ii) grade linkage records | |
| + // These are looked up conjointly in the grade registry, and then any further dynamic grades references | |
| // are expanded and added into the list and concatenated into "resolved". Additional grades discovered during this function are returned as | |
| // "furtherResolved". | |
| fluid.collectDynamicGrades = function (that, shadow, defaultsBlock, gradeNames, dynamicGrades, resolved) { | |
| @@ -509,9 +563,9 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| fluid.cacheShadowGrades(that, shadow); | |
| // This cheap strategy patches FLUID-5091 for now - some more sophisticated activity will take place | |
| // at this site when we have a full fix for FLUID-5028 | |
| - shadow.mergeOptions.destroyValue("mergePolicy"); | |
| - shadow.mergeOptions.destroyValue("components"); | |
| - shadow.mergeOptions.destroyValue("invokers"); | |
| + shadow.mergeOptions.destroyValue(["mergePolicy"]); | |
| + shadow.mergeOptions.destroyValue(["components"]); | |
| + shadow.mergeOptions.destroyValue(["invokers"]); | |
| defaultsBlock.source = newDefaults; | |
| shadow.mergeOptions.updateBlocks(); | |
| @@ -527,7 +581,6 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| return furtherResolved; | |
| }; | |
| - // unsupported, NON-API function | |
| fluid.computeDynamicGrades = function (that, shadow, strategy) { | |
| delete that.options.gradeNames; // Recompute gradeNames for FLUID-5012 and others | |
| @@ -555,7 +608,6 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| return recordKey + (sourceKey === 0 ? "" : "-" + sourceKey); // TODO: configurable name strategies | |
| }; | |
| - // unsupported, NON-API function | |
| fluid.registerDynamicRecord = function (that, recordKey, sourceKey, record, toCensor) { | |
| var key = fluid.computeDynamicComponentKey(recordKey, sourceKey); | |
| var cRecord = fluid.copy(record); | |
| @@ -564,7 +616,6 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| return key; | |
| }; | |
| - // unsupported, NON-API function | |
| fluid.computeDynamicComponents = function (that, mergeOptions) { | |
| var shadow = fluid.shadowForComponent(that); | |
| var localSub = shadow.subcomponentLocal = {}; | |
| @@ -596,8 +647,8 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| // Second sequence point for mergeOptions from Fluid.js - here we construct all further | |
| // strategies required on the IoC side and mount them into the shadow's getConfig for universal use | |
| - // unsupported, NON-API function | |
| fluid.computeComponentAccessor = function (that) { | |
| + var instantiator = fluid.globalInstantiator; | |
| var shadow = fluid.shadowForComponent(that); | |
| var options = that.options; | |
| var strategy = shadow.mergeOptions.strategy; | |
| @@ -605,22 +656,53 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| shadow.invokerStrategy = fluid.recordStrategy(that, options, strategy, "invokers", fluid.invokerFromRecord); | |
| shadow.eventStrategyBlock = fluid.recordStrategy(that, options, strategy, "events", fluid.eventFromRecord, ["events"]); | |
| var eventStrategy = fluid.mountStrategy(["events"], that, shadow.eventStrategyBlock.strategy, ["events"]); | |
| - shadow.memberStrategy = fluid.recordStrategy(that, options, strategy, "members", fluid.memberFromRecord); | |
| + shadow.memberStrategy = fluid.recordStrategy(that, options, strategy, "members", fluid.memberFromRecord, null, {model: true, modelRelay: true}); | |
| // NB - ginger strategy handles concrete, rationalise | |
| shadow.getConfig = {strategies: [fluid.model.funcResolverStrategy, fluid.makeGingerStrategy(that), | |
| optionsStrategy, shadow.invokerStrategy.strategy, shadow.memberStrategy.strategy, eventStrategy]}; | |
| fluid.computeDynamicGrades(that, shadow, strategy, shadow.mergeOptions.mergeBlocks); | |
| fluid.distributeOptions(that, strategy); | |
| + if (shadow.contextHash["fluid.resolveRoot"]) { | |
| + var memberName; | |
| + if (shadow.contextHash["fluid.resolveRootSingle"]) { | |
| + var singleRootType = fluid.getForComponent(that, ["options", "singleRootType"]); | |
| + if (!singleRootType) { | |
| + fluid.fail("Cannot register object with grades " + Object.keys(shadow.contextHash).join(", ") + " as fluid.resolveRootSingle since it has not defined option singleRootType"); | |
| + } | |
| + memberName = fluid.typeNameToMemberName(singleRootType); | |
| + } else { | |
| + memberName = fluid.computeGlobalMemberName(that); | |
| + } | |
| + var parent = fluid.resolveRootComponent; | |
| + if (parent[memberName]) { | |
| + instantiator.clearComponent(parent, memberName); | |
| + } | |
| + instantiator.recordKnownComponent(parent, that, memberName, false); | |
| + } | |
| return shadow.getConfig; | |
| }; | |
| + // About the SHADOW: | |
| + // Allocated at: instantiator's "recordComponent" | |
| + // Contents: | |
| + // path {String} Principal allocated path (point of construction) in tree | |
| + // that {Component} The component itself | |
| + // contextHash {String to Boolean} Map of context names which this component matches | |
| + // mergePolicy, mergeOptions: Machinery for last phase of options merging | |
| + // invokerStrategy, eventStrategyBlock, memberStrategy, getConfig: Junk required to operate the accessor | |
| + // listeners: Listeners registered during this component's construction, to be cleared during clearListeners | |
| + // distributions, collectedClearer: Managing options distributions | |
| + // subcomponentLocal: Signalling local record from computeDynamicComponents to assembleCreatorArguments | |
| + | |
| fluid.shadowForComponent = function (component) { | |
| var instantiator = fluid.getInstantiator(component); | |
| return instantiator && component ? instantiator.idToShadow[component.id] : null; | |
| }; | |
| + // Access the member at a particular path in a component, forcing it to be constructed gingerly if necessary | |
| + // supported, PUBLIC API function | |
| fluid.getForComponent = function (component, path) { | |
| var shadow = fluid.shadowForComponent(component); | |
| var getConfig = shadow ? shadow.getConfig : undefined; | |
| @@ -630,7 +712,6 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| // An EL segment resolver strategy that will attempt to trigger creation of | |
| // components that it discovers along the EL path, if they have been defined but not yet | |
| // constructed. | |
| - // unsupported, NON-API function | |
| fluid.makeGingerStrategy = function (that) { | |
| var instantiator = fluid.getInstantiator(that); | |
| return function (component, thisSeg, index, segs) { | |
| @@ -647,7 +728,7 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| } | |
| if (atval === undefined) { // pick up components in instantiation here - we can cut this branch by attaching early | |
| var parentPath = instantiator.idToShadow[component.id].path; | |
| - var childPath = fluid.composePath(parentPath, thisSeg); | |
| + var childPath = instantiator.composePath(parentPath, thisSeg); | |
| atval = instantiator.pathToComponent[childPath]; | |
| } | |
| if (atval === undefined) { | |
| @@ -667,10 +748,13 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| return atval; | |
| }; | |
| }; | |
| - | |
| + | |
| + // Listed in dependence order | |
| + fluid.frameworkGrades = ["fluid.component", "fluid.modelComponent", "fluid.viewComponent", "fluid.rendererComponent"]; | |
| + | |
| fluid.filterBuiltinGrades = function (gradeNames) { | |
| return fluid.remove_if(fluid.makeArray(gradeNames), function (gradeName) { | |
| - return (/^(autoInit|fluid.littleComponent|fluid.modelComponent|fluid.eventedComponent|fluid.viewComponent|fluid.typeFount)$/).test(gradeName); | |
| + return fluid.frameworkGrades.indexOf(gradeName) !== -1; | |
| }); | |
| }; | |
| @@ -679,12 +763,10 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| " gradeNames: " + JSON.stringify(fluid.filterBuiltinGrades(that.options.gradeNames)) : ""; | |
| }; | |
| - // unsupported, non-API function | |
| fluid.dumpThat = function (that) { | |
| return "{ typeName: \"" + that.typeName + "\"" + fluid.dumpGradeNames(that) + " id: " + that.id + "}"; | |
| }; | |
| - // unsupported, non-API function | |
| fluid.dumpThatStack = function (thatStack, instantiator) { | |
| var togo = fluid.transform(thatStack, function(that) { | |
| var path = instantiator.idToPath(that.id); | |
| @@ -693,53 +775,47 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| return togo.join("\n"); | |
| }; | |
| - // unsupported, NON-API function | |
| - fluid.resolveContext = function (context, that) { | |
| + fluid.resolveContext = function (context, that, fast) { | |
| var instantiator = fluid.getInstantiator(that); | |
| - if (context === "instantiator") { | |
| - return instantiator; | |
| - } | |
| - else if (context === "that") { | |
| + if (context === "that") { | |
| return that; | |
| } | |
| var foundComponent; | |
| - var thatStack = instantiator.getFullStack(that); | |
| - visitComponents(instantiator, thatStack, function (component, name) { | |
| - var shadow = fluid.shadowForComponent(component); | |
| - // TODO: Some components, e.g. the static environment and typeTags do not have a shadow, which slows us down here | |
| - if (context === name || shadow && shadow.contextHash && shadow.contextHash[context] || context === component.typeName || context === component.nickName) { | |
| - foundComponent = component; | |
| - return true; // YOUR VISIT IS AT AN END!! | |
| - } | |
| - if (fluid.getForComponent(component, ["options", "components", context, "type"]) && !component[context]) { | |
| - // This is an expensive guess since we make it for every component up the stack - must apply the WAVE OF EXPLOSIONS (FLUID-4925) to discover all components first | |
| - // This line attempts a hopeful construction of components that could be guessed by nickname through finding them unconstructed | |
| - // in options. In the near future we should eagerly BEGIN the process of constructing components, discovering their | |
| - // types and then attaching them to the tree VERY EARLY so that we get consistent results from different strategies. | |
| - foundComponent = fluid.getForComponent(component, context); | |
| - return true; | |
| - } | |
| - }); | |
| - return foundComponent; | |
| + if (fast) { | |
| + var shadow = instantiator.idToShadow[that.id]; | |
| + return shadow.ownScope[context]; | |
| + } else { | |
| + var thatStack = instantiator.getFullStack(that); | |
| + fluid.visitComponentsForVisibility(instantiator, thatStack, function (component, name) { | |
| + var shadow = fluid.shadowForComponent(component); | |
| + // TODO: Some components, e.g. the static environment and typeTags do not have a shadow, which slows us down here | |
| + if (context === name || shadow && shadow.contextHash && shadow.contextHash[context] || context === component.typeName) { | |
| + foundComponent = component; | |
| + return true; // YOUR VISIT IS AT AN END!! | |
| + } | |
| + if (fluid.getForComponent(component, ["options", "components", context]) && !component[context]) { | |
| + // This is an expensive guess since we make it for every component up the stack - must apply the WAVE OF EXPLOSIONS (FLUID-4925) to discover all components first | |
| + // This line attempts a hopeful construction of components that could be guessed by nickname through finding them unconstructed | |
| + // in options. In the near future we should eagerly BEGIN the process of constructing components, discovering their | |
| + // types and then attaching them to the tree VERY EARLY so that we get consistent results from different strategies. | |
| + foundComponent = fluid.getForComponent(component, context); | |
| + return true; | |
| + } | |
| + }); | |
| + return foundComponent; | |
| + } | |
| }; | |
| - | |
| - var localRecordExpected = /^(arguments|options|container|source|sourcePath|change)$/; | |
| - | |
| - // unsupported, NON-API function | |
| - fluid.makeStackFetcher = function (parentThat, localRecord) { | |
| + | |
| + fluid.makeStackFetcher = function (parentThat, localRecord, fast) { | |
| var fetcher = function (parsed) { | |
| - if (parentThat && parentThat.destroy === fluid.destroyedMarker) { | |
| + if (parentThat && parentThat.lifecycleStatus === "destroyed") { | |
| fluid.fail("Cannot resolve reference " + fluid.renderContextReference(parsed) + " from component " + fluid.dumpThat(parentThat) + " which has been destroyed"); | |
| } | |
| var context = parsed.context; | |
| - if (localRecord && localRecordExpected.test(context)) { | |
| - var fetched = fluid.get(localRecord[context], parsed.path); | |
| - return context === "arguments" || context === "source" || context === "sourcePath" || context === "change" ? fetched : { | |
| - marker: context === "options" ? fluid.EXPAND : fluid.EXPAND_NOW, | |
| - value: fetched | |
| - }; | |
| + if (localRecord && context in localRecord) { | |
| + return fluid.get(localRecord[context], parsed.path); | |
| } | |
| - var foundComponent = fluid.resolveContext(context, parentThat); | |
| + var foundComponent = fluid.resolveContext(context, parentThat, fast); | |
| if (!foundComponent && parsed.path !== "") { | |
| var ref = fluid.renderContextReference(parsed); | |
| fluid.fail("Failed to resolve reference " + ref + " - could not match context with name " + | |
| @@ -750,15 +826,15 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| return fetcher; | |
| }; | |
| - // unsupported, NON-API function | |
| - fluid.makeStackResolverOptions = function (parentThat, localRecord) { | |
| + fluid.makeStackResolverOptions = function (parentThat, localRecord, fast) { | |
| return $.extend(fluid.copy(fluid.rawDefaults("fluid.makeExpandOptions")), { | |
| - fetcher: fluid.makeStackFetcher(parentThat, localRecord), | |
| - contextThat: parentThat | |
| + localRecord: localRecord || {}, | |
| + fetcher: fluid.makeStackFetcher(parentThat, localRecord, fast), | |
| + contextThat: parentThat, | |
| + exceptions: {members: {model: true, modelRelay: true}} | |
| }); | |
| }; | |
| - // unsupported, non-API function | |
| fluid.clearListeners = function (shadow) { | |
| // TODO: bug here - "afterDestroy" listeners will be unregistered already unless they come from this component | |
| fluid.each(shadow.listeners, function (rec) { | |
| @@ -767,7 +843,6 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| delete shadow.listeners; | |
| }; | |
| - // unsupported, non-API function | |
| fluid.recordListener = function (event, listener, shadow) { | |
| if (event.ownerId !== shadow.that.id) { // don't bother recording listeners registered from this component itself | |
| var listeners = shadow.listeners; | |
| @@ -777,61 +852,89 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| listeners.push({event: event, listener: listener}); | |
| } | |
| }; | |
| - | |
| - var idToInstantiator = {}; | |
| + | |
| + fluid.constructScopeObjects = function (instantiator, parent, child, childShadow) { | |
| + var parentShadow = parent ? instantiator.idToShadow[parent.id] : null; | |
| + childShadow.childrenScope = parentShadow ? Object.create(parentShadow.ownScope) : {}; | |
| + childShadow.ownScope = Object.create(childShadow.childrenScope); | |
| + childShadow.parentShadow = parentShadow; | |
| + }; | |
| + | |
| + fluid.clearChildrenScope = function (instantiator, parentShadow, child, childShadow) { | |
| + fluid.each(childShadow.contextHash, function (troo, context) { | |
| + if (parentShadow.childrenScope[context] === child) { | |
| + delete parentShadow.childrenScope[context]; // TODO: ambiguous resolution | |
| + } | |
| + }); | |
| + }; | |
| // unsupported, non-API function - however, this structure is of considerable interest to those debugging | |
| // into IoC issues. The structures idToShadow and pathToComponent contain a complete map of the component tree | |
| // forming the surrounding scope | |
| - fluid.instantiator = function (freeInstantiator) { | |
| + fluid.instantiator = function () { | |
| var that = { | |
| id: fluid.allocateGuid(), | |
| - free: freeInstantiator, | |
| - nickName: "instantiator", | |
| + typeName: "instantiator", | |
| + lifecycleStatus: "constructed", | |
| pathToComponent: {}, | |
| idToShadow: {}, | |
| modelTransactions: {init: {}}, // a map of transaction id to map of component id to records of components enlisted in a current model initialisation transaction | |
| - composePath: fluid.composePath // For speed, we declare that no component's name may contain a period | |
| + composePath: fluid.model.composePath, // For speed, we declare that no component's name may contain a period | |
| + composeSegments: fluid.model.composeSegments, | |
| + parseEL: fluid.model.parseEL, | |
| + events: { | |
| + onComponentAttach: fluid.makeEventFirer({name: "instantiator's onComponentAttach event"}), | |
| + onComponentClear: fluid.makeEventFirer({name: "instantiator's onComponentClear event"}) | |
| + } | |
| }; | |
| - // We frequently get requests for components not in this instantiator - e.g. from the dynamicEnvironment or manually created ones | |
| + // TODO: this API can shortly be removed | |
| that.idToPath = function (id) { | |
| var shadow = that.idToShadow[id]; | |
| return shadow ? shadow.path : ""; | |
| }; | |
| + // Note - the returned stack is assumed writeable and does not include the root | |
| that.getThatStack = function (component) { | |
| var shadow = that.idToShadow[component.id]; | |
| if (shadow) { | |
| var path = shadow.path; | |
| var parsed = fluid.model.parseEL(path); | |
| - var togo = fluid.transform(parsed, function (value, i) { | |
| - var parentPath = fluid.model.composeSegments.apply(null, parsed.slice(0, i + 1)); | |
| - return that.pathToComponent[parentPath]; | |
| - }); | |
| - var root = that.pathToComponent[""]; | |
| - if (root) { | |
| - togo.unshift(root); | |
| + var root = that.pathToComponent[""], togo = []; | |
| + for (var i = 0; i < parsed.length; ++ i) { | |
| + root = root[parsed[i]]; | |
| + togo.push(root); | |
| } | |
| return togo; | |
| } | |
| - else { return [component];} | |
| - }; | |
| - that.getEnvironmentalStack = function () { | |
| - var togo = [fluid.staticEnvironment]; | |
| - if (!freeInstantiator) { | |
| - togo.push(fluid.globalThreadLocal()); | |
| - } | |
| - return togo; | |
| + else { return [];} | |
| }; | |
| that.getFullStack = function (component) { | |
| var thatStack = component? that.getThatStack(component) : []; | |
| - return that.getEnvironmentalStack().concat(thatStack); | |
| + thatStack.unshift(fluid.resolveRootComponent); | |
| + return thatStack; | |
| }; | |
| - function recordComponent(component, path, created) { | |
| + function recordComponent(parent, component, path, name, created) { | |
| + var shadow; | |
| if (created) { | |
| - idToInstantiator[component.id] = that; | |
| - var shadow = that.idToShadow[component.id] = {}; | |
| + shadow = that.idToShadow[component.id] = {}; | |
| shadow.that = component; | |
| shadow.path = path; | |
| + shadow.memberName = name; | |
| + fluid.constructScopeObjects(that, parent, component, shadow); | |
| + } else { | |
| + shadow = that.idToShadow[component.id]; | |
| + shadow.injectedPaths = shadow.injectedPaths || []; | |
| + shadow.injectedPaths.push(path); | |
| + var parentShadow = that.idToShadow[parent.id]; // structural parent shadow - e.g. resolveRootComponent | |
| + var keys = fluid.keys(shadow.contextHash); | |
| + keys.push(name); // add local name - FLUID-5696 | |
| + fluid.remove_if(keys, function (key) { | |
| + return shadow.contextHash && shadow.contextHash[key] === "memberName"; | |
| + }); | |
| + fluid.each(keys, function (context) { | |
| + if (!parentShadow.childrenScope[context]) { | |
| + parentShadow.childrenScope[context] = component; | |
| + } | |
| + }); | |
| } | |
| if (that.pathToComponent[path]) { | |
| fluid.fail("Error during instantiation - path " + path + " which has just created component " + fluid.dumpThat(component) + | |
| @@ -841,44 +944,58 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| that.pathToComponent[path] = component; | |
| } | |
| that.recordRoot = function (component) { | |
| - if (component && component.id && !that.pathToComponent[""]) { | |
| - recordComponent(component, "", true); | |
| - } | |
| + recordComponent(null, component, "", "", true); | |
| }; | |
| that.recordKnownComponent = function (parent, component, name, created) { | |
| - var parentPath = that.idToShadow[parent.id].path; | |
| - var path = that.composePath(parentPath, name); | |
| - recordComponent(component, path, created); | |
| + parent[name] = component; | |
| + if (fluid.isComponent(component) || component.type === "instantiator") { | |
| + var parentPath = that.idToShadow[parent.id].path; | |
| + var path = that.composePath(parentPath, name); | |
| + recordComponent(parent, component, path, name, created); | |
| + that.events.onComponentAttach.fire(component, path, that, created); | |
| + } else { | |
| + fluid.fail("Cannot record non-component"); | |
| + } | |
| }; | |
| that.clearComponent = function (component, name, child, options, noModTree, path) { | |
| - var record = that.idToShadow[component.id].path; | |
| + // options are visitor options for recursive driving | |
| + var shadow = that.idToShadow[component.id]; | |
| // use flat recursion since we want to use our own recursion rather than rely on "visited" records | |
| options = options || {flat: true, instantiator: that}; | |
| child = child || component[name]; | |
| - path = path || record; | |
| + path = path || shadow.path; | |
| if (path === undefined) { | |
| fluid.fail("Cannot clear component " + name + " from component ", component, | |
| " which was not created by this instantiator"); | |
| } | |
| - fluid.fireEvent(child, "events.onClear", [child, name, component]); | |
| var childPath = that.composePath(path, name); | |
| - var childRecord = that.idToShadow[child.id]; | |
| + var childShadow = that.idToShadow[child.id]; | |
| + var created = childShadow.path === childPath; | |
| + that.events.onComponentClear.fire(child, childPath, component, created); | |
| // only recurse on components which were created in place - if the id record disagrees with the | |
| // recurse path, it must have been injected | |
| - if (childRecord && childRecord.path === childPath) { | |
| - fluid.doDestroy(child, name, component); | |
| - // TODO: There needs to be a call to fluid.clearDistributions here | |
| - fluid.clearListeners(childRecord); | |
| - fluid.visitComponentChildren(child, function(gchild, gchildname, newPath, parentPath) { | |
| + if (created) { | |
| + // Clear injected instance of this component from all other paths - historically we didn't bother | |
| + // to do this since injecting into a shorter scope is an error - but now we have resolveRoot area | |
| + fluid.each(childShadow.injectedPaths, function (injectedPath) { | |
| + var parentPath = fluid.model.getToTailPath(injectedPath); | |
| + var otherParent = that.pathToComponent[parentPath]; | |
| + that.clearComponent(otherParent, fluid.model.getTailPath(injectedPath), child); | |
| + }); | |
| + fluid.visitComponentChildren(child, function(gchild, gchildname, segs, i) { | |
| + var parentPath = that.composeSegments.apply(null, segs.slice(0, i)); | |
| that.clearComponent(child, gchildname, null, options, true, parentPath); | |
| - }, options, childPath); | |
| + }, options, that.parseEL(childPath)); | |
| + fluid.doDestroy(child, name, component); | |
| + fluid.clearDistributions(childShadow); | |
| + fluid.clearListeners(childShadow); | |
| fluid.fireEvent(child, "events.afterDestroy", [child, name, component]); | |
| delete that.idToShadow[child.id]; | |
| - delete idToInstantiator[child.id]; | |
| } | |
| - delete that.pathToComponent[childPath]; // there may be no entry - if created informally | |
| + fluid.clearChildrenScope(that, shadow, child, childShadow); | |
| + delete that.pathToComponent[childPath]; | |
| if (!noModTree) { | |
| delete component[name]; // there may be no entry - if creation is not concluded | |
| } | |
| @@ -886,16 +1003,47 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| return that; | |
| }; | |
| - // An instantiator to be used in the "free environment", unattached to any component tree | |
| - fluid.freeInstantiator = fluid.instantiator(true); | |
| - | |
| - // Look up the globally registered instantiator for a particular component | |
| + // The global instantiator, holding all components instantiated in this context (instance of Infusion) | |
| + fluid.globalInstantiator = fluid.instantiator(); | |
| + | |
| + // Look up the globally registered instantiator for a particular component - we now only really support a | |
| + // single, global instantiator, but this method is left as a notation point in case this ever reverts | |
| fluid.getInstantiator = function (component) { | |
| - return component && idToInstantiator[component.id] || fluid.freeInstantiator; | |
| + var instantiator = fluid.globalInstantiator; | |
| + return component && instantiator.idToShadow[component.id] ? instantiator : null; | |
| + }; | |
| + | |
| + // The grade supplied to components which will be resolvable from all parts of the component tree | |
| + fluid.defaults("fluid.resolveRoot"); | |
| + // In addition to being resolvable at the root, "resolveRootSingle" component will have just a single instance available. Fresh | |
| + // instances will displace older ones. | |
| + fluid.defaults("fluid.resolveRootSingle", { | |
| + gradeNames: "fluid.resolveRoot" | |
| + }); | |
| + | |
| + fluid.constructRootComponents = function (instantiator) { | |
| + // Instantiate the primordial components at the root of each context tree | |
| + fluid.rootComponent = instantiator.rootComponent = fluid.typeTag("fluid.rootComponent"); | |
| + instantiator.recordRoot(fluid.rootComponent); | |
| + | |
| + // The component which for convenience holds injected instances of all components with fluid.resolveRoot grade | |
| + fluid.resolveRootComponent = instantiator.resolveRootComponent = fluid.typeTag("fluid.resolveRootComponent"); | |
| + instantiator.recordKnownComponent(fluid.rootComponent, fluid.resolveRootComponent, "resolveRootComponent", true); | |
| + | |
| + // obliterate resolveRoot's scope objects and replace by the real root scope - which is unused by its own children | |
| + var rootShadow = instantiator.idToShadow[fluid.rootComponent.id]; | |
| + var resolveRootShadow = instantiator.idToShadow[fluid.resolveRootComponent.id]; | |
| + resolveRootShadow.ownScope = rootShadow.ownScope; | |
| + resolveRootShadow.childrenScope = rootShadow.childrenScope; | |
| + | |
| + instantiator.recordKnownComponent(fluid.resolveRootComponent, instantiator, "instantiator", true); // needs to have a shadow so it can be injected | |
| + resolveRootShadow.childrenScope.instantiator = instantiator; // needs to be mounted since it never passes through cacheShadowGrades | |
| }; | |
| + | |
| + fluid.constructRootComponents(fluid.globalInstantiator); // currently a singleton - in future, alternative instantiators might come back | |
| /** Expand a set of component options either immediately, or with deferred effect. | |
| - * The current policy is to expand immediately function arguments within fluid.embodyDemands which are not the main options of a | |
| + * The current policy is to expand immediately function arguments within fluid.assembleCreatorArguments which are not the main options of a | |
| * component. The component's own options take <code>{defer: true}</code> as part of | |
| * <code>outerExpandOptions</code> which produces an "expandOptions" structure holding the "strategy" and "initter" pattern | |
| * common to ginger participants. | |
| @@ -903,6 +1051,7 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| * of the IoC API structure especially with respect to the first arguments. | |
| */ | |
| +// TODO: Can we move outerExpandOptions to 2nd place? only user of 3 and 4 is fluid.makeExpandBlock | |
| fluid.expandOptions = function (args, that, mergePolicy, localRecord, outerExpandOptions) { | |
| if (!args) { | |
| return args; | |
| @@ -910,16 +1059,14 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| fluid.pushActivity("expandOptions", "expanding options %args for component %that ", {that: that, args: args}); | |
| var expandOptions = fluid.makeStackResolverOptions(that, localRecord); | |
| expandOptions.mergePolicy = mergePolicy; | |
| - expandOptions.freeRoot = outerExpandOptions && outerExpandOptions.freeRoot; | |
| var expanded = outerExpandOptions && outerExpandOptions.defer ? | |
| fluid.makeExpandOptions(args, expandOptions) : fluid.expand(args, expandOptions); | |
| fluid.popActivity(); | |
| return expanded; | |
| }; | |
| - // unsupported, non-API function | |
| - fluid.localRecordExpected = ["type", "options", "args", "mergeOptions", "createOnEvent", "priority", "recordType"]; // last element unavoidably polluting | |
| - // unsupported, non-API function | |
| + fluid.localRecordExpected = ["type", "options", "args", "createOnEvent", "priority", "recordType"]; // last element unavoidably polluting | |
| + | |
| fluid.checkComponentRecord = function (defaults, localRecord) { | |
| var expected = fluid.arrayToHash(fluid.localRecordExpected); | |
| fluid.each(defaults && defaults.argumentMap, function(value, key) { | |
| @@ -927,50 +1074,23 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| }); | |
| fluid.each(localRecord, function (value, key) { | |
| if (!expected[key]) { | |
| - fluid.fail("Probable error in subcomponent record - key \"" + key + | |
| + fluid.fail("Probable error in subcomponent record ", localRecord, " - key \"" + key + | |
| "\" found, where the only legal options are " + | |
| fluid.keys(expected).join(", ")); | |
| } | |
| }); | |
| }; | |
| - // unsupported, non-API function | |
| - fluid.pushDemands = function (list, demands) { | |
| - demands = fluid.makeArray(demands); | |
| - var thisp = fluid.mergeRecordTypes.demands; | |
| - function push(rec) { | |
| - rec.recordType = "demands"; | |
| - rec.priority = thisp++; | |
| - list.push(rec); | |
| - } | |
| - function buildAndPush(rec) { | |
| - push({options: rec}); | |
| - } | |
| - // Assume these are sorted at source by intersect count (can't pre-merge if we want "mergeOptions") | |
| - for (var i = 0; i < demands.length; ++ i) { | |
| - var thisd = demands[i]; | |
| - if (thisd.options) { | |
| - push(thisd); | |
| - } | |
| - else if (thisd.mergeOptions) { | |
| - var mergeOptions = fluid.makeArray(thisd.mergeOptions); | |
| - fluid.each(mergeOptions, buildAndPush); | |
| - } | |
| - else { | |
| - fluid.fail("Uninterpretable demands record without options or mergeOptions ", thisd); | |
| - } | |
| - } | |
| - }; | |
| - | |
| - // unsupported, non-API function | |
| - fluid.mergeRecordsToList = function (mergeRecords) { | |
| + fluid.mergeRecordsToList = function (that, mergeRecords) { | |
| var list = []; | |
| fluid.each(mergeRecords, function (value, key) { | |
| value.recordType = key; | |
| if (key === "distributions") { | |
| - list.push.apply(list, value); | |
| + list.push.apply(list, fluid.transform(value, function (distributedBlock) { | |
| + return fluid.computeDistributionPriority(that, distributedBlock); | |
| + })); | |
| } | |
| - else if (key !== "demands") { | |
| + else { | |
| if (!value.options) { return; } | |
| value.priority = fluid.mergeRecordTypes[key]; | |
| if (value.priority === undefined) { | |
| @@ -978,9 +1098,6 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| } | |
| list.push(value); | |
| } | |
| - else { | |
| - fluid.pushDemands(list, value); | |
| - } | |
| }); | |
| return list; | |
| }; | |
| @@ -988,21 +1105,22 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| // TODO: overall efficiency could huge be improved by resorting to the hated PROTOTYPALISM as an optimisation | |
| // for this mergePolicy which occurs in every component. Although it is a deep structure, the root keys are all we need | |
| var addPolicyBuiltins = function (policy) { | |
| - fluid.each(["gradeNames", "mergePolicy", "argumentMap", "components", "dynamicComponents", "members", "invokers", "events", "listeners", "modelListeners", "distributeOptions", "transformOptions"], function (key) { | |
| + fluid.each(["gradeNames", "mergePolicy", "argumentMap", "components", "dynamicComponents", "invokers", "events", "listeners", "modelListeners", "distributeOptions", "transformOptions"], function (key) { | |
| fluid.set(policy, [key, "*", "noexpand"], true); | |
| }); | |
| return policy; | |
| }; | |
| - // unsupported, NON-API function - used from Fluid.js | |
| + // used from Fluid.js | |
| fluid.generateExpandBlock = function (record, that, mergePolicy, localRecord) { | |
| var expanded = fluid.expandOptions(record.options, record.contextThat || that, mergePolicy, localRecord, {defer: true}); | |
| expanded.priority = record.priority; | |
| + expanded.namespace = record.namespace; | |
| expanded.recordType = record.recordType; | |
| return expanded; | |
| }; | |
| - var expandComponentOptionsImpl = function (mergePolicy, defaults, userOptions, that) { | |
| + var expandComponentOptionsImpl = function (mergePolicy, defaults, initRecord, that) { | |
| var defaultCopy = fluid.copy(defaults); | |
| addPolicyBuiltins(mergePolicy); | |
| var shadow = fluid.shadowForComponent(that); | |
| @@ -1011,177 +1129,96 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| defaults: {options: defaultCopy} | |
| }; | |
| - if (userOptions) { | |
| - if (userOptions.marker === fluid.EXPAND) { | |
| - $.extend(mergeRecords, userOptions.mergeRecords); | |
| - // Do this here for gradeless components that were corrected by "localOptions" | |
| - if (mergeRecords.subcomponentRecord) { | |
| - fluid.checkComponentRecord(defaults, mergeRecords.subcomponentRecord); | |
| - } | |
| - } | |
| - else { | |
| - mergeRecords.user = {options: fluid.expandCompact(userOptions, true)}; | |
| - } | |
| + $.extend(mergeRecords, initRecord.mergeRecords); | |
| + // Do this here for gradeless components that were corrected by "localOptions" | |
| + if (mergeRecords.subcomponentRecord) { | |
| + fluid.checkComponentRecord(defaults, mergeRecords.subcomponentRecord); | |
| } | |
| - var expandList = fluid.mergeRecordsToList(mergeRecords); | |
| + | |
| + var expandList = fluid.mergeRecordsToList(that, mergeRecords); | |
| var togo = fluid.transform(expandList, function (value) { | |
| - return fluid.generateExpandBlock(value, that, mergePolicy, userOptions && userOptions.localRecord); | |
| + return fluid.generateExpandBlock(value, that, mergePolicy, initRecord.localRecord); | |
| }); | |
| return togo; | |
| }; | |
| - // unsupported, non-API function | |
| - fluid.makeIoCRootDestroy = function (instantiator, that) { | |
| - return function () { | |
| - instantiator.clearComponent(that, "", that, null, true); | |
| - }; | |
| - }; | |
| - | |
| - // NON-API function | |
| fluid.fabricateDestroyMethod = function (that, name, instantiator, child) { | |
| return function () { | |
| instantiator.clearComponent(that, name, child); | |
| }; | |
| }; | |
| + | |
| + // Computes a name for a component appearing at the global root which is globally unique, from its nickName and id | |
| + fluid.computeGlobalMemberName = function (that) { | |
| + var nickName = fluid.computeNickName(that.typeName); | |
| + return nickName + "-" + that.id; | |
| + }; | |
| + | |
| + // Maps a type name to the member name to be used for it at a particular path level where it is intended to be unique | |
| + // Note that "." is still not supported within a member name | |
| + // unsupported, NON-API function | |
| + fluid.typeNameToMemberName = function (typeName) { | |
| + return typeName.replace(/\./g, "_"); | |
| + }; | |
| - // unsupported, non-API function | |
| + // This is the initial entry point from the non-IoC side reporting the first presence of a new component - called from fluid.mergeComponentOptions | |
| fluid.expandComponentOptions = function (mergePolicy, defaults, userOptions, that) { | |
| - var instantiator = userOptions && userOptions.marker === fluid.EXPAND && userOptions.memberName !== undefined ? | |
| - userOptions.instantiator : null; | |
| - var fresh; | |
| - if (!instantiator) { | |
| - instantiator = fluid.instantiator(); | |
| - fresh = true; | |
| - fluid.log("Created new instantiator with id " + instantiator.id + " in order to operate on component " + (that? that.typeName : "[none]")); | |
| - that.destroy = fluid.makeIoCRootDestroy(instantiator, that); | |
| + var initRecord = userOptions; // might have been tunnelled through "userOptions" from "assembleCreatorArguments" | |
| + var instantiator = userOptions && userOptions.marker === fluid.EXPAND ? userOptions.instantiator : null; | |
| + if (!instantiator) { // it is a top-level component which needs to be attached to the global root | |
| + instantiator = fluid.globalInstantiator; | |
| + initRecord = { // upgrade "userOptions" to the same format produced by fluid.assembleCreatorArguments via the subcomponent route | |
| + mergeRecords: {user: {options: fluid.expandCompact(userOptions, true)}}, | |
| + memberName: fluid.computeGlobalMemberName(that), | |
| + instantiator: instantiator, | |
| + parentThat: fluid.rootComponent | |
| + }; | |
| } | |
| + that.destroy = fluid.fabricateDestroyMethod(initRecord.parentThat, initRecord.memberName, instantiator, that); | |
| fluid.pushActivity("expandComponentOptions", "expanding component options %options with record %record for component %that", | |
| - {options: userOptions && userOptions.mergeRecords, record: userOptions, that: that}); | |
| - if (fresh) { | |
| - instantiator.recordRoot(that); | |
| - } | |
| - else { | |
| - instantiator.recordKnownComponent(userOptions.parentThat, that, userOptions.memberName, true); | |
| - } | |
| - var togo = expandComponentOptionsImpl(mergePolicy, defaults, userOptions, that); | |
| + {options: fluid.get(initRecord.mergeRecords, "user.options"), record: initRecord, that: that}); | |
| + | |
| + instantiator.recordKnownComponent(initRecord.parentThat, that, initRecord.memberName, true); | |
| + var togo = expandComponentOptionsImpl(mergePolicy, defaults, initRecord, that); | |
| + | |
| fluid.popActivity(); | |
| return togo; | |
| }; | |
| - // unsupported, non-API function | |
| - fluid.argMapToDemands = function (argMap) { | |
| - var togo = []; | |
| - fluid.each(argMap, function (value, key) { | |
| - togo[value] = "{" + key + "}"; | |
| - }); | |
| - return togo; | |
| - }; | |
| - | |
| - // unsupported, non-API function | |
| - fluid.makePassArgsSpec = function (initArgs) { | |
| - return fluid.transform(initArgs, function(arg, index) { | |
| - return "{arguments}." + index; | |
| - }); | |
| - }; | |
| - | |
| - // unsupported, NON-API function | |
| - fluid.pushDemandSpec = function (record, options, mergeOptions) { | |
| - if (options && options !== "{options}") { | |
| - record.push({options: options}); | |
| - } | |
| - if (mergeOptions) { | |
| - record.push({mergeOptions: mergeOptions}); | |
| - } | |
| - }; | |
| - | |
| - /** Given a concrete argument list and/or options, determine the final concrete | |
| - * "invocation specification" which is coded by the supplied demandspec in the | |
| - * environment "thatStack" - the return is a package of concrete global function name | |
| + /** Given a typeName, determine the final concrete | |
| + * "invocation specification" consisting of a concrete global function name | |
| * and argument list which is suitable to be executed directly by fluid.invokeGlobalFunction. | |
| */ | |
| - // unsupported, non-API function | |
| - // options is just a disposition record containing memberName, componentRecord + passArgs | |
| - // various built-in effects of this method | |
| - // i) note that it makes no effort to actually propagate direct | |
| - // options from "initArgs", assuming that they will be seen again in expandComponentOptions | |
| - fluid.embodyDemands = function (parentThat, demandspec, initArgs, options) { | |
| - options = options || {}; | |
| - | |
| - if (demandspec.mergeOptions && demandspec.options) { | |
| - fluid.fail("demandspec ", demandspec, | |
| - " is invalid - cannot specify literal options together with mergeOptions"); | |
| - } | |
| - if (demandspec.transformOptions) { // Support for "transformOptions" at top level in a demands record | |
| - demandspec.options = $.extend(true, {}, demandspec.options, { | |
| - transformOptions: demandspec.transformOptions | |
| - }); | |
| + // options is just a disposition record containing memberName, componentRecord | |
| + fluid.assembleCreatorArguments = function (parentThat, typeName, options) { | |
| + var upDefaults = fluid.defaults(typeName); // we're not responsive to dynamic changes in argMap, but we don't believe in these anyway | |
| + if (!upDefaults || !upDefaults.argumentMap) { | |
| + fluid.fail("Error in assembleCreatorArguments: cannot look up component type name " + typeName + " to a component creator grade with an argumentMap"); | |
| } | |
| - var demands = fluid.makeArray(demandspec.args); | |
| - | |
| - var upDefaults = fluid.defaults(demandspec.funcName); | |
| var fakeThat = {}; // fake "that" for receiveDistributions since we try to match selectors before creation for FLUID-5013 | |
| - var distributions = upDefaults && parentThat ? fluid.receiveDistributions(parentThat, upDefaults.gradeNames, options.memberName, fakeThat) : []; | |
| + var distributions = parentThat ? fluid.receiveDistributions(parentThat, upDefaults.gradeNames, options.memberName, fakeThat) : []; | |
| - var argMap = upDefaults? upDefaults.argumentMap : null; | |
| - var inferMap = false; | |
| - if (upDefaults) { | |
| - options.passArgs = false; // Don't attempt to construct a component using "passArgs" spec | |
| - } | |
| - if (!argMap && (upDefaults || (options && options.componentRecord))) { | |
| - inferMap = true; | |
| - // infer that it must be a little component if we have any reason to believe it is a component | |
| - if (demands.length < 2) { | |
| - argMap = fluid.rawDefaults("fluid.littleComponent").argumentMap; | |
| - } | |
| - else { | |
| - var optionpos = $.inArray("{options}", demands); | |
| - if (optionpos === -1) { | |
| - optionpos = demands.length - 1; // wild guess in the old style | |
| - } | |
| - argMap = {options: optionpos}; | |
| - } | |
| - } | |
| - options = options || {}; | |
| - if (demands.length === 0) { | |
| - if (argMap) { | |
| - demands = fluid.argMapToDemands(argMap); | |
| - } | |
| - else if (options.passArgs) { | |
| - demands = fluid.makePassArgsSpec(initArgs); | |
| - } | |
| - } | |
| var shadow = fluid.shadowForComponent(parentThat); | |
| - var localDynamic = shadow && options.memberName ? shadow.subcomponentLocal[options.memberName] : null; | |
| + var localDynamic = shadow && shadow.subcomponentLocal && options.memberName ? shadow.subcomponentLocal[options.memberName] : null; | |
| - // confusion remains with "localRecord" - it is a random mishmash of user arguments and the component record | |
| - // this should itself be absorbed into "mergeRecords" and let stackFetcher sort it out | |
| - var localRecord = $.extend({"arguments": initArgs}, fluid.censorKeys(options.componentRecord, ["type"]), localDynamic); | |
| + var localRecord = $.extend({}, fluid.censorKeys(options.componentRecord, ["type"]), localDynamic); | |
| + | |
| + var argMap = upDefaults.argumentMap; | |
| + var findKeys = Object.keys(argMap).concat(["type"]); | |
| - fluid.each(argMap, function (index, name) { | |
| - // this is incorrect anyway! What if the supplied arguments were not in the same order as the target argmap, | |
| - // which was obtained from the target defaults | |
| - if (initArgs.length > 0) { | |
| - localRecord[name] = localRecord["arguments"][index]; | |
| - } | |
| - if (demandspec[name] !== undefined && localRecord[name] === undefined) { | |
| - localRecord[name] = demandspec[name]; | |
| - } | |
| - if (name !== "options") { | |
| - for (var i = 0; i < distributions.length; ++ i) { // Apply non-options material from distributions (FLUID-5013) | |
| - if (distributions[i][name] !== undefined) { | |
| - localRecord[name] = distributions[i][name]; | |
| - } | |
| + fluid.each(findKeys, function (name) { | |
| + for (var i = 0; i < distributions.length; ++ i) { // Apply non-options material from distributions (FLUID-5013) | |
| + if (distributions[i][name] !== undefined) { | |
| + localRecord[name] = distributions[i][name]; | |
| } | |
| } | |
| }); | |
| - var i; | |
| - for (i = 0; i < distributions.length; ++ i) { | |
| - if (distributions[i].type !== undefined) { | |
| - demandspec.funcName = distributions[i].type; | |
| - } | |
| - } | |
| + typeName = localRecord.type || typeName; | |
| + | |
| + delete localRecord.type; | |
| + delete localRecord.options; | |
| var mergeRecords = {distributions: distributions}; | |
| @@ -1189,66 +1226,26 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| // Deliberately put too many things here so they can be checked in expandComponentOptions (FLUID-4285) | |
| mergeRecords.subcomponentRecord = $.extend({}, options.componentRecord); | |
| } | |
| - var expandOptions = fluid.makeStackResolverOptions(parentThat, localRecord); | |
| - var pushBackSpec = function (backSpec) { | |
| - fluid.pushDemandSpec(mergeRecords.demands, backSpec.options, backSpec.mergeOptions); | |
| - }; | |
| var args = []; | |
| - if (demands) { | |
| - for (i = 0; i < demands.length; ++i) { | |
| - var arg = demands[i]; | |
| - // Weak detection since we cannot guarantee this material has not been copied | |
| - if (fluid.isMarker(arg) && arg.value === fluid.COMPONENT_OPTIONS.value) { | |
| - arg = "{options}"; | |
| - // Backwards compatibility for non-users of GRADES - last-ditch chance to correct the inference | |
| - if (inferMap) { | |
| - argMap = {options: i}; | |
| - } | |
| - } | |
| - if (typeof(arg) === "string") { | |
| - if (arg.charAt(0) === "@") { | |
| - var argpos = arg.substring(1); | |
| - arg = "{arguments}." + argpos; | |
| - } | |
| - } | |
| - demands[i] = arg; | |
| - if (!argMap || argMap.options !== i) { | |
| - // expand immediately if there can be no options or this is not the options | |
| - args[i] = fluid.expand(arg, expandOptions); | |
| - } | |
| - else { // It is the component options | |
| - if (options.passArgs) { | |
| - fluid.fail("Error invoking function " + demandspec.funcName + ": found component creator rather than free function"); | |
| - } | |
| - if (typeof(arg) === "object" && !arg.targetTypeName) { | |
| - arg.targetTypeName = demandspec.funcName; | |
| - } | |
| - mergeRecords.demands = []; | |
| - fluid.each((demandspec.backSpecs).reverse(), pushBackSpec); | |
| - fluid.pushDemandSpec(mergeRecords.demands, demandspec.options || arg, demandspec.mergeOptions); | |
| - if (initArgs.length > 0) { | |
| - mergeRecords.user = {options: localRecord.options}; | |
| - } | |
| - args[i] = {marker: fluid.EXPAND, | |
| - localRecord: localDynamic, | |
| - mergeRecords: mergeRecords, | |
| - instantiator: fluid.getInstantiator(parentThat), | |
| - parentThat: parentThat, | |
| - memberName: options.memberName}; | |
| - } | |
| - if (args[i] && fluid.isMarker(args[i].marker, fluid.EXPAND_NOW)) { | |
| - args[i] = fluid.expand(args[i].value, expandOptions); | |
| - } | |
| + fluid.each(argMap, function (index, name) { | |
| + var arg; | |
| + if (name === "options") { | |
| + arg = {marker: fluid.EXPAND, | |
| + localRecord: localDynamic, | |
| + mergeRecords: mergeRecords, | |
| + instantiator: fluid.getInstantiator(parentThat), | |
| + parentThat: parentThat, | |
| + memberName: options.memberName}; | |
| + } else { | |
| + var value = localRecord[name]; | |
| + arg = fluid.expandImmediate(value, parentThat, localRecord); | |
| } | |
| - } | |
| - else { | |
| - args = initArgs? initArgs : []; | |
| - } | |
| + args[index] = arg; | |
| + }); | |
| var togo = { | |
| args: args, | |
| - preExpand: demands, | |
| - funcName: demandspec.funcName | |
| + funcName: typeName | |
| }; | |
| return togo; | |
| }; | |
| @@ -1261,55 +1258,39 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| * @param name {String} the name of the component - the index of the options block which configures it as part of the | |
| * <code>components</code> section of its parent's options | |
| */ | |
| - // NB "directArgs" is now disused by the framework | |
| - | |
| - fluid.initDependent = function (that, name, directArgs) { | |
| + fluid.initDependent = function (that, name) { | |
| if (that[name]) { return; } // TODO: move this into strategy | |
| - directArgs = directArgs || []; | |
| var component = that.options.components[name]; | |
| fluid.pushActivity("initDependent", "instantiating dependent component with name \"%name\" with record %record as child of %parent", | |
| {name: name, record: component, parent: that}); | |
| var instance; | |
| - var instantiator = idToInstantiator[that.id]; | |
| + var instantiator = fluid.globalInstantiator; | |
| if (typeof(component) === "string") { | |
| - instance = fluid.expandOptions(component, that); | |
| - instantiator.recordKnownComponent(that, instance, name, false); | |
| + that[name] = fluid.inEvaluationMarker; | |
| + instance = fluid.expandImmediate(component, that); | |
| + if (instance) { | |
| + instantiator.recordKnownComponent(that, instance, name, false); | |
| + } else { | |
| + delete that[name]; | |
| + } | |
| } | |
| else if (component.type) { | |
| - var type = fluid.expandOptions(component.type, that); | |
| + var type = fluid.expandImmediate(component.type, that); | |
| if (!type) { | |
| fluid.fail("Error in subcomponent record: ", component.type, " could not be resolved to a type for component ", name, | |
| " of parent ", that); | |
| } | |
| - var invokeSpec = fluid.resolveDemands(that, [type, name], directArgs, | |
| - {componentRecord: component, memberName: name}); | |
| + var invokeSpec = fluid.assembleCreatorArguments(that, type, {componentRecord: component, memberName: name}); | |
| instance = fluid.initSubcomponentImpl(that, {type: invokeSpec.funcName}, invokeSpec.args); | |
| - // The existing instantiator record will be provisional, adjust it to take account of the true return | |
| - // TODO: Instantiator contents are generally extremely incomplete | |
| - var path = instantiator.composePath(instantiator.idToPath(that.id), name); | |
| - var existing = instantiator.pathToComponent[path]; | |
| - // This branch deals with the case where the component creator registered a component into "pathToComponent" | |
| - // that does not agree with the component which was the return value. We need to clear out "pathToComponent" but | |
| - // not shred the component since most of it is probably still valid | |
| - if (existing && existing !== instance) { | |
| - instantiator.clearComponent(that, name, existing); | |
| - } | |
| - if (instance && instance.typeName && instance.id && instance !== existing) { | |
| - instantiator.recordKnownComponent(that, instance, name, true); | |
| - } | |
| - instance.destroy = fluid.fabricateDestroyMethod(that, name, instantiator, instance); | |
| } | |
| else { | |
| fluid.fail("Unrecognised material in place of subcomponent " + name + " - no \"type\" field found"); | |
| } | |
| - that[name] = instance; | |
| - fluid.fireEvent(instance, "events.onAttach", [instance, name, that]); | |
| fluid.popActivity(); | |
| return instance; | |
| }; | |
| - // unsupported, non-API function | |
| fluid.bindDeferredComponent = function (that, componentName, component) { | |
| var events = fluid.makeArray(component.createOnEvent); | |
| fluid.each(events, function(eventName) { | |
| @@ -1322,16 +1303,14 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| fluid.pushActivity("initDeferred", "instantiating deferred component %componentName of parent %that due to event %eventName", | |
| {componentName: componentName, that: that, eventName: eventName}); | |
| if (that[componentName]) { | |
| - var instantiator = idToInstantiator[that.id]; | |
| - instantiator.clearComponent(that, componentName); | |
| + fluid.globalInstantiator.clearComponent(that, componentName); | |
| } | |
| fluid.initDependent(that, componentName); | |
| fluid.popActivity(); | |
| - }, null, null, component.priority); | |
| + }, null, component.priority); | |
| }); | |
| }; | |
| - // unsupported, non-API function | |
| fluid.priorityForComponent = function (component) { | |
| return component.priority? component.priority : | |
| (component.type === "fluid.typeFount" || fluid.hasGrade(fluid.defaults(component.type), "fluid.typeFount"))? | |
| @@ -1342,171 +1321,130 @@ var fluid_2_0 = fluid_2_0 || {}; | |
| fluid.pushActivity("initDependents", "instantiating dependent components for component %that", {that: that}); | |
| var shadow = fluid.shadowForComponent(that); | |
| shadow.memberStrategy.initter(); | |
| + shadow.invokerStrategy.initter(); | |
| + | |
| + fluid.getForComponent(that, "modelRelay"); | |
| + fluid.getForComponent(that, "model"); // trigger this as late as possible - but must be before components so that child component has model on its onCreate | |
| var options = that.options; | |
| var components = options.components || {}; | |
| - var componentSort = {}; | |
| + var componentSort = []; | |
| fluid.each(components, function (component, name) { | |
| if (!component.createOnEvent) { | |
| var priority = fluid.priorityForComponent(component); | |
| - componentSort[name] = [{key: name, priority: fluid.event.mapPriority(priority, 0)}]; | |
| + componentSort.push({namespace: name, priority: fluid.parsePriority(priority)}); | |
| } | |
| else { | |
| fluid.bindDeferredComponent(that, name, component); | |
| } | |
| }); | |
| - var componentList = fluid.event.sortListeners(componentSort); | |
| - fluid.each(componentList, function (entry) { | |
| - fluid.initDependent(that, entry.key); | |
| + fluid.sortByPriority(componentSort); | |
| + fluid.each(componentSort, function (entry) { | |
| + fluid.initDependent(that, entry.namespace); | |
| }); | |
| - shadow.invokerStrategy.initter(); | |
| fluid.popActivity(); | |
| }; | |
| - var dependentStore = {}; | |
| - | |
| - function searchDemands (demandingName, contextNames) { | |
| - var exist = dependentStore[demandingName] || []; | |
| -outer: for (var i = 0; i < exist.length; ++i) { | |
| - var rec = exist[i]; | |
| - for (var j = 0; j < contextNames.length; ++j) { | |
| - if (rec.contexts[j] !== contextNames[j]) { | |
| - continue outer; | |
| - } | |
| - } | |
| - return rec.spec; // jslint:ok | |
| - } | |
| - } | |
| - | |
| - var isDemandLogging = false; | |
| - fluid.setDemandLogging = function (set) { | |
| - isDemandLogging = set; | |
| - }; | |
| - | |
| - // unsupported, non-API function | |
| - fluid.isDemandLogging = function () { | |
| - return isDemandLogging && fluid.isLogging(); | |
| + | |
| + /** BEGIN NEXUS METHODS **/ | |
| + | |
| + /** Construct a component with the supplied options at the specified path in the component tree. The parent path of the location must already be a component. | |
| + * @param path {String|Array of String} Path where the new component is to be constructed, represented as a string or array of segments | |
| + * @param typeName {String} The principal type of the component (name of its creator function) | |
| + * @param options {Object} [optional] Options supplied to the component | |
| + * @param instantiator {Instantiator} [optional] The instantiator holding the component to be created - if blank, the global instantiator will be used | |
| + */ | |
| + fluid.construct = function (path, options, instantiator) { | |
| + var record = fluid.destroy(path, instantiator); | |
| + // TODO: We must construct a more principled scheme for designating child components than this - especially once options become immutable | |
| + fluid.set(record.parent, ["options", "components", record.memberName], { | |
| + type: options.type, | |
| + options: options | |
| + }); | |
| + return fluid.initDependent(record.parent, record.memberName); | |
| }; | |
| - fluid.demands = function (demandingName, contextName, spec) { | |
| - var contextNames = fluid.makeArray(contextName).sort(); | |
| - if (!spec) { | |
| - return searchDemands(demandingName, contextNames); | |
| - } | |
| - else if (spec.length) { | |
| - spec = {args: spec}; | |
| + /** Destroys a component held at the specified path. The parent path must represent a component, although the component itself may be nonexistent | |
| + * @param path {String|Array of String} Path where the new component is to be destroyed, represented as a string or array of segments | |
| + * @param instantiator {Instantiator} [optional] The instantiator holding the component to be destroyed - if blank, the global instantiator will be used | |
| + */ | |
| + fluid.destroy = function (path, instantiator) { | |
| + instantiator = instantiator || fluid.globalInstantiator; | |
| + var segs = fluid.model.parseToSegments(path, instantiator.parseEL, true); | |
| + if (segs.length === 0) { | |
| + fluid.fail("Cannot destroy the root component"); | |
| } | |
| - if (fluid.getCallerInfo && fluid.isDemandLogging()) { | |
| - var callerInfo = fluid.getCallerInfo(5); | |
| - if (callerInfo) { | |
| - spec.registeredFrom = callerInfo; | |
| - } | |
| + var memberName = segs.pop(), parentPath = instantiator.composeSegments.apply(null, segs); | |
| + var parent = instantiator.pathToComponent[parentPath]; | |
| + if (!parent) { | |
| + fluid.fail("Cannot modify component with nonexistent parent at path ", path); | |
| } | |
| - spec.demandId = fluid.allocateGuid(); | |
| - var exist = dependentStore[demandingName]; | |
| - if (!exist) { | |
| - exist = []; | |
| - dependentStore[demandingName] = exist; | |
| + if (parent[memberName]) { | |
| + parent[memberName].destroy(); | |
| } | |
| - exist.push({contexts: contextNames, spec: spec}); | |
| + return { | |
| + parent: parent, | |
| + memberName: memberName | |
| + }; | |
| }; | |
| - | |
| - // unsupported, non-API function | |
| - fluid.compareDemands = function (speca, specb) { | |
| - return specb.intersect - speca.intersect; | |
| + | |
| + /** END NEXUS METHODS **/ | |
| + | |
| + /** BEGIN IOC DEBUGGING METHODS **/ | |
| + fluid["debugger"] = function () { | |
| + /* jshint ignore:start */ | |
| + debugger; | |
| + /* jshint ignore:end */ | |
| }; | |
| - | |
| - // unsupported, non-API function | |
| - fluid.locateAllDemands = function (parentThat, demandingNames) { | |
| - var demandLogging = fluid.isDemandLogging(demandingNames); | |
| - if (demandLogging) { | |
| - fluid.log("Resolving demands for function names ", demandingNames, " in context of " + | |
| - (parentThat? "component " + parentThat.typeName : "no component")); | |
| - } | |
| - | |
| - var contextNames = {}; | |
| - var visited = []; | |
| - var instantiator = fluid.getInstantiator(parentThat); | |
| - var thatStack = instantiator.getFullStack(parentThat); | |
| - visitComponents(instantiator, thatStack, function (component, xname, path, xpath, depth) { | |
| - // NB - don't use shadow's cache here because we allow fewer names for demand resolution than for value resolution | |
| - contextNames[component.typeName] = depth; | |
| - var gradeNames = fluid.makeArray(fluid.get(component, ["options", "gradeNames"])); | |
| - fluid.each(gradeNames, function (gradeName) { | |
| - contextNames[gradeName] = depth; | |
| - }); | |
| - visited.push(component); | |
| - }); | |
| - if (demandLogging) { | |
| - fluid.log("Components in scope for resolution:\n" + fluid.dumpThatStack(visited, instantiator)); | |
| - } | |
| - var matches = []; | |
| - for (var i = 0; i < demandingNames.length; ++i) { | |
| - var rec = dependentStore[demandingNames[i]] || []; | |
| - for (var j = 0; j < rec.length; ++j) { | |
| - var spec = rec[j]; | |
| - var horizonLevel = spec.spec.horizon ? contextNames[spec.spec.horizon] : -1; | |
| - var record = {spec: spec, intersect: 0, uncess: 0}; | |
| - for (var k = 0; k < spec.contexts.length; ++k) { | |
| - var depth = contextNames[spec.contexts[k]]; | |
| - record[depth !== undefined && depth >= horizonLevel ? "intersect" : "uncess"] += 2; | |
| - } | |
| - if (spec.contexts.length === 0) { // allow weak priority for contextless matches | |
| - record.intersect++; | |
| - } | |
| - if (record.uncess === 0) { | |
| - matches.push(record); | |
| - } | |
| - } | |
| + | |
| + fluid.defaults("fluid.debuggingProbe", { | |
| + gradeNames: ["fluid.component"] | |
| + }); | |
| + | |
| + // probe looks like: | |
| + // target: {preview other}.listeners.eventName | |
| + // priority: first/last | |
| + // func: console.log/fluid.log/fluid.debugger | |
| + fluid.probeToDistribution = function (probe) { | |
| + var instantiator = fluid.globalInstantiator; | |
| + var parsed = fluid.parseContextReference(probe.target); | |
| + var segs = fluid.model.parseToSegments(parsed.path, instantiator.parseEL, true); | |
| + if (segs[0] !== "options") { | |
| + segs.unshift("options"); // compensate for this insanity until we have the great options flattening | |
| + } | |
| + var parsedPriority = fluid.parsePriority(probe.priority); | |
| + if (parsedPriority.constraint && !parsedPriority.constraint.target) { | |
| + parsedPriority.constraint.target = "authoring"; | |
| } | |
| - matches.sort(fluid.compareDemands); | |
| - return matches; | |
| - }; | |
| - | |
| - // unsupported, non-API function | |
| - fluid.locateDemands = function (parentThat, demandingNames) { | |
| - var matches = fluid.locateAllDemands(parentThat, demandingNames); | |
| - var demandspec = fluid.getMembers(matches, ["spec", "spec"]); | |
| - if (fluid.isDemandLogging(demandingNames)) { | |
| - if (demandspec.length) { | |
| - fluid.log("Located " + matches.length + " potential match" + (matches.length === 1? "" : "es") + ", selected best match with " + matches[0].intersect + | |
| - " matched context names: ", demandspec); | |
| - } | |
| - else { | |
| - fluid.log("No matches found for demands, using direct implementation"); | |
| + return { | |
| + target: "{/ " + parsed.context + "}." + instantiator.composeSegments.apply(null, segs), | |
| + record: { | |
| + func: probe.func, | |
| + funcName: probe.funcName, | |
| + args: probe.args, | |
| + priority: fluid.renderPriority(parsedPriority) | |
| } | |
| - } | |
| - return demandspec; | |
| + }; | |
| }; | |
| - | |
| - /** Determine the appropriate demand specification held in the fluid.demands environment | |
| - * relative the supplied component position for the function name(s) funcNames. | |
| - */ | |
| - // unsupported, non-API function | |
| - fluid.determineDemands = function (parentThat, funcNames) { | |
| - funcNames = fluid.makeArray(funcNames); | |
| - var newFuncName = funcNames[0]; | |
| - var demandspec = fluid.locateDemands(parentThat, funcNames); | |
| - if (demandspec.length && demandspec[0].funcName) { | |
| - newFuncName = demandspec[0].funcName; | |
| - } | |
| - | |
| - return $.extend(true, {funcName: newFuncName, | |
| - args: demandspec[0] ? fluid.makeArray(demandspec[0].args) : [] | |
| - }, | |
| - { backSpecs: demandspec.slice(1) }, // Fix for FLUID-5126 | |
| - fluid.censorKeys(demandspec[0], ["funcName", "args"])); | |
| + | |
| + fluid.registerProbes = function (probes) { | |
| + var probeDistribution = fluid.transform(probes, fluid.probeToDistribution); | |
| + var memberName = "fluid_debuggingProbe_" + fluid.allocateGuid(); | |
| + fluid.construct([memberName], { | |
| + type: "fluid.debuggingProbe", | |
| + distributeOptions: probeDistribution | |
| + }); | |
| + return memberName; | |
| }; | |
| - // "options" includes - passArgs, componentRecord, memberName (latter two from initDependent route) | |
| - // unsupported, non-API function | |
| - fluid.resolveDemands = function (parentThat, funcNames, initArgs, options) { | |
| - var demandspec = fluid.determineDemands(parentThat, funcNames); | |
| - return fluid.embodyDemands(parentThat, demandspec, initArgs, options); | |
| + | |
| + fluid.deregisterProbes = function (probeName) { | |
| + fluid.destroy([probeName]); | |
| }; | |
| + | |
| + /** END IOC DEBUGGING METHODS **/ | |
| - // unsupported, non-API function | |
| fluid.thisistToApplicable = function (record, recthis, that) { | |
| return { | |
| apply: function (noThis, args) { | |
| @@ -1540,7 +1478,6 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| }; | |
| // Convert "exotic records" into an applicable form ("this/method" for FLUID-4878 or "changePath" for FLUID-3674) | |
| - // unsupported, non-API function | |
| fluid.recordToApplicable = function (record, that) { | |
| if (record.changePath) { | |
| return fluid.changeToApplicable(record, that); | |
| @@ -1551,109 +1488,47 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| } | |
| return record.method ? fluid.thisistToApplicable(record, recthis, that) : null; | |
| }; | |
| - | |
| - // TODO: make a *slightly* more performant version of fluid.invoke that perhaps caches the demands | |
| - // after the first successful invocation | |
| - fluid.invoke = function (functionName, args, that, environment) { | |
| - fluid.pushActivity("invokeFunc", "invoking function with name \"%functionName\" from component %that", {functionName: functionName, that: that}); | |
| - var invokeSpec = fluid.resolveDemands(that, functionName, fluid.makeArray(args), {passArgs: true}); | |
| - var togo = fluid.invokeGlobalFunction(invokeSpec.funcName, invokeSpec.args, environment); | |
| - fluid.popActivity(); | |
| - return togo; | |
| - }; | |
| - | |
| - /** Make a function which performs only "static redispatch" of the supplied function name - | |
| - * that is, taking only account of the contents of the "static environment". Since the static | |
| - * environment is assumed to be constant, the dispatch of the call will be evaluated at the | |
| - * time this call is made, as an optimisation. | |
| - */ | |
| - // unsupported, non-API function | |
| - fluid.makeFreeInvoker = function (functionName, environment) { | |
| - var demandSpec = fluid.determineDemands(null, functionName); | |
| - return function () { | |
| - var invokeSpec = fluid.embodyDemands(null, demandSpec, fluid.makeArray(arguments), {passArgs: true}); | |
| - return fluid.invokeGlobalFunction(invokeSpec.funcName, invokeSpec.args, environment); | |
| - }; | |
| - }; | |
| - | |
| - var argPrefix = "{arguments}."; | |
| - | |
| - fluid.parseInteger = function (string) { | |
| - return isFinite(string) && ((string % 1) === 0) ? Number(string) : NaN; | |
| - }; | |
| - | |
| - fluid.makeFastInvoker = function (invokeSpec, func) { | |
| - var argMap; | |
| - if (invokeSpec.preExpand) { | |
| - argMap = {}; | |
| - for (var i = 0; i < invokeSpec.preExpand.length; ++ i) { | |
| - var value = invokeSpec.preExpand[i]; | |
| - if (typeof(value) === "string") { | |
| - if (value.indexOf("}.model") !== -1) { | |
| - return {noFast: true}; | |
| - } | |
| - if (value === "{arguments}") { | |
| - argMap[i] = "*"; | |
| - } else if (value.indexOf(argPrefix) === 0) { | |
| - var argIndex = fluid.parseInteger(value.substring(argPrefix.length)); | |
| - if (isNaN(argIndex)) { | |
| - return {noFast: true}; | |
| - } | |
| - else { | |
| - argMap[i] = argIndex; // target arg pos = original arg pos | |
| - } | |
| - } | |
| - } | |
| - } | |
| + | |
| + fluid.getGlobalValueNonComponent = function (funcName, context) { // TODO: Guard this in listeners as well | |
| + var defaults = fluid.defaults(funcName); | |
| + if (defaults && fluid.hasGrade(defaults, "fluid.component")) { | |
| + fluid.fail("Error in function specification - cannot invoke function " + funcName + " in the context of " + context + ": component creator functions can only be used as subcomponents"); | |
| } | |
| - var outArgs = invokeSpec.args; | |
| - var invoke = argMap ? function invoke(args) { | |
| - for (var i in argMap) { | |
| - outArgs[i] = argMap[i] === "*" ? args : args[argMap[i]]; | |
| - } | |
| - return func.apply(null, outArgs); | |
| - } : function invoke (args) { | |
| - return func.apply(null, args); | |
| - }; | |
| - return { | |
| - invoke: invoke | |
| - }; | |
| + return fluid.getGlobalValue(funcName); | |
| }; | |
| - // unsupported, non-API function | |
| - fluid.makeInvoker = function (that, invokerec, name, environment) { | |
| - var functionName; | |
| + fluid.makeInvoker = function (that, invokerec, name) { | |
| if (typeof(invokerec) === "string") { | |
| - if (invokerec.charAt(0) === "{") { // shorthand case for direct function invokers (FLUID-4926) | |
| + if (fluid.isIoCReference(invokerec)) { // shorthand case for direct function invokers (FLUID-4926) | |
| invokerec = {func: invokerec}; | |
| } else { | |
| - functionName = invokerec; | |
| + invokerec = {funcName: invokerec}; | |
| } | |
| } | |
| - var demandspec = functionName? fluid.determineDemands(that, functionName) : invokerec; | |
| - var fastRec = {noFast: invokerec.dynamic}; | |
| + if (invokerec.args !== undefined && !fluid.isArrayable(invokerec.args)) { | |
| + invokerec.args = fluid.makeArray(invokerec.args); | |
| + } | |
| + var func = fluid.recordToApplicable(invokerec, that); | |
| + var invokePre = fluid.preExpand(invokerec.args); | |
| + var localRecord = {}; | |
| + var expandOptions = fluid.makeStackResolverOptions(that, localRecord, true); | |
| + func = func || (invokerec.funcName? fluid.getGlobalValueNonComponent(invokerec.funcName, "an invoker") : fluid.expandImmediate(invokerec.func, that)); | |
| + if (!func || !func.apply) { | |
| + fluid.fail("Error in invoker record: could not resolve members func, funcName or method to a function implementation - got " + func + " from ", invokerec); | |
| + } | |
| return function invokeInvoker () { | |
| if (fluid.defeatLogging === false) { | |
| fluid.pushActivity("invokeInvoker", "invoking invoker with name %name and record %record from component %that", {name: name, record: invokerec, that: that}); | |
| } | |
| - var togo; | |
| - if (fastRec.invoke) { | |
| - togo = fastRec.invoke(arguments); | |
| - } | |
| - else { | |
| - var func = fluid.recordToApplicable(invokerec, that); | |
| - var args = fluid.makeArray(arguments); | |
| - var invokeSpec = fluid.embodyDemands(that, demandspec, args, {passArgs: true}); | |
| - func = func || (invokeSpec.funcName? fluid.getGlobalValue(invokeSpec.funcName, environment) | |
| - : fluid.expandOptions(demandspec.func, that)); | |
| - if (!func || !func.apply) { | |
| - fluid.fail("Error in invoker record: could not resolve members func, funcName or method to a function implementation - got " + func + " from ", demandspec); | |
| - } | |
| - if (fastRec.noFast !== true) { | |
| - fastRec = fluid.makeFastInvoker(invokeSpec, func); | |
| - } | |
| - togo = func.apply(null, invokeSpec.args); | |
| + var togo, finalArgs; | |
| + localRecord["arguments"] = arguments; | |
| + if (invokerec.args === undefined) { | |
| + finalArgs = arguments; | |
| + } else { | |
| + fluid.expandImmediateImpl(invokePre, expandOptions); | |
| + finalArgs = invokePre.source; | |
| } | |
| + togo = func.apply(null, finalArgs); | |
| if (fluid.defeatLogging === false) { | |
| fluid.popActivity(); | |
| } | |
| @@ -1661,7 +1536,6 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| }; | |
| }; | |
| - // unsupported, non-API function | |
| // weird higher-order function so that we can staightforwardly dispatch original args back onto listener | |
| fluid.event.makeTrackedListenerAdder = function (source) { | |
| var shadow = fluid.shadowForComponent(source); | |
| @@ -1674,7 +1548,6 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| }; | |
| }; | |
| - // unsupported, non-API function | |
| fluid.event.listenerEngine = function (eventSpec, callback, adder) { | |
| var argstruc = {}; | |
| function checkFire() { | |
| @@ -1697,20 +1570,20 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| }); | |
| }; | |
| - // unsupported, non-API function | |
| fluid.event.dispatchListener = function (that, listener, eventName, eventSpec, indirectArgs) { | |
| var togo = function () { | |
| fluid.pushActivity("dispatchListener", "firing to listener to event named %eventName of component %that", | |
| {eventName: eventName, that: that}); | |
| - | |
| - var args = indirectArgs? arguments[0] : fluid.makeArray(arguments); | |
| - var demandspec = fluid.determineDemands(that, eventName); // TODO: This name may contain a namespace | |
| - if (demandspec.args.length === 0 && eventSpec.args) { | |
| - demandspec.args = eventSpec.args; | |
| + | |
| + var args = indirectArgs ? arguments[0] : fluid.makeArray(arguments); | |
| + if (eventSpec.args !== undefined) { | |
| + if (!fluid.isArrayable(eventSpec.args)) { | |
| + eventSpec.args = fluid.makeArray(eventSpec.args); | |
| + } | |
| + args = fluid.expandImmediate(eventSpec.args, that, {"arguments": args}); | |
| } | |
| - // TODO: create a "fast path" here as for invokers. Eliminate redundancy with invoker code | |
| - var resolved = fluid.embodyDemands(that, demandspec, args, {passArgs: true}); | |
| - var togo = fluid.event.invokeListener(listener, resolved.args); | |
| + var togo = fluid.event.invokeListener(listener, args); | |
| + | |
| fluid.popActivity(); | |
| return togo; | |
| }; | |
| @@ -1718,7 +1591,6 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| return togo; | |
| }; | |
| - // unsupported, non-API function | |
| fluid.event.resolveSoftNamespace = function (key) { | |
| if (typeof(key) !== "string") { | |
| return null; | |
| @@ -1728,7 +1600,6 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| } | |
| }; | |
| - // unsupported, non-API function | |
| fluid.event.resolveListenerRecord = function (lisrec, that, eventName, namespace, standard) { | |
| var badRec = function (record, extra) { | |
| fluid.fail("Error in listener record - could not resolve reference ", record, " to a listener or firer. " + | |
| @@ -1778,7 +1649,6 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| return togo; | |
| }; | |
| - // unsupported, non-API function | |
| fluid.event.expandOneEvent = function (that, event) { | |
| var origin; | |
| if (typeof(event) === "string" && event.charAt(0) !== "{") { | |
| @@ -1794,7 +1664,6 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| return origin; | |
| }; | |
| - // unsupported, non-API function | |
| fluid.event.expandEvents = function (that, event) { | |
| return typeof(event) === "string" ? | |
| fluid.event.expandOneEvent(that, event) : | |
| @@ -1803,7 +1672,6 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| }); | |
| }; | |
| - // unsupported, non-API function | |
| fluid.event.resolveEvent = function (that, eventName, eventSpec) { | |
| fluid.pushActivity("resolveEvent", "resolving event with name %eventName attached to component %that", | |
| {eventName: eventName, that: that}); | |
| @@ -1811,12 +1679,12 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| if (typeof(eventSpec) === "string") { | |
| eventSpec = {event: eventSpec}; | |
| } | |
| - var event = eventSpec.event || eventSpec.events; | |
| + var event = eventSpec.typeName === "fluid.event.firer" ? eventSpec : eventSpec.event || eventSpec.events; | |
| if (!event) { | |
| fluid.fail("Event specification for event with name " + eventName + " does not include a base event specification: ", eventSpec); | |
| } | |
| - var origin = fluid.event.expandEvents(that, event); | |
| + var origin = event.typeName === "fluid.event.firer" ? event : fluid.event.expandEvents(that, event); | |
| var isMultiple = origin.typeName !== "fluid.event.firer"; | |
| var isComposite = eventSpec.args || isMultiple; | |
| @@ -1843,9 +1711,9 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| fluid.popActivity(); | |
| return togo; | |
| }; | |
| - firer.addListener = function (listener, namespace, predicate, priority, softNamespace) { | |
| + firer.addListener = function (listener, namespace, priority, predicate, softNamespace) { | |
| var dispatcher = fluid.event.dispatchListener(that, listener, eventName, eventSpec); | |
| - adder(origin).addListener(dispatcher, namespace, predicate, priority, softNamespace); | |
| + adder(origin).addListener(dispatcher, namespace, priority, predicate, softNamespace); | |
| }; | |
| firer.removeListener = function (listener) { | |
| origin.removeListener(listener); | |
| @@ -1856,27 +1724,24 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| }; | |
| /** BEGIN unofficial IoC material **/ | |
| - // Although the following three functions are unsupported and not part of the IoC | |
| - // implementation proper, they are still used in the renderer | |
| - // expander as well as in some old-style tests and various places in CSpace. | |
| + // The following three functions are unsupported ane only used in the renderer expander. | |
| + // The material they produce is no longer recognised for component resolution. | |
| - // unsupported, non-API function | |
| fluid.withEnvironment = function (envAdd, func, root) { | |
| root = root || fluid.globalThreadLocal(); | |
| - return fluid.tryCatch(function() { | |
| + try { | |
| for (var key in envAdd) { | |
| root[key] = envAdd[key]; | |
| } | |
| $.extend(root, envAdd); | |
| return func(); | |
| - }, null, function() { | |
| - for (var key in envAdd) { // jslint:ok duplicate "value" | |
| + } finally { | |
| + for (var key in envAdd) { /* jshint ignore:line */ /* duplicate "key" */ | |
| delete root[key]; // TODO: users may want a recursive "scoping" model | |
| } | |
| - }); | |
| + } | |
| }; | |
| - // unsupported, NON-API function | |
| fluid.fetchContextReference = function (parsed, directModel, env, elResolver, externalFetcher) { | |
| // The "elResolver" is a hack to make certain common idioms in protoTrees work correctly, where a contextualised EL | |
| // path actually resolves onto a further EL reference rather than directly onto a value target | |
| @@ -1891,7 +1756,6 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| return parsed.noDereference? parsed.path : fluid.get(base, parsed.path); | |
| }; | |
| - // unsupported, non-API function | |
| fluid.makeEnvironmentFetcher = function (directModel, elResolver, envGetter, externalFetcher) { | |
| envGetter = envGetter || fluid.globalThreadLocal; | |
| return function(parsed) { | |
| @@ -1901,14 +1765,14 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| }; | |
| /** END of unofficial IoC material **/ | |
| + | |
| + /* Compact expansion machinery - for short form invoker and expander references such as @expand:func(arg) and func(arg) */ | |
| - // unsupported, non-API function | |
| fluid.coerceToPrimitive = function (string) { | |
| return string === "false" ? false : (string === "true" ? true : | |
| (isFinite(string) ? Number(string) : string)); | |
| }; | |
| - // unsupported, non-API function | |
| fluid.compactStringToRec = function (string, type) { | |
| var openPos = string.indexOf("("); | |
| var closePos = string.indexOf(")"); | |
| @@ -1922,10 +1786,6 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| var togo = { | |
| args: args | |
| }; | |
| - if (type === "invoker" && prefix.charAt(openPos - 1) === "!") { | |
| - prefix = string.substring(0, openPos - 1); | |
| - togo.dynamic = true; | |
| - } | |
| togo[prefix.charAt(0) === "{" ? "func" : "funcName"] = prefix; | |
| return togo; | |
| } | |
| @@ -1936,7 +1796,7 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| }; | |
| fluid.expandPrefix = "@expand:"; | |
| - // unsupported, non-API function | |
| + | |
| fluid.expandCompactString = function (string, active) { | |
| var rec = string; | |
| if (string.indexOf(fluid.expandPrefix) === 0) { | |
| @@ -1960,8 +1820,8 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| invokers: "invoker" | |
| }, singularPenRecord); | |
| - // unsupported, non-API function | |
| fluid.expandCompactRec = function (segs, target, source, userOptions) { | |
| + fluid.guardCircularExpansion(segs, segs.length); | |
| var pen = segs.length > 0 ? segs[segs.length - 1] : ""; | |
| var active = singularRecord[pen]; | |
| if (!active && segs.length > 1) { | |
| @@ -1983,14 +1843,18 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| }); | |
| }; | |
| - // unsupported, non-API function | |
| fluid.expandCompact = function (options, userOptions) { | |
| var togo = {}; | |
| fluid.expandCompactRec([], togo, options, userOptions); | |
| return togo; | |
| }; | |
| + | |
| + /** End compact record expansion machinery **/ | |
| + | |
| + fluid.isIoCReference = function (ref) { | |
| + return typeof(ref) === "string" && ref.charAt(0) === "{" && ref.indexOf("}") > 0; | |
| + }; | |
| - // unsupported, non-API function | |
| fluid.extractEL = function (string, options) { | |
| if (options.ELstyle === "ALL") { | |
| return string; | |
| @@ -2009,10 +1873,9 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| } | |
| }; | |
| - // unsupported, non-API function | |
| fluid.extractELWithContext = function (string, options) { | |
| var EL = fluid.extractEL(string, options); | |
| - if (EL && EL.charAt(0) === "{" && EL.indexOf("}") > 0) { | |
| + if (fluid.isIoCReference(EL)) { | |
| return fluid.parseContextReference(EL); | |
| } | |
| return EL? {path: EL} : EL; | |
| @@ -2036,8 +1899,8 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| fluid.renderContextReference = function (parsed) { | |
| return "{" + parsed.context + "}" + (parsed.path ? "." + parsed.path : ""); | |
| }; | |
| - | |
| - // unsupported, non-API function | |
| + | |
| + // TODO: Once we eliminate expandSource, all of this tree of functions can be hived off to RendererUtilities | |
| fluid.resolveContextValue = function (string, options) { | |
| function fetch(parsed) { | |
| fluid.pushActivity("resolveContextValue", "resolving context value %string", {string: string}); | |
| @@ -2047,7 +1910,7 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| return togo; | |
| } | |
| var parsed; | |
| - if (options.bareContextRefs && string.charAt(0) === "{" && string.indexOf("}") > 0) { | |
| + if (options.bareContextRefs && fluid.isIoCReference(string)) { | |
| parsed = fluid.parseContextReference(string); | |
| return fetch(parsed); | |
| } | |
| @@ -2083,23 +1946,15 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| return string; | |
| }; | |
| - // unsupported, NON-API function | |
| - fluid.expandExpander = function (target, source, options) { | |
| - var expander = fluid.getGlobalValue(source.expander.type || "fluid.deferredInvokeCall"); | |
| - if (expander) { | |
| - return expander.call(null, target, source, options); | |
| - } | |
| - }; | |
| - | |
| // This function appears somewhat reusable, but not entirely - it probably needs to be packaged | |
| // along with the particular "strategy". Very similar to the old "filter"... the "outer driver" needs | |
| // to execute it to get the first recursion going at top level. This was one of the most odd results | |
| // of the reorganisation, since the "old work" seemed much more naturally expressed in terms of values | |
| // and what happened to them. The "new work" is expressed in terms of paths and how to move amongst them. | |
| - fluid.fetchExpandChildren = function (target, i, segs, source, mergePolicy, miniWorld, options) { | |
| - if (source.expander /* && source.expander.type */) { // possible expander at top level | |
| + fluid.fetchExpandChildren = function (target, i, segs, source, mergePolicy, options) { | |
| + if (source.expander) { // possible expander at top level | |
| var expanded = fluid.expandExpander(target, source, options); | |
| - if (options.freeRoot || fluid.isPrimitive(expanded) || fluid.isDOMish(expanded) || !fluid.isPlainObject(expanded) || (fluid.isArrayable(expanded) ^ fluid.isArrayable(target))) { | |
| + if (fluid.isPrimitive(expanded) || fluid.isDOMish(expanded) || !fluid.isPlainObject(expanded) || (fluid.isArrayable(expanded) ^ fluid.isArrayable(target))) { | |
| return expanded; | |
| } | |
| else { // make an attempt to preserve the root reference if possible | |
| @@ -2117,7 +1972,9 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| } | |
| else if (key !== "expander") { | |
| segs[i] = key; | |
| - options.strategy(target, key, i + 1, segs, source, mergePolicy, miniWorld); | |
| + if (fluid.getImmediate(options.exceptions, segs, i) !== true) { | |
| + options.strategy(target, key, i + 1, segs, source, mergePolicy); | |
| + } | |
| } | |
| }); | |
| return target; | |
| @@ -2134,14 +1991,12 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| return source; | |
| } | |
| - // unsupported, NON-API function | |
| - fluid.isUnexpandable = function (source) { | |
| + fluid.isUnexpandable = function (source) { // slightly more efficient compound of fluid.isCopyable and fluid.isComponent - review performance | |
| return fluid.isPrimitive(source) || fluid.isComponent(source) || source.nodeType !== undefined || source.jquery || !fluid.isPlainObject(source); | |
| }; | |
| - // unsupported, NON-API function | |
| - fluid.expandSource = function (options, target, i, segs, deliverer, source, policy, miniWorld, recurse) { | |
| - var expanded, isTrunk, isLate; | |
| + fluid.expandSource = function (options, target, i, segs, deliverer, source, policy, recurse) { | |
| + var expanded, isTrunk; | |
| var thisPolicy = fluid.derefMergePolicy(policy); | |
| if (typeof (source) === "string" && !thisPolicy.noexpand) { | |
| if (!options.defaultEL || source.charAt(0) === "{") { // hard-code this for performance | |
| @@ -2159,40 +2014,34 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| expanded = fluid.expandExpander(deliverer, source, options); | |
| } | |
| else { | |
| - if (thisPolicy.preserve) { | |
| - expanded = source; | |
| - isLate = true; | |
| - } | |
| - else { | |
| - expanded = fluid.freshContainer(source); | |
| - } | |
| + expanded = fluid.freshContainer(source); | |
| isTrunk = true; | |
| } | |
| - if (!isLate && expanded !== fluid.NO_VALUE) { | |
| + if (expanded !== fluid.NO_VALUE) { | |
| deliverer(expanded); | |
| } | |
| if (isTrunk) { | |
| - recurse(expanded, source, i, segs, policy, miniWorld || isLate); | |
| - } | |
| - if (isLate && expanded !== fluid.NO_VALUE) { | |
| - deliverer(expanded); | |
| + recurse(expanded, source, i, segs, policy); | |
| } | |
| return expanded; | |
| }; | |
| + | |
| + fluid.guardCircularExpansion = function (segs, i) { | |
| + if (i > fluid.strategyRecursionBailout) { | |
| + fluid.fail("Overflow/circularity in options expansion, current path is ", segs, " at depth " , i, " - please ensure options are not circularly connected, or protect from expansion using the \"noexpand\" policy or expander"); | |
| + } | |
| + }; | |
| - // unsupported, NON-API function | |
| fluid.makeExpandStrategy = function (options) { | |
| - var recurse = function (target, source, i, segs, policy, miniWorld) { | |
| - return fluid.fetchExpandChildren(target, i || 0, segs || [], source, policy, miniWorld, options); | |
| + var recurse = function (target, source, i, segs, policy) { | |
| + return fluid.fetchExpandChildren(target, i || 0, segs || [], source, policy, options); | |
| }; | |
| - var strategy = function (target, name, i, segs, source, policy, miniWorld) { | |
| - if (i > fluid.strategyRecursionBailout) { | |
| - fluid.fail("Overflow/circularity in options expansion, current path is ", segs, " at depth " , i, " - please ensure options are not circularly connected, or protect from expansion using the \"noexpand\" policy or expander"); | |
| - } | |
| + var strategy = function (target, name, i, segs, source, policy) { | |
| + fluid.guardCircularExpansion(segs, i); | |
| if (!target) { | |
| return; | |
| } | |
| - if (!miniWorld && target.hasOwnProperty(name)) { // bail out if our work has already been done | |
| + if (target.hasOwnProperty(name)) { // bail out if our work has already been done | |
| return target[name]; | |
| } | |
| if (source === undefined) { // recover our state in case this is an external entry point | |
| @@ -2204,7 +2053,7 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| function deliverer(value) { | |
| target[name] = value; | |
| } | |
| - return fluid.expandSource(options, target, i, segs, deliverer, thisSource, thisPolicy, miniWorld, recurse); | |
| + return fluid.expandSource(options, target, i, segs, deliverer, thisSource, thisPolicy, recurse); | |
| }; | |
| options.recurse = recurse; | |
| options.strategy = strategy; | |
| @@ -2217,7 +2066,6 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| target: fluid.inCreationMarker | |
| }); | |
| - // unsupported, NON-API function | |
| fluid.makeExpandOptions = function (source, options) { | |
| options = $.extend({}, fluid.rawDefaults("fluid.makeExpandOptions"), options); | |
| options.defaultEL = options.ELStyle === "${}" && options.bareContextRefs; // optimisation to help expander | |
| @@ -2230,7 +2078,7 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| options.sourceStrategy = options.sourceStrategy || fluid.concreteTrundler; | |
| fluid.makeExpandStrategy(options); | |
| options.initter = function () { | |
| - options.target = fluid.fetchExpandChildren(options.target, 0, [], options.source, options.mergePolicy, false, options); | |
| + options.target = fluid.fetchExpandChildren(options.target, 0, [], options.source, options.mergePolicy, options); | |
| }; | |
| } | |
| else { // these init immediately since we must deliver a valid root target | |
| @@ -2246,47 +2094,127 @@ outer: for (var i = 0; i < exist.length; ++i) { | |
| return options; | |
| }; | |
| + // supported, PUBLIC API function | |
| fluid.expand = function (source, options) { | |
| var expandOptions = fluid.makeExpandOptions(source, options); | |
| expandOptions.initter(); | |
| return expandOptions.target; | |
| }; | |
| + | |
| + fluid.preExpandRecurse = function (root, source, holder, member, rootSegs) { // on entry, holder[member] = source | |
| + fluid.guardCircularExpansion(rootSegs, rootSegs.length); | |
| + function pushExpander(expander) { | |
| + root.expanders.push({expander: expander, holder: holder, member: member}); | |
| + delete holder[member]; | |
| + } | |
| + if (fluid.isIoCReference(source)) { | |
| + var parsed = fluid.parseContextReference(source); | |
| + var segs = fluid.model.parseEL(parsed.path); | |
| + pushExpander({ | |
| + typeFunc: fluid.expander.fetch, | |
| + context: parsed.context, | |
| + segs: segs | |
| + }); | |
| + } else if (fluid.isPlainObject(source)) { | |
| + if (source.expander) { | |
| + source.expander.typeFunc = fluid.getGlobalValue(source.expander.type || "fluid.invokeFunc"); | |
| + pushExpander(source.expander); | |
| + } else { | |
| + fluid.each(source, function (value, key) { | |
| + rootSegs.push(key); | |
| + fluid.preExpandRecurse(root, value, source, key, rootSegs); | |
| + rootSegs.pop(); | |
| + }); | |
| + } | |
| + } | |
| + }; | |
| + | |
| + fluid.preExpand = function (source) { | |
| + var root = { | |
| + expanders: [], | |
| + source: fluid.isUnexpandable(source) ? source : fluid.copy(source) | |
| + }; | |
| + fluid.preExpandRecurse(root, root.source, root, "source", []); | |
| + return root; | |
| + }; | |
| + | |
| + // Main pathway for freestanding material that is not part of a component's options | |
| + fluid.expandImmediate = function (source, that, localRecord) { | |
| + var options = fluid.makeStackResolverOptions(that, localRecord, true); // TODO: ELstyle and target are now ignored | |
| + var root = fluid.preExpand(source); | |
| + fluid.expandImmediateImpl(root, options); | |
| + return root.source; | |
| + }; | |
| + | |
| + // High performance expander for situations such as invokers, listeners, where raw materials can be cached - consumes "root" structure produced by preExpand | |
| + fluid.expandImmediateImpl = function (root, options) { | |
| + var expanders = root.expanders; | |
| + for (var i = 0; i < expanders.length; ++ i) { | |
| + var expander = expanders[i]; | |
| + expander.holder[expander.member] = expander.expander.typeFunc(null, expander, options); | |
| + } | |
| + }; | |
| + | |
| + fluid.expandExpander = function (deliverer, source, options) { | |
| + var expander = fluid.getGlobalValue(source.expander.type || "fluid.invokeFunc"); | |
| + if (!expander) { | |
| + fluid.fail("Unknown expander with type " + source.expander.type); | |
| + } | |
| + return expander(deliverer, source, options); | |
| + }; | |
| fluid.registerNamespace("fluid.expander"); | |
| + | |
| + fluid.expander.fetch = function (deliverer, source, options) { | |
| + var localRecord = options.localRecord, context = source.expander.context, segs = source.expander.segs; | |
| + var inLocal = localRecord[context] !== undefined; | |
| + // somewhat hack to anticipate "fits" for FLUID-4925 - we assume that if THIS component is in construction, its reference target might be too | |
| + var component = inLocal ? localRecord[context] : fluid.resolveContext(context, options.contextThat, options.contextThat.lifecycleStatus === "constructed"); | |
| + if (component) { | |
| + var root = component; | |
| + if (inLocal || component.lifecycleStatus === "constructed") { | |
| + for (var i = 0; i < segs.length; ++ i) { | |
| + root = root ? root[segs[i]] : undefined; | |
| + } | |
| + } else if (component.lifecycleStatus !== "destroyed") { | |
| + root = fluid.getForComponent(component, segs); | |
| + } else { | |
| + fluid.fail("Cannot resolve path " + segs.join(".") + " into component ", component, " which has been destroyed"); | |
| + } | |
| + if (root === undefined && !inLocal) { // last-ditch attempt to get exotic EL value from component | |
| + root = fluid.getForComponent(component, segs); | |
| + } | |
| + return root; | |
| + } | |
| + }; | |
| - /** "light" expanders, starting with support functions for the so-called "deferredCall" expanders, | |
| - which make an arbitrary function call (after expanding arguments) and are then replaced in | |
| + /** "light" expanders, starting with the default expander invokeFunc, | |
| + which makes an arbitrary function call (after expanding arguments) and are then replaced in | |
| the configuration with the call results. These will probably be abolished and replaced with | |
| equivalent model transformation machinery **/ | |
| - fluid.expander.deferredCall = function (deliverer, source, options) { | |
| - var expander = source.expander; | |
| - var args = (!expander.args || fluid.isArrayable(expander.args))? expander.args : fluid.makeArray(expander.args); | |
| - args = options.recurse([], args); | |
| - return fluid.invokeGlobalFunction(expander.func, args); | |
| - }; | |
| - | |
| - fluid.deferredCall = fluid.expander.deferredCall; // put in top namespace for convenience | |
| - | |
| // This one is now positioned as the "universal expander" - default if no type supplied | |
| - fluid.deferredInvokeCall = function (deliverer, source, options) { | |
| + fluid.invokeFunc = function (deliverer, source, options) { | |
| var expander = source.expander; | |
| var args = fluid.makeArray(expander.args); | |
| - args = options.recurse([], args); // TODO: risk of double expansion here. embodyDemands will sometimes expand, sometimes not... | |
| + expander.args = args; // head off case where args is an EL reference which resolves to an array | |
| + if (options.recurse) { // only available in the path from fluid.expandOptions - this will be abolished in the end | |
| + args = options.recurse([], args); | |
| + } else { | |
| + expander = fluid.expandImmediate(expander, options.contextThat); | |
| + args = expander.args; | |
| + } | |
| var funcEntry = expander.func || expander.funcName; | |
| - var func = options.expandSource(funcEntry) || fluid.recordToApplicable(expander, options.contextThat); | |
| + var func = (options.expandSource ? options.expandSource(funcEntry) : funcEntry) || fluid.recordToApplicable(expander, options.contextThat); | |
| if (!func) { | |
| fluid.fail("Error in expander record - " + funcEntry + " could not be resolved to a function for component ", options.contextThat); | |
| } | |
| - return func.apply ? func.apply(null, args) : fluid.invoke(func, args, options.contextThat); | |
| + return func.apply ? func.apply(null, args) : fluid.invokeGlobalFunction(func, args); | |
| }; | |
| // The "noexpand" expander which simply unwraps one level of expansion and ceases. | |
| - fluid.expander.noexpand = function (deliverer, source) { | |
| + fluid.noexpand = function (deliverer, source) { | |
| return source.expander.value ? source.expander.value : source.expander.tree; | |
| }; | |
| - fluid.noexpand = fluid.expander.noexpand; // TODO: check naming and namespacing | |
| - | |
| - | |
| })(jQuery, fluid_2_0); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment