123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- // 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.DataNode');
- 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|visibility}
- */
- 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}
- * @final
- */
- 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_;
- };
|