Update wmts-hidpi, add nicer-api-docs

This commit is contained in:
Andreas Hocevar
2014-05-06 13:02:46 -05:00
parent b3ac1afd00
commit 1e25fc5585
2239 changed files with 3726515 additions and 37010 deletions

View File

@@ -0,0 +1,559 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview
* Central class for registering and accessing data sources
* Also handles processing of data events.
*
* There is a shared global instance that most client code should access via
* goog.ds.DataManager.getInstance(). However you can also create your own
* DataManager using new
*
* Implements DataNode to provide the top element in a data registry
* Prepends '$' to top level data names in path to denote they are root object
*
*/
goog.provide('goog.ds.DataManager');
goog.require('goog.ds.BasicNodeList');
goog.require('goog.ds.DataNode');
goog.require('goog.ds.Expr');
goog.require('goog.string');
goog.require('goog.structs');
goog.require('goog.structs.Map');
/**
* Create a DataManger
* @extends {goog.ds.DataNode}
* @constructor
*/
goog.ds.DataManager = function() {
this.dataSources_ = new goog.ds.BasicNodeList();
this.autoloads_ = new goog.structs.Map();
this.listenerMap_ = {};
this.listenersByFunction_ = {};
this.aliases_ = {};
this.eventCount_ = 0;
this.indexedListenersByFunction_ = {};
};
/**
* Global instance
* @private
*/
goog.ds.DataManager.instance_ = null;
goog.inherits(goog.ds.DataManager, goog.ds.DataNode);
/**
* Get the global instance
* @return {goog.ds.DataManager} The data manager singleton.
*/
goog.ds.DataManager.getInstance = function() {
if (!goog.ds.DataManager.instance_) {
goog.ds.DataManager.instance_ = new goog.ds.DataManager();
}
return goog.ds.DataManager.instance_;
};
/**
* Clears the global instance (for unit tests to reset state).
*/
goog.ds.DataManager.clearInstance = function() {
goog.ds.DataManager.instance_ = null;
};
/**
* Add a data source
* @param {goog.ds.DataNode} ds The data source.
* @param {boolean=} opt_autoload Whether to automatically load the data,
* defaults to false.
* @param {string=} opt_name Optional name, can also get name
* from the datasource.
*/
goog.ds.DataManager.prototype.addDataSource = function(ds, opt_autoload,
opt_name) {
var autoload = !!opt_autoload;
var name = opt_name || ds.getDataName();
if (!goog.string.startsWith(name, '$')) {
name = '$' + name;
}
ds.setDataName(name);
this.dataSources_.add(ds);
this.autoloads_.set(name, autoload);
};
/**
* Create an alias for a data path, very similar to assigning a variable.
* For example, you can set $CurrentContact -> $Request/Contacts[5], and all
* references to $CurrentContact will be procesed on $Request/Contacts[5].
*
* Aliases will hide datasources of the same name.
*
* @param {string} name Alias name, must be a top level path ($Foo).
* @param {string} dataPath Data path being aliased.
*/
goog.ds.DataManager.prototype.aliasDataSource = function(name, dataPath) {
if (!this.aliasListener_) {
this.aliasListener_ = goog.bind(this.listenForAlias_, this);
}
if (this.aliases_[name]) {
var oldPath = this.aliases_[name].getSource();
this.removeListeners(this.aliasListener_, oldPath + '/...', name);
}
this.aliases_[name] = goog.ds.Expr.create(dataPath);
this.addListener(this.aliasListener_, dataPath + '/...', name);
this.fireDataChange(name);
};
/**
* Listener function for matches of paths that have been aliased.
* Fires a data change on the alias as well.
*
* @param {string} dataPath Path of data event fired.
* @param {string} name Name of the alias.
* @private
*/
goog.ds.DataManager.prototype.listenForAlias_ = function(dataPath, name) {
var aliasedExpr = this.aliases_[name];
if (aliasedExpr) {
// If it's a subpath, appends the subpath to the alias name
// otherwise just fires on the top level alias
var aliasedPath = aliasedExpr.getSource();
if (dataPath.indexOf(aliasedPath) == 0) {
this.fireDataChange(name + dataPath.substring(aliasedPath.length));
} else {
this.fireDataChange(name);
}
}
};
/**
* Gets a named child node of the current node.
*
* @param {string} name The node name.
* @return {goog.ds.DataNode} The child node,
* or null if no node of this name exists.
*/
goog.ds.DataManager.prototype.getDataSource = function(name) {
if (this.aliases_[name]) {
return this.aliases_[name].getNode();
} else {
return this.dataSources_.get(name);
}
};
/**
* Get the value of the node
* @return {Object} The value of the node, or null if no value.
* @override
*/
goog.ds.DataManager.prototype.get = function() {
return this.dataSources_;
};
/** @override */
goog.ds.DataManager.prototype.set = function(value) {
throw Error('Can\'t set on DataManager');
};
/** @override */
goog.ds.DataManager.prototype.getChildNodes = function(opt_selector) {
if (opt_selector) {
return new goog.ds.BasicNodeList(
[this.getChildNode(/** @type {string} */(opt_selector))]);
} else {
return this.dataSources_;
}
};
/**
* Gets a named child node of the current node
* @param {string} name The node name.
* @return {goog.ds.DataNode} The child node,
* or null if no node of this name exists.
* @override
*/
goog.ds.DataManager.prototype.getChildNode = function(name) {
return this.getDataSource(name);
};
/** @override */
goog.ds.DataManager.prototype.getChildNodeValue = function(name) {
var ds = this.getDataSource(name);
return ds ? ds.get() : null;
};
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
* @override
*/
goog.ds.DataManager.prototype.getDataName = function() {
return '';
};
/**
* Gets the a qualified data path to this node
* @return {string} The data path.
* @override
*/
goog.ds.DataManager.prototype.getDataPath = function() {
return '';
};
/**
* Load or reload the backing data for this node
* only loads datasources flagged with autoload
* @override
*/
goog.ds.DataManager.prototype.load = function() {
var len = this.dataSources_.getCount();
for (var i = 0; i < len; i++) {
var ds = this.dataSources_.getByIndex(i);
var autoload = this.autoloads_.get(ds.getDataName());
if (autoload) {
ds.load();
}
}
};
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.DataManager.prototype.getLoadState = goog.abstractMethod;
/**
* Whether the value of this node is a homogeneous list of data
* @return {boolean} True if a list.
* @override
*/
goog.ds.DataManager.prototype.isList = function() {
return false;
};
/**
* Get the total count of events fired (mostly for debugging)
* @return {number} Count of events.
*/
goog.ds.DataManager.prototype.getEventCount = function() {
return this.eventCount_;
};
/**
* Adds a listener
* Listeners should fire when any data with path that has dataPath as substring
* is changed.
* TODO(user) Look into better listener handling
*
* @param {Function} fn Callback function, signature function(dataPath, id).
* @param {string} dataPath Fully qualified data path.
* @param {string=} opt_id A value passed back to the listener when the dataPath
* is matched.
*/
goog.ds.DataManager.prototype.addListener = function(fn, dataPath, opt_id) {
// maxAncestor sets how distant an ancestor you can be of the fired event
// and still fire (you always fire if you are a descendant).
// 0 means you don't fire if you are an ancestor
// 1 means you only fire if you are parent
// 1000 means you will fire if you are ancestor (effectively infinite)
var maxAncestors = 0;
if (goog.string.endsWith(dataPath, '/...')) {
maxAncestors = 1000;
dataPath = dataPath.substring(0, dataPath.length - 4);
} else if (goog.string.endsWith(dataPath, '/*')) {
maxAncestors = 1;
dataPath = dataPath.substring(0, dataPath.length - 2);
}
opt_id = opt_id || '';
var key = dataPath + ':' + opt_id + ':' + goog.getUid(fn);
var listener = {dataPath: dataPath, id: opt_id, fn: fn};
var expr = goog.ds.Expr.create(dataPath);
var fnUid = goog.getUid(fn);
if (!this.listenersByFunction_[fnUid]) {
this.listenersByFunction_[fnUid] = {};
}
this.listenersByFunction_[fnUid][key] = {listener: listener, items: []};
while (expr) {
var listenerSpec = {listener: listener, maxAncestors: maxAncestors};
var matchingListeners = this.listenerMap_[expr.getSource()];
if (matchingListeners == null) {
matchingListeners = {};
this.listenerMap_[expr.getSource()] = matchingListeners;
}
matchingListeners[key] = listenerSpec;
maxAncestors = 0;
expr = expr.getParent();
this.listenersByFunction_[fnUid][key].items.push(
{key: key, obj: matchingListeners});
}
};
/**
* Adds an indexed listener.
*
* Indexed listeners allow for '*' in data paths. If a * exists, will match
* all values and return the matched values in an array to the callback.
*
* Currently uses a promiscuous match algorithm: Matches everything before the
* first '*', and then does a regex match for all of the returned events.
* Although this isn't optimized, it is still an improvement as you can collapse
* 100's of listeners into a single regex match
*
* @param {Function} fn Callback function, signature (dataPath, id, indexes).
* @param {string} dataPath Fully qualified data path.
* @param {string=} opt_id A value passed back to the listener when the dataPath
* is matched.
*/
goog.ds.DataManager.prototype.addIndexedListener = function(fn, dataPath,
opt_id) {
var firstStarPos = dataPath.indexOf('*');
// Just need a regular listener
if (firstStarPos == -1) {
this.addListener(fn, dataPath, opt_id);
return;
}
var listenPath = dataPath.substring(0, firstStarPos) + '...';
// Create regex that matches * to any non '\' character
var ext = '$';
if (goog.string.endsWith(dataPath, '/...')) {
dataPath = dataPath.substring(0, dataPath.length - 4);
ext = '';
}
var regExpPath = goog.string.regExpEscape(dataPath);
var matchRegExp = regExpPath.replace(/\\\*/g, '([^\\\/]+)') + ext;
// Matcher function applies the regex and calls back the original function
// if the regex matches, passing in an array of the matched values
var matchRegExpRe = new RegExp(matchRegExp);
var matcher = function(path, id) {
var match = matchRegExpRe.exec(path);
if (match) {
match.shift();
fn(path, opt_id, match);
}
};
this.addListener(matcher, listenPath, opt_id);
// Add the indexed listener to the map so that we can remove it later.
var fnUid = goog.getUid(fn);
if (!this.indexedListenersByFunction_[fnUid]) {
this.indexedListenersByFunction_[fnUid] = {};
}
var key = dataPath + ':' + opt_id;
this.indexedListenersByFunction_[fnUid][key] = {
listener: {dataPath: listenPath, fn: matcher, id: opt_id}
};
};
/**
* Removes indexed listeners with a given callback function, and optional
* matching datapath and matching id.
*
* @param {Function} fn Callback function, signature function(dataPath, id).
* @param {string=} opt_dataPath Fully qualified data path.
* @param {string=} opt_id A value passed back to the listener when the dataPath
* is matched.
*/
goog.ds.DataManager.prototype.removeIndexedListeners = function(
fn, opt_dataPath, opt_id) {
this.removeListenersByFunction_(
this.indexedListenersByFunction_, true, fn, opt_dataPath, opt_id);
};
/**
* Removes listeners with a given callback function, and optional
* matching dataPath and matching id
*
* @param {Function} fn Callback function, signature function(dataPath, id).
* @param {string=} opt_dataPath Fully qualified data path.
* @param {string=} opt_id A value passed back to the listener when the dataPath
* is matched.
*/
goog.ds.DataManager.prototype.removeListeners = function(fn, opt_dataPath,
opt_id) {
// Normalize data path root
if (opt_dataPath && goog.string.endsWith(opt_dataPath, '/...')) {
opt_dataPath = opt_dataPath.substring(0, opt_dataPath.length - 4);
} else if (opt_dataPath && goog.string.endsWith(opt_dataPath, '/*')) {
opt_dataPath = opt_dataPath.substring(0, opt_dataPath.length - 2);
}
this.removeListenersByFunction_(
this.listenersByFunction_, false, fn, opt_dataPath, opt_id);
};
/**
* Removes listeners with a given callback function, and optional
* matching dataPath and matching id from the given listenersByFunction
* data structure.
*
* @param {Object} listenersByFunction The listeners by function.
* @param {boolean} indexed Indicates whether the listenersByFunction are
* indexed or not.
* @param {Function} fn Callback function, signature function(dataPath, id).
* @param {string=} opt_dataPath Fully qualified data path.
* @param {string=} opt_id A value passed back to the listener when the dataPath
* is matched.
* @private
*/
goog.ds.DataManager.prototype.removeListenersByFunction_ = function(
listenersByFunction, indexed, fn, opt_dataPath, opt_id) {
var fnUid = goog.getUid(fn);
var functionMatches = listenersByFunction[fnUid];
if (functionMatches != null) {
for (var key in functionMatches) {
var functionMatch = functionMatches[key];
var listener = functionMatch.listener;
if ((!opt_dataPath || opt_dataPath == listener.dataPath) &&
(!opt_id || opt_id == listener.id)) {
if (indexed) {
this.removeListeners(
listener.fn, listener.dataPath, listener.id);
}
if (functionMatch.items) {
for (var i = 0; i < functionMatch.items.length; i++) {
var item = functionMatch.items[i];
delete item.obj[item.key];
}
}
delete functionMatches[key];
}
}
}
};
/**
* Get the total number of listeners (per expression listened to, so may be
* more than number of times addListener() has been called
* @return {number} Number of listeners.
*/
goog.ds.DataManager.prototype.getListenerCount = function() {
var count = 0;
goog.structs.forEach(this.listenerMap_, function(matchingListeners) {
count += goog.structs.getCount(matchingListeners);
});
return count;
};
/**
* Disables the sending of all data events during the execution of the given
* callback. This provides a way to avoid useless notifications of small changes
* when you will eventually send a data event manually that encompasses them
* all.
*
* Note that this function can not be called reentrantly.
*
* @param {Function} callback Zero-arg function to execute.
*/
goog.ds.DataManager.prototype.runWithoutFiringDataChanges = function(callback) {
if (this.disableFiring_) {
throw Error('Can not nest calls to runWithoutFiringDataChanges');
}
this.disableFiring_ = true;
try {
callback();
} finally {
this.disableFiring_ = false;
}
};
/**
* Fire a data change event to all listeners
*
* If the path matches the path of a listener, the listener will fire
*
* If your path is the parent of a listener, the listener will fire. I.e.
* if $Contacts/bob@bob.com changes, then we will fire listener for
* $Contacts/bob@bob.com/Name as well, as the assumption is that when
* a parent changes, all children are invalidated.
*
* If your path is the child of a listener, the listener may fire, depending
* on the ancestor depth.
*
* A listener for $Contacts might only be interested if the contact name changes
* (i.e. $Contacts doesn't fire on $Contacts/bob@bob.com/Name),
* while a listener for a specific contact might
* (i.e. $Contacts/bob@bob.com would fire on $Contacts/bob@bob.com/Name).
* Adding "/..." to a lisetener path listens to all children, and adding "/*" to
* a listener path listens only to direct children
*
* @param {string} dataPath Fully qualified data path.
*/
goog.ds.DataManager.prototype.fireDataChange = function(dataPath) {
if (this.disableFiring_) {
return;
}
var expr = goog.ds.Expr.create(dataPath);
var ancestorDepth = 0;
// Look for listeners for expression and all its parents.
// Parents of listener expressions are all added to the listenerMap as well,
// so this will evaluate inner loop every time the dataPath is a child or
// an ancestor of the original listener path
while (expr) {
var matchingListeners = this.listenerMap_[expr.getSource()];
if (matchingListeners) {
for (var id in matchingListeners) {
var match = matchingListeners[id];
var listener = match.listener;
if (ancestorDepth <= match.maxAncestors) {
listener.fn(dataPath, listener.id);
}
}
}
ancestorDepth++;
expr = expr.getParent();
}
this.eventCount_++;
};

