src/main/namespace.js
function newNamespace (opts/* : {tree ?: boolean, global ?: boolean, root ?: Object} */) {
var options/* : {
tree: boolean,
global: boolean,
root: Object
} */ = {
tree: typeof opts.tree === "boolean" ? opts.tree : false,
global: typeof opts.global === "boolean" ? opts.global : false,
root: typeof opts.root === "object" ? opts.root : {}
};
/*::
type Node = {
route: ?string,
parent: ?Node,
children: any,
watchers: any,
data: any,
ready: boolean,
lazy: any
};
*/
function initNode(options)/* : Node */ {
return {
route: typeof options.route === "string" ? options.route : null,
parent: typeof options.parent === "object" ? options.parent : null,
ready: typeof options.ready === "boolean" ? options.ready : false,
children: {},
watchers: [],
data: {},
lazy: []
};
}
var nsRoot = initNode({ready: true});
if (options.tree) {
if (options.global) {
try {
if (window)
nsRoot.data = window;
} catch (e) { }
try {
if (global)
nsRoot.data = global;
} catch (e) { }
try {
if (self)
nsRoot.data = self;
} catch (e) { }
} else
nsRoot.data = options.root;
}
function nodeDigest(node/* : Node */) {
if (node.ready)
return;
if (node.parent && !node.parent.ready) {
nodeDigest(node.parent);
return;
}
if (node.route && node.parent && (node.route in node.parent.data)) {
node.data = node.parent.data[node.route];
node.ready = true;
for (var i = 0; i < node.watchers.length; ++i)
node.watchers[i].callback.call(node.watchers[i].context || this, node.data);
node.watchers = [];
for (var key in node.children)
nodeDigest(node.children[key]);
}
}
function nodeEnforce(node/* : Node */) {
if (node.ready)
return;
if (node.parent && !node.parent.ready)
nodeEnforce(node.parent);
node.ready = true;
if (node.parent) {
if (options.tree && typeof node.parent.data == "object")
node.parent.data[node.route] = node.data;
}
for (var i = 0; i < node.watchers.length; ++i)
node.watchers[i].callback.call(node.watchers[i].context || this, node.data);
node.watchers = [];
}
function nodeSetData(node/* : Node */, value) {
if (typeof value == "object" && node.ready) {
for (var key in value)
node.data[key] = value[key];
} else
node.data = value;
if (typeof value == "object") {
for (var ckey in value) {
if (node.children[ckey])
node.children[ckey].data = value[ckey];
}
}
nodeEnforce(node);
for (var k in node.children)
nodeDigest(node.children[k]);
}
function nodeClearData(node/* : Node */) {
if (node.ready && node.data) {
for (var key in node.data)
delete node.data[key];
}
}
function nodeNavigate(path/* : ?String */) {
if (!path)
return nsRoot;
var routes = path.split(".");
var current = nsRoot;
for (var i = 0; i < routes.length; ++i) {
if (routes[i] in current.children)
current = current.children[routes[i]];
else {
current.children[routes[i]] = initNode({
parent: current,
route: routes[i]
});
current = current.children[routes[i]];
nodeDigest(current);
}
}
return current;
}
function nodeAddWatcher(node/* : Node */, callback, context) {
if (node.ready)
callback.call(context || this, node.data);
else {
node.watchers.push({
callback: callback,
context: context
});
if (node.lazy.length > 0) {
var f = function (node) {
if (node.lazy.length > 0) {
var lazy = node.lazy.shift();
lazy.callback.call(lazy.context || this, node.data);
f(node);
}
};
f(node);
}
}
}
function nodeUnresolvedWatchers(node/* : Node */, base, result) {
node = node || nsRoot;
result = result || [];
if (!node.ready && node.lazy.length === 0 && node.watchers.length > 0)
result.push(base);
for (var k in node.children) {
var c = node.children[k];
var r = (base ? base + "." : "") + c.route;
result = nodeUnresolvedWatchers(c, r, result);
}
return result;
}
/**
* The namespace module manages a namespace in the Scoped system.
*
* @module Namespace
* @access public
*/
return {
/**
* Extend a node in the namespace by an object.
*
* @param {string} path path to the node in the namespace
* @param {object} value object that should be used for extend the namespace node
*/
extend: function (path, value) {
nodeSetData(nodeNavigate(path), value);
},
/**
* Set the object value of a node in the namespace.
*
* @param {string} path path to the node in the namespace
* @param {object} value object that should be used as value for the namespace node
*/
set: function (path, value) {
var node = nodeNavigate(path);
if (node.data)
nodeClearData(node);
nodeSetData(node, value);
},
/**
* Read the object value of a node in the namespace.
*
* @param {string} path path to the node in the namespace
* @return {object} object value of the node or null if undefined
*/
get: function (path) {
var node = nodeNavigate(path);
return node.ready ? node.data : null;
},
/**
* Lazily navigate to a node in the namespace.
* Will asynchronously call the callback as soon as the node is being touched.
*
* @param {string} path path to the node in the namespace
* @param {function} callback callback function accepting the node's object value
* @param {context} context optional callback context
*/
lazy: function (path, callback, context) {
var node = nodeNavigate(path);
if (node.ready)
callback(context || this, node.data);
else {
node.lazy.push({
callback: callback,
context: context
});
}
},
/**
* Digest a node path, checking whether it has been defined by an external system.
*
* @param {string} path path to the node in the namespace
*/
digest: function (path) {
nodeDigest(nodeNavigate(path));
},
/**
* Asynchronously access a node in the namespace.
* Will asynchronously call the callback as soon as the node is being defined.
*
* @param {string} path path to the node in the namespace
* @param {function} callback callback function accepting the node's object value
* @param {context} context optional callback context
*/
obtain: function (path, callback, context) {
nodeAddWatcher(nodeNavigate(path), callback, context);
},
/**
* Returns all unresolved watchers under a certain path.
*
* @param {string} path path to the node in the namespace
* @return {array} list of all unresolved watchers
*/
unresolvedWatchers: function (path) {
return nodeUnresolvedWatchers(nodeNavigate(path), path);
},
__export: function () {
return {
options: options,
nsRoot: nsRoot
};
},
__import: function (data) {
options = data.options;
nsRoot = data.nsRoot;
}
};
}