Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
...
Checking mergeability… Don't worry, you can still create the pull request.
  • 15 commits
  • 8 files changed
  • 0 commit comments
  • 4 contributors
Commits on Feb 29, 2012
@amb26 amb26 FLUID-4625: Fix to matchPath implementation which meets the goals of …
…receiving notifications for changes at higher paths
7602f5d
@amb26 amb26 FLUID-4626: New "heterogeneous instantiator stack" system replaces ol…
…d system with single instantiator entry with stack count, resolving problems in various multiple-instantiator scenarios
df9389b
Commits on Mar 05, 2012
@amb26 amb26 FLUID-4633: Incomplete work on the ChangeApplier ef7f237
Commits on Mar 08, 2012
@amb26 amb26 FLUID-4633: Incomplete changeApplier work e653ecd
@amb26 amb26 FLUID-4609: Added new fluid.getLoader() function which allows path-aw…
…are fluid loading to be based from user's directory rather than Fluid's. Also a similar facility for fluid.require which allows the module lookup path to be based from the user's position (usually higher up the directory tree than fluid). We now also export fluid's "Error" object so that it can be used for configuring stack traces in node.js
2b28337
Commits on Mar 09, 2012
@colinbdclark colinbdclark FLUID-4675: A testing stub for require() and fluid.require().
Thanks to Kasper for coding this with me.
a62a341
@yzen yzen Merge branch 'FLUID-4609'
* FLUID-4609:
  FLUID-4609: Added new fluid.getLoader() function which allows path-aware fluid loading to be based from user's directory rather than Fluid's. Also a similar facility for fluid.require which allows the module lookup path to be based from the user's position (usually higher up the directory tree than fluid). We now also export fluid's "Error" object so that it can be used for configuring stack traces in node.js
9be57fe
@yzen yzen Merge branch 'master' into FLUID-4675 27a8019
@yzen yzen Merge branch 'FLUID-4675'
* FLUID-4675:
  FLUID-4675: A testing stub for require() and fluid.require(). Thanks to Kasper for coding this with me.
651d4af
Commits on Mar 14, 2012
@amb26 amb26 FLUID-4633: Implementation and test cases for source tracking facilit…
…y in ChangeApplier. Note that this has required some upgrade in the underlying framework implementation of ThreadLocal - core facility moved into Fluid.js for use in DataBinding.js
9446c4c
@amb26 amb26 FLUID-4633: Reorganised threadLocal support to simplify renderer expa…
…nder, which was previously broken. Weakened support for withEnvironment, added comments for these "loosely supported methods". Linting fixes to DataBinding.js which has seemingly not been linted in years.
b91a923
Commits on Mar 21, 2012
@michelled michelled Merge remote-tracking branch 'amb26/FLUID-4625'
* amb26/FLUID-4625:
  FLUID-4625: Fix to matchPath implementation which meets the goals of receiving notifications for changes at higher paths
1ff854d
@michelled michelled Merge remote-tracking branch 'amb26/FLUID-4626'
* amb26/FLUID-4626:
  FLUID-4626: New "heterogeneous instantiator stack" system replaces old system with single instantiator entry with stack count, resolving problems in various multiple-instantiator scenarios
edc0401
@amb26 amb26 Merged conflict with FLUID-4626 changes which improved use of instant…
…iator stack

Merge remote-tracking branch 'origin/master' into FLUID-4633

Conflicts:
	src/webapp/framework/core/js/FluidIoC.js
de9e08a
Commits on Mar 22, 2012
@michelled michelled Merge remote-tracking branch 'amb26/FLUID-4633'
* amb26/FLUID-4633:
  FLUID-4633: Reorganised threadLocal support to simplify renderer expander, which was previously broken. Weakened support for withEnvironment, added comments for these "loosely supported methods". Linting fixes to DataBinding.js which has seemingly not been linted in years.
  FLUID-4633: Implementation and test cases for source tracking facility in ChangeApplier. Note that this has required some upgrade in the underlying framework implementation of ThreadLocal - core facility moved into Fluid.js for use in DataBinding.js
  FLUID-4633: Incomplete changeApplier work
  FLUID-4633: Incomplete work on the ChangeApplier