View File

@@ -0,0 +1,656 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Generic rich data access API.
*
* Abstraction for data sources that allows listening for changes at different
* levels of the data tree and updating the data via XHR requests
*
*/
goog.provide('goog.ds.BaseDataNode');
goog.provide('goog.ds.BasicNodeList');
goog.provide('goog.ds.DataNode');
goog.provide('goog.ds.DataNodeList');
goog.provide('goog.ds.EmptyNodeList');
goog.provide('goog.ds.LoadState');
goog.provide('goog.ds.SortedNodeList');
goog.provide('goog.ds.Util');
goog.provide('goog.ds.logger');
goog.require('goog.array');
goog.require('goog.log');
/**
* Interface for node in rich data tree.
*
* Names that are reserved for system use and shouldn't be used for data node
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
* undefined if these names are used.
*
* @constructor
*/
goog.ds.DataNode = function() {};
/**
* Get the value of the node
* @param {...?} var_args Do not check arity of arguments, because
* some subclasses require args.
* @return {*} The value of the node, or null if no value.
*/
goog.ds.DataNode.prototype.get = goog.abstractMethod;
/**
* Set the value of the node
* @param {*} value The new value of the node.
*/
goog.ds.DataNode.prototype.set = goog.abstractMethod;
/**
* Gets all of the child nodes of the current node.
* Should return an empty DataNode list if no child nodes.
* @param {string=} opt_selector String selector to choose child nodes.
* @return {goog.ds.DataNodeList} The child nodes.
*/
goog.ds.DataNode.prototype.getChildNodes = goog.abstractMethod;
/**
* Gets a named child node of the current node
* @param {string} name The node name.
* @param {boolean=} opt_canCreate Whether to create a child node if it does not
* exist.
* @return {goog.ds.DataNode} The child node, or null
* if no node of this name exists.
*/
goog.ds.DataNode.prototype.getChildNode = goog.abstractMethod;
/**
* Gets the value of a child node
* @param {string} name The node name.
* @return {*} The value of the node, or null if no value or the child node
* doesn't exist.
*/
goog.ds.DataNode.prototype.getChildNodeValue = goog.abstractMethod;
/**
* Sets a named child node of the current node.
*
* @param {string} name The node name.
* @param {Object} value The value to set, can be DataNode, object, property,
* or null. If value is null, removes the child node.
* @return {Object} The child node, if the node was set.
*/
goog.ds.DataNode.prototype.setChildNode = goog.abstractMethod;
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
*/
goog.ds.DataNode.prototype.getDataName = goog.abstractMethod;
/**
* Set the name of the node relative to the parent node
* @param {string} name The name of the node.
*/
goog.ds.DataNode.prototype.setDataName = goog.abstractMethod;
/**
* Gets the a qualified data path to this node
* @return {string} The data path.
*/
goog.ds.DataNode.prototype.getDataPath = goog.abstractMethod;
/**
* Load or reload the backing data for this node
*/
goog.ds.DataNode.prototype.load = goog.abstractMethod;
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
*/
goog.ds.DataNode.prototype.getLoadState = goog.abstractMethod;
/**
* Whether the value of this node is a homogeneous list of data
* @return {boolean} True if a list.
*/
goog.ds.DataNode.prototype.isList = goog.abstractMethod;
/**
* Enum for load state of a DataNode.
* @enum {string}
*/
goog.ds.LoadState = {
LOADED: 'LOADED',
LOADING: 'LOADING',
FAILED: 'FAILED',
NOT_LOADED: 'NOT_LOADED'
};
/**
* Base class for data node functionality, has default implementations for
* many of the functions.
*
* implements {goog.ds.DataNode}
* @constructor
*/
goog.ds.BaseDataNode = function() {};
/**
* Set the value of the node
* @param {Object} value The new value of the node.
*/
goog.ds.BaseDataNode.prototype.set = goog.abstractMethod;
/**
* Gets all of the child nodes of the current node.
* Should return an empty DataNode list if no child nodes.
* @param {string=} opt_selector String selector to choose child nodes.
* @return {goog.ds.DataNodeList} The child nodes.
*/
goog.ds.BaseDataNode.prototype.getChildNodes = function(opt_selector) {
return new goog.ds.EmptyNodeList();
};
/**
* Gets a named child node of the current node
* @param {string} name The node name.
* @param {boolean=} opt_canCreate Whether you can create the child node if
* it doesn't exist already.
* @return {goog.ds.DataNode} The child node, or null if no node of
* this name exists and opt_create is false.
*/
goog.ds.BaseDataNode.prototype.getChildNode = function(name, opt_canCreate) {
return null;
};
/**
* Gets the value of a child node
* @param {string} name The node name.
* @return {Object} The value of the node, or null if no value or the
* child node doesn't exist.
*/
goog.ds.BaseDataNode.prototype.getChildNodeValue = function(name) {
return null;
};
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
*/
goog.ds.BaseDataNode.prototype.getDataName = goog.abstractMethod;
/**
* Gets the a qualified data path to this node
* @return {string} The data path.
*/
goog.ds.BaseDataNode.prototype.getDataPath = function() {
var parentPath = '';
var myName = this.getDataName();
if (this.getParent && this.getParent()) {
parentPath = this.getParent().getDataPath() +
(myName.indexOf(goog.ds.STR_ARRAY_START) != -1 ? '' :
goog.ds.STR_PATH_SEPARATOR);
}
return parentPath + myName;
};
/**
* Load or reload the backing data for this node
*/
goog.ds.BaseDataNode.prototype.load = goog.nullFunction;
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
*/
goog.ds.BaseDataNode.prototype.getLoadState = function() {
return goog.ds.LoadState.LOADED;
};
/**
* Gets the parent node. Subclasses implement this function
* @type {Function}
* @protected
*/
goog.ds.BaseDataNode.prototype.getParent = null;
/**
* Interface for node list in rich data tree.
*
* Has both map and list-style accessors
*
* @constructor
* @extends {goog.ds.DataNode}
*/
// TODO(arv): Use interfaces when available.
goog.ds.DataNodeList = function() {};
/**
* Add a node to the node list.
* If the node has a dataName, uses this for the key in the map.
*
* @param {goog.ds.DataNode} node The node to add.
*/
goog.ds.DataNodeList.prototype.add = goog.abstractMethod;
/**
* Get a node by string key.
* Returns null if node doesn't exist.
*
* @param {string} key String lookup key.
* @return {*} The node, or null if doesn't exist.
* @override
*/
goog.ds.DataNodeList.prototype.get = goog.abstractMethod;
/**
* Get a node by index
* Returns null if the index is out of range
*
* @param {number} index The index of the node.
* @return {goog.ds.DataNode} The node, or null if doesn't exist.
*/
goog.ds.DataNodeList.prototype.getByIndex = goog.abstractMethod;
/**
* Gets the size of the node list
*
* @return {number} The size of the list.
*/
goog.ds.DataNodeList.prototype.getCount = goog.abstractMethod;
/**
* Sets a node in the list of a given name
* @param {string} name Name of the node.
* @param {goog.ds.DataNode} node The node.
*/
goog.ds.DataNodeList.prototype.setNode = goog.abstractMethod;
/**
* Removes a node in the list of a given name
* @param {string} name Name of the node.
* @return {boolean} True if node existed and was deleted.
*/
goog.ds.DataNodeList.prototype.removeNode = goog.abstractMethod;
/**
* Simple node list implementation with underlying array and map
* implements goog.ds.DataNodeList.
*
* Names that are reserved for system use and shouldn't be used for data node
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
* undefined if these names are used.
*
* @param {Array.<goog.ds.DataNode>=} opt_nodes optional nodes to add to list.
* @constructor
* @extends {goog.ds.DataNodeList}
*/
// TODO(arv): Use interfaces when available.
goog.ds.BasicNodeList = function(opt_nodes) {
this.map_ = {};
this.list_ = [];
this.indexMap_ = {};
if (opt_nodes) {
for (var i = 0, node; node = opt_nodes[i]; i++) {
this.add(node);
}
}
};
/**
* Add a node to the node list.
* If the node has a dataName, uses this for the key in the map.
* TODO(user) Remove function as well
*
* @param {goog.ds.DataNode} node The node to add.
* @override
*/
goog.ds.BasicNodeList.prototype.add = function(node) {
this.list_.push(node);
var dataName = node.getDataName();
if (dataName) {
this.map_[dataName] = node;
this.indexMap_[dataName] = this.list_.length - 1;
}
};
/**
* Get a node by string key.
* Returns null if node doesn't exist.
*
* @param {string} key String lookup key.
* @return {goog.ds.DataNode} The node, or null if doesn't exist.
* @override
*/
goog.ds.BasicNodeList.prototype.get = function(key) {
return this.map_[key] || null;
};
/**
* Get a node by index
* Returns null if the index is out of range
*
* @param {number} index The index of the node.
* @return {goog.ds.DataNode} The node, or null if doesn't exist.
* @override
*/
goog.ds.BasicNodeList.prototype.getByIndex = function(index) {
return this.list_[index] || null;
};
/**
* Gets the size of the node list
*
* @return {number} The size of the list.
* @override
*/
goog.ds.BasicNodeList.prototype.getCount = function() {
return this.list_.length;
};
/**
* Sets a node in the list of a given name
* @param {string} name Name of the node.
* @param {goog.ds.DataNode} node The node.
* @override
*/
goog.ds.BasicNodeList.prototype.setNode = function(name, node) {
if (node == null) {
this.removeNode(name);
} else {
var existingNode = this.indexMap_[name];
if (existingNode != null) {
this.map_[name] = node;
this.list_[existingNode] = node;
} else {
this.add(node);
}
}
};
/**
* Removes a node in the list of a given name
* @param {string} name Name of the node.
* @return {boolean} True if node existed and was deleted.
* @override
*/
goog.ds.BasicNodeList.prototype.removeNode = function(name) {
var existingNode = this.indexMap_[name];
if (existingNode != null) {
this.list_.splice(existingNode, 1);
delete this.map_[name];
delete this.indexMap_[name];
for (var index in this.indexMap_) {
if (this.indexMap_[index] > existingNode) {
this.indexMap_[index]--;
}
}
}
return existingNode != null;
};
/**
* Get the index of a named node
* @param {string} name The name of the node to get the index of.
* @return {number|undefined} The index.
*/
goog.ds.BasicNodeList.prototype.indexOf = function(name) {
return this.indexMap_[name];
};
/**
* Immulatable empty node list
* @extends {goog.ds.BasicNodeList}
* @constructor
*/
goog.ds.EmptyNodeList = function() {
goog.ds.BasicNodeList.call(this);
};
goog.inherits(goog.ds.EmptyNodeList, goog.ds.BasicNodeList);
/**
* Add a node to the node list.
* If the node has a dataName, uses this for the key in the map.
*
* @param {goog.ds.DataNode} node The node to add.
* @override
*/
goog.ds.EmptyNodeList.prototype.add = function(node) {
throw Error('Can\'t add to EmptyNodeList');
};
/**
* Node list implementation which maintains sort order during insertion and
* modification operations based on a comparison function.
*
* The SortedNodeList does not guarantee sort order will be maintained if
* the underlying data nodes are modified externally.
*
* Names that are reserved for system use and shouldn't be used for data node
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
* undefined if these names are used.
*
* @param {Function} compareFn Comparison function by which the
* node list is sorted. Should take 2 arguments to compare, and return a
* negative integer, zero, or a positive integer depending on whether the
* first argument is less than, equal to, or greater than the second.
* @param {Array.<goog.ds.DataNode>=} opt_nodes optional nodes to add to list;
* these are assumed to be in sorted order.
* @extends {goog.ds.BasicNodeList}
* @constructor
*/
goog.ds.SortedNodeList = function(compareFn, opt_nodes) {
this.compareFn_ = compareFn;
goog.ds.BasicNodeList.call(this, opt_nodes);
};
goog.inherits(goog.ds.SortedNodeList, goog.ds.BasicNodeList);
/**
* Add a node to the node list, maintaining sort order.
* If the node has a dataName, uses this for the key in the map.
*
* @param {goog.ds.DataNode} node The node to add.
* @override
*/
goog.ds.SortedNodeList.prototype.add = function(node) {
if (!this.compareFn_) {
this.append(node);
return;
}
var searchLoc = goog.array.binarySearch(this.list_, node, this.compareFn_);
// if there is another node that is "equal" according to the comparison
// function, insert before that one; otherwise insert at the location
// goog.array.binarySearch indicated
if (searchLoc < 0) {
searchLoc = -(searchLoc + 1);
}
// update any indexes that are after the insertion point
for (var index in this.indexMap_) {
if (this.indexMap_[index] >= searchLoc) {
this.indexMap_[index]++;
}
}
goog.array.insertAt(this.list_, node, searchLoc);
var dataName = node.getDataName();
if (dataName) {
this.map_[dataName] = node;
this.indexMap_[dataName] = searchLoc;
}
};
/**
* Adds the given node to the end of the SortedNodeList. This should
* only be used when the caller can guarantee that the sort order will
* be maintained according to this SortedNodeList's compareFn (e.g.
* when initializing a new SortedNodeList from a list of nodes that has
* already been sorted).
* @param {goog.ds.DataNode} node The node to append.
*/
goog.ds.SortedNodeList.prototype.append = function(node) {
goog.ds.SortedNodeList.superClass_.add.call(this, node);
};
/**
* Sets a node in the list of a given name, maintaining sort order.
* @param {string} name Name of the node.
* @param {goog.ds.DataNode} node The node.
* @override
*/
goog.ds.SortedNodeList.prototype.setNode = function(name, node) {
if (node == null) {
this.removeNode(name);
} else {
var existingNode = this.indexMap_[name];
if (existingNode != null) {
if (this.compareFn_) {
var compareResult = this.compareFn_(this.list_[existingNode], node);
if (compareResult == 0) {
// the new node can just replace the old one
this.map_[name] = node;
this.list_[existingNode] = node;
} else {
// remove the old node, then add the new one
this.removeNode(name);
this.add(node);
}
}
} else {
this.add(node);
}
}
};
/**
* The character denoting an attribute.
* @type {string}
*/
goog.ds.STR_ATTRIBUTE_START = '@';
/**
* The character denoting all children.
* @type {string}
*/
goog.ds.STR_ALL_CHILDREN_SELECTOR = '*';
/**
* The wildcard character.
* @type {string}
*/
goog.ds.STR_WILDCARD = '*';
/**
* The character denoting path separation.
* @type {string}
*/
goog.ds.STR_PATH_SEPARATOR = '/';
/**
* The character denoting the start of an array.
* @type {string}
*/
goog.ds.STR_ARRAY_START = '[';
/**
* Shared logger instance for data package
* @type {goog.log.Logger}
*/
goog.ds.logger = goog.log.getLogger('goog.ds');
/**
* Create a data node that references another data node,
* useful for pointer-like functionality.
* All functions will return same values as the original node except for
* getDataName()
* @param {!goog.ds.DataNode} node The original node.
* @param {string} name The new name.
* @return {!goog.ds.DataNode} The new data node.
*/
goog.ds.Util.makeReferenceNode = function(node, name) {
/**
* @constructor
* @extends {goog.ds.DataNode}
*/
var nodeCreator = function() {};
nodeCreator.prototype = node;
var newNode = new nodeCreator();
newNode.getDataName = function() {
return name;
};
return newNode;
};

