PageRenderTime 21ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/ajax/libs/baobab/1.1.2/baobab.js

https://gitlab.com/Mirros/cdnjs
JavaScript | 256 lines | 162 code | 51 blank | 43 comment | 29 complexity | 5754f42919739d79390f6d32d967f23f MD5 | raw file
  1. /**
  2. * Baobab Data Structure
  3. * ======================
  4. *
  5. * A handy data tree with cursors.
  6. */
  7. var Cursor = require('./cursor.js'),
  8. EventEmitter = require('emmett'),
  9. Facet = require('./facet.js'),
  10. helpers = require('./helpers.js'),
  11. update = require('./update.js'),
  12. merge = require('./merge.js'),
  13. defaults = require('../defaults.js'),
  14. type = require('./type.js');
  15. var uniqid = (function() {
  16. var i = 0;
  17. return function() {
  18. return i++;
  19. };
  20. })();
  21. /**
  22. * Main Class
  23. */
  24. function Baobab(initialData, opts) {
  25. if (arguments.length < 1)
  26. initialData = {};
  27. // New keyword optional
  28. if (!(this instanceof Baobab))
  29. return new Baobab(initialData, opts);
  30. if (!type.Object(initialData) && !type.Array(initialData))
  31. throw Error('Baobab: invalid data.');
  32. // Extending
  33. EventEmitter.call(this);
  34. // Merging defaults
  35. this.options = helpers.shallowMerge(defaults, opts);
  36. // Privates
  37. this._transaction = {};
  38. this._future = undefined;
  39. this._cursors = {};
  40. this._identity = '[object Baobab]';
  41. // Properties
  42. this.log = [];
  43. this.previousData = null;
  44. this.data = initialData;
  45. this.root = this.select();
  46. this.facets = {};
  47. // Immutable tree?
  48. if (this.options.immutable)
  49. helpers.deepFreeze(this.data);
  50. // Boostrapping root cursor's methods
  51. function bootstrap(name) {
  52. this[name] = function() {
  53. var r = this.root[name].apply(this.root, arguments);
  54. return r instanceof Cursor ? this : r;
  55. };
  56. }
  57. [
  58. 'apply',
  59. 'chain',
  60. 'get',
  61. 'merge',
  62. 'push',
  63. 'set',
  64. 'splice',
  65. 'unset',
  66. 'unshift',
  67. 'update'
  68. ].forEach(bootstrap.bind(this));
  69. // Facets
  70. if (!type.Object(this.options.facets))
  71. throw Error('Baobab: invalid facets.');
  72. for (var k in this.options.facets)
  73. this.addFacet(k, this.options.facets[k]);
  74. }
  75. helpers.inherits(Baobab, EventEmitter);
  76. /**
  77. * Prototype
  78. */
  79. Baobab.prototype.addFacet = function(name, definition, args) {
  80. this.facets[name] = this.createFacet(definition, args);
  81. return this;
  82. };
  83. Baobab.prototype.createFacet = function(definition, args) {
  84. return new Facet(this, definition, args);
  85. };
  86. Baobab.prototype.select = function(path) {
  87. path = path || [];
  88. if (arguments.length > 1)
  89. path = helpers.arrayOf(arguments);
  90. if (!type.Path(path))
  91. throw Error('Baobab.select: invalid path.');
  92. // Casting to array
  93. path = [].concat(path);
  94. // Computing hash
  95. var hash = path.map(function(step) {
  96. if (type.Function(step) || type.Object(step))
  97. return '$' + uniqid() + '$';
  98. else
  99. return step;
  100. }).join('|λ|');
  101. // Registering a new cursor or giving the already existing one for path
  102. var cursor;
  103. if (!this._cursors[hash]) {
  104. cursor = new Cursor(this, path, hash);
  105. this._cursors[hash] = cursor;
  106. }
  107. else {
  108. cursor = this._cursors[hash];
  109. }
  110. // Emitting an event
  111. this.emit('select', {path: path, cursor: cursor});
  112. return cursor;
  113. };
  114. // TODO: if syncwrite wins: drop skipMerge, this._transaction etc.
  115. // TODO: uniq'ing the log through path hashing
  116. Baobab.prototype.stack = function(spec, skipMerge) {
  117. var self = this;
  118. if (!type.Object(spec))
  119. throw Error('Baobab.update: wrong specification.');
  120. if (!this.previousData)
  121. this.previousData = this.data;
  122. // Applying modifications
  123. if (this.options.syncwrite) {
  124. var result = update(this.data, spec, this.options);
  125. this.data = result.data;
  126. this.log = [].concat(this.log).concat(result.log);
  127. }
  128. else {
  129. this._transaction = (skipMerge && !Object.keys(this._transaction).length) ?
  130. spec :
  131. merge(this._transaction, spec);
  132. }
  133. // Should we let the user commit?
  134. if (!this.options.autoCommit)
  135. return this;
  136. // Should we update synchronously?
  137. if (!this.options.asynchronous)
  138. return this.commit();
  139. // Updating asynchronously
  140. if (!this._future)
  141. this._future = setTimeout(self.commit.bind(self, null), 0);
  142. return this;
  143. };
  144. Baobab.prototype.commit = function() {
  145. if (this._future)
  146. this._future = clearTimeout(this._future);
  147. if (!this.options.syncwrite) {
  148. // Applying the asynchronous transaction
  149. var result = update(this.data, this._transaction, this.options);
  150. this.data = result.data;
  151. this.log = result.log;
  152. }
  153. // Resetting transaction
  154. this._transaction = {};
  155. // Validate?
  156. var validate = this.options.validate,
  157. behavior = this.options.validationBehavior;
  158. if (typeof validate === 'function') {
  159. var error = validate.call(this, this.previousData, this.data, this.log);
  160. if (error instanceof Error) {
  161. this.emit('invalid', {error: error});
  162. if (behavior === 'rollback') {
  163. this.data = this.previousData;
  164. return this;
  165. }
  166. }
  167. }
  168. // Baobab-level update event
  169. this.emit('update', {
  170. log: this.log,
  171. previousData: this.previousData,
  172. data: this.data
  173. });
  174. this.log = [];
  175. this.previousData = null;
  176. return this;
  177. };
  178. Baobab.prototype.release = function() {
  179. var k;
  180. delete this.data;
  181. delete this._transaction;
  182. // Releasing cursors
  183. for (k in this._cursors)
  184. this._cursors[k].release();
  185. delete this._cursors;
  186. // Releasing facets
  187. for (k in this.facets)
  188. this.facets[k].release();
  189. delete this.facets;
  190. // Killing event emitter
  191. this.kill();
  192. };
  193. /**
  194. * Output
  195. */
  196. Baobab.prototype.toJSON = function() {
  197. return this.get();
  198. };
  199. Baobab.prototype.toString = function() {
  200. return this._identity;
  201. };
  202. /**
  203. * Export
  204. */
  205. module.exports = Baobab;