a610019
View
115 src/webapp/framework/core/js/DataBinding.js
@@ -16,7 +16,7 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
/*global fluid_1_5:true, jQuery*/
// JSLint options
-/*jslint white: true, funcinvoke: true, undef: true, newcap: true, nomen: true, regexp: true, bitwise: true, browser: true, forin: true, maxerr: 100, indent: 4 */
+/*jslint white: true, funcinvoke: true, continue: true, elsecatch: true, operator: true, jslintok:true, undef: true, newcap: true, regexp: true, bitwise: true, browser: true, forin: true, maxerr: 100, indent: 4 */
var fluid_1_5 = fluid_1_5 || {};
@@ -45,7 +45,7 @@ var fluid_1_5 = fluid_1_5 || {};
node = fluid.unwrap(node);
var key = node.name || node.id;
var record = fossils[key];
- return record ? record.EL: null;
+ return record ? record.EL : null;
};
fluid.findForm = function (node) {
@@ -65,9 +65,9 @@ var fluid_1_5 = fluid_1_5 || {};
node = node[0];
multiple = true;
}
- if ("input" !== node.nodeName.toLowerCase() || ! /radio|checkbox/.test(node.type)) {
+ if ("input" !== node.nodeName.toLowerCase() || !/radio|checkbox/.test(node.type)) {
// resist changes to contract of jQuery.val() in jQuery 1.5.1 (see FLUID-4113)
- return newValue === undefined? $(node).val() : $(node).val(newValue);
+ return newValue === undefined ? $(node).val() : $(node).val(newValue);
}
var name = node.name;
if (name === undefined) {
@@ -76,12 +76,10 @@ var fluid_1_5 = fluid_1_5 || {};
var elements;
if (multiple) {
elements = nodeIn;
- }
- else {
+ } else {
elements = node.ownerDocument.getElementsByName(name);
var scope = fluid.findForm(node);
- elements = $.grep(elements,
- function (element) {
+ elements = $.grep(elements, function (element) {
if (element.name !== name) {
return false;
}
@@ -99,8 +97,7 @@ var fluid_1_5 = fluid_1_5 || {};
this.checked = (newValue instanceof Array ?
$.inArray(this.value, newValue) !== -1 : newValue === this.value);
});
- }
- else { // this part jQuery will not do - extracting value from <input> array
+ } else { // this part jQuery will not do - extracting value from <input> array
var checked = $.map(elements, function (element) {
return element.checked ? element.value : null;
});
@@ -128,13 +125,12 @@ var fluid_1_5 = fluid_1_5 || {};
fluid.fail("No fossil discovered for name " + name + " in fossil record above " + fluid.dumpEl(node));
}
if (typeof(fossil.oldvalue) === "boolean") { // deal with the case of an "isolated checkbox"
- newValue = newValue[0] ? true: false;
+ newValue = newValue[0] ? true : false;
}
var EL = root.fossils[name].EL;
if (applier) {
applier.fireChangeRequest({path: EL, value: newValue, source: node.id});
- }
- else {
+ } else {
fluid.set(root.data, EL, newValue);
}
};
@@ -378,12 +374,12 @@ var fluid_1_5 = fluid_1_5 || {};
fluid.pathUtil.matchPath = function (spec, path) {
var togo = "";
while (true) {
- if (!spec || path === "") {
+ // FLUID-4625 - symmetry on spec and path is actually undesirable, but this
+ // quickly avoids at least missed notifications - improved (but slower)
+ // implementation should explode composite changes
+ if (!spec || !path) {
break;
}
- if (!path) {
- return null;
- }
var spechead = fluid.pathUtil.getHeadPath(spec);
var pathhead = fluid.pathUtil.getHeadPath(path);
// if we fail to match on a specific component, fail.
@@ -417,7 +413,7 @@ var fluid_1_5 = fluid_1_5 || {};
if (request.type === "ADD") {
fluid.clear(pen.root);
}
- $.extend(true, request.path === "" ? pen.root: pen.root[pen.last], request.value);
+ $.extend(true, request.path === "" ? pen.root : pen.root[pen.last], request.value);
}
else {
pen.root[pen.last] = request.value;
@@ -432,7 +428,44 @@ var fluid_1_5 = fluid_1_5 || {};
}
}
};
+
+ /** Add a listener to a ChangeApplier event that only acts in the case the event
+ * has not come from the specified source (typically ourself)
+ * @param modelEvent An model event held by a changeApplier (typically applier.modelChanged)
+ * @param path The path specification to listen to
+ * @param source The source value to exclude (direct equality used)
+ * @param func The listener to be notified of a change
+ * @param [eventName] - optional - the event name to be listened to - defaults to "modelChanged"
+ */
+ fluid.addSourceGuardedListener = function(applier, path, source, func, eventName) {
+ eventName = eventName || "modelChanged";
+ applier[eventName].addListener(path,
+ function() {
+ if (!applier.hasChangeSource(source)) {
+ func.apply(null, arguments);
+ }
+ });
+ };
+
+ /** Convenience method to fire a change event to a specified applier, including
+ * a supplied "source" identified (perhaps for use with addSourceGuardedListener)
+ */
+ fluid.fireSourcedChange = function (applier, path, value, source) {
+ applier.fireChangeRequest({
+ path: path,
+ value: value,
+ source: source
+ });
+ };
+ /** Dispatches a list of changes to the supplied applier */
+ fluid.requestChanges = function (applier, changes) {
+ for (var i = 0; i < changes.length; ++i) {
+ applier.fireChangeRequest(changes[i]);
+ }
+ };
+
+
// Utility shared between changeApplier and superApplier
function bindRequestChange(that) {
@@ -446,6 +479,24 @@ var fluid_1_5 = fluid_1_5 || {};
};
}
+ // Utility used for source tracking in changeApplier
+
+ function sourceWrapModelChanged(modelChanged, threadLocal) {
+ return function(changeRequest) {
+ var sources = threadLocal().sources;
+ var args = arguments;
+ var source = changeRequest.source || "";
+ fluid.tryCatch(function() {
+ if (sources[source] === undefined) {
+ sources[source] = 0;
+ }
+ ++sources[source];
+ modelChanged.apply(null, args);
+ }, null, function() {
+ --sources[source];
+ });
+ };
+ }
fluid.makeChangeApplier = function (model, options) {
options = options || {};
@@ -454,7 +505,11 @@ var fluid_1_5 = fluid_1_5 || {};
postGuards: fluid.event.getEventFirer(false, true, "postGuard event"),
modelChanged: fluid.event.getEventFirer(false, false, "modelChanged event")
};
+ var threadLocal = fluid.threadLocal(function() { return {sources: {}};});
var that = {
+ // For now, we don't use "id" to avoid confusing component detection which uses
+ // a simple algorithm looking for that field
+ changeid: fluid.allocateGuid(),
model: model
};
@@ -513,7 +568,7 @@ var fluid_1_5 = fluid_1_5 || {};
record.accumulate = [accum];
}
fireSpec.guids[guid] = record;
- var collection = transactional ? "transListeners": "listeners";
+ var collection = transactional ? "transListeners" : "listeners";
fireSpec[collection].push(record);
fireSpec.all.push(record);
}
@@ -610,14 +665,15 @@ var fluid_1_5 = fluid_1_5 || {};
}
};
+ that.fireChangeRequest = sourceWrapModelChanged(that.fireChangeRequest, threadLocal);
bindRequestChange(that);
function fireAgglomerated(eventName, formName, changes, args, accpos) {
var fireSpec = makeFireSpec();
- for (var i = 0; i < changes.length; ++ i) {
+ for (var i = 0; i < changes.length; ++i) {
prepareFireEvent(eventName, changes[i].path, fireSpec, changes[i]);
}
- for (var j = 0; j < fireSpec[formName].length; ++ j) {
+ for (var j = 0; j < fireSpec[formName].length; ++j) {
var spec = fireSpec[formName][j];
if (accpos) {
args[accpos] = spec.accumulate;
@@ -641,12 +697,13 @@ var fluid_1_5 = fluid_1_5 || {};
}
// the guard in the inner world is given a private applier to "fast track"
// and glob collateral changes it requires
- var internalApplier =
- {fireChangeRequest: function (changeRequest) {
+ var internalApplier = {
+ fireChangeRequest: function (changeRequest) {
preFireChangeRequest(changeRequest);
fluid.model.applyChangeRequest(newModel, changeRequest, options.resolverSetConfig);
changes.push(changeRequest);
- }};
+ }
+ };
bindRequestChange(internalApplier);
var ation = {
commit: function () {
@@ -687,11 +744,17 @@ var fluid_1_5 = fluid_1_5 || {};
}
}
};
+
+ ation.fireChangeRequest = sourceWrapModelChanged(ation.fireChangeRequest, threadLocal);
bindRequestChange(ation);
return ation;
};
+ that.hasChangeSource = function (source) {
+ return threadLocal().sources[source] > 0;
+ };
+
return that;
};
@@ -702,7 +765,7 @@ var fluid_1_5 = fluid_1_5 || {};
subAppliers.push({path: path, subApplier: subApplier});
};
that.fireChangeRequest = function (request) {
- for (var i = 0; i < subAppliers.length; ++ i) {
+ for (var i = 0; i < subAppliers.length; ++i) {
var path = subAppliers[i].path;
if (request.path.indexOf(path) === 0) {
var subpath = request.path.substring(path.length + 1);
@@ -719,7 +782,7 @@ var fluid_1_5 = fluid_1_5 || {};
fluid.attachModel = function (baseModel, path, model) {
var segs = fluid.model.parseEL(path);
- for (var i = 0; i < segs.length - 1; ++ i) {
+ for (var i = 0; i < segs.length - 1; ++i) {
var seg = segs[i];
var subModel = baseModel[seg];
if (!subModel) {
View
15 src/webapp/framework/core/js/Fluid.js
@@ -37,11 +37,26 @@ var fluid = fluid || fluid_1_5;
fluid.version = "Infusion 1.5";
+ // Export this for use in environments like node.js, where it is useful for
+ // configuring stack trace behaviour
+ fluid.Error = Error;
+
fluid.environment = {
fluid: fluid
};
+
var globalObject = window || {};
+ fluid.singleThreadLocal = function(initFunc) {
+ var value = initFunc();
+ return function() {
+ return value;
+ };
+ };
+
+ // Return to the old strategy of monkey-patching this, since this is a most frequently used function within IoC
+ fluid.threadLocal = fluid.singleThreadLocal;
+
var softFailure = [false];
// This function will be patched from FluidIoC.js in order to describe complex activities
View
162 src/webapp/framework/core/js/FluidIoC.js
@@ -129,7 +129,7 @@ var fluid_1_5 = fluid_1_5 || {};
// Return an array of objects describing the current activity
// unsupported, non-API function
fluid.describeActivity = function() {
- return fluid.threadLocal().activityStack || [];
+ return fluid.globalThreadLocal().activityStack || [];
};
// Execute the supplied function with the specified activity description pushed onto the stack
@@ -138,7 +138,7 @@ var fluid_1_5 = fluid_1_5 || {};
if (!message || fluid.notrycatch) {
return func();
}
- var root = fluid.threadLocal();
+ var root = fluid.globalThreadLocal();
if (!root.activityStack) {
root.activityStack = [];
}
@@ -179,6 +179,10 @@ var fluid_1_5 = fluid_1_5 || {};
var fetchStrategies = [fluid.model.funcResolverStrategy, makeGingerStrategy(instantiator, parentThat, thatStack)];
var fetcher = function(parsed) {
var context = parsed.context;
+ if (context === "instantiator") {
+ // special treatment for the current instantiator which used to be discovered as unique in threadLocal
+ return instantiator;
+ }
if (localRecord && localRecordExpected.test(context)) {
var fetched = fluid.get(localRecord[context], parsed.path);
return (context === "arguments" || expandOptions.direct)? fetched : {
@@ -227,15 +231,11 @@ var fluid_1_5 = fluid_1_5 || {};
},
idToPath: {},
pathToComponent: {},
- stackCount: 0,
nickName: "instantiator"
};
var that = fluid.typeTag("fluid.instantiator");
that = $.extend(that, preThat);
- that.stack = function(count) {
- return that.stackCount += count;
- };
that.getThatStack = function(component) {
var path = that.idToPath[component.id] || "";
var parsed = fluid.model.parseEL(path);
@@ -252,7 +252,7 @@ var fluid_1_5 = fluid_1_5 || {};
that.getEnvironmentalStack = function() {
var togo = [fluid.staticEnvironment];
if (!freeInstantiator) {
- togo.push(fluid.threadLocal());
+ togo.push(fluid.globalThreadLocal());
}
return togo;
};
@@ -693,7 +693,7 @@ outer: for (var i = 0; i < exist.length; ++i) {
var listener = fluid.expandOptions(record.listener, that);
if (!listener) {
fluid.fail("Error in listener record - could not resolve reference " + record.listener + " to a listener or firer. "
- + "Did you miss out \"events.\" when referring to an event firer?");
+ + "Did you miss out \"events.\" when referring to an event firer?");
}
if (listener.typeName === "fluid.event.firer") {
listener = listener.fire;
@@ -760,9 +760,10 @@ outer: for (var i = 0; i < exist.length; ++i) {
firer = {typeName: "fluid.event.firer"}; // jslint:ok - already defined
firer.fire = function () {
var outerArgs = fluid.makeArray(arguments);
- return fluid.applyInstantiator(instantiator, that, function () {
+ // TODO: this resolution should really be supplied for ALL events!
+ return fluid.withInstantiator(that, function () {
return origin.fire.apply(null, outerArgs);
- });
+ }, " firing synthetic event " + eventName, instantiator);
};
firer.addListener = function (listener, namespace, predicate, priority) {
var dispatcher = fluid.event.dispatchListener(instantiator, that, listener, eventName, eventSpec);
@@ -916,21 +917,58 @@ outer: for (var i = 0; i < exist.length; ++i) {
fluid.expandComponentOptions = fluid.wrapActivity(fluid.expandComponentOptions,
[" while expanding component options ", "arguments.1.value", " with record ", "arguments.1", " for component ", "arguments.2"]);
-
- fluid.applyInstantiator = function(userInstantiator, that, func, message) {
- var root = fluid.threadLocal();
- if (userInstantiator) {
- var existing = root["fluid.instantiator"];
- if (existing && existing !== userInstantiator) {
- fluid.fail("Error in applyInstantiator: user instantiator supplied with id " + userInstantiator.id
- + " which differs from that for currently active instantiation with id " + existing.id);
- }
- else {
- root["fluid.instantiator"] = userInstantiator;
- fluid.log("*** restored USER instantiator with id " + userInstantiator.id + " - STORED");
- }
+
+ // NON-API function
+ fluid.getInstantiators = function() {
+ var root = fluid.globalThreadLocal();
+ var ins = root["fluid.instantiator"];
+ if (!ins) {
+ ins = root["fluid.instantiator"] = [];
}
- return fluid.withInstantiator(that, func, message);
+ return ins;
+ };
+
+ // These two are the only functions which touch the instantiator stack
+ // NON-API function
+ // This function is stateful and MUST NOT be called by client code
+ fluid.withInstantiator = function(that, func, message, userInstantiator) {
+ var ins = fluid.getInstantiators();
+ var oldLength;
+ var instantiator = userInstantiator;
+
+ return fluid.pushActivity(function() {
+ return fluid.tryCatch(function() {
+ if (!instantiator) {
+ if (ins.length === 0) {
+ instantiator = fluid.instantiator();
+ fluid.log("Created new instantiator with id " + instantiator.id + " in order to operate on component " + (that? that.typeName : "[none]"));
+ }
+ else {
+ instantiator = ins[ins.length - 1];
+ }
+ }
+ ins.push(instantiator);
+ oldLength = ins.length;
+
+ if (that) {
+ instantiator.recordComponent(that);
+ }
+ //fluid.log("Instantiator stack +1 to " + instantiator.stackCount + " for " + typeName);
+ return func(instantiator);
+ }, null, function() {
+ if (ins.length !== oldLength) {
+ fluid.fail("Instantiator stack corrupted - old length " + oldLength + " new length " + ins.length);
+ }
+ if (ins[ins.length - 1] != instantiator) {
+ fluid.fail("Instantiator stack corrupted at stack top - old id " + instantiator.id + " new id " + ins[ins.length-1].id);
+ }
+ ins.length--;
+ //fluid.log("Instantiator stack -1 to " + instantiator.stackCount + " for " + typeName);
+ if (ins.length === 0) {
+ fluid.log("Cleared instantiators (last id " + instantiator.id + ") from threadLocal for end of " + (that? that.typeName : "[none]"));
+ }
+ });
+ }, message);
};
// The case without the instantiator is from the ginger strategy - this logic is still a little ragged
@@ -941,7 +979,7 @@ outer: for (var i = 0; i < exist.length; ++i) {
var component = that.options.components[name];
var instance; // escape to here for debugging purposes
- fluid.applyInstantiator(userInstantiator, that, function(instantiator) {
+ fluid.withInstantiator(that, function(instantiator) {
if (typeof(component) === "string") {
that[name] = fluid.expandOptions([component], that)[0]; // TODO: expose more sensible semantic for expandOptions
}
@@ -970,40 +1008,13 @@ outer: for (var i = 0; i < exist.length; ++i) {
else {
that[name] = component;
}
- }, [" while instantiating dependent component with name \"" + name + "\" with record ", component, " as child of ", that]);
+ }, [" while instantiating dependent component with name \"" + name + "\" with record ", component, " as child of ", that],
+ userInstantiator);
if (instance) {
fluid.log("Finished instantiation of component with name \"" + name + "\" and id " + instance.id + " as child of " + fluid.dumpThat(that));
}
};
- // NON-API function
- // This function is stateful and MUST NOT be called by client code
- fluid.withInstantiator = function(that, func, message) {
- var root = fluid.threadLocal();
- var instantiator = root["fluid.instantiator"];
- if (!instantiator) {
- instantiator = root["fluid.instantiator"] = fluid.instantiator();
- fluid.log("Created new instantiator with id " + instantiator.id + " in order to operate on component " + (that? that.typeName : "[none]"));
- }
- return fluid.pushActivity(function() {
- return fluid.tryCatch(function() {
- if (that) {
- instantiator.recordComponent(that);
- }
- instantiator.stack(1);
- //fluid.log("Instantiator stack +1 to " + instantiator.stackCount + " for " + typeName);
- return func(instantiator);
- }, null, function() {
- var count = instantiator.stack(-1);
- //fluid.log("Instantiator stack -1 to " + instantiator.stackCount + " for " + typeName);
- if (count === 0) {
- fluid.log("Clearing instantiator with id " + instantiator.id + " from threadLocal for end of " + (that? that.typeName : "[none]"));
- delete root["fluid.instantiator"];
- }
- });
- }, message);
- };
-
// unsupported, non-API function
fluid.bindDeferredComponent = function(that, componentName, component, instantiator) {
var events = fluid.makeArray(component.createOnEvent);
@@ -1060,54 +1071,39 @@ outer: for (var i = 0; i < exist.length; ++i) {
fluid.staticEnvironment.environmentClass = fluid.typeTag("fluid.browser");
- // fluid.environmentalRoot.environmentClass = fluid.typeTag("fluid.rhino");
+ fluid.globalThreadLocal = fluid.threadLocal(function() {
+ return fluid.typeTag("fluid.dynamicEnvironment");
+ });
- var singleThreadLocal = fluid.typeTag("fluid.dynamicEnvironment");
+ // Although the following two 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.
- fluid.singleThreadLocal = function() {
- return singleThreadLocal;
- };
-
- // Return to the old strategy of monkey-patching this, since this is a most frequently used function within IoC
- fluid.threadLocal = fluid.singleThreadLocal;
-
- function applyLocalChange(applier, type, path, value) {
- var change = {
- type: type,
- path: path,
- value: value
- };
- applier.fireChangeRequest(change);
- }
-
// unsupported, non-API function
- fluid.withEnvironment = function(envAdd, func, prefix) {
- prefix = prefix || "";
- var root = fluid.threadLocal();
- var applier = fluid.makeChangeApplier(root, {thin: true});
+ fluid.withEnvironment = function(envAdd, func, root) {
+ root = root || fluid.globalThreadLocal();
return fluid.tryCatch(function() {
for (var key in envAdd) {
- applyLocalChange(applier, "ADD", fluid.model.composePath(prefix, key), envAdd[key]);
+ root[key] = envAdd[key];
}
$.extend(root, envAdd);
return func();
}, null, function() {
for (var key in envAdd) { // jslint:ok duplicate "value"
- // TODO: This could be much better through i) refactoring the ChangeApplier so we could naturally use "rollback" semantics
- // and/or implementing this material using some form of "prototype chain"
- applyLocalChange(applier, "DELETE", fluid.model.composePath(prefix, key));
+ delete root[key]; // TODO: users may want a recursive "scoping" model
}
});
};
// unsupported, non-API function
- fluid.makeEnvironmentFetcher = function(prefix, directModel, elResolver) {
+ fluid.makeEnvironmentFetcher = function(directModel, elResolver, envGetter) {
+ envGetter = envGetter || fluid.globalThreadLocal;
return function(parsed) {
- var env = fluid.get(fluid.threadLocal(), prefix);
+ var env = envGetter();
return fluid.fetchContextReference(parsed, directModel, env, elResolver);
};
};
-
+
// unsupported, non-API function
fluid.extractEL = function(string, options) {
if (options.ELstyle === "ALL") {
View
27 src/webapp/framework/renderer/js/RendererUtilities.js
@@ -298,7 +298,8 @@ fluid_1_5 = fluid_1_5 || {};
fluid.renderer.repeat = function (options, container, key, config) {
fluid.expect("Repetition expander", ["controlledBy", "tree"], options);
- var path = fluid.extractContextualPath(options.controlledBy, {ELstyle: "ALL"}, fluid.threadLocal());
+ var env = config.threadLocal();
+ var path = fluid.extractContextualPath(options.controlledBy, {ELstyle: "ALL"}, env);
var list = fluid.get(config.model, path, config.resolverGetConfig);
var togo = {};
@@ -307,15 +308,17 @@ fluid_1_5 = fluid_1_5 || {};
}
var expanded = [];
fluid.each(list, function (element, i) {
- var EL = fluid.model.composePath(path, i);
- var envAdd = {};
+ var EL = fluid.model.composePath(path, i);
+ var envAdd = {};
if (options.pathAs) {
envAdd[options.pathAs] = "${" + EL + "}";
}
if (options.valueAs) {
envAdd[options.valueAs] = fluid.get(config.model, EL, config.resolverGetConfig);
}
- var expandrow = fluid.withEnvironment(envAdd, function () {return config.expander(options.tree); }, "rendererEnvironment");
+ var expandrow = fluid.withEnvironment(envAdd, function() {
+ return config.expander(options.tree);
+ }, env);
if (fluid.isArrayable(expandrow)) {
if (expandrow.length > 0) {
expanded.push({children: expandrow});
@@ -384,7 +387,7 @@ fluid_1_5 = fluid_1_5 || {};
return {
noDereference: parsed.path === "",
path: fluid.model.composePath(EL, parsed.path)
- };
+ };
}
}
return parsed;
@@ -415,11 +418,10 @@ fluid_1_5 = fluid_1_5 || {};
var options = $.extend({
ELstyle: "${}"
}, expandOptions); // shallow copy of options
-
- options.fetcher = fluid.makeEnvironmentFetcher("rendererEnvironment", options.model, fluid.transformContextPath);
+ var threadLocal; // rebound on every expansion at entry point
function fetchEL(string) {
- var env = fluid.threadLocal().rendererEnvironment;
+ var env = threadLocal();
return fluid.extractContextualPath(string, options, env);
}
@@ -493,6 +495,7 @@ fluid_1_5 = fluid_1_5 || {};
resolverSetConfig: options.resolverSetConfig,
expander: expandExternal,
expandLight: expandLight
+ //threadLocal: threadLocal
};
var expandLeaf = function (leaf, componentType) {
@@ -599,10 +602,12 @@ fluid_1_5 = fluid_1_5 || {};
};
return function(entry) {
- var initEnvironment = $.extend({}, options.envAdd);
- return fluid.withEnvironment({rendererEnvironment: initEnvironment}, function() {
- return expandEntry(entry);
+ threadLocal = fluid.threadLocal(function() {
+ return $.extend({}, options.envAdd);
});
+ options.fetcher = fluid.makeEnvironmentFetcher(options.model, fluid.transformContextPath, threadLocal);
+ expandConfig.threadLocal = threadLocal;
+ return expandEntry(entry);
};
};
View
18 src/webapp/module/fluid.js
@@ -1,5 +1,5 @@
/*
-Copyright 2012 OCAD University
+Copyright 2012 OCAD University, Antranig Basman
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
@@ -48,12 +48,24 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
var fluid = context.fluid;
- fluid.require = function (moduleName, namespace) {
+ fluid.require = function (moduleName, foreignRequire, namespace) {
+ foreignRequire = foreignRequire || require;
namespace = namespace || moduleName;
- var module = require(moduleName);
+ var module = foreignRequire(moduleName);
fluid.set(context, namespace, module);
return module;
};
+
+ fluid.getLoader = function (dirName, foreignRequire) {
+ return {
+ require: function (moduleName, namespace) {
+ if (moduleName.indexOf("/") > -1) {
+ moduleName = dirName + "/" + moduleName;
+ }
+ return fluid.require(moduleName, foreignRequire, namespace);
+ }
+ }
+ };
module.exports = fluid;
View
33 src/webapp/module/requireStub.js
@@ -0,0 +1,33 @@
+/*
+Copyright 2012 OCAD University
+
+Licensed under the Educational Community License (ECL), Version 2.0 or the New
+BSD license. You may not use this file except in compliance with one these
+Licenses.
+
+You may obtain a copy of the ECL 2.0 License and BSD License at
+https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
+*/
+
+/*global window, require*/
+/*jslint white: true, funcinvoke: true, undef: true, newcap: true, regexp: true, bitwise: true, browser: true, forin: true, maxerr: 100, indent: 4 */
+
+var fluid_1_5 = fluid_1_5 || {};
+
+(function (fluid) {
+
+ var requireStub = function (moduleName) {
+ if (moduleName !== "infusion") {
+ throw new Error("requireStub.js cannot be used to test modules other than Infusion, " +
+ "which is capable of running in both Node.js and a browser.");
+ }
+ return fluid;
+ };
+
+ window.require = requireStub;
+ if (typeof (fluid) === "undefined") {
+ throw new Error("Please include requireStub.js after Fluid Infusion in the document's <head>");
+ }
+ fluid.require = requireStub;
+
+}(fluid_1_5));
View
47 src/webapp/tests/framework-tests/core/js/DataBindingTests.js
@@ -383,6 +383,34 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
jqUnit.assertFalse("Model unchanged ", modelChangedCheck);
});
+
+
+ DataBindingTests.test("FLUID-4633 test - source tracking", function() {
+ var model = {
+ property1: 1,
+ property2: 2
+ };
+ var applier = fluid.makeChangeApplier(model);
+
+ var indirect = fluid.makeEventFirer();
+ applier.modelChanged.addListener("property1", function() {
+ indirect.fire();
+ });
+ indirect.addListener(function() {
+ fluid.fireSourcedChange(applier, "property2", 3, "indirectSource");
+ });
+ var listenerFired = false;
+ fluid.addSourceGuardedListener(applier, "property2", "originalSource", function() {
+ listenerFired = applier.hasChangeSource("indirectSource");
+ });
+ fluid.fireSourcedChange(applier, "property1", 2, "originalSource");
+ jqUnit.assertFalse("Recurrence censored from originalSource", listenerFired);
+ fluid.fireSourcedChange(applier, "property1", 3, "alternateSource");
+ jqUnit.assertTrue("Recurrence propagated from alternate source", listenerFired);
+
+
+ });
+
DataBindingTests.test("ChangeApplier", function () {
var outerDAR = null;
function checkingGuard(model, dar) {
@@ -451,5 +479,24 @@ https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
applier.fireChangeRequest({path: "innerProperty.innerPath2", type: "DELETE"});
jqUnit.assertEquals("Removed via deletion", undefined, model.innerProperty.innerpath2);
});
+
+ DataBindingTests.test("FLUID-4625 test: Over-broad changes", function() {
+ // This tests FLUID-4625 - we don't test at the utility level of matchPath since this is the functional
+ // behaviour required. In practice we may want a better implementation which explodes composite changes into
+ // smaller increments so that we can avoid unnecessary notifications, but this at least covers the case
+ // of missed notifications
+ var model = {
+ selections: {
+ lineSpacing: 1.0
+ }
+ };
+ var applier = fluid.makeChangeApplier(model);
+ var notified = false;
+ applier.modelChanged.addListener("selections.linespacing", function() {
+ notified = true;
+ });
+ applier.requestChange("selections", {lineSpacing: 1.5});
+ jqUnit.assertTrue("Over-broad change triggers listener", notified);
+ });
});
})(jQuery);
View
32 src/webapp/tests/framework-tests/core/js/FluidIoCTests.js
@@ -1228,5 +1228,37 @@ fluid.registerNamespace("fluid.tests");
fluid.pushSoftFailure(-1);
}
});
+
+ fluid.defaults("fluid.tests.island1", {
+ gradeNames: ["fluid.eventedComponent", "autoInit"],
+ events: {
+ outEvent1: null,
+ // note inconsistency - only IoC-resolved events get instantiator wrapping!
+ outEvent2: "{island1}.events.outEvent1"
+ }
+ });
+
+ fluid.defaults("fluid.tests.island2", {
+ gradeNames: ["fluid.eventedComponent", "autoInit"],
+ events: {
+ inEvent: null
+ },
+ components: {
+ instantRequires: {
+ type: "fluid.tests.news.child",
+ createOnEvent: "inEvent"
+ }
+ }
+ });
+ fluidIoCTests.test("FLUID-4626 test - cross-island use of instantiators", function() {
+ jqUnit.expect(1);
+ var island1 = fluid.tests.island1();
+ var island2 = fluid.tests.island2();
+ island1.events.outEvent2.addListener(function() {
+ island2.events.inEvent.fire()
+ });
+ island1.events.outEvent2.fire();
+ jqUnit.assert("No error fired on cross-island dispatch");
+ });
})(jQuery);

No commit comments for this range

Something went wrong with that request. Please try again.