View File

@@ -0,0 +1,544 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview
* Expression evaluation utilities. Expression format is very similar to XPath.
*
* Expression details:
* - Of format A/B/C, which will evaluate getChildNode('A').getChildNode('B').
* getChildNodes('C')|getChildNodeValue('C')|getChildNode('C') depending on
* call
* - If expression ends with '/name()', will get the name() of the node
* referenced by the preceding path.
* - If expression ends with '/count()', will get the count() of the nodes that
* match the expression referenced by the preceding path.
* - If expression ends with '?', the value is OK to evaluate to null. This is
* not enforced by the expression evaluation functions, instead it is
* provided as a flag for client code which may ignore depending on usage
* - If expression has [INDEX], will use getChildNodes().getByIndex(INDEX)
*
*/
goog.provide('goog.ds.Expr');
goog.require('goog.ds.BasicNodeList');
goog.require('goog.ds.EmptyNodeList');
goog.require('goog.string');
/**
* Create a new expression. An expression uses a string expression language, and
* from this string and a passed in DataNode can evaluate to a value, DataNode,
* or a DataNodeList.
*
* @param {string=} opt_expr The string expression.
* @constructor
*/
goog.ds.Expr = function(opt_expr) {
if (opt_expr) {
this.setSource_(opt_expr);
}
};
/**
* Set the source expression text & parse
*
* @param {string} expr The string expression source.
* @param {Array=} opt_parts Array of the parts of an expression.
* @param {goog.ds.Expr=} opt_childExpr Optional child of this expression,
* passed in as a hint for processing.
* @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression
* (i.e. $A/B/C is previous expression to B/C) passed in as a hint for
* processing.
* @private
*/
goog.ds.Expr.prototype.setSource_ = function(expr, opt_parts,
opt_childExpr, opt_prevExpr) {
this.src_ = expr;
if (!opt_childExpr && !opt_prevExpr) {
// Check whether it can be empty
if (goog.string.endsWith(expr, goog.ds.Expr.String_.CAN_BE_EMPTY)) {
this.canBeEmpty_ = true;
expr = expr.substring(0, expr.length - 1);
}
// Check whether this is an node function
if (goog.string.endsWith(expr, '()')) {
if (goog.string.endsWith(expr, goog.ds.Expr.String_.NAME_EXPR) ||
goog.string.endsWith(expr, goog.ds.Expr.String_.COUNT_EXPR) ||
goog.string.endsWith(expr, goog.ds.Expr.String_.POSITION_EXPR)) {
var lastPos = expr.lastIndexOf(goog.ds.Expr.String_.SEPARATOR);
if (lastPos != -1) {
this.exprFn_ = expr.substring(lastPos + 1);
expr = expr.substring(0, lastPos);
} else {
this.exprFn_ = expr;
expr = goog.ds.Expr.String_.CURRENT_NODE_EXPR;
}
if (this.exprFn_ == goog.ds.Expr.String_.COUNT_EXPR) {
this.isCount_ = true;
}
}
}
}
// Split into component parts
this.parts_ = opt_parts || expr.split('/');
this.size_ = this.parts_.length;
this.last_ = this.parts_[this.size_ - 1];
this.root_ = this.parts_[0];
if (this.size_ == 1) {
this.rootExpr_ = this;
this.isAbsolute_ = goog.string.startsWith(expr, '$');
} else {
this.rootExpr_ = goog.ds.Expr.createInternal_(this.root_, null,
this, null);
this.isAbsolute_ = this.rootExpr_.isAbsolute_;
this.root_ = this.rootExpr_.root_;
}
if (this.size_ == 1 && !this.isAbsolute_) {
// Check whether expression maps to current node, for convenience
this.isCurrent_ = (expr == goog.ds.Expr.String_.CURRENT_NODE_EXPR ||
expr == goog.ds.Expr.String_.EMPTY_EXPR);
// Whether this expression is just an attribute (i.e. '@foo')
this.isJustAttribute_ =
goog.string.startsWith(expr, goog.ds.Expr.String_.ATTRIBUTE_START);
// Check whether this is a common node expression
this.isAllChildNodes_ = expr == goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR;
this.isAllAttributes_ = expr == goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR;
this.isAllElements_ = expr == goog.ds.Expr.String_.ALL_ELEMENTS_EXPR;
}
};
/**
* Get the source data path for the expression
* @return {string} The path.
*/
goog.ds.Expr.prototype.getSource = function() {
return this.src_;
};
/**
* Gets the last part of the expression.
* @return {?string} Last part of the expression.
*/
goog.ds.Expr.prototype.getLast = function() {
return this.last_;
};
/**
* Gets the parent expression of this expression, or null if this is top level
* @return {goog.ds.Expr} The parent.
*/
goog.ds.Expr.prototype.getParent = function() {
if (!this.parentExprSet_) {
if (this.size_ > 1) {
this.parentExpr_ = goog.ds.Expr.createInternal_(null,
this.parts_.slice(0, this.parts_.length - 1), this, null);
}
this.parentExprSet_ = true;
}
return this.parentExpr_;
};
/**
* Gets the parent expression of this expression, or null if this is top level
* @return {goog.ds.Expr} The parent.
*/
goog.ds.Expr.prototype.getNext = function() {
if (!this.nextExprSet_) {
if (this.size_ > 1) {
this.nextExpr_ = goog.ds.Expr.createInternal_(null, this.parts_.slice(1),
null, this);
}
this.nextExprSet_ = true;
}
return this.nextExpr_;
};
/**
* Evaluate an expression on a data node, and return a value
* Recursively walks through child nodes to evaluate
* TODO(user) Support other expression functions
*
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
* If not provided, evaluates against DataManager global root.
* @return {*} Value of the node, or null if doesn't exist.
*/
goog.ds.Expr.prototype.getValue = function(opt_ds) {
if (opt_ds == null) {
opt_ds = goog.ds.DataManager.getInstance();
} else if (this.isAbsolute_) {
opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() :
goog.ds.DataManager.getInstance();
}
if (this.isCount_) {
var nodes = this.getNodes(opt_ds);
return nodes.getCount();
}
if (this.size_ == 1) {
return opt_ds.getChildNodeValue(this.root_);
} else if (this.size_ == 0) {
return opt_ds.get();
}
var nextDs = opt_ds.getChildNode(this.root_);
if (nextDs == null) {
return null;
} else {
return this.getNext().getValue(nextDs);
}
};
/**
* Evaluate an expression on a data node, and return matching nodes
* Recursively walks through child nodes to evaluate
*
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
* If not provided, evaluates against data root.
* @param {boolean=} opt_canCreate If true, will try to create new nodes.
* @return {goog.ds.DataNodeList} Matching nodes.
*/
goog.ds.Expr.prototype.getNodes = function(opt_ds, opt_canCreate) {
return /** @type {goog.ds.DataNodeList} */(this.getNodes_(opt_ds,
false, opt_canCreate));
};
/**
* Evaluate an expression on a data node, and return the first matching node
* Recursively walks through child nodes to evaluate
*
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
* If not provided, evaluates against DataManager global root.
* @param {boolean=} opt_canCreate If true, will try to create new nodes.
* @return {goog.ds.DataNode} Matching nodes, or null if doesn't exist.
*/
goog.ds.Expr.prototype.getNode = function(opt_ds, opt_canCreate) {
return /** @type {goog.ds.DataNode} */(this.getNodes_(opt_ds,
true, opt_canCreate));
};
/**
* Evaluate an expression on a data node, and return the first matching node
* Recursively walks through child nodes to evaluate
*
* @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against.
* If not provided, evaluates against DataManager global root.
* @param {boolean=} opt_selectOne Whether to return single matching DataNode
* or matching nodes in DataNodeList.
* @param {boolean=} opt_canCreate If true, will try to create new nodes.
* @return {goog.ds.DataNode|goog.ds.DataNodeList} Matching node or nodes,
* depending on value of opt_selectOne.
* @private
*/
goog.ds.Expr.prototype.getNodes_ = function(opt_ds, opt_selectOne,
opt_canCreate) {
if (opt_ds == null) {
opt_ds = goog.ds.DataManager.getInstance();
} else if (this.isAbsolute_) {
opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() :
goog.ds.DataManager.getInstance();
}
if (this.size_ == 0 && opt_selectOne) {
return opt_ds;
} else if (this.size_ == 0 && !opt_selectOne) {
return new goog.ds.BasicNodeList([opt_ds]);
} else if (this.size_ == 1) {
if (opt_selectOne) {
return opt_ds.getChildNode(this.root_, opt_canCreate);
}
else {
var possibleListChild = opt_ds.getChildNode(this.root_);
if (possibleListChild && possibleListChild.isList()) {
return possibleListChild.getChildNodes();
} else {
return opt_ds.getChildNodes(this.root_);
}
}
} else {
var nextDs = opt_ds.getChildNode(this.root_, opt_canCreate);
if (nextDs == null && opt_selectOne) {
return null;
} else if (nextDs == null && !opt_selectOne) {
return new goog.ds.EmptyNodeList();
}
return this.getNext().getNodes_(nextDs, opt_selectOne, opt_canCreate);
}
};
/**
* Whether the expression can be null.
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.canBeEmpty_ = false;
/**
* The parsed paths in the expression
*
* @type {Array.<string>}
* @private
*/
goog.ds.Expr.prototype.parts_ = [];
/**
* Number of paths in the expression
*
* @type {?number}
* @private
*/
goog.ds.Expr.prototype.size_ = null;
/**
* The root node path in the expression
*
* @type {string}
* @private
*/
goog.ds.Expr.prototype.root_;
/**
* The last path in the expression
*
* @type {?string}
* @private
*/
goog.ds.Expr.prototype.last_ = null;
/**
* Whether the expression evaluates to current node
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.isCurrent_ = false;
/**
* Whether the expression is just an attribute
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.isJustAttribute_ = false;
/**
* Does this expression select all DOM-style child nodes (element and text)
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.isAllChildNodes_ = false;
/**
* Does this expression select all DOM-style attribute nodes (starts with '@')
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.isAllAttributes_ = false;
/**
* Does this expression select all DOM-style element child nodes
*
* @type {boolean}
* @private
*/
goog.ds.Expr.prototype.isAllElements_ = false;
/**
* The function used by this expression
*
* @type {?string}
* @private
*/
goog.ds.Expr.prototype.exprFn_ = null;
/**
* Cached value for the parent expression.
* @type {goog.ds.Expr?}
* @private
*/
goog.ds.Expr.prototype.parentExpr_ = null;
/**
* Cached value for the next expression.
* @type {goog.ds.Expr?}
* @private
*/
goog.ds.Expr.prototype.nextExpr_ = null;
/**
* Create an expression from a string, can use cached values
*
* @param {string} expr The expression string.
* @return {goog.ds.Expr} The expression object.
*/
goog.ds.Expr.create = function(expr) {
var result = goog.ds.Expr.cache_[expr];
if (result == null) {
result = new goog.ds.Expr(expr);
goog.ds.Expr.cache_[expr] = result;
}
return result;
};
/**
* Create an expression from a string, can use cached values
* Uses hints from related expressions to help in creation
*
* @param {?string=} opt_expr The string expression source.
* @param {Array=} opt_parts Array of the parts of an expression.
* @param {goog.ds.Expr=} opt_childExpr Optional child of this expression,
* passed in as a hint for processing.
* @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression
* (i.e. $A/B/C is previous expression to B/C) passed in as a hint for
* processing.
* @return {goog.ds.Expr} The expression object.
* @private
*/
goog.ds.Expr.createInternal_ = function(opt_expr, opt_parts, opt_childExpr,
opt_prevExpr) {
var expr = opt_expr || opt_parts.join('/');
var result = goog.ds.Expr.cache_[expr];
if (result == null) {
result = new goog.ds.Expr();
result.setSource_(expr, opt_parts, opt_childExpr, opt_prevExpr);
goog.ds.Expr.cache_[expr] = result;
}
return result;
};
/**
* Cache of pre-parsed expressions
* @private
*/
goog.ds.Expr.cache_ = {};
/**
* Commonly used strings in expressions.
* @enum {string}
* @private
*/
goog.ds.Expr.String_ = {
SEPARATOR: '/',
CURRENT_NODE_EXPR: '.',
EMPTY_EXPR: '',
ATTRIBUTE_START: '@',
ALL_CHILD_NODES_EXPR: '*|text()',
ALL_ATTRIBUTES_EXPR: '@*',
ALL_ELEMENTS_EXPR: '*',
NAME_EXPR: 'name()',
COUNT_EXPR: 'count()',
POSITION_EXPR: 'position()',
INDEX_START: '[',
INDEX_END: ']',
CAN_BE_EMPTY: '?'
};
/**
* Standard expressions
*/
/**
* The current node
*/
goog.ds.Expr.CURRENT = goog.ds.Expr.create(
goog.ds.Expr.String_.CURRENT_NODE_EXPR);
/**
* For DOM interop - all DOM child nodes (text + element).
* Text nodes have dataName #text
*/
goog.ds.Expr.ALL_CHILD_NODES =
goog.ds.Expr.create(goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR);
/**
* For DOM interop - all DOM element child nodes
*/
goog.ds.Expr.ALL_ELEMENTS =
goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ELEMENTS_EXPR);
/**
* For DOM interop - all DOM attribute nodes
* Attribute nodes have dataName starting with "@"
*/
goog.ds.Expr.ALL_ATTRIBUTES =
goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR);
/**
* Get the dataName of a node
*/
goog.ds.Expr.NAME = goog.ds.Expr.create(goog.ds.Expr.String_.NAME_EXPR);
/**
* Get the count of nodes matching an expression
*/
goog.ds.Expr.COUNT = goog.ds.Expr.create(goog.ds.Expr.String_.COUNT_EXPR);
/**
* Get the position of the "current" node in the current node list
* This will only apply for datasources that support the concept of a current
* node (none exist yet). This is similar to XPath position() and concept of
* current node
*/
goog.ds.Expr.POSITION = goog.ds.Expr.create(goog.ds.Expr.String_.POSITION_EXPR);

