jsdatasource.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview An implementation of DataNode for wrapping JS data.
  16. *
  17. */
  18. goog.provide('goog.ds.JsDataSource');
  19. goog.provide('goog.ds.JsPropertyDataSource');
  20. goog.require('goog.ds.BaseDataNode');
  21. goog.require('goog.ds.BasicNodeList');
  22. goog.require('goog.ds.DataManager');
  23. goog.require('goog.ds.DataNode');
  24. goog.require('goog.ds.EmptyNodeList');
  25. goog.require('goog.ds.LoadState');
  26. /**
  27. * Data source whose backing is JavaScript data
  28. *
  29. * Names that are reserved for system use and shouldn't be used for data node
  30. * names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
  31. * undefined if these names are used.
  32. *
  33. * @param {Object} root The root JS node.
  34. * @param {string} dataName The name of this node relative to the parent node.
  35. * @param {Object=} opt_parent Optional parent of this JsDataSource.
  36. *
  37. * implements goog.ds.DataNode.
  38. * @constructor
  39. * @extends {goog.ds.DataNode}
  40. */
  41. // TODO(arv): Use interfaces when available.
  42. goog.ds.JsDataSource = function(root, dataName, opt_parent) {
  43. this.parent_ = opt_parent;
  44. this.dataName_ = dataName;
  45. this.setRoot(root);
  46. };
  47. /**
  48. * The root JS object. Can be null.
  49. * @type {*}
  50. * @protected
  51. * @suppress {underscore|visibility}
  52. */
  53. goog.ds.JsDataSource.prototype.root_;
  54. /**
  55. * Sets the root JS object
  56. * @param {Object} root The root JS object. Can be null.
  57. *
  58. * @protected
  59. */
  60. goog.ds.JsDataSource.prototype.setRoot = function(root) {
  61. this.root_ = root;
  62. this.childNodeList_ = null;
  63. };
  64. /**
  65. * Set this data source to use list semantics. List data sources:
  66. * - Are assumed to have child nodes of all of the same type of data
  67. * - Fire data changes on the root node of the list whenever children
  68. * are added or removed
  69. * @param {?boolean} isList True to use list semantics.
  70. * @private
  71. */
  72. goog.ds.JsDataSource.prototype.setIsList_ = function(isList) {
  73. this.isList_ = isList;
  74. };
  75. /** @override */
  76. goog.ds.JsDataSource.prototype.get = function() {
  77. return !goog.isObject(this.root_) ? this.root_ : this.getChildNodes();
  78. };
  79. /**
  80. * Set the value of the node
  81. * @param {*} value The new value of the node.
  82. * @override
  83. */
  84. goog.ds.JsDataSource.prototype.set = function(value) {
  85. if (value && goog.isObject(this.root_)) {
  86. throw Error('Can\'t set group nodes to new values yet');
  87. }
  88. if (this.parent_) {
  89. this.parent_.root_[this.dataName_] = value;
  90. }
  91. this.root_ = value;
  92. this.childNodeList_ = null;
  93. goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
  94. };
  95. /**
  96. * TODO(user) revisit lazy creation.
  97. * @override
  98. */
  99. goog.ds.JsDataSource.prototype.getChildNodes = function(opt_selector) {
  100. if (!this.root_) {
  101. return new goog.ds.EmptyNodeList();
  102. }
  103. if (!opt_selector || opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
  104. this.createChildNodes_(false);
  105. return this.childNodeList_;
  106. } else if (opt_selector.indexOf(goog.ds.STR_WILDCARD) == -1) {
  107. if (this.root_[opt_selector] != null) {
  108. return new goog.ds.BasicNodeList([this.getChildNode(opt_selector)]);
  109. } else {
  110. return new goog.ds.EmptyNodeList();
  111. }
  112. } else {
  113. throw Error('Selector not supported yet (' + opt_selector + ')');
  114. }
  115. };
  116. /**
  117. * Creates the DataNodeList with the child nodes for this element.
  118. * Allows for only building list as needed.
  119. *
  120. * @param {boolean=} opt_force Whether to force recreating child nodes,
  121. * defaults to false.
  122. * @private
  123. */
  124. goog.ds.JsDataSource.prototype.createChildNodes_ = function(opt_force) {
  125. if (this.childNodeList_ && !opt_force) {
  126. return;
  127. }
  128. if (!goog.isObject(this.root_)) {
  129. this.childNodeList_ = new goog.ds.EmptyNodeList();
  130. return;
  131. }
  132. var childNodeList = new goog.ds.BasicNodeList();
  133. var newNode;
  134. if (goog.isArray(this.root_)) {
  135. var len = this.root_.length;
  136. for (var i = 0; i < len; i++) {
  137. // "id" is reserved node name that will map to a named child node
  138. // TODO(user) Configurable logic for choosing id node
  139. var node = this.root_[i];
  140. var id = node.id;
  141. var name = id != null ? String(id) : '[' + i + ']';
  142. newNode = new goog.ds.JsDataSource(node, name, this);
  143. childNodeList.add(newNode);
  144. }
  145. } else {
  146. for (var name in this.root_) {
  147. var obj = this.root_[name];
  148. // If the node is already a datasource, then add it.
  149. if (obj.getDataName) {
  150. childNodeList.add(obj);
  151. } else if (!goog.isFunction(obj)) {
  152. newNode = new goog.ds.JsDataSource(obj, name, this);
  153. childNodeList.add(newNode);
  154. }
  155. }
  156. }
  157. this.childNodeList_ = childNodeList;
  158. };
  159. /**
  160. * Gets a named child node of the current node
  161. * @param {string} name The node name.
  162. * @param {boolean=} opt_canCreate If true, can create child node.
  163. * @return {goog.ds.DataNode} The child node, or null if no node of
  164. * this name exists.
  165. * @override
  166. */
  167. goog.ds.JsDataSource.prototype.getChildNode = function(name, opt_canCreate) {
  168. if (!this.root_) {
  169. return null;
  170. }
  171. var node = /** @type {goog.ds.DataNode} */ (this.getChildNodes().get(name));
  172. if (!node && opt_canCreate) {
  173. var newObj = {};
  174. if (goog.isArray(this.root_)) {
  175. newObj['id'] = name;
  176. this.root_.push(newObj);
  177. } else {
  178. this.root_[name] = newObj;
  179. }
  180. node = new goog.ds.JsDataSource(newObj, name, this);
  181. if (this.childNodeList_) {
  182. this.childNodeList_.add(node);
  183. }
  184. }
  185. return node;
  186. };
  187. /**
  188. * Gets the value of a child node
  189. * @param {string} name The node name.
  190. * @return {Object} The value of the node, or null if no value or the child
  191. * node doesn't exist.
  192. * @override
  193. */
  194. goog.ds.JsDataSource.prototype.getChildNodeValue = function(name) {
  195. if (this.childNodeList_) {
  196. var node = this.getChildNodes().get(name);
  197. return node ? node.get() : null;
  198. } else if (this.root_) {
  199. return this.root_[name];
  200. } else {
  201. return null;
  202. }
  203. };
  204. /**
  205. * Sets a named child node of the current node.
  206. * If value is null, removes the child node.
  207. * @param {string} name The node name.
  208. * @param {Object} value The value to set, can be DataNode, object,
  209. * property, or null.
  210. * @return {Object} The child node, if set.
  211. * @override
  212. */
  213. goog.ds.JsDataSource.prototype.setChildNode = function(name, value) {
  214. var removedPath = null;
  215. var node = null;
  216. var addedNode = false;
  217. // Set node to the DataNode to add - if the value isn't already a DataNode,
  218. // creates a JsDataSource or JsPropertyDataSource wrapper
  219. if (value != null) {
  220. if (value.getDataName) {
  221. // The value is a DataNode. We must update its parent.
  222. node = value;
  223. node.parent_ = this;
  224. } else {
  225. if (goog.isArray(value) || goog.isObject(value)) {
  226. node = new goog.ds.JsDataSource(value, name, this);
  227. } else {
  228. node = new goog.ds.JsPropertyDataSource(
  229. /** @type {goog.ds.DataNode} */ (this.root_), name, this);
  230. }
  231. }
  232. }
  233. // This logic will get cleaner once we can remove the backing array / object
  234. // and just rely on the childNodeList_. This is needed until dependent code
  235. // is cleaned up.
  236. // TODO(user) Remove backing array / object and just use childNodeList_
  237. if (goog.isArray(this.root_)) {
  238. // To remove by name, need to create a map of the child nodes by ID
  239. this.createChildNodes_();
  240. var index = this.childNodeList_.indexOf(name);
  241. if (value == null) {
  242. // Remove the node
  243. var nodeToRemove = this.childNodeList_.get(name);
  244. if (nodeToRemove) {
  245. removedPath = nodeToRemove.getDataPath();
  246. }
  247. this.root_.splice(index, 1);
  248. } else {
  249. // Add the node
  250. if (index) {
  251. this.root_[index] = value;
  252. } else {
  253. this.root_.push(value);
  254. }
  255. }
  256. if (index == null) {
  257. addedNode = true;
  258. }
  259. this.childNodeList_.setNode(name, /** @type {goog.ds.DataNode} */ (node));
  260. } else if (goog.isObject(this.root_)) {
  261. if (value == null) {
  262. // Remove the node
  263. this.createChildNodes_();
  264. var nodeToRemove = this.childNodeList_.get(name);
  265. if (nodeToRemove) {
  266. removedPath = nodeToRemove.getDataPath();
  267. }
  268. delete this.root_[name];
  269. } else {
  270. // Add the node
  271. if (!this.root_[name]) {
  272. addedNode = true;
  273. }
  274. this.root_[name] = value;
  275. }
  276. // Only need to update childNodeList_ if has been created already
  277. if (this.childNodeList_) {
  278. this.childNodeList_.setNode(name, /** @type {goog.ds.DataNode} */ (node));
  279. }
  280. }
  281. // Fire the event that the node changed
  282. var dm = goog.ds.DataManager.getInstance();
  283. if (node) {
  284. dm.fireDataChange(node.getDataPath());
  285. if (addedNode && this.isList()) {
  286. dm.fireDataChange(this.getDataPath());
  287. dm.fireDataChange(this.getDataPath() + '/count()');
  288. }
  289. } else if (removedPath) {
  290. dm.fireDataChange(removedPath);
  291. if (this.isList()) {
  292. dm.fireDataChange(this.getDataPath());
  293. dm.fireDataChange(this.getDataPath() + '/count()');
  294. }
  295. }
  296. return node;
  297. };
  298. /**
  299. * Get the name of the node relative to the parent node
  300. * @return {string} The name of the node.
  301. * @override
  302. */
  303. goog.ds.JsDataSource.prototype.getDataName = function() {
  304. return this.dataName_;
  305. };
  306. /**
  307. * Setthe name of the node relative to the parent node
  308. * @param {string} dataName The name of the node.
  309. * @override
  310. */
  311. goog.ds.JsDataSource.prototype.setDataName = function(dataName) {
  312. this.dataName_ = dataName;
  313. };
  314. /**
  315. * Gets the a qualified data path to this node
  316. * @return {string} The data path.
  317. * @override
  318. */
  319. goog.ds.JsDataSource.prototype.getDataPath = function() {
  320. var parentPath = '';
  321. if (this.parent_) {
  322. parentPath = this.parent_.getDataPath() + goog.ds.STR_PATH_SEPARATOR;
  323. }
  324. return parentPath + this.dataName_;
  325. };
  326. /**
  327. * Load or reload the backing data for this node
  328. * @override
  329. */
  330. goog.ds.JsDataSource.prototype.load = function() {
  331. // Nothing to do
  332. };
  333. /**
  334. * Gets the state of the backing data for this node
  335. * TODO(user) Discuss null value handling
  336. * @return {goog.ds.LoadState} The state.
  337. * @override
  338. */
  339. goog.ds.JsDataSource.prototype.getLoadState = function() {
  340. return (this.root_ == null) ? goog.ds.LoadState.NOT_LOADED :
  341. goog.ds.LoadState.LOADED;
  342. };
  343. /**
  344. * Whether the value of this node is a homogeneous list of data
  345. * @return {boolean} True if a list.
  346. * @override
  347. */
  348. goog.ds.JsDataSource.prototype.isList = function() {
  349. return this.isList_ != null ? this.isList_ : goog.isArray(this.root_);
  350. };
  351. /**
  352. * Data source for JavaScript properties that arent objects. Contains reference
  353. * to parent object so that you can set the vaule
  354. *
  355. * @param {goog.ds.DataNode} parent Parent object.
  356. * @param {string} dataName Name of this property.
  357. * @param {goog.ds.DataNode=} opt_parentDataNode The parent data node. If
  358. * omitted, assumes that the parent object is the parent data node.
  359. *
  360. * @constructor
  361. * @extends {goog.ds.BaseDataNode}
  362. * @final
  363. */
  364. goog.ds.JsPropertyDataSource = function(parent, dataName, opt_parentDataNode) {
  365. goog.ds.BaseDataNode.call(this);
  366. this.dataName_ = dataName;
  367. this.parent_ = parent;
  368. this.parentDataNode_ = opt_parentDataNode || this.parent_;
  369. };
  370. goog.inherits(goog.ds.JsPropertyDataSource, goog.ds.BaseDataNode);
  371. /**
  372. * Get the value of the node
  373. * @return {Object} The value of the node, or null if no value.
  374. */
  375. goog.ds.JsPropertyDataSource.prototype.get = function() {
  376. return this.parent_[this.dataName_];
  377. };
  378. /**
  379. * Set the value of the node
  380. * @param {Object} value The new value of the node.
  381. * @override
  382. */
  383. goog.ds.JsPropertyDataSource.prototype.set = function(value) {
  384. var oldValue = this.parent_[this.dataName_];
  385. this.parent_[this.dataName_] = value;
  386. if (oldValue != value) {
  387. goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
  388. }
  389. };
  390. /**
  391. * Get the name of the node relative to the parent node
  392. * @return {string} The name of the node.
  393. * @override
  394. */
  395. goog.ds.JsPropertyDataSource.prototype.getDataName = function() {
  396. return this.dataName_;
  397. };
  398. /** @override */
  399. goog.ds.JsPropertyDataSource.prototype.getParent = function() {
  400. return this.parentDataNode_;
  401. };