// 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()) { parentPath = this.getParent().getDataPath() + (myName.indexOf( /** @suppress {missingRequire} */ goog.ds.STR_ARRAY_START) != -1 ? '' : /** @suppress {missingRequire} */ 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 * @return {?goog.ds.DataNode} * @protected */ goog.ds.BaseDataNode.prototype.getParent = goog.abstractMethod; /** * 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=} 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=} 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; };