View File

@@ -0,0 +1,811 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview
* Efficient implementation of DataNode API.
*
* The implementation consists of three concrete classes for modelling
* DataNodes with different characteristics: FastDataNode,
* FastPrimitiveDataNode and FastListNode.
*
* FastDataNode is for bean-like or map-like objects that consists of
* key/value mappings and where the primary access pattern is by key.
*
* FastPrimitiveDataNode wraps primitives like strings, boolean, and numbers.
*
* FastListNode is for array-like data nodes. It also supports key-based
* lookups if the data nodes have an "id" property or if child nodes are
* explicitly added by name. It is most efficient if these features are not
* used.
*
* FastDataNodes can be constructed from JSON-like objects via the function
* goog.ds.FastDataNode.fromJs.
*/
goog.provide('goog.ds.AbstractFastDataNode');
goog.provide('goog.ds.FastDataNode');
goog.provide('goog.ds.FastListNode');
goog.provide('goog.ds.PrimitiveFastDataNode');
goog.require('goog.ds.DataManager');
goog.require('goog.ds.EmptyNodeList');
goog.require('goog.string');
/*
* Implementation note: In order to reduce the number of objects,
* FastDataNode stores its key/value mappings directly in the FastDataNode
* object iself (instead of a separate map). To make this work we have to
* sure that there are no name clashes with other attribute names used by
* FastDataNode (like dataName and parent). This is especially difficult in
* the light of automatic renaming by the JavaScript compiler. For this reason,
* all internal attributes start with "__" so that they are not renamed
* by the compiler.
*/
/**
* Creates a new abstract data node.
* @param {string} dataName Name of the datanode.
* @param {goog.ds.DataNode=} opt_parent Parent of this data node.
* @constructor
* @extends {goog.ds.DataNodeList}
*/
// TODO(arv): Use interfaces when available.
goog.ds.AbstractFastDataNode = function(dataName, opt_parent) {
if (!dataName) {
throw Error('Cannot create a fast data node without a data name');
}
this['__dataName'] = dataName;
this['__parent'] = opt_parent;
};
/**
* Return the name of this data node.
* @return {string} Name of this data noden.
* @override
*/
goog.ds.AbstractFastDataNode.prototype.getDataName = function() {
return this['__dataName'];
};
/**
* Set the name of this data node.
* @param {string} value Name.
* @override
*/
goog.ds.AbstractFastDataNode.prototype.setDataName = function(value) {
this['__dataName'] = value;
};
/**
* Get the path leading to this data node.
* @return {string} Data path.
* @override
*/
goog.ds.AbstractFastDataNode.prototype.getDataPath = function() {
var parentPath;
if (this['__parent']) {
parentPath = this['__parent'].getDataPath() + goog.ds.STR_PATH_SEPARATOR;
} else {
parentPath = '';
}
return parentPath + this.getDataName();
};
/**
* Creates a new fast data node, using the properties of root.
* @param {Object} root JSON-like object to initialize data node from.
* @param {string} dataName Name of this data node.
* @param {goog.ds.DataNode=} opt_parent Parent of this data node.
* @extends {goog.ds.AbstractFastDataNode}
* @constructor
*/
goog.ds.FastDataNode = function(root, dataName, opt_parent) {
goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
this.extendWith(root);
};
goog.inherits(goog.ds.FastDataNode, goog.ds.AbstractFastDataNode);
/**
* Add all attributes of object to this data node.
* @param {Object} object Object to add attributes from.
* @protected
*/
goog.ds.FastDataNode.prototype.extendWith = function(object) {
for (var key in object) {
this[key] = object[key];
}
};
/**
* Creates a new FastDataNode structure initialized from object. This will
* return an instance of the most suitable sub-class of FastDataNode.
*
* You should not modify object after creating a fast data node from it
* or assume that changing object changes the data node. Doing so results
* in undefined behaviour.
*
* @param {Object|number|boolean|string} object Object to initialize data
* node from.
* @param {string} dataName Name of data node.
* @param {goog.ds.DataNode=} opt_parent Parent of data node.
* @return {goog.ds.AbstractFastDataNode} Data node representing object.
*/
goog.ds.FastDataNode.fromJs = function(object, dataName, opt_parent) {
if (goog.isArray(object)) {
return new goog.ds.FastListNode(object, dataName, opt_parent);
} else if (goog.isObject(object)) {
return new goog.ds.FastDataNode(object, dataName, opt_parent);
} else {
return new goog.ds.PrimitiveFastDataNode(object || !!object,
dataName,
opt_parent);
}
};
/**
* Static instance of an empty list.
* @type {goog.ds.EmptyNodeList}
* @private
*/
goog.ds.FastDataNode.emptyList_ = new goog.ds.EmptyNodeList();
/**
* Not supported for normal FastDataNodes.
* @param {*} value Value to set data node to.
* @override
*/
goog.ds.FastDataNode.prototype.set = function(value) {
throw 'Not implemented yet';
};
/** @override */
goog.ds.FastDataNode.prototype.getChildNodes = function(opt_selector) {
if (!opt_selector || opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
return this;
} else if (opt_selector.indexOf(goog.ds.STR_WILDCARD) == -1) {
var child = this.getChildNode(opt_selector);
return child ? new goog.ds.FastListNode([child], '') :
new goog.ds.EmptyNodeList();
} else {
throw Error('Unsupported selector: ' + opt_selector);
}
};
/**
* Makes sure that a named child is wrapped in a data node structure.
* @param {string} name Name of child to wrap.
* @private
*/
goog.ds.FastDataNode.prototype.wrapChild_ = function(name) {
var child = this[name];
if (child != null && !child.getDataName) {
this[name] = goog.ds.FastDataNode.fromJs(this[name], name, this);
}
};
/**
* Get a child node by name.
* @param {string} name Name of child node.
* @param {boolean=} opt_create Whether to create the child if it does not
* exist.
* @return {goog.ds.DataNode} Child node.
* @override
*/
goog.ds.FastDataNode.prototype.getChildNode = function(name, opt_create) {
this.wrapChild_(name);
// this[name] always is a data node object, so using "||" is fine.
var child = this[name] || null;
if (child == null && opt_create) {
child = new goog.ds.FastDataNode({}, name, this);
this[name] = child;
}
return child;
};
/**
* Sets a child node. Creates the child if it does not exist.
*
* Calling this function makes any child nodes previously obtained for name
* invalid. You should not use these child nodes but instead obtain a new
* instance by calling getChildNode.
*
* @override
*/
goog.ds.FastDataNode.prototype.setChildNode = function(name, value) {
if (value != null) {
this[name] = value;
} else {
delete this[name];
}
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath() +
goog.ds.STR_PATH_SEPARATOR + name);
return null;
};
/**
* Returns the value of a child node. By using this method you can avoid
* the need to create PrimitiveFastData nodes.
* @param {string} name Name of child node.
* @return {Object} Value of child node.
* @override
*/
goog.ds.FastDataNode.prototype.getChildNodeValue = function(name) {
var child = this[name];
if (child != null) {
return (child.getDataName ? child.get() : child);
} else {
return null;
}
};
/**
* Returns whether this data node is a list. Always returns false for
* instances of FastDataNode but may return true for subclasses.
* @return {boolean} Whether this data node is array-like.
* @override
*/
goog.ds.FastDataNode.prototype.isList = function() {
return false;
};
/**
* Returns a javascript object representation of this data node. You should
* not modify the object returned by this function.
* @return {Object} Javascript object representation of this data node.
*/
goog.ds.FastDataNode.prototype.getJsObject = function() {
var result = {};
for (var key in this) {
if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
result[key] = (this[key]['__dataName'] ? this[key].getJsObject() :
this[key]);
}
}
return result;
};
/**
* Creates a deep copy of this data node.
* @return {goog.ds.FastDataNode} Clone of this data node.
*/
goog.ds.FastDataNode.prototype.clone = function() {
return /** @type {goog.ds.FastDataNode} */(goog.ds.FastDataNode.fromJs(
this.getJsObject(), this.getDataName()));
};
/*
* Implementation of goog.ds.DataNodeList for FastDataNode.
*/
/**
* Adds a child to this data node.
* @param {goog.ds.DataNode} value Child node to add.
* @override
*/
goog.ds.FastDataNode.prototype.add = function(value) {
this.setChildNode(value.getDataName(), value);
};
/**
* Gets the value of this data node (if called without opt_key) or
* gets a child node (if called with opt_key).
* @param {string=} opt_key Name of child node.
* @return {*} This data node or a child node.
* @override
*/
goog.ds.FastDataNode.prototype.get = function(opt_key) {
if (!goog.isDef(opt_key)) {
// if there is no key, DataNode#get was called
return this;
} else {
return this.getChildNode(opt_key);
}
};
/**
* Gets a child node by index. This method has a complexity of O(n) where
* n is the number of children. If you need a faster implementation of this
* method, you should use goog.ds.FastListNode.
* @param {number} index Index of child node (starting from 0).
* @return {goog.ds.DataNode} Child node at specified index.
* @override
*/
goog.ds.FastDataNode.prototype.getByIndex = function(index) {
var i = 0;
for (var key in this) {
if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
if (i == index) {
this.wrapChild_(key);
return this[key];
}
++i;
}
}
return null;
};
/**
* Gets the number of child nodes. This method has a complexity of O(n) where
* n is the number of children. If you need a faster implementation of this
* method, you should use goog.ds.FastListNode.
* @return {number} Number of child nodes.
* @override
*/
goog.ds.FastDataNode.prototype.getCount = function() {
var count = 0;
for (var key in this) {
if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
++count;
}
}
// maybe cache this?
return count;
};
/**
* Sets a child node.
* @param {string} name Name of child node.
* @param {Object} value Value of child node.
* @override
*/
goog.ds.FastDataNode.prototype.setNode = function(name, value) {
this.setChildNode(name, value);
};
/**
* Removes a child node.
* @override
*/
goog.ds.FastDataNode.prototype.removeNode = function(name) {
delete this[name];
return false;
};
/**
* Creates a new data node wrapping a primitive value.
* @param {number|boolean|string} value Value the value to wrap.
* @param {string} dataName name Name of this data node.
* @param {goog.ds.DataNode=} opt_parent Parent of this data node.
* @extends {goog.ds.AbstractFastDataNode}
* @constructor
*/
goog.ds.PrimitiveFastDataNode = function(value, dataName, opt_parent) {
this.value_ = value;
goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
};
goog.inherits(goog.ds.PrimitiveFastDataNode, goog.ds.AbstractFastDataNode);
/**
* Returns the value of this data node.
* @return {(boolean|number|string)} Value of this data node.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.get = function() {
return this.value_;
};
/**
* Sets this data node to a new value.
* @param {*} value Value to set data node to.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.set = function(value) {
if (goog.isArray(value) || goog.isObject(value)) {
throw Error('can only set PrimitiveFastDataNode to primitive values');
}
this.value_ = value;
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
};
/**
* Returns child nodes of this data node. Always returns an unmodifiable,
* empty list.
* @return {goog.ds.DataNodeList} (Empty) list of child nodes.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.getChildNodes = function() {
return goog.ds.FastDataNode.emptyList_;
};
/**
* Get a child node by name. Always returns null.
* @param {string} name Name of child node.
* @return {goog.ds.DataNode} Child node.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.getChildNode = function(name) {
return null;
};
/**
* Returns the value of a child node. Always returns null.
* @param {string} name Name of child node.
* @return {Object} Value of child node.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.getChildNodeValue = function(name) {
return null;
};
/**
* Not supported by primitive data nodes.
* @param {string} name Name of child node.
* @param {Object} value Value of child node.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.setChildNode =
function(name, value) {
throw Error('Cannot set a child node for a PrimitiveFastDataNode');
};
/**
* Returns whether this data node is a list. Always returns false for
* instances of PrimitiveFastDataNode.
* @return {boolean} Whether this data node is array-like.
* @override
*/
goog.ds.PrimitiveFastDataNode.prototype.isList = function() {
return false;
};
/**
* Returns a javascript object representation of this data node. You should
* not modify the object returned by this function.
* @return {*} Javascript object representation of this data node.
*/
goog.ds.PrimitiveFastDataNode.prototype.getJsObject = function() {
return this.value_;
};
/**
* Creates a new list node from an array.
* @param {Array} values values hold by this list node.
* @param {string} dataName name of this node.
* @param {goog.ds.DataNode=} opt_parent parent of this node.
* @extends {goog.ds.AbstractFastDataNode}
* @constructor
*/
// TODO(arv): Use interfaces when available. This implements DataNodeList
// as well.
goog.ds.FastListNode = function(values, dataName, opt_parent) {
this.values_ = [];
for (var i = 0; i < values.length; ++i) {
var name = values[i].id || ('[' + i + ']');
this.values_.push(goog.ds.FastDataNode.fromJs(values[i], name, this));
if (values[i].id) {
if (!this.map_) {
this.map_ = {};
}
this.map_[values[i].id] = i;
}
}
goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
};
goog.inherits(goog.ds.FastListNode, goog.ds.AbstractFastDataNode);
/**
* Not supported for FastListNodes.
* @param {*} value Value to set data node to.
* @override
*/
goog.ds.FastListNode.prototype.set = function(value) {
throw Error('Cannot set a FastListNode to a new value');
};
/**
* Returns child nodes of this data node. Currently, only supports
* returning all children.
* @return {goog.ds.DataNodeList} List of child nodes.
* @override
*/
goog.ds.FastListNode.prototype.getChildNodes = function() {
return this;
};
/**
* Get a child node by name.
* @param {string} key Name of child node.
* @param {boolean=} opt_create Whether to create the child if it does not
* exist.
* @return {goog.ds.DataNode} Child node.
* @override
*/
goog.ds.FastListNode.prototype.getChildNode = function(key, opt_create) {
var index = this.getKeyAsNumber_(key);
if (index == null && this.map_) {
index = this.map_[key];
}
if (index != null && this.values_[index]) {
return this.values_[index];
} else if (opt_create) {
this.setChildNode(key, {});
return this.getChildNode(key);
} else {
return null;
}
};
/**
* Returns the value of a child node.
* @param {string} key Name of child node.
* @return {*} Value of child node.
* @override
*/
goog.ds.FastListNode.prototype.getChildNodeValue = function(key) {
var child = this.getChildNode(key);
return (child ? child.get() : null);
};
/**
* Tries to interpret key as a numeric index enclosed by square brakcets.
* @param {string} key Key that should be interpreted as a number.
* @return {?number} Numeric index or null if key is not of the form
* described above.
* @private
*/
goog.ds.FastListNode.prototype.getKeyAsNumber_ = function(key) {
if (key.charAt(0) == '[' && key.charAt(key.length - 1) == ']') {
return Number(key.substring(1, key.length - 1));
} else {
return null;
}
};
/**
* Sets a child node. Creates the child if it does not exist. To set
* children at a certain index, use a key of the form '[index]'. Note, that
* you can only set values at existing numeric indices. To add a new node
* to this list, you have to use the add method.
*
* Calling this function makes any child nodes previously obtained for name
* invalid. You should not use these child nodes but instead obtain a new
* instance by calling getChildNode.
*
* @override
*/
goog.ds.FastListNode.prototype.setChildNode = function(key, value) {
var count = this.values_.length;
if (value != null) {
if (!value.getDataName) {
value = goog.ds.FastDataNode.fromJs(value, key, this);
}
var index = this.getKeyAsNumber_(key);
if (index != null) {
if (index < 0 || index >= this.values_.length) {
throw Error('List index out of bounds: ' + index);
}
this.values_[key] = value;
} else {
if (!this.map_) {
this.map_ = {};
}
this.values_.push(value);
this.map_[key] = this.values_.length - 1;
}
} else {
this.removeNode(key);
}
var dm = goog.ds.DataManager.getInstance();
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR + key);
if (this.values_.length != count) {
this.listSizeChanged_();
}
return null;
};
/**
* Fire data changes that are appropriate when the size of this list changes.
* Should be called whenever the list size has changed.
* @private
*/
goog.ds.FastListNode.prototype.listSizeChanged_ = function() {
var dm = goog.ds.DataManager.getInstance();
dm.fireDataChange(this.getDataPath());
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR +
'count()');
};
/**
* Returns whether this data node is a list. Always returns true.
* @return {boolean} Whether this data node is array-like.
* @override
*/
goog.ds.FastListNode.prototype.isList = function() {
return true;
};
/**
* Returns a javascript object representation of this data node. You should
* not modify the object returned by this function.
* @return {Object} Javascript object representation of this data node.
*/
goog.ds.FastListNode.prototype.getJsObject = function() {
var result = [];
for (var i = 0; i < this.values_.length; ++i) {
result.push(this.values_[i].getJsObject());
}
return result;
};
/*
* Implementation of goog.ds.DataNodeList for FastListNode.
*/
/**
* Adds a child to this data node
* @param {goog.ds.DataNode} value Child node to add.
* @override
*/
goog.ds.FastListNode.prototype.add = function(value) {
if (!value.getDataName) {
value = goog.ds.FastDataNode.fromJs(value,
String('[' + (this.values_.length) + ']'), this);
}
this.values_.push(value);
var dm = goog.ds.DataManager.getInstance();
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR +
'[' + (this.values_.length - 1) + ']');
this.listSizeChanged_();
};
/**
* Gets the value of this data node (if called without opt_key) or
* gets a child node (if called with opt_key).
* @param {string=} opt_key Name of child node.
* @return {Array|goog.ds.DataNode} Array of child nodes (if called without
* opt_key), or a named child node otherwise.
* @override
*/
goog.ds.FastListNode.prototype.get = function(opt_key) {
// if there are no arguments, DataNode.get was called
if (!goog.isDef(opt_key)) {
return this.values_;
} else {
return this.getChildNode(opt_key);
}
};
/**
* Gets a child node by (numeric) index.
* @param {number} index Index of child node (starting from 0).
* @return {goog.ds.DataNode} Child node at specified index.
* @override
*/
goog.ds.FastListNode.prototype.getByIndex = function(index) {
var child = this.values_[index];
return (child != null ? child : null); // never return undefined
};
/**
* Gets the number of child nodes.
* @return {number} Number of child nodes.
* @override
*/
goog.ds.FastListNode.prototype.getCount = function() {
return this.values_.length;
};
/**
* Sets a child node.
* @param {string} name Name of child node.
* @param {Object} value Value of child node.
* @override
*/
goog.ds.FastListNode.prototype.setNode = function(name, value) {
throw Error('Setting child nodes of a FastListNode is not implemented, yet');
};
/**
* Removes a child node.
* @override
*/
goog.ds.FastListNode.prototype.removeNode = function(name) {
var index = this.getKeyAsNumber_(name);
if (index == null && this.map_) {
index = this.map_[name];
}
if (index != null) {
this.values_.splice(index, 1);
if (this.map_) {
var keyToDelete = null;
for (var key in this.map_) {
if (this.map_[key] == index) {
keyToDelete = key;
} else if (this.map_[key] > index) {
--this.map_[key];
}
}
if (keyToDelete) {
delete this.map_[keyToDelete];
}
}
var dm = goog.ds.DataManager.getInstance();
dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR +
'[' + index + ']');
this.listSizeChanged_();
}
return false;
};
/**
* Returns the index of a named child nodes. This method only works if
* this list uses mixed name/indexed lookup, i.e. if its child node have
* an 'id' attribute.
* @param {string} name Name of child node to determine index of.
* @return {number} Index of child node named name.
*/
goog.ds.FastListNode.prototype.indexOf = function(name) {
var index = this.getKeyAsNumber_(name);
if (index == null && this.map_) {
index = this.map_[name];
}
if (index == null) {
throw Error('Cannot determine index for: ' + name);
}
return /** @type {number} */(index);
};

