657 lines
16 KiB
JavaScript
657 lines
16 KiB
JavaScript
// 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;
|
|
};
|