blob: 4cb0db6fd388d8a07fb5d89a546e9742a9754d90 [file] [log] [blame]
// 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
* @final
*/
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}
* @final
*/
var nodeCreator = function() {};
nodeCreator.prototype = node;
var newNode = new nodeCreator();
newNode.getDataName = function() {
return name;
};
return newNode;
};