View File

@@ -0,0 +1,460 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview An implementation of DataNode for wrapping JS data.
*
*/
goog.provide('goog.ds.JsDataSource');
goog.provide('goog.ds.JsPropertyDataSource');
goog.require('goog.ds.BaseDataNode');
goog.require('goog.ds.BasicNodeList');
goog.require('goog.ds.DataManager');
goog.require('goog.ds.EmptyNodeList');
goog.require('goog.ds.LoadState');
/**
* Data source whose backing is JavaScript data
*
* Names that are reserved for system use and shouldn't be used for data node
* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
* undefined if these names are used.
*
* @param {Object} root The root JS node.
* @param {string} dataName The name of this node relative to the parent node.
* @param {Object=} opt_parent Optional parent of this JsDataSource.
*
* implements goog.ds.DataNode.
* @constructor
* @extends {goog.ds.DataNode}
*/
// TODO(arv): Use interfaces when available.
goog.ds.JsDataSource = function(root, dataName, opt_parent) {
this.parent_ = opt_parent;
this.dataName_ = dataName;
this.setRoot(root);
};
/**
* The root JS object. Can be null.
* @type {*}
* @protected
* @suppress {underscore}
*/
goog.ds.JsDataSource.prototype.root_;
/**
* Sets the root JS object
* @param {Object} root The root JS object. Can be null.
*
* @protected
*/
goog.ds.JsDataSource.prototype.setRoot = function(root) {
this.root_ = root;
this.childNodeList_ = null;
};
/**
* Set this data source to use list semantics. List data sources:
* - Are assumed to have child nodes of all of the same type of data
* - Fire data changes on the root node of the list whenever children
* are added or removed
* @param {?boolean} isList True to use list semantics.
* @private
*/
goog.ds.JsDataSource.prototype.setIsList_ = function(isList) {
this.isList_ = isList;
};
/** @override */
goog.ds.JsDataSource.prototype.get = function() {
return !goog.isObject(this.root_) ? this.root_ : this.getChildNodes();
};
/**
* Set the value of the node
* @param {*} value The new value of the node.
* @override
*/
goog.ds.JsDataSource.prototype.set = function(value) {
if (value && goog.isObject(this.root_)) {
throw Error('Can\'t set group nodes to new values yet');
}
if (this.parent_) {
this.parent_.root_[this.dataName_] = value;
}
this.root_ = value;
this.childNodeList_ = null;
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
};
/**
* TODO(user) revisit lazy creation.
* @override
*/
goog.ds.JsDataSource.prototype.getChildNodes = function(opt_selector) {
if (!this.root_) {
return new goog.ds.EmptyNodeList();
}
if (!opt_selector || opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
this.createChildNodes_(false);
return this.childNodeList_;
} else if (opt_selector.indexOf(goog.ds.STR_WILDCARD) == -1) {
if (this.root_[opt_selector] != null) {
return new goog.ds.BasicNodeList([this.getChildNode(opt_selector)]);
} else {
return new goog.ds.EmptyNodeList();
}
} else {
throw Error('Selector not supported yet (' + opt_selector + ')');
}
};
/**
* Creates the DataNodeList with the child nodes for this element.
* Allows for only building list as needed.
*
* @param {boolean=} opt_force Whether to force recreating child nodes,
* defaults to false.
* @private
*/
goog.ds.JsDataSource.prototype.createChildNodes_ = function(opt_force) {
if (this.childNodeList_ && !opt_force) {
return;
}
if (!goog.isObject(this.root_)) {
this.childNodeList_ = new goog.ds.EmptyNodeList();
return;
}
var childNodeList = new goog.ds.BasicNodeList();
var newNode;
if (goog.isArray(this.root_)) {
var len = this.root_.length;
for (var i = 0; i < len; i++) {
// "id" is reserved node name that will map to a named child node
// TODO(user) Configurable logic for choosing id node
var node = this.root_[i];
var id = node.id;
var name = id != null ? String(id) : '[' + i + ']';
newNode = new goog.ds.JsDataSource(node, name, this);
childNodeList.add(newNode);
}
} else {
for (var name in this.root_) {
var obj = this.root_[name];
// If the node is already a datasource, then add it.
if (obj.getDataName) {
childNodeList.add(obj);
} else if (!goog.isFunction(obj)) {
newNode = new goog.ds.JsDataSource(obj, name, this);
childNodeList.add(newNode);
}
}
}
this.childNodeList_ = childNodeList;
};
/**
* Gets a named child node of the current node
* @param {string} name The node name.
* @param {boolean=} opt_canCreate If true, can create child node.
* @return {goog.ds.DataNode} The child node, or null if no node of
* this name exists.
* @override
*/
goog.ds.JsDataSource.prototype.getChildNode = function(name, opt_canCreate) {
if (!this.root_) {
return null;
}
var node = /** @type {goog.ds.DataNode} */ (this.getChildNodes().get(name));
if (!node && opt_canCreate) {
var newObj = {};
if (goog.isArray(this.root_)) {
newObj['id'] = name;
this.root_.push(newObj);
} else {
this.root_[name] = newObj;
}
node = new goog.ds.JsDataSource(newObj, name, this);
if (this.childNodeList_) {
this.childNodeList_.add(node);
}
}
return node;
};
/**
* Gets the value of a child node
* @param {string} name The node name.
* @return {Object} The value of the node, or null if no value or the child
* node doesn't exist.
* @override
*/
goog.ds.JsDataSource.prototype.getChildNodeValue = function(name) {
if (this.childNodeList_) {
var node = this.getChildNodes().get(name);
return node ? node.get() : null;
} else if (this.root_) {
return this.root_[name];
} else {
return null;
}
};
/**
* Sets a named child node of the current node.
* If value is null, removes the child node.
* @param {string} name The node name.
* @param {Object} value The value to set, can be DataNode, object,
* property, or null.
* @return {Object} The child node, if set.
* @override
*/
goog.ds.JsDataSource.prototype.setChildNode = function(name, value) {
var removedPath = null;
var node = null;
var addedNode = false;
// Set node to the DataNode to add - if the value isn't already a DataNode,
// creates a JsDataSource or JsPropertyDataSource wrapper
if (value != null) {
if (value.getDataName) {
// The value is a DataNode. We must update its parent.
node = value;
node.parent_ = this;
} else {
if (goog.isArray(value) || goog.isObject(value)) {
node = new goog.ds.JsDataSource(value, name, this);
} else {
node = new goog.ds.JsPropertyDataSource(
/** @type {goog.ds.DataNode} */ (this.root_), name, this);
}
}
}
// This logic will get cleaner once we can remove the backing array / object
// and just rely on the childNodeList_. This is needed until dependent code
// is cleaned up.
// TODO(user) Remove backing array / object and just use childNodeList_
if (goog.isArray(this.root_)) {
// To remove by name, need to create a map of the child nodes by ID
this.createChildNodes_();
var index = this.childNodeList_.indexOf(name);
if (value == null) {
// Remove the node
var nodeToRemove = this.childNodeList_.get(name);
if (nodeToRemove) {
removedPath = nodeToRemove.getDataPath();
}
this.root_.splice(index, 1);
} else {
// Add the node
if (index) {
this.root_[index] = value;
} else {
this.root_.push(value);
}
}
if (index == null) {
addedNode = true;
}
this.childNodeList_.setNode(name, /** @type {goog.ds.DataNode} */ (node));
} else if (goog.isObject(this.root_)) {
if (value == null) {
// Remove the node
this.createChildNodes_();
var nodeToRemove = this.childNodeList_.get(name);
if (nodeToRemove) {
removedPath = nodeToRemove.getDataPath();
}
delete this.root_[name];
} else {
// Add the node
if (!this.root_[name]) {
addedNode = true;
}
this.root_[name] = value;
}
// Only need to update childNodeList_ if has been created already
if (this.childNodeList_) {
this.childNodeList_.setNode(name, /** @type {goog.ds.DataNode} */ (node));
}
}
// Fire the event that the node changed
var dm = goog.ds.DataManager.getInstance();
if (node) {
dm.fireDataChange(node.getDataPath());
if (addedNode && this.isList()) {
dm.fireDataChange(this.getDataPath());
dm.fireDataChange(this.getDataPath() + '/count()');
}
} else if (removedPath) {
dm.fireDataChange(removedPath);
if (this.isList()) {
dm.fireDataChange(this.getDataPath());
dm.fireDataChange(this.getDataPath() + '/count()');
}
}
return node;
};
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
* @override
*/
goog.ds.JsDataSource.prototype.getDataName = function() {
return this.dataName_;
};
/**
* Setthe name of the node relative to the parent node
* @param {string} dataName The name of the node.
* @override
*/
goog.ds.JsDataSource.prototype.setDataName = function(dataName) {
this.dataName_ = dataName;
};
/**
* Gets the a qualified data path to this node
* @return {string} The data path.
* @override
*/
goog.ds.JsDataSource.prototype.getDataPath = function() {
var parentPath = '';
if (this.parent_) {
parentPath = this.parent_.getDataPath() + goog.ds.STR_PATH_SEPARATOR;
}
return parentPath + this.dataName_;
};
/**
* Load or reload the backing data for this node
* @override
*/
goog.ds.JsDataSource.prototype.load = function() {
// Nothing to do
};
/**
* Gets the state of the backing data for this node
* TODO(user) Discuss null value handling
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.JsDataSource.prototype.getLoadState = function() {
return (this.root_ == null) ? goog.ds.LoadState.NOT_LOADED :
goog.ds.LoadState.LOADED;
};
/**
* Whether the value of this node is a homogeneous list of data
* @return {boolean} True if a list.
* @override
*/
goog.ds.JsDataSource.prototype.isList = function() {
return this.isList_ != null ? this.isList_ : goog.isArray(this.root_);
};
/**
* Data source for JavaScript properties that arent objects. Contains reference
* to parent object so that you can set the vaule
*
* @param {goog.ds.DataNode} parent Parent object.
* @param {string} dataName Name of this property.
* @param {goog.ds.DataNode=} opt_parentDataNode The parent data node. If
* omitted, assumes that the parent object is the parent data node.
*
* @constructor
* @extends {goog.ds.BaseDataNode}
*/
goog.ds.JsPropertyDataSource = function(parent, dataName, opt_parentDataNode) {
goog.ds.BaseDataNode.call(this);
this.dataName_ = dataName;
this.parent_ = parent;
this.parentDataNode_ = opt_parentDataNode || this.parent_;
};
goog.inherits(goog.ds.JsPropertyDataSource, goog.ds.BaseDataNode);
/**
* Get the value of the node
* @return {Object} The value of the node, or null if no value.
*/
goog.ds.JsPropertyDataSource.prototype.get = function() {
return this.parent_[this.dataName_];
};
/**
* Set the value of the node
* @param {Object} value The new value of the node.
* @override
*/
goog.ds.JsPropertyDataSource.prototype.set = function(value) {
var oldValue = this.parent_[this.dataName_];
this.parent_[this.dataName_] = value;
if (oldValue != value) {
goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
}
};
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
* @override
*/
goog.ds.JsPropertyDataSource.prototype.getDataName = function() {
return this.dataName_;
};
/** @override */
goog.ds.JsPropertyDataSource.prototype.getParent = function() {
return this.parentDataNode_;
};

