datasource.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  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 Generic rich data access API.
  16. *
  17. * Abstraction for data sources that allows listening for changes at different
  18. * levels of the data tree and updating the data via XHR requests
  19. *
  20. */
  21. goog.provide('goog.ds.BaseDataNode');
  22. goog.provide('goog.ds.BasicNodeList');
  23. goog.provide('goog.ds.DataNode');
  24. goog.provide('goog.ds.DataNodeList');
  25. goog.provide('goog.ds.EmptyNodeList');
  26. goog.provide('goog.ds.LoadState');
  27. goog.provide('goog.ds.SortedNodeList');
  28. goog.provide('goog.ds.Util');
  29. goog.provide('goog.ds.logger');
  30. goog.require('goog.array');
  31. goog.require('goog.log');
  32. /**
  33. * Interface for node in rich data tree.
  34. *
  35. * Names that are reserved for system use and shouldn't be used for data node
  36. * names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
  37. * undefined if these names are used.
  38. *
  39. * @constructor
  40. */
  41. goog.ds.DataNode = function() {};
  42. /**
  43. * Get the value of the node
  44. * @param {...?} var_args Do not check arity of arguments, because
  45. * some subclasses require args.
  46. * @return {*} The value of the node, or null if no value.
  47. */
  48. goog.ds.DataNode.prototype.get = goog.abstractMethod;
  49. /**
  50. * Set the value of the node
  51. * @param {*} value The new value of the node.
  52. */
  53. goog.ds.DataNode.prototype.set = goog.abstractMethod;
  54. /**
  55. * Gets all of the child nodes of the current node.
  56. * Should return an empty DataNode list if no child nodes.
  57. * @param {string=} opt_selector String selector to choose child nodes.
  58. * @return {!goog.ds.DataNodeList} The child nodes.
  59. */
  60. goog.ds.DataNode.prototype.getChildNodes = goog.abstractMethod;
  61. /**
  62. * Gets a named child node of the current node
  63. * @param {string} name The node name.
  64. * @param {boolean=} opt_canCreate Whether to create a child node if it does not
  65. * exist.
  66. * @return {goog.ds.DataNode} The child node, or null
  67. * if no node of this name exists.
  68. */
  69. goog.ds.DataNode.prototype.getChildNode = goog.abstractMethod;
  70. /**
  71. * Gets the value of a child node
  72. * @param {string} name The node name.
  73. * @return {*} The value of the node, or null if no value or the child node
  74. * doesn't exist.
  75. */
  76. goog.ds.DataNode.prototype.getChildNodeValue = goog.abstractMethod;
  77. /**
  78. * Sets a named child node of the current node.
  79. *
  80. * @param {string} name The node name.
  81. * @param {Object} value The value to set, can be DataNode, object, property,
  82. * or null. If value is null, removes the child node.
  83. * @return {Object} The child node, if the node was set.
  84. */
  85. goog.ds.DataNode.prototype.setChildNode = goog.abstractMethod;
  86. /**
  87. * Get the name of the node relative to the parent node
  88. * @return {string} The name of the node.
  89. */
  90. goog.ds.DataNode.prototype.getDataName = goog.abstractMethod;
  91. /**
  92. * Set the name of the node relative to the parent node
  93. * @param {string} name The name of the node.
  94. */
  95. goog.ds.DataNode.prototype.setDataName = goog.abstractMethod;
  96. /**
  97. * Gets the a qualified data path to this node
  98. * @return {string} The data path.
  99. */
  100. goog.ds.DataNode.prototype.getDataPath = goog.abstractMethod;
  101. /**
  102. * Load or reload the backing data for this node
  103. */
  104. goog.ds.DataNode.prototype.load = goog.abstractMethod;
  105. /**
  106. * Gets the state of the backing data for this node
  107. * @return {goog.ds.LoadState} The state.
  108. */
  109. goog.ds.DataNode.prototype.getLoadState = goog.abstractMethod;
  110. /**
  111. * Whether the value of this node is a homogeneous list of data
  112. * @return {boolean} True if a list.
  113. */
  114. goog.ds.DataNode.prototype.isList = goog.abstractMethod;
  115. /**
  116. * Enum for load state of a DataNode.
  117. * @enum {string}
  118. */
  119. goog.ds.LoadState = {
  120. LOADED: 'LOADED',
  121. LOADING: 'LOADING',
  122. FAILED: 'FAILED',
  123. NOT_LOADED: 'NOT_LOADED'
  124. };
  125. /**
  126. * Base class for data node functionality, has default implementations for
  127. * many of the functions.
  128. *
  129. * implements {goog.ds.DataNode}
  130. * @constructor
  131. */
  132. goog.ds.BaseDataNode = function() {};
  133. /**
  134. * Set the value of the node
  135. * @param {Object} value The new value of the node.
  136. */
  137. goog.ds.BaseDataNode.prototype.set = goog.abstractMethod;
  138. /**
  139. * Gets all of the child nodes of the current node.
  140. * Should return an empty DataNode list if no child nodes.
  141. * @param {string=} opt_selector String selector to choose child nodes.
  142. * @return {!goog.ds.DataNodeList} The child nodes.
  143. */
  144. goog.ds.BaseDataNode.prototype.getChildNodes = function(opt_selector) {
  145. return new goog.ds.EmptyNodeList();
  146. };
  147. /**
  148. * Gets a named child node of the current node
  149. * @param {string} name The node name.
  150. * @param {boolean=} opt_canCreate Whether you can create the child node if
  151. * it doesn't exist already.
  152. * @return {goog.ds.DataNode} The child node, or null if no node of
  153. * this name exists and opt_create is false.
  154. */
  155. goog.ds.BaseDataNode.prototype.getChildNode = function(name, opt_canCreate) {
  156. return null;
  157. };
  158. /**
  159. * Gets the value of a child node
  160. * @param {string} name The node name.
  161. * @return {Object} The value of the node, or null if no value or the
  162. * child node doesn't exist.
  163. */
  164. goog.ds.BaseDataNode.prototype.getChildNodeValue = function(name) {
  165. return null;
  166. };
  167. /**
  168. * Get the name of the node relative to the parent node
  169. * @return {string} The name of the node.
  170. */
  171. goog.ds.BaseDataNode.prototype.getDataName = goog.abstractMethod;
  172. /**
  173. * Gets the a qualified data path to this node
  174. * @return {string} The data path.
  175. */
  176. goog.ds.BaseDataNode.prototype.getDataPath = function() {
  177. var parentPath = '';
  178. var myName = this.getDataName();
  179. if (this.getParent()) {
  180. parentPath = this.getParent().getDataPath() +
  181. (myName.indexOf(
  182. /** @suppress {missingRequire} */ goog.ds.STR_ARRAY_START) != -1 ?
  183. '' :
  184. /** @suppress {missingRequire} */ goog.ds.STR_PATH_SEPARATOR);
  185. }
  186. return parentPath + myName;
  187. };
  188. /**
  189. * Load or reload the backing data for this node
  190. */
  191. goog.ds.BaseDataNode.prototype.load = goog.nullFunction;
  192. /**
  193. * Gets the state of the backing data for this node
  194. * @return {goog.ds.LoadState} The state.
  195. */
  196. goog.ds.BaseDataNode.prototype.getLoadState = function() {
  197. return goog.ds.LoadState.LOADED;
  198. };
  199. /**
  200. * Gets the parent node. Subclasses implement this function
  201. * @return {?goog.ds.DataNode}
  202. * @protected
  203. */
  204. goog.ds.BaseDataNode.prototype.getParent = goog.abstractMethod;
  205. /**
  206. * Interface for node list in rich data tree.
  207. *
  208. * Has both map and list-style accessors
  209. *
  210. * @constructor
  211. * @extends {goog.ds.DataNode}
  212. */
  213. // TODO(arv): Use interfaces when available.
  214. goog.ds.DataNodeList = function() {};
  215. /**
  216. * Add a node to the node list.
  217. * If the node has a dataName, uses this for the key in the map.
  218. *
  219. * @param {goog.ds.DataNode} node The node to add.
  220. */
  221. goog.ds.DataNodeList.prototype.add = goog.abstractMethod;
  222. /**
  223. * Get a node by string key.
  224. * Returns null if node doesn't exist.
  225. *
  226. * @param {string} key String lookup key.
  227. * @return {*} The node, or null if doesn't exist.
  228. * @override
  229. */
  230. goog.ds.DataNodeList.prototype.get = goog.abstractMethod;
  231. /**
  232. * Get a node by index
  233. * Returns null if the index is out of range
  234. *
  235. * @param {number} index The index of the node.
  236. * @return {goog.ds.DataNode} The node, or null if doesn't exist.
  237. */
  238. goog.ds.DataNodeList.prototype.getByIndex = goog.abstractMethod;
  239. /**
  240. * Gets the size of the node list
  241. *
  242. * @return {number} The size of the list.
  243. */
  244. goog.ds.DataNodeList.prototype.getCount = goog.abstractMethod;
  245. /**
  246. * Sets a node in the list of a given name
  247. * @param {string} name Name of the node.
  248. * @param {goog.ds.DataNode} node The node.
  249. */
  250. goog.ds.DataNodeList.prototype.setNode = goog.abstractMethod;
  251. /**
  252. * Removes a node in the list of a given name
  253. * @param {string} name Name of the node.
  254. * @return {boolean} True if node existed and was deleted.
  255. */
  256. goog.ds.DataNodeList.prototype.removeNode = goog.abstractMethod;
  257. /**
  258. * Simple node list implementation with underlying array and map
  259. * implements goog.ds.DataNodeList.
  260. *
  261. * Names that are reserved for system use and shouldn't be used for data node
  262. * names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
  263. * undefined if these names are used.
  264. *
  265. * @param {Array<goog.ds.DataNode>=} opt_nodes optional nodes to add to list.
  266. * @constructor
  267. * @extends {goog.ds.DataNodeList}
  268. */
  269. // TODO(arv): Use interfaces when available.
  270. goog.ds.BasicNodeList = function(opt_nodes) {
  271. this.map_ = {};
  272. this.list_ = [];
  273. this.indexMap_ = {};
  274. if (opt_nodes) {
  275. for (var i = 0, node; node = opt_nodes[i]; i++) {
  276. this.add(node);
  277. }
  278. }
  279. };
  280. /**
  281. * Add a node to the node list.
  282. * If the node has a dataName, uses this for the key in the map.
  283. * TODO(user) Remove function as well
  284. *
  285. * @param {goog.ds.DataNode} node The node to add.
  286. * @override
  287. */
  288. goog.ds.BasicNodeList.prototype.add = function(node) {
  289. this.list_.push(node);
  290. var dataName = node.getDataName();
  291. if (dataName) {
  292. this.map_[dataName] = node;
  293. this.indexMap_[dataName] = this.list_.length - 1;
  294. }
  295. };
  296. /**
  297. * Get a node by string key.
  298. * Returns null if node doesn't exist.
  299. *
  300. * @param {string} key String lookup key.
  301. * @return {goog.ds.DataNode} The node, or null if doesn't exist.
  302. * @override
  303. */
  304. goog.ds.BasicNodeList.prototype.get = function(key) {
  305. return this.map_[key] || null;
  306. };
  307. /**
  308. * Get a node by index
  309. * Returns null if the index is out of range
  310. *
  311. * @param {number} index The index of the node.
  312. * @return {goog.ds.DataNode} The node, or null if doesn't exist.
  313. * @override
  314. */
  315. goog.ds.BasicNodeList.prototype.getByIndex = function(index) {
  316. return this.list_[index] || null;
  317. };
  318. /**
  319. * Gets the size of the node list
  320. *
  321. * @return {number} The size of the list.
  322. * @override
  323. */
  324. goog.ds.BasicNodeList.prototype.getCount = function() {
  325. return this.list_.length;
  326. };
  327. /**
  328. * Sets a node in the list of a given name
  329. * @param {string} name Name of the node.
  330. * @param {goog.ds.DataNode} node The node.
  331. * @override
  332. */
  333. goog.ds.BasicNodeList.prototype.setNode = function(name, node) {
  334. if (node == null) {
  335. this.removeNode(name);
  336. } else {
  337. var existingNode = this.indexMap_[name];
  338. if (existingNode != null) {
  339. this.map_[name] = node;
  340. this.list_[existingNode] = node;
  341. } else {
  342. this.add(node);
  343. }
  344. }
  345. };
  346. /**
  347. * Removes a node in the list of a given name
  348. * @param {string} name Name of the node.
  349. * @return {boolean} True if node existed and was deleted.
  350. * @override
  351. */
  352. goog.ds.BasicNodeList.prototype.removeNode = function(name) {
  353. var existingNode = this.indexMap_[name];
  354. if (existingNode != null) {
  355. this.list_.splice(existingNode, 1);
  356. delete this.map_[name];
  357. delete this.indexMap_[name];
  358. for (var index in this.indexMap_) {
  359. if (this.indexMap_[index] > existingNode) {
  360. this.indexMap_[index]--;
  361. }
  362. }
  363. }
  364. return existingNode != null;
  365. };
  366. /**
  367. * Get the index of a named node
  368. * @param {string} name The name of the node to get the index of.
  369. * @return {number|undefined} The index.
  370. */
  371. goog.ds.BasicNodeList.prototype.indexOf = function(name) {
  372. return this.indexMap_[name];
  373. };
  374. /**
  375. * Immulatable empty node list
  376. * @extends {goog.ds.BasicNodeList}
  377. * @constructor
  378. * @final
  379. */
  380. goog.ds.EmptyNodeList = function() {
  381. goog.ds.BasicNodeList.call(this);
  382. };
  383. goog.inherits(goog.ds.EmptyNodeList, goog.ds.BasicNodeList);
  384. /**
  385. * Add a node to the node list.
  386. * If the node has a dataName, uses this for the key in the map.
  387. *
  388. * @param {goog.ds.DataNode} node The node to add.
  389. * @override
  390. */
  391. goog.ds.EmptyNodeList.prototype.add = function(node) {
  392. throw Error('Can\'t add to EmptyNodeList');
  393. };
  394. /**
  395. * Node list implementation which maintains sort order during insertion and
  396. * modification operations based on a comparison function.
  397. *
  398. * The SortedNodeList does not guarantee sort order will be maintained if
  399. * the underlying data nodes are modified externally.
  400. *
  401. * Names that are reserved for system use and shouldn't be used for data node
  402. * names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is
  403. * undefined if these names are used.
  404. *
  405. * @param {Function} compareFn Comparison function by which the
  406. * node list is sorted. Should take 2 arguments to compare, and return a
  407. * negative integer, zero, or a positive integer depending on whether the
  408. * first argument is less than, equal to, or greater than the second.
  409. * @param {Array<goog.ds.DataNode>=} opt_nodes optional nodes to add to list;
  410. * these are assumed to be in sorted order.
  411. * @extends {goog.ds.BasicNodeList}
  412. * @constructor
  413. */
  414. goog.ds.SortedNodeList = function(compareFn, opt_nodes) {
  415. this.compareFn_ = compareFn;
  416. goog.ds.BasicNodeList.call(this, opt_nodes);
  417. };
  418. goog.inherits(goog.ds.SortedNodeList, goog.ds.BasicNodeList);
  419. /**
  420. * Add a node to the node list, maintaining sort order.
  421. * If the node has a dataName, uses this for the key in the map.
  422. *
  423. * @param {goog.ds.DataNode} node The node to add.
  424. * @override
  425. */
  426. goog.ds.SortedNodeList.prototype.add = function(node) {
  427. if (!this.compareFn_) {
  428. this.append(node);
  429. return;
  430. }
  431. var searchLoc = goog.array.binarySearch(this.list_, node, this.compareFn_);
  432. // if there is another node that is "equal" according to the comparison
  433. // function, insert before that one; otherwise insert at the location
  434. // goog.array.binarySearch indicated
  435. if (searchLoc < 0) {
  436. searchLoc = -(searchLoc + 1);
  437. }
  438. // update any indexes that are after the insertion point
  439. for (var index in this.indexMap_) {
  440. if (this.indexMap_[index] >= searchLoc) {
  441. this.indexMap_[index]++;
  442. }
  443. }
  444. goog.array.insertAt(this.list_, node, searchLoc);
  445. var dataName = node.getDataName();
  446. if (dataName) {
  447. this.map_[dataName] = node;
  448. this.indexMap_[dataName] = searchLoc;
  449. }
  450. };
  451. /**
  452. * Adds the given node to the end of the SortedNodeList. This should
  453. * only be used when the caller can guarantee that the sort order will
  454. * be maintained according to this SortedNodeList's compareFn (e.g.
  455. * when initializing a new SortedNodeList from a list of nodes that has
  456. * already been sorted).
  457. * @param {goog.ds.DataNode} node The node to append.
  458. */
  459. goog.ds.SortedNodeList.prototype.append = function(node) {
  460. goog.ds.SortedNodeList.superClass_.add.call(this, node);
  461. };
  462. /**
  463. * Sets a node in the list of a given name, maintaining sort order.
  464. * @param {string} name Name of the node.
  465. * @param {goog.ds.DataNode} node The node.
  466. * @override
  467. */
  468. goog.ds.SortedNodeList.prototype.setNode = function(name, node) {
  469. if (node == null) {
  470. this.removeNode(name);
  471. } else {
  472. var existingNode = this.indexMap_[name];
  473. if (existingNode != null) {
  474. if (this.compareFn_) {
  475. var compareResult = this.compareFn_(this.list_[existingNode], node);
  476. if (compareResult == 0) {
  477. // the new node can just replace the old one
  478. this.map_[name] = node;
  479. this.list_[existingNode] = node;
  480. } else {
  481. // remove the old node, then add the new one
  482. this.removeNode(name);
  483. this.add(node);
  484. }
  485. }
  486. } else {
  487. this.add(node);
  488. }
  489. }
  490. };
  491. /**
  492. * The character denoting an attribute.
  493. * @type {string}
  494. */
  495. goog.ds.STR_ATTRIBUTE_START = '@';
  496. /**
  497. * The character denoting all children.
  498. * @type {string}
  499. */
  500. goog.ds.STR_ALL_CHILDREN_SELECTOR = '*';
  501. /**
  502. * The wildcard character.
  503. * @type {string}
  504. */
  505. goog.ds.STR_WILDCARD = '*';
  506. /**
  507. * The character denoting path separation.
  508. * @type {string}
  509. */
  510. goog.ds.STR_PATH_SEPARATOR = '/';
  511. /**
  512. * The character denoting the start of an array.
  513. * @type {string}
  514. */
  515. goog.ds.STR_ARRAY_START = '[';
  516. /**
  517. * Shared logger instance for data package
  518. * @type {goog.log.Logger}
  519. */
  520. goog.ds.logger = goog.log.getLogger('goog.ds');
  521. /**
  522. * Create a data node that references another data node,
  523. * useful for pointer-like functionality.
  524. * All functions will return same values as the original node except for
  525. * getDataName()
  526. * @param {!goog.ds.DataNode} node The original node.
  527. * @param {string} name The new name.
  528. * @return {!goog.ds.DataNode} The new data node.
  529. */
  530. goog.ds.Util.makeReferenceNode = function(node, name) {
  531. /**
  532. * @constructor
  533. * @extends {goog.ds.DataNode}
  534. * @final
  535. */
  536. var nodeCreator = function() {};
  537. nodeCreator.prototype = node;
  538. var newNode = new nodeCreator();
  539. newNode.getDataName = function() { return name; };
  540. return newNode;
  541. };