diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 6913f2af6c..d87d72dc29 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -8,7 +8,7 @@ Package.on_use(function (api, where) { // It would be sort of nice if minimongo didn't depend on // underscore, so we could ship it separately. - api.use(['underscore', 'json', 'ejson'], where); + api.use(['underscore', 'json', 'ejson', 'ordereddict'], where); api.add_files([ 'minimongo.js', diff --git a/packages/ordereddict/ordereddict.js b/packages/ordereddict/ordereddict.js new file mode 100644 index 0000000000..769e88672c --- /dev/null +++ b/packages/ordereddict/ordereddict.js @@ -0,0 +1,158 @@ +(function () { + var element = function (key, value, next, prev) { + return { + key: key, + value: value, + next: next, + prev: prev + }; + }; + OrderedDict = function (/* ... */) { + var self = this; + self._dict = {}; + self._first = null; + self._last = null; + _.each(arguments, function (kv) { + self.putBefore(kv[0], kv[1], null); + }); + }; + + _.extend(OrderedDict.prototype, { + putBefore: function (key, item, before) { + var self = this; + var elt = before ? + element(key, item, self._dict[before]) : + element(key, item, null); + if (elt.next === undefined) + throw new Error("could not find item to put this one before"); + if (!elt.next) { + elt.prev = self._last; + self._last.next = elt; + self._last = elt; + } else { + elt.prev = elt.next.prev; + elt.next.prev = elt; + elt.prev.next = elt; + } + if (self._first === null || self._first === elt.next) + self._first = elt; + }, + remove: function (key) { + var self = this; + var elt = self._dict[key]; + if (elt !== undefined) { + elt.next.prev = elt.prev; + elt.prev.next = elt.next; + if (elt === self._last) + self._last = elt.prev; + if (elt === self._first) + self._first = elt.next; + delete self._dict[key]; + return elt.value; + } else { + return undefined; + } + }, + get: function (key) { + var self = this; + if (_.has(self._dict, key)) + return self._dict[key].value; + return undefined; + }, + has: function (key) { + var self = this; + return _.has(self._dict[key]); + }, + each: function (iter) { + var self = this; + var i = 0; + var elt = self._first; + while (elt !== null) { + var b = iter(elt.value, elt.key, i); + if (b === OrderedDict.BREAK) + return; + elt = elt.next; + } + }, + first: function () { + var self = this; + return self._first.key; + }, + firstValue: function () { + var self = this; + return self._first.value; + }, + last: function () { + var self = this; + return self._last.key; + }, + lastValue: function () { + var self = this; + return self._last.value; + }, + prev: function (key) { + var self = this; + if (_.has(self._dict, key)) { + var elt = self._dict[key]; + if (elt.prev) + return elt.prev.key; + } + return null; + }, + next: function (key) { + var self = this; + if (_.has(self._dict, key)) { + var elt = self._dict[key]; + if (elt.next) + return elt.next.key; + } + return null; + }, + moveBefore: function (key, before) { + var self = this; + var elt = self._dict[key]; + var eltBefore = before ? self._dict[before] : null; + if (elt === undefined) + throw new Error("Item to move is not present"); + if (eltBefore === undefined) + throw new Error("Could not find element to move this one before"); + if (eltBefore === elt.next) // no moving necessary. + return; + // remove from its old place + elt.next.prev = elt.prev; + elt.prev.next = elt.next; + if (elt === self._last) + self._last = elt.prev; + if (elt === self._first) + self._first = elt.next; + + // now patch it in to its new place + if (eltBefore === null) { + elt.next = null; + elt.prev = self._last; + self._last.next = elt.prev; + self._last = elt; + } else { + elt.next = eltBefore; + elt.prev = eltBefore.prev; + eltBefore.prev = elt; + elt.prev.next = elt; + } + + }, + getIndex: function (key) { + var self = this; + var ret = null; + self.each(function (v, k, i) { + if (k === key) { + ret = i; + return OrderedDict.BREAK; + } + return undefined; + }); + return ret; + } + }); + +OrderedDict.BREAK = {break: true}; +})(); diff --git a/packages/ordereddict/package.js b/packages/ordereddict/package.js new file mode 100644 index 0000000000..c248a27020 --- /dev/null +++ b/packages/ordereddict/package.js @@ -0,0 +1,9 @@ +Package.describe({ + summary: "Ordered traversable dictionary with a mutable ordering", + internal: true +}); + +Package.on_use(function (api) { + api.use('underscore'); + api.add_files('ordereddict.js', ['client', 'server']); +}); diff --git a/packages/spark/spark.js b/packages/spark/spark.js index 866c64db09..58756215cf 100644 --- a/packages/spark/spark.js +++ b/packages/spark/spark.js @@ -892,15 +892,18 @@ Spark.list = function (cursor, itemFunc, elseFunc) { }); // Get the current contents of the cursor. - // XXX currently we count on observe() using only added() to deliver - // the initial contents. are we allow to do that, or do we need to - // implement removed/moved/changed here as well? + + // TODO: unify initialContents and itemRanges into one + // OrderedDict from id -> (doc, LiveRange), called + // `itemDict`. var initialContents = []; _.extend(callbacks, { added: function (item, beforeIndex) { + // TODO: itemDict.addBefore ... initialContents.splice(beforeIndex, 0, item); } }); + // TODO: observeChanges var handle = cursor.observe(observerCallbacks); // Get the renderer, if any @@ -914,15 +917,18 @@ Spark.list = function (cursor, itemFunc, elseFunc) { // off for later. var html = ''; var outerRange; + // TODO: itemRanges is a map from id to LiveRange var itemRanges = []; if (! initialContents.length) html = elseFunc(); else { + // TODO: iterate over itemDict for (var i = 0; i < initialContents.length; i++) { (function (i) { html += maybeAnnotate(itemFunc(initialContents[i]), Spark._ANNOTATION_LIST_ITEM, function (range) { + // TOOD: mutate value in itemDict itemRanges[i] = range; }); })(i); // scope i to closure