View File

@@ -0,0 +1,148 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Implementation of DataNode for wrapping JSON data.
*
*/
goog.provide('goog.ds.JsonDataSource');
goog.require('goog.Uri');
goog.require('goog.dom');
goog.require('goog.ds.DataManager');
goog.require('goog.ds.JsDataSource');
goog.require('goog.ds.LoadState');
goog.require('goog.ds.logger');
/**
* Data source whose backing is a JSON-like service, in which
* retreiving the resource specified by URL with the additional parameter
* callback. The resource retreived is executable JavaScript that
* makes a call to the named function with a JavaScript object literal
* as the only parameter.
*
* Example URI could be:
* http://www.google.com/data/search?q=monkey&callback=mycb
* which might return the JS:
* mycb({searchresults:
* [{uri: 'http://www.monkey.com', title: 'Site About Monkeys'}]});
*
* TODO(user): Evaluate using goog.net.Jsonp here.
*
* A URI of an empty string will mean that no request is made
* and the data source will be a data source with no child nodes
*
* @param {string|goog.Uri} uri URI for the request.
* @param {string} name Name of the datasource.
* @param {string=} opt_callbackParamName The parameter name that is used to
* specify the callback. Defaults to 'callback'.
*
* @extends {goog.ds.JsDataSource}
* @constructor
*/
goog.ds.JsonDataSource = function(uri, name, opt_callbackParamName) {
goog.ds.JsDataSource.call(this, null, name, null);
if (uri) {
this.uri_ = new goog.Uri(uri);
} else {
this.uri_ = null;
}
/**
* This is the callback parameter name that is added to the uri.
* @type {string}
* @private
*/
this.callbackParamName_ = opt_callbackParamName || 'callback';
};
goog.inherits(goog.ds.JsonDataSource, goog.ds.JsDataSource);
/**
* Default load state is NOT_LOADED
* @private
*/
goog.ds.JsonDataSource.prototype.loadState_ = goog.ds.LoadState.NOT_LOADED;
/**
* Map of all data sources, needed for callbacks
* Doesn't work unless dataSources is exported (not renamed)
*/
goog.ds.JsonDataSource['dataSources'] = {};
/**
* Load or reload the backing data for this node.
* Fires the JsonDataSource
* @override
*/
goog.ds.JsonDataSource.prototype.load = function() {
if (this.uri_) {
// NOTE: "dataSources" is expose above by name so that it will not be
// renamed. It should therefore be accessed via array notation here so
// that it also doesn't get renamed and stops the compiler from complaining
goog.ds.JsonDataSource['dataSources'][this.dataName_] = this;
goog.log.info(goog.ds.logger, 'Sending JS request for DataSource ' +
this.getDataName() + ' to ' + this.uri_);
this.loadState_ = goog.ds.LoadState.LOADING;
var uriToCall = new goog.Uri(this.uri_);
uriToCall.setParameterValue(this.callbackParamName_,
'JsonReceive.' + this.dataName_);
goog.global['JsonReceive'][this.dataName_] =
goog.bind(this.receiveData, this);
var scriptEl = goog.dom.createDom('script', {'src': uriToCall});
goog.dom.getElementsByTagNameAndClass('head')[0].appendChild(scriptEl);
} else {
this.root_ = {};
this.loadState_ = goog.ds.LoadState.NOT_LOADED;
}
};
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.JsonDataSource.prototype.getLoadState = function() {
return this.loadState_;
};
/**
* Receives data from a Json request
* @param {Object} obj The JSON data.
*/
goog.ds.JsonDataSource.prototype.receiveData = function(obj) {
this.setRoot(obj);
this.loadState_ = goog.ds.LoadState.LOADED;
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
};
/**
* Temp variable to hold callbacks
* until BUILD supports multiple externs.js files
*/
goog.global['JsonReceive'] = {};

