fastdatanode.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. // Copyright 2007 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
  16. * Efficient implementation of DataNode API.
  17. *
  18. * The implementation consists of three concrete classes for modelling
  19. * DataNodes with different characteristics: FastDataNode,
  20. * FastPrimitiveDataNode and FastListNode.
  21. *
  22. * FastDataNode is for bean-like or map-like objects that consists of
  23. * key/value mappings and where the primary access pattern is by key.
  24. *
  25. * FastPrimitiveDataNode wraps primitives like strings, boolean, and numbers.
  26. *
  27. * FastListNode is for array-like data nodes. It also supports key-based
  28. * lookups if the data nodes have an "id" property or if child nodes are
  29. * explicitly added by name. It is most efficient if these features are not
  30. * used.
  31. *
  32. * FastDataNodes can be constructed from JSON-like objects via the function
  33. * goog.ds.FastDataNode.fromJs.
  34. */
  35. goog.provide('goog.ds.AbstractFastDataNode');
  36. goog.provide('goog.ds.FastDataNode');
  37. goog.provide('goog.ds.FastListNode');
  38. goog.provide('goog.ds.PrimitiveFastDataNode');
  39. goog.require('goog.ds.DataManager');
  40. goog.require('goog.ds.DataNodeList');
  41. goog.require('goog.ds.EmptyNodeList');
  42. goog.require('goog.string');
  43. /*
  44. * Implementation note: In order to reduce the number of objects,
  45. * FastDataNode stores its key/value mappings directly in the FastDataNode
  46. * object iself (instead of a separate map). To make this work we have to
  47. * sure that there are no name clashes with other attribute names used by
  48. * FastDataNode (like dataName and parent). This is especially difficult in
  49. * the light of automatic renaming by the JavaScript compiler. For this reason,
  50. * all internal attributes start with "__" so that they are not renamed
  51. * by the compiler.
  52. */
  53. /**
  54. * Creates a new abstract data node.
  55. * @param {string} dataName Name of the datanode.
  56. * @param {goog.ds.DataNode=} opt_parent Parent of this data node.
  57. * @constructor
  58. * @extends {goog.ds.DataNodeList}
  59. */
  60. // TODO(arv): Use interfaces when available.
  61. goog.ds.AbstractFastDataNode = function(dataName, opt_parent) {
  62. if (!dataName) {
  63. throw Error('Cannot create a fast data node without a data name');
  64. }
  65. this['__dataName'] = dataName;
  66. this['__parent'] = opt_parent;
  67. };
  68. /**
  69. * Return the name of this data node.
  70. * @return {string} Name of this data noden.
  71. * @override
  72. */
  73. goog.ds.AbstractFastDataNode.prototype.getDataName = function() {
  74. return this['__dataName'];
  75. };
  76. /**
  77. * Set the name of this data node.
  78. * @param {string} value Name.
  79. * @override
  80. */
  81. goog.ds.AbstractFastDataNode.prototype.setDataName = function(value) {
  82. this['__dataName'] = value;
  83. };
  84. /**
  85. * Get the path leading to this data node.
  86. * @return {string} Data path.
  87. * @override
  88. */
  89. goog.ds.AbstractFastDataNode.prototype.getDataPath = function() {
  90. var parentPath;
  91. if (this['__parent']) {
  92. parentPath = this['__parent'].getDataPath() + goog.ds.STR_PATH_SEPARATOR;
  93. } else {
  94. parentPath = '';
  95. }
  96. return parentPath + this.getDataName();
  97. };
  98. /**
  99. * Creates a new fast data node, using the properties of root.
  100. * @param {Object} root JSON-like object to initialize data node from.
  101. * @param {string} dataName Name of this data node.
  102. * @param {goog.ds.DataNode=} opt_parent Parent of this data node.
  103. * @extends {goog.ds.AbstractFastDataNode}
  104. * @constructor
  105. */
  106. goog.ds.FastDataNode = function(root, dataName, opt_parent) {
  107. goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
  108. this.extendWith(root);
  109. };
  110. goog.inherits(goog.ds.FastDataNode, goog.ds.AbstractFastDataNode);
  111. /**
  112. * Add all attributes of object to this data node.
  113. * @param {Object} object Object to add attributes from.
  114. * @protected
  115. */
  116. goog.ds.FastDataNode.prototype.extendWith = function(object) {
  117. for (var key in object) {
  118. this[key] = object[key];
  119. }
  120. };
  121. /**
  122. * Creates a new FastDataNode structure initialized from object. This will
  123. * return an instance of the most suitable sub-class of FastDataNode.
  124. *
  125. * You should not modify object after creating a fast data node from it
  126. * or assume that changing object changes the data node. Doing so results
  127. * in undefined behaviour.
  128. *
  129. * @param {Object|number|boolean|string} object Object to initialize data
  130. * node from.
  131. * @param {string} dataName Name of data node.
  132. * @param {goog.ds.DataNode=} opt_parent Parent of data node.
  133. * @return {!goog.ds.AbstractFastDataNode} Data node representing object.
  134. */
  135. goog.ds.FastDataNode.fromJs = function(object, dataName, opt_parent) {
  136. if (goog.isArray(object)) {
  137. return new goog.ds.FastListNode(object, dataName, opt_parent);
  138. } else if (goog.isObject(object)) {
  139. return new goog.ds.FastDataNode(object, dataName, opt_parent);
  140. } else {
  141. return new goog.ds.PrimitiveFastDataNode(
  142. object || !!object, dataName, opt_parent);
  143. }
  144. };
  145. /**
  146. * Static instance of an empty list.
  147. * @type {!goog.ds.EmptyNodeList}
  148. * @private
  149. */
  150. goog.ds.FastDataNode.emptyList_ = new goog.ds.EmptyNodeList();
  151. /**
  152. * Not supported for normal FastDataNodes.
  153. * @param {*} value Value to set data node to.
  154. * @override
  155. */
  156. goog.ds.FastDataNode.prototype.set = function(value) {
  157. throw new Error('Not implemented yet');
  158. };
  159. /** @override */
  160. goog.ds.FastDataNode.prototype.getChildNodes = function(opt_selector) {
  161. if (!opt_selector || opt_selector == goog.ds.STR_ALL_CHILDREN_SELECTOR) {
  162. return this;
  163. } else if (opt_selector.indexOf(goog.ds.STR_WILDCARD) == -1) {
  164. var child = this.getChildNode(opt_selector);
  165. return child ? new goog.ds.FastListNode([child], '') :
  166. new goog.ds.EmptyNodeList();
  167. } else {
  168. throw Error('Unsupported selector: ' + opt_selector);
  169. }
  170. };
  171. /**
  172. * Makes sure that a named child is wrapped in a data node structure.
  173. * @param {string} name Name of child to wrap.
  174. * @private
  175. */
  176. goog.ds.FastDataNode.prototype.wrapChild_ = function(name) {
  177. var child = this[name];
  178. if (child != null && !child.getDataName) {
  179. this[name] = goog.ds.FastDataNode.fromJs(this[name], name, this);
  180. }
  181. };
  182. /**
  183. * Get a child node by name.
  184. * @param {string} name Name of child node.
  185. * @param {boolean=} opt_create Whether to create the child if it does not
  186. * exist.
  187. * @return {goog.ds.DataNode} Child node.
  188. * @override
  189. */
  190. goog.ds.FastDataNode.prototype.getChildNode = function(name, opt_create) {
  191. this.wrapChild_(name);
  192. // this[name] always is a data node object, so using "||" is fine.
  193. var child = this[name] || null;
  194. if (child == null && opt_create) {
  195. child = new goog.ds.FastDataNode({}, name, this);
  196. this[name] = child;
  197. }
  198. return child;
  199. };
  200. /**
  201. * Sets a child node. Creates the child if it does not exist.
  202. *
  203. * Calling this function makes any child nodes previously obtained for name
  204. * invalid. You should not use these child nodes but instead obtain a new
  205. * instance by calling getChildNode.
  206. *
  207. * @override
  208. */
  209. goog.ds.FastDataNode.prototype.setChildNode = function(name, value) {
  210. if (value != null) {
  211. this[name] = value;
  212. } else {
  213. delete this[name];
  214. }
  215. goog.ds.DataManager.getInstance().fireDataChange(
  216. this.getDataPath() + goog.ds.STR_PATH_SEPARATOR + name);
  217. return null;
  218. };
  219. /**
  220. * Returns the value of a child node. By using this method you can avoid
  221. * the need to create PrimitiveFastData nodes.
  222. * @param {string} name Name of child node.
  223. * @return {Object} Value of child node.
  224. * @override
  225. */
  226. goog.ds.FastDataNode.prototype.getChildNodeValue = function(name) {
  227. var child = this[name];
  228. if (child != null) {
  229. return (child.getDataName ? child.get() : child);
  230. } else {
  231. return null;
  232. }
  233. };
  234. /**
  235. * Returns whether this data node is a list. Always returns false for
  236. * instances of FastDataNode but may return true for subclasses.
  237. * @return {boolean} Whether this data node is array-like.
  238. * @override
  239. */
  240. goog.ds.FastDataNode.prototype.isList = function() {
  241. return false;
  242. };
  243. /**
  244. * Returns a javascript object representation of this data node. You should
  245. * not modify the object returned by this function.
  246. * @return {!Object} Javascript object representation of this data node.
  247. */
  248. goog.ds.FastDataNode.prototype.getJsObject = function() {
  249. var result = {};
  250. for (var key in this) {
  251. if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
  252. result[key] =
  253. (this[key]['__dataName'] ? this[key].getJsObject() : this[key]);
  254. }
  255. }
  256. return result;
  257. };
  258. /**
  259. * Creates a deep copy of this data node.
  260. * @return {goog.ds.FastDataNode} Clone of this data node.
  261. */
  262. goog.ds.FastDataNode.prototype.clone = function() {
  263. return /** @type {!goog.ds.FastDataNode} */ (
  264. goog.ds.FastDataNode.fromJs(this.getJsObject(), this.getDataName()));
  265. };
  266. /*
  267. * Implementation of goog.ds.DataNodeList for FastDataNode.
  268. */
  269. /**
  270. * Adds a child to this data node.
  271. * @param {goog.ds.DataNode} value Child node to add.
  272. * @override
  273. */
  274. goog.ds.FastDataNode.prototype.add = function(value) {
  275. this.setChildNode(value.getDataName(), value);
  276. };
  277. /**
  278. * Gets the value of this data node (if called without opt_key) or
  279. * gets a child node (if called with opt_key).
  280. * @param {string=} opt_key Name of child node.
  281. * @return {*} This data node or a child node.
  282. * @override
  283. */
  284. goog.ds.FastDataNode.prototype.get = function(opt_key) {
  285. if (!goog.isDef(opt_key)) {
  286. // if there is no key, DataNode#get was called
  287. return this;
  288. } else {
  289. return this.getChildNode(opt_key);
  290. }
  291. };
  292. /**
  293. * Gets a child node by index. This method has a complexity of O(n) where
  294. * n is the number of children. If you need a faster implementation of this
  295. * method, you should use goog.ds.FastListNode.
  296. * @param {number} index Index of child node (starting from 0).
  297. * @return {goog.ds.DataNode} Child node at specified index.
  298. * @override
  299. */
  300. goog.ds.FastDataNode.prototype.getByIndex = function(index) {
  301. var i = 0;
  302. for (var key in this) {
  303. if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
  304. if (i == index) {
  305. this.wrapChild_(key);
  306. return this[key];
  307. }
  308. ++i;
  309. }
  310. }
  311. return null;
  312. };
  313. /**
  314. * Gets the number of child nodes. This method has a complexity of O(n) where
  315. * n is the number of children. If you need a faster implementation of this
  316. * method, you should use goog.ds.FastListNode.
  317. * @return {number} Number of child nodes.
  318. * @override
  319. */
  320. goog.ds.FastDataNode.prototype.getCount = function() {
  321. var count = 0;
  322. for (var key in this) {
  323. if (!goog.string.startsWith(key, '__') && !goog.isFunction(this[key])) {
  324. ++count;
  325. }
  326. }
  327. // maybe cache this?
  328. return count;
  329. };
  330. /**
  331. * Sets a child node.
  332. * @param {string} name Name of child node.
  333. * @param {Object} value Value of child node.
  334. * @override
  335. */
  336. goog.ds.FastDataNode.prototype.setNode = function(name, value) {
  337. this.setChildNode(name, value);
  338. };
  339. /**
  340. * Removes a child node.
  341. * @override
  342. */
  343. goog.ds.FastDataNode.prototype.removeNode = function(name) {
  344. delete this[name];
  345. return false;
  346. };
  347. /**
  348. * Creates a new data node wrapping a primitive value.
  349. * @param {number|boolean|string} value Value the value to wrap.
  350. * @param {string} dataName name Name of this data node.
  351. * @param {goog.ds.DataNode=} opt_parent Parent of this data node.
  352. * @extends {goog.ds.AbstractFastDataNode}
  353. * @constructor
  354. * @final
  355. */
  356. goog.ds.PrimitiveFastDataNode = function(value, dataName, opt_parent) {
  357. this.value_ = value;
  358. goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
  359. };
  360. goog.inherits(goog.ds.PrimitiveFastDataNode, goog.ds.AbstractFastDataNode);
  361. /**
  362. * Returns the value of this data node.
  363. * @return {(boolean|number|string)} Value of this data node.
  364. * @override
  365. */
  366. goog.ds.PrimitiveFastDataNode.prototype.get = function() {
  367. return this.value_;
  368. };
  369. /**
  370. * Sets this data node to a new value.
  371. * @param {*} value Value to set data node to.
  372. * @override
  373. */
  374. goog.ds.PrimitiveFastDataNode.prototype.set = function(value) {
  375. if (goog.isArray(value) || goog.isObject(value)) {
  376. throw Error('can only set PrimitiveFastDataNode to primitive values');
  377. }
  378. this.value_ = value;
  379. goog.ds.DataManager.getInstance().fireDataChange(this.getDataPath());
  380. };
  381. /**
  382. * Returns child nodes of this data node. Always returns an unmodifiable,
  383. * empty list.
  384. * @return {!goog.ds.DataNodeList} (Empty) list of child nodes.
  385. * @override
  386. */
  387. goog.ds.PrimitiveFastDataNode.prototype.getChildNodes = function() {
  388. return goog.ds.FastDataNode.emptyList_;
  389. };
  390. /**
  391. * Get a child node by name. Always returns null.
  392. * @param {string} name Name of child node.
  393. * @return {goog.ds.DataNode} Child node.
  394. * @override
  395. */
  396. goog.ds.PrimitiveFastDataNode.prototype.getChildNode = function(name) {
  397. return null;
  398. };
  399. /**
  400. * Returns the value of a child node. Always returns null.
  401. * @param {string} name Name of child node.
  402. * @return {Object} Value of child node.
  403. * @override
  404. */
  405. goog.ds.PrimitiveFastDataNode.prototype.getChildNodeValue = function(name) {
  406. return null;
  407. };
  408. /**
  409. * Not supported by primitive data nodes.
  410. * @param {string} name Name of child node.
  411. * @param {Object} value Value of child node.
  412. * @override
  413. */
  414. goog.ds.PrimitiveFastDataNode.prototype.setChildNode = function(name, value) {
  415. throw Error('Cannot set a child node for a PrimitiveFastDataNode');
  416. };
  417. /**
  418. * Returns whether this data node is a list. Always returns false for
  419. * instances of PrimitiveFastDataNode.
  420. * @return {boolean} Whether this data node is array-like.
  421. * @override
  422. */
  423. goog.ds.PrimitiveFastDataNode.prototype.isList = function() {
  424. return false;
  425. };
  426. /**
  427. * Returns a javascript object representation of this data node. You should
  428. * not modify the object returned by this function.
  429. * @return {*} Javascript object representation of this data node.
  430. */
  431. goog.ds.PrimitiveFastDataNode.prototype.getJsObject = function() {
  432. return this.value_;
  433. };
  434. /**
  435. * Creates a new list node from an array.
  436. * @param {Array<?>} values values hold by this list node.
  437. * @param {string} dataName name of this node.
  438. * @param {goog.ds.DataNode=} opt_parent parent of this node.
  439. * @extends {goog.ds.AbstractFastDataNode}
  440. * @constructor
  441. * @final
  442. */
  443. // TODO(arv): Use interfaces when available. This implements DataNodeList
  444. // as well.
  445. goog.ds.FastListNode = function(values, dataName, opt_parent) {
  446. this.values_ = [];
  447. for (var i = 0; i < values.length; ++i) {
  448. var name = values[i].id || ('[' + i + ']');
  449. this.values_.push(goog.ds.FastDataNode.fromJs(values[i], name, this));
  450. if (values[i].id) {
  451. if (!this.map_) {
  452. this.map_ = {};
  453. }
  454. this.map_[values[i].id] = i;
  455. }
  456. }
  457. goog.ds.AbstractFastDataNode.call(this, dataName, opt_parent);
  458. };
  459. goog.inherits(goog.ds.FastListNode, goog.ds.AbstractFastDataNode);
  460. /**
  461. * Not supported for FastListNodes.
  462. * @param {*} value Value to set data node to.
  463. * @override
  464. */
  465. goog.ds.FastListNode.prototype.set = function(value) {
  466. throw Error('Cannot set a FastListNode to a new value');
  467. };
  468. /**
  469. * Returns child nodes of this data node. Currently, only supports
  470. * returning all children.
  471. * @return {!goog.ds.DataNodeList} List of child nodes.
  472. * @override
  473. */
  474. goog.ds.FastListNode.prototype.getChildNodes = function() {
  475. return this;
  476. };
  477. /**
  478. * Get a child node by name.
  479. * @param {string} key Name of child node.
  480. * @param {boolean=} opt_create Whether to create the child if it does not
  481. * exist.
  482. * @return {goog.ds.DataNode} Child node.
  483. * @override
  484. */
  485. goog.ds.FastListNode.prototype.getChildNode = function(key, opt_create) {
  486. var index = this.getKeyAsNumber_(key);
  487. if (index == null && this.map_) {
  488. index = this.map_[key];
  489. }
  490. if (index != null && this.values_[index]) {
  491. return this.values_[index];
  492. } else if (opt_create) {
  493. this.setChildNode(key, {});
  494. return this.getChildNode(key);
  495. } else {
  496. return null;
  497. }
  498. };
  499. /**
  500. * Returns the value of a child node.
  501. * @param {string} key Name of child node.
  502. * @return {*} Value of child node.
  503. * @override
  504. */
  505. goog.ds.FastListNode.prototype.getChildNodeValue = function(key) {
  506. var child = this.getChildNode(key);
  507. return (child ? child.get() : null);
  508. };
  509. /**
  510. * Tries to interpret key as a numeric index enclosed by square brakcets.
  511. * @param {string} key Key that should be interpreted as a number.
  512. * @return {?number} Numeric index or null if key is not of the form
  513. * described above.
  514. * @private
  515. */
  516. goog.ds.FastListNode.prototype.getKeyAsNumber_ = function(key) {
  517. if (key.charAt(0) == '[' && key.charAt(key.length - 1) == ']') {
  518. return Number(key.substring(1, key.length - 1));
  519. } else {
  520. return null;
  521. }
  522. };
  523. /**
  524. * Sets a child node. Creates the child if it does not exist. To set
  525. * children at a certain index, use a key of the form '[index]'. Note, that
  526. * you can only set values at existing numeric indices. To add a new node
  527. * to this list, you have to use the add method.
  528. *
  529. * Calling this function makes any child nodes previously obtained for name
  530. * invalid. You should not use these child nodes but instead obtain a new
  531. * instance by calling getChildNode.
  532. *
  533. * @override
  534. */
  535. goog.ds.FastListNode.prototype.setChildNode = function(key, value) {
  536. var count = this.values_.length;
  537. if (value != null) {
  538. if (!value.getDataName) {
  539. value = goog.ds.FastDataNode.fromJs(value, key, this);
  540. }
  541. var index = this.getKeyAsNumber_(key);
  542. if (index != null) {
  543. if (index < 0 || index >= this.values_.length) {
  544. throw Error('List index out of bounds: ' + index);
  545. }
  546. // NOTE: This code here appears to want to use "index" rather than
  547. // "key" here (which would be better for an array. However, changing
  548. // that would require knowing that there wasn't a mix of non-number
  549. // keys, as using index that would risk overwriting those values if
  550. // they were set first. Instead we loosen the type so we can use
  551. // strings as indexes.
  552. /** @type {!Object} */
  553. var values = this.values_;
  554. values[key] = value;
  555. } else {
  556. if (!this.map_) {
  557. this.map_ = {};
  558. }
  559. this.values_.push(value);
  560. this.map_[key] = this.values_.length - 1;
  561. }
  562. } else {
  563. this.removeNode(key);
  564. }
  565. var dm = goog.ds.DataManager.getInstance();
  566. dm.fireDataChange(this.getDataPath() + goog.ds.STR_PATH_SEPARATOR + key);
  567. if (this.values_.length != count) {
  568. this.listSizeChanged_();
  569. }
  570. return null;
  571. };
  572. /**
  573. * Fire data changes that are appropriate when the size of this list changes.
  574. * Should be called whenever the list size has changed.
  575. * @private
  576. */
  577. goog.ds.FastListNode.prototype.listSizeChanged_ = function() {
  578. var dm = goog.ds.DataManager.getInstance();
  579. dm.fireDataChange(this.getDataPath());
  580. dm.fireDataChange(
  581. this.getDataPath() + goog.ds.STR_PATH_SEPARATOR + 'count()');
  582. };
  583. /**
  584. * Returns whether this data node is a list. Always returns true.
  585. * @return {boolean} Whether this data node is array-like.
  586. * @override
  587. */
  588. goog.ds.FastListNode.prototype.isList = function() {
  589. return true;
  590. };
  591. /**
  592. * Returns a javascript object representation of this data node. You should
  593. * not modify the object returned by this function.
  594. * @return {!Object} Javascript object representation of this data node.
  595. */
  596. goog.ds.FastListNode.prototype.getJsObject = function() {
  597. var result = [];
  598. for (var i = 0; i < this.values_.length; ++i) {
  599. result.push(this.values_[i].getJsObject());
  600. }
  601. return result;
  602. };
  603. /*
  604. * Implementation of goog.ds.DataNodeList for FastListNode.
  605. */
  606. /**
  607. * Adds a child to this data node
  608. * @param {goog.ds.DataNode} value Child node to add.
  609. * @override
  610. */
  611. goog.ds.FastListNode.prototype.add = function(value) {
  612. if (!value.getDataName) {
  613. value = goog.ds.FastDataNode.fromJs(
  614. value, String('[' + (this.values_.length) + ']'), this);
  615. }
  616. this.values_.push(value);
  617. var dm = goog.ds.DataManager.getInstance();
  618. dm.fireDataChange(
  619. this.getDataPath() + goog.ds.STR_PATH_SEPARATOR + '[' +
  620. (this.values_.length - 1) + ']');
  621. this.listSizeChanged_();
  622. };
  623. /**
  624. * Gets the value of this data node (if called without opt_key) or
  625. * gets a child node (if called with opt_key).
  626. * @param {string=} opt_key Name of child node.
  627. * @return {Array|goog.ds.DataNode} Array of child nodes (if called without
  628. * opt_key), or a named child node otherwise.
  629. * @override
  630. */
  631. goog.ds.FastListNode.prototype.get = function(opt_key) {
  632. // if there are no arguments, DataNode.get was called
  633. if (!goog.isDef(opt_key)) {
  634. return this.values_;
  635. } else {
  636. return this.getChildNode(opt_key);
  637. }
  638. };
  639. /**
  640. * Gets a child node by (numeric) index.
  641. * @param {number} index Index of child node (starting from 0).
  642. * @return {goog.ds.DataNode} Child node at specified index.
  643. * @override
  644. */
  645. goog.ds.FastListNode.prototype.getByIndex = function(index) {
  646. var child = this.values_[index];
  647. return (child != null ? child : null); // never return undefined
  648. };
  649. /**
  650. * Gets the number of child nodes.
  651. * @return {number} Number of child nodes.
  652. * @override
  653. */
  654. goog.ds.FastListNode.prototype.getCount = function() {
  655. return this.values_.length;
  656. };
  657. /**
  658. * Sets a child node.
  659. * @param {string} name Name of child node.
  660. * @param {Object} value Value of child node.
  661. * @override
  662. */
  663. goog.ds.FastListNode.prototype.setNode = function(name, value) {
  664. throw Error('Setting child nodes of a FastListNode is not implemented, yet');
  665. };
  666. /**
  667. * Removes a child node.
  668. * @override
  669. */
  670. goog.ds.FastListNode.prototype.removeNode = function(name) {
  671. var index = this.getKeyAsNumber_(name);
  672. if (index == null && this.map_) {
  673. index = this.map_[name];
  674. }
  675. if (index != null) {
  676. this.values_.splice(index, 1);
  677. if (this.map_) {
  678. var keyToDelete = null;
  679. for (var key in this.map_) {
  680. if (this.map_[key] == index) {
  681. keyToDelete = key;
  682. } else if (this.map_[key] > index) {
  683. --this.map_[key];
  684. }
  685. }
  686. if (keyToDelete) {
  687. delete this.map_[keyToDelete];
  688. }
  689. }
  690. var dm = goog.ds.DataManager.getInstance();
  691. dm.fireDataChange(
  692. this.getDataPath() + goog.ds.STR_PATH_SEPARATOR + '[' + index + ']');
  693. this.listSizeChanged_();
  694. }
  695. return false;
  696. };
  697. /**
  698. * Returns the index of a named child nodes. This method only works if
  699. * this list uses mixed name/indexed lookup, i.e. if its child node have
  700. * an 'id' attribute.
  701. * @param {string} name Name of child node to determine index of.
  702. * @return {number} Index of child node named name.
  703. */
  704. goog.ds.FastListNode.prototype.indexOf = function(name) {
  705. var index = this.getKeyAsNumber_(name);
  706. if (index == null && this.map_) {
  707. index = this.map_[name];
  708. }
  709. if (index == null) {
  710. throw Error('Cannot determine index for: ' + name);
  711. }
  712. return /** @type {number} */ (index);
  713. };