Path: blob/trunk/third_party/closure/goog/datasource/jsdatasource.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview An implementation of DataNode for wrapping JS data.16*17*/181920goog.provide('goog.ds.JsDataSource');21goog.provide('goog.ds.JsPropertyDataSource');2223goog.require('goog.ds.BaseDataNode');24goog.require('goog.ds.BasicNodeList');25goog.require('goog.ds.DataManager');26goog.require('goog.ds.DataNode');27goog.require('goog.ds.EmptyNodeList');28goog.require('goog.ds.LoadState');293031/**32* Data source whose backing is JavaScript data33*34* Names that are reserved for system use and shouldn't be used for data node35* names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is36* undefined if these names are used.37*38* @param {Object} root The root JS node.39* @param {string} dataName The name of this node relative to the parent node.40* @param {Object=} opt_parent Optional parent of this JsDataSource.41*42* implements goog.ds.DataNode.43* @constructor44* @extends {goog.ds.DataNode}45*/46// TODO(arv): Use interfaces when available.47goog.ds.JsDataSource = function(root, dataName, opt_parent) {48this.parent_ = opt_parent;49this.dataName_ = dataName;50this.setRoot(root);51};525354/**55* The root JS object. Can be null.56* @type {*}57* @protected58* @suppress {underscore|visibility}59*/60goog.ds.JsDataSource.prototype.root_;616263/**64* Sets the root JS object65* @param {Object} root The root JS object. Can be null.66*67* @protected68*/69goog.ds.JsDataSource.prototype.setRoot = function(root) {70this.root_ = root;71this.childNodeList_ = null;72};737475/**76* Set this data source to use list semantics. List data sources:77* - Are assumed to have child nodes of all of the same type of data78* - Fire data changes on the root node of the list whenever children79* are added or removed80* @param {?boolean} isList True to use list semantics.81* @private82*/83goog.ds.JsDataSource.prototype.setIsList_ = function(isList) {84this.isList_ = isList;85};868788/** @override */89goog.ds.JsDataSource.prototype.get = function() {90return !goog.isObject(this.root_) ? this.root_ : this.getChildNodes();91};929394/**95* Set the value of the node96* @param {*} value The new value of the node.97* @override98*/99goog.ds.JsDataSource.prototype.set = function(value) {100if (value && goog.isObject(this.root_)) {101throw Error('Can\'t set group nodes to new values yet');102}103104if (this.parent_) {105this.parent_.root_[this.dataName_] = value;106}107this.root_ = value;108this.childNodeList_ = null;109110goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());111};112113114/**115* TODO(user) revisit lazy creation.116* @override117*/118goog.ds.JsDataSource.prototype.getChildNodes = function(opt_selector) {119if (!this.root_) {120return new goog.ds.EmptyNodeList();121}122123if (!opt_selector || opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {124this.createChildNodes_(false);125return this.childNodeList_;126} else if (opt_selector.indexOf(goog.ds.STR_WILDCARD) == -1) {127if (this.root_[opt_selector] != null) {128return new goog.ds.BasicNodeList([this.getChildNode(opt_selector)]);129} else {130return new goog.ds.EmptyNodeList();131}132} else {133throw Error('Selector not supported yet (' + opt_selector + ')');134}135136};137138139/**140* Creates the DataNodeList with the child nodes for this element.141* Allows for only building list as needed.142*143* @param {boolean=} opt_force Whether to force recreating child nodes,144* defaults to false.145* @private146*/147goog.ds.JsDataSource.prototype.createChildNodes_ = function(opt_force) {148if (this.childNodeList_ && !opt_force) {149return;150}151152if (!goog.isObject(this.root_)) {153this.childNodeList_ = new goog.ds.EmptyNodeList();154return;155}156157var childNodeList = new goog.ds.BasicNodeList();158var newNode;159if (goog.isArray(this.root_)) {160var len = this.root_.length;161for (var i = 0; i < len; i++) {162// "id" is reserved node name that will map to a named child node163// TODO(user) Configurable logic for choosing id node164var node = this.root_[i];165var id = node.id;166var name = id != null ? String(id) : '[' + i + ']';167newNode = new goog.ds.JsDataSource(node, name, this);168childNodeList.add(newNode);169}170} else {171for (var name in this.root_) {172var obj = this.root_[name];173// If the node is already a datasource, then add it.174if (obj.getDataName) {175childNodeList.add(obj);176} else if (!goog.isFunction(obj)) {177newNode = new goog.ds.JsDataSource(obj, name, this);178childNodeList.add(newNode);179}180}181}182this.childNodeList_ = childNodeList;183};184185186/**187* Gets a named child node of the current node188* @param {string} name The node name.189* @param {boolean=} opt_canCreate If true, can create child node.190* @return {goog.ds.DataNode} The child node, or null if no node of191* this name exists.192* @override193*/194goog.ds.JsDataSource.prototype.getChildNode = function(name, opt_canCreate) {195if (!this.root_) {196return null;197}198var node = /** @type {goog.ds.DataNode} */ (this.getChildNodes().get(name));199if (!node && opt_canCreate) {200var newObj = {};201if (goog.isArray(this.root_)) {202newObj['id'] = name;203this.root_.push(newObj);204} else {205this.root_[name] = newObj;206}207node = new goog.ds.JsDataSource(newObj, name, this);208if (this.childNodeList_) {209this.childNodeList_.add(node);210}211}212return node;213};214215216/**217* Gets the value of a child node218* @param {string} name The node name.219* @return {Object} The value of the node, or null if no value or the child220* node doesn't exist.221* @override222*/223goog.ds.JsDataSource.prototype.getChildNodeValue = function(name) {224if (this.childNodeList_) {225var node = this.getChildNodes().get(name);226return node ? node.get() : null;227} else if (this.root_) {228return this.root_[name];229} else {230return null;231}232};233234235/**236* Sets a named child node of the current node.237* If value is null, removes the child node.238* @param {string} name The node name.239* @param {Object} value The value to set, can be DataNode, object,240* property, or null.241* @return {Object} The child node, if set.242* @override243*/244goog.ds.JsDataSource.prototype.setChildNode = function(name, value) {245var removedPath = null;246var node = null;247var addedNode = false;248249// Set node to the DataNode to add - if the value isn't already a DataNode,250// creates a JsDataSource or JsPropertyDataSource wrapper251if (value != null) {252if (value.getDataName) {253// The value is a DataNode. We must update its parent.254node = value;255node.parent_ = this;256} else {257if (goog.isArray(value) || goog.isObject(value)) {258node = new goog.ds.JsDataSource(value, name, this);259} else {260node = new goog.ds.JsPropertyDataSource(261/** @type {goog.ds.DataNode} */ (this.root_), name, this);262}263}264}265266// This logic will get cleaner once we can remove the backing array / object267// and just rely on the childNodeList_. This is needed until dependent code268// is cleaned up.269// TODO(user) Remove backing array / object and just use childNodeList_270271if (goog.isArray(this.root_)) {272// To remove by name, need to create a map of the child nodes by ID273this.createChildNodes_();274var index = this.childNodeList_.indexOf(name);275if (value == null) {276// Remove the node277var nodeToRemove = this.childNodeList_.get(name);278if (nodeToRemove) {279removedPath = nodeToRemove.getDataPath();280}281this.root_.splice(index, 1);282} else {283// Add the node284if (index) {285this.root_[index] = value;286} else {287this.root_.push(value);288}289}290if (index == null) {291addedNode = true;292}293this.childNodeList_.setNode(name, /** @type {goog.ds.DataNode} */ (node));294} else if (goog.isObject(this.root_)) {295if (value == null) {296// Remove the node297this.createChildNodes_();298var nodeToRemove = this.childNodeList_.get(name);299if (nodeToRemove) {300removedPath = nodeToRemove.getDataPath();301}302delete this.root_[name];303} else {304// Add the node305if (!this.root_[name]) {306addedNode = true;307}308this.root_[name] = value;309}310// Only need to update childNodeList_ if has been created already311if (this.childNodeList_) {312this.childNodeList_.setNode(name, /** @type {goog.ds.DataNode} */ (node));313}314}315316// Fire the event that the node changed317var dm = goog.ds.DataManager.getInstance();318if (node) {319dm.fireDataChange(node.getDataPath());320if (addedNode && this.isList()) {321dm.fireDataChange(this.getDataPath());322dm.fireDataChange(this.getDataPath() + '/count()');323}324} else if (removedPath) {325dm.fireDataChange(removedPath);326if (this.isList()) {327dm.fireDataChange(this.getDataPath());328dm.fireDataChange(this.getDataPath() + '/count()');329}330}331return node;332};333334335/**336* Get the name of the node relative to the parent node337* @return {string} The name of the node.338* @override339*/340goog.ds.JsDataSource.prototype.getDataName = function() {341return this.dataName_;342};343344345/**346* Setthe name of the node relative to the parent node347* @param {string} dataName The name of the node.348* @override349*/350goog.ds.JsDataSource.prototype.setDataName = function(dataName) {351this.dataName_ = dataName;352};353354355/**356* Gets the a qualified data path to this node357* @return {string} The data path.358* @override359*/360goog.ds.JsDataSource.prototype.getDataPath = function() {361var parentPath = '';362if (this.parent_) {363parentPath = this.parent_.getDataPath() + goog.ds.STR_PATH_SEPARATOR;364}365366return parentPath + this.dataName_;367};368369370/**371* Load or reload the backing data for this node372* @override373*/374goog.ds.JsDataSource.prototype.load = function() {375// Nothing to do376};377378379/**380* Gets the state of the backing data for this node381* TODO(user) Discuss null value handling382* @return {goog.ds.LoadState} The state.383* @override384*/385goog.ds.JsDataSource.prototype.getLoadState = function() {386return (this.root_ == null) ? goog.ds.LoadState.NOT_LOADED :387goog.ds.LoadState.LOADED;388};389390391/**392* Whether the value of this node is a homogeneous list of data393* @return {boolean} True if a list.394* @override395*/396goog.ds.JsDataSource.prototype.isList = function() {397return this.isList_ != null ? this.isList_ : goog.isArray(this.root_);398};399400401402/**403* Data source for JavaScript properties that arent objects. Contains reference404* to parent object so that you can set the vaule405*406* @param {goog.ds.DataNode} parent Parent object.407* @param {string} dataName Name of this property.408* @param {goog.ds.DataNode=} opt_parentDataNode The parent data node. If409* omitted, assumes that the parent object is the parent data node.410*411* @constructor412* @extends {goog.ds.BaseDataNode}413* @final414*/415goog.ds.JsPropertyDataSource = function(parent, dataName, opt_parentDataNode) {416goog.ds.BaseDataNode.call(this);417this.dataName_ = dataName;418this.parent_ = parent;419this.parentDataNode_ = opt_parentDataNode || this.parent_;420};421goog.inherits(goog.ds.JsPropertyDataSource, goog.ds.BaseDataNode);422423424/**425* Get the value of the node426* @return {Object} The value of the node, or null if no value.427*/428goog.ds.JsPropertyDataSource.prototype.get = function() {429return this.parent_[this.dataName_];430};431432433/**434* Set the value of the node435* @param {Object} value The new value of the node.436* @override437*/438goog.ds.JsPropertyDataSource.prototype.set = function(value) {439var oldValue = this.parent_[this.dataName_];440this.parent_[this.dataName_] = value;441442if (oldValue != value) {443goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());444}445};446447448/**449* Get the name of the node relative to the parent node450* @return {string} The name of the node.451* @override452*/453goog.ds.JsPropertyDataSource.prototype.getDataName = function() {454return this.dataName_;455};456457458/** @override */459goog.ds.JsPropertyDataSource.prototype.getParent = function() {460return this.parentDataNode_;461};462463464