View File

@@ -0,0 +1,195 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview
* DataSource implementation that uses XMLHttpRequest as transport, with
* response as serialized JS object (not required to be JSON) that can
* be evaluated and set to a variable.
*
* Response can have unexecutable starting/ending text to prevent inclusion
* using <script src="...">
*
*/
goog.provide('goog.ds.JsXmlHttpDataSource');
goog.require('goog.Uri');
goog.require('goog.ds.DataManager');
goog.require('goog.ds.FastDataNode');
goog.require('goog.ds.LoadState');
goog.require('goog.ds.logger');
goog.require('goog.events');
goog.require('goog.log');
goog.require('goog.net.EventType');
goog.require('goog.net.XhrIo');
/**
* Similar to JsonDataSource, with using XMLHttpRequest for transport
* Currently requires the result be a JS object that can be evaluated and
* set to a variable and doesn't require strict JSON notation.
*
* @param {string || goog.Uri} uri URI for the request.
* @param {string} name Name of the datasource.
* @param {string=} opt_startText Text to expect/strip before JS response.
* @param {string=} opt_endText Text to expect/strip after JS response.
* @param {boolean=} opt_usePost If true, use POST. Defaults to false (GET).
*
* @extends {goog.ds.FastDataNode}
* @constructor
*/
goog.ds.JsXmlHttpDataSource = function(uri, name, opt_startText, opt_endText,
opt_usePost) {
goog.ds.FastDataNode.call(this, {}, name, null);
if (uri) {
this.uri_ = new goog.Uri(uri);
this.xhr_ = new goog.net.XhrIo();
this.usePost_ = !!opt_usePost;
goog.events.listen(this.xhr_, goog.net.EventType.COMPLETE,
this.completed_, false, this);
} else {
this.uri_ = null;
}
this.startText_ = opt_startText;
this.endText_ = opt_endText;
};
goog.inherits(goog.ds.JsXmlHttpDataSource, goog.ds.FastDataNode);
/**
* Delimiter for start of JSON data in response.
* null = starts at first character of response
* @type {string|undefined}
* @private
*/
goog.ds.JsXmlHttpDataSource.prototype.startText_;
/**
* Delimiter for end of JSON data in response.
* null = ends at last character of response
* @type {string|undefined}
* @private
*/
goog.ds.JsXmlHttpDataSource.prototype.endText_;
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.JsXmlHttpDataSource.prototype.getLoadState = function() {
return this.loadState_;
};
/**
* Sets the request data. This can be used if it is required to
* send a specific body rather than build the body from the query
* parameters. Only used in POST requests.
* @param {string} data The data to send in the request body.
*/
goog.ds.JsXmlHttpDataSource.prototype.setQueryData = function(data) {
this.queryData_ = data;
};
/**
* Load or reload the backing data for this node.
* Fires the JsonDataSource
* @override
*/
goog.ds.JsXmlHttpDataSource.prototype.load = function() {
goog.log.info(goog.ds.logger, 'Sending JS request for DataSource ' +
this.getDataName() + ' to ' + this.uri_);
if (this.uri_) {
if (this.usePost_) {
var queryData;
if (!this.queryData_) {
queryData = this.uri_.getQueryData().toString();
} else {
queryData = this.queryData_;
}
var uriNoQuery = this.uri_.clone();
uriNoQuery.setQueryData(null);
this.xhr_.send(String(uriNoQuery), 'POST', queryData);
} else {
this.xhr_.send(String(this.uri_));
}
} else {
this.loadState_ = goog.ds.LoadState.NOT_LOADED;
}
};
/**
* Called on successful request.
* @private
*/
goog.ds.JsXmlHttpDataSource.prototype.success_ = function() {
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
};
/**
* Completed callback. Loads data if successful, otherwise sets
* state to FAILED
* @param {goog.events.Event} e Event object, Xhr is target.
* @private
*/
goog.ds.JsXmlHttpDataSource.prototype.completed_ = function(e) {
if (this.xhr_.isSuccess()) {
goog.log.info(goog.ds.logger,
'Got data for DataSource ' + this.getDataName());
var text = this.xhr_.getResponseText();
// Look for start and end token and trim text
if (this.startText_) {
var startpos = text.indexOf(this.startText_);
text = text.substring(startpos + this.startText_.length);
}
if (this.endText_) {
var endpos = text.lastIndexOf(this.endText_);
text = text.substring(0, endpos);
}
// Eval result
/** @preserveTry */
try {
var jsonObj = /** @type {Object} */ (eval('[' + text + '][0]'));
this.extendWith(jsonObj);
this.loadState_ = goog.ds.LoadState.LOADED;
}
catch (ex) {
// Invalid JS
this.loadState_ = goog.ds.LoadState.FAILED;
goog.log.error(goog.ds.logger, 'Failed to parse data: ' + ex.message);
}
// Call on a timer to avoid threading issues on IE.
goog.global.setTimeout(goog.bind(this.success_, this), 0);
} else {
goog.log.info(goog.ds.logger, 'Data retrieve failed for DataSource ' +
this.getDataName());
this.loadState_ = goog.ds.LoadState.FAILED;
}
};

View File

@@ -0,0 +1,415 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview
* Implementations of DataNode for wrapping XML data.
*
*/
goog.provide('goog.ds.XmlDataSource');
goog.provide('goog.ds.XmlHttpDataSource');
goog.require('goog.Uri');
goog.require('goog.dom.NodeType');
goog.require('goog.dom.xml');
goog.require('goog.ds.BasicNodeList');
goog.require('goog.ds.DataManager');
goog.require('goog.ds.LoadState');
goog.require('goog.ds.logger');
goog.require('goog.net.XhrIo');
goog.require('goog.string');
/**
* Data source whose backing is an xml node
*
* @param {Node} node The XML node. Can be null.
* @param {goog.ds.XmlDataSource} parent Parent of XML element. Can be null.
* @param {string=} opt_name The name of this node relative to the parent node.
*
* @extends {goog.ds.DataNode}
* @constructor
*/
// TODO(arv): Use interfaces when available.
goog.ds.XmlDataSource = function(node, parent, opt_name) {
this.parent_ = parent;
this.dataName_ = opt_name || (node ? node.nodeName : '');
this.setNode_(node);
};
/**
* Constant to select XML attributes for getChildNodes
* @type {string}
* @private
*/
goog.ds.XmlDataSource.ATTRIBUTE_SELECTOR_ = '@*';
/**
* Set the current root nodeof the data source.
* Can be an attribute node, text node, or element node
* @param {Node} node The node. Can be null.
*
* @private
*/
goog.ds.XmlDataSource.prototype.setNode_ = function(node) {
this.node_ = node;
if (node != null) {
switch (node.nodeType) {
case goog.dom.NodeType.ATTRIBUTE:
case goog.dom.NodeType.TEXT:
this.value_ = node.nodeValue;
break;
case goog.dom.NodeType.ELEMENT:
if (node.childNodes.length == 1 &&
node.firstChild.nodeType == goog.dom.NodeType.TEXT) {
this.value_ = node.firstChild.nodeValue;
}
}
}
};
/**
* Creates the DataNodeList with the child nodes for this element.
* Allows for only building list as needed.
*
* @private
*/
goog.ds.XmlDataSource.prototype.createChildNodes_ = function() {
if (this.childNodeList_) {
return;
}
var childNodeList = new goog.ds.BasicNodeList();
if (this.node_ != null) {
var childNodes = this.node_.childNodes;
for (var i = 0, childNode; childNode = childNodes[i]; i++) {
if (childNode.nodeType != goog.dom.NodeType.TEXT ||
!goog.ds.XmlDataSource.isEmptyTextNodeValue_(childNode.nodeValue)) {
var newNode = new goog.ds.XmlDataSource(childNode,
this, childNode.nodeName);
childNodeList.add(newNode);
}
}
}
this.childNodeList_ = childNodeList;
};
/**
* Creates the DataNodeList with the attributes for the element
* Allows for only building list as needed.
*
* @private
*/
goog.ds.XmlDataSource.prototype.createAttributes_ = function() {
if (this.attributes_) {
return;
}
var attributes = new goog.ds.BasicNodeList();
if (this.node_ != null && this.node_.attributes != null) {
var atts = this.node_.attributes;
for (var i = 0, att; att = atts[i]; i++) {
var newNode = new goog.ds.XmlDataSource(att, this, att.nodeName);
attributes.add(newNode);
}
}
this.attributes_ = attributes;
};
/**
* Get the value of the node
* @return {Object} The value of the node, or null if no value.
* @override
*/
goog.ds.XmlDataSource.prototype.get = function() {
this.createChildNodes_();
return this.value_;
};
/**
* Set the value of the node
* @param {*} value The new value of the node.
* @override
*/
goog.ds.XmlDataSource.prototype.set = function(value) {
throw Error('Can\'t set on XmlDataSource yet');
};
/** @override */
goog.ds.XmlDataSource.prototype.getChildNodes = function(opt_selector) {
if (opt_selector && opt_selector ==
goog.ds.XmlDataSource.ATTRIBUTE_SELECTOR_) {
this.createAttributes_();
return this.attributes_;
} else if (opt_selector == null ||
opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
this.createChildNodes_();
return this.childNodeList_;
} else {
throw Error('Unsupported selector');
}
};
/**
* Gets a named child node of the current node
* @param {string} name The node name.
* @return {goog.ds.DataNode} The child node, or null if
* no node of this name exists.
* @override
*/
goog.ds.XmlDataSource.prototype.getChildNode = function(name) {
if (goog.string.startsWith(name, goog.ds.STR_ATTRIBUTE_START)) {
var att = this.node_.getAttributeNode(name.substring(1));
return att ? new goog.ds.XmlDataSource(att, this) : null;
} else {
return /** @type {goog.ds.DataNode} */ (this.getChildNodes().get(name));
}
};
/**
* Gets the value of a child node
* @param {string} name The node name.
* @return {*} The value of the node, or null if no value or the child node
* doesn't exist.
* @override
*/
goog.ds.XmlDataSource.prototype.getChildNodeValue = function(name) {
if (goog.string.startsWith(name, goog.ds.STR_ATTRIBUTE_START)) {
var node = this.node_.getAttributeNode(name.substring(1));
return node ? node.nodeValue : null;
} else {
var node = this.getChildNode(name);
return node ? node.get() : null;
}
};
/**
* Get the name of the node relative to the parent node
* @return {string} The name of the node.
* @override
*/
goog.ds.XmlDataSource.prototype.getDataName = function() {
return this.dataName_;
};
/**
* Setthe name of the node relative to the parent node
* @param {string} name The name of the node.
* @override
*/
goog.ds.XmlDataSource.prototype.setDataName = function(name) {
this.dataName_ = name;
};
/**
* Gets the a qualified data path to this node
* @return {string} The data path.
* @override
*/
goog.ds.XmlDataSource.prototype.getDataPath = function() {
var parentPath = '';
if (this.parent_) {
parentPath = this.parent_.getDataPath() +
(this.dataName_.indexOf(goog.ds.STR_ARRAY_START) != -1 ? '' :
goog.ds.STR_PATH_SEPARATOR);
}
return parentPath + this.dataName_;
};
/**
* Load or reload the backing data for this node
* @override
*/
goog.ds.XmlDataSource.prototype.load = function() {
// Nothing to do
};
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.XmlDataSource.prototype.getLoadState = function() {
return this.node_ ? goog.ds.LoadState.LOADED : goog.ds.LoadState.NOT_LOADED;
};
/**
* Check whether a node is an empty text node. Nodes consisting of only white
* space (#x20, #xD, #xA, #x9) can generally be collapsed to a zero length
* text string.
* @param {string} str String to match.
* @return {boolean} True if string equates to empty text node.
* @private
*/
goog.ds.XmlDataSource.isEmptyTextNodeValue_ = function(str) {
return /^[\r\n\t ]*$/.test(str);
};
/**
* Creates an XML document with one empty node.
* Useful for places where you need a node that
* can be queried against.
*
* @return {Document} Document with one empty node.
* @private
*/
goog.ds.XmlDataSource.createChildlessDocument_ = function() {
return goog.dom.xml.createDocument('nothing');
};
/**
* Data source whose backing is an XMLHttpRequest,
*
* A URI of an empty string will mean that no request is made
* and the data source will be a single, empty node.
*
* @param {(string,goog.Uri)} uri URL of the XMLHttpRequest.
* @param {string} name Name of the datasource.
*
* implements goog.ds.XmlHttpDataSource.
* @constructor
* @extends {goog.ds.XmlDataSource}
*/
goog.ds.XmlHttpDataSource = function(uri, name) {
goog.ds.XmlDataSource.call(this, null, null, name);
if (uri) {
this.uri_ = new goog.Uri(uri);
} else {
this.uri_ = null;
}
};
goog.inherits(goog.ds.XmlHttpDataSource, goog.ds.XmlDataSource);
/**
* Default load state is NOT_LOADED
* @private
*/
goog.ds.XmlHttpDataSource.prototype.loadState_ = goog.ds.LoadState.NOT_LOADED;
/**
* Load or reload the backing data for this node.
* Fires the XMLHttpRequest
* @override
*/
goog.ds.XmlHttpDataSource.prototype.load = function() {
if (this.uri_) {
goog.log.info(goog.ds.logger, 'Sending XML request for DataSource ' +
this.getDataName() + ' to ' + this.uri_);
this.loadState_ = goog.ds.LoadState.LOADING;
goog.net.XhrIo.send(this.uri_, goog.bind(this.complete_, this));
} else {
this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
this.loadState_ = goog.ds.LoadState.NOT_LOADED;
}
};
/**
* Gets the state of the backing data for this node
* @return {goog.ds.LoadState} The state.
* @override
*/
goog.ds.XmlHttpDataSource.prototype.getLoadState = function() {
return this.loadState_;
};
/**
* Handles the completion of an XhrIo request. Dispatches to success or load
* based on the result.
* @param {!goog.events.Event} e The XhrIo event object.
* @private
*/
goog.ds.XmlHttpDataSource.prototype.complete_ = function(e) {
var xhr = /** @type {goog.net.XhrIo} */ (e.target);
if (xhr && xhr.isSuccess()) {
this.success_(xhr);
} else {
this.failure_();
}
};
/**
* Success result. Checks whether valid XML was returned
* and sets the XML and loadstate.
*
* @param {!goog.net.XhrIo} xhr The successful XhrIo object.
* @private
*/
goog.ds.XmlHttpDataSource.prototype.success_ = function(xhr) {
goog.log.info(goog.ds.logger,
'Got data for DataSource ' + this.getDataName());
var xml = xhr.getResponseXml();
// Fix for case where IE returns valid XML as text but
// doesn't parse by default
if (xml && !xml.hasChildNodes() &&
goog.isObject(xhr.getResponseText())) {
xml = goog.dom.xml.loadXml(xhr.getResponseText());
}
// Failure result
if (!xml || !xml.hasChildNodes()) {
this.loadState_ = goog.ds.LoadState.FAILED;
this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
} else {
this.loadState_ = goog.ds.LoadState.LOADED;
this.node_ = xml.documentElement;
}
if (this.getDataName()) {
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
}
};
/**
* Failure result
*
* @private
*/
goog.ds.XmlHttpDataSource.prototype.failure_ = function() {
goog.log.info(goog.ds.logger, 'Data retrieve failed for DataSource ' +
this.getDataName());
this.loadState_ = goog.ds.LoadState.FAILED;
this.node_ = goog.ds.XmlDataSource.createChildlessDocument_();
if (this.getDataName()) {
goog.ds.DataManager.getInstance().fireDataChange(this.getDataName());
}
};