diff --git a/example/.meteor/packages b/example/.meteor/packages index eff429b..68cb3aa 100644 --- a/example/.meteor/packages +++ b/example/.meteor/packages @@ -5,6 +5,5 @@ autopublish insecure -preserve-inputs standard-app-packages reactive-extra diff --git a/example/.meteor/release b/example/.meteor/release index dd8bfff..100435b 100644 --- a/example/.meteor/release +++ b/example/.meteor/release @@ -1 +1 @@ -0.6.5.1 +0.8.2 diff --git a/example/packages/reactive-extra/lib/handlebars-list.js b/example/packages/reactive-extra/lib/handlebars-list.js deleted file mode 100644 index 3998801..0000000 --- a/example/packages/reactive-extra/lib/handlebars-list.js +++ /dev/null @@ -1,177 +0,0 @@ -// Generated by CoffeeScript 1.6.3 -(function() { - var HandlebarsEach, SparkListObserve, findParentOfType, makeRange; - - HandlebarsEach = Handlebars._default_helpers.each; - - Handlebars._default_helpers.each = function(arg, options) { - var elseFunc, itemFunc; - if (!(arg && arg instanceof ReactiveList)) { - return HandlebarsEach.call(this, arg, options); - } - itemFunc = function(item) { - return Spark.labelBranch((item && item._id) || Spark.UNIQUE_LABEL, function() { - return Spark.setDataContext(item, Spark.isolate(_.bind(options.fn, null, item))); - }); - }; - elseFunc = function() { - if (options.inverse) { - return Spark.isolate(options.inverse); - } else { - return ''; - } - }; - return SparkListObserve(arg, itemFunc, elseFunc); - }; - - Spark._ANNOTATION_LIST_OBSERVE = "list_observe"; - - Spark._ANNOTATION_LIST_OBSERVE_ITEM = "list_observe_item"; - - SparkListObserve = function(observable, itemFunc, elseFunc) { - var callbacks, cleanup, handle, html, itemArr, later, maybeAnnotate, notifyParentsRendered, observerCallbacks, outerRange, renderer, stopped; - elseFunc = elseFunc || function() { - return ''; - }; - callbacks = {}; - observerCallbacks = {}; - _.each(["addedAt", "changedAt", "removedAt", "movedTo"], function(name) { - return observerCallbacks[name] = function() { - return callbacks[name].apply(null, arguments); - }; - }); - itemArr = []; - _.extend(callbacks, { - addedAt: function(val, idx) { - return itemArr[idx] = { - liveRange: null, - value: val - }; - } - }); - handle = observable.observe(observerCallbacks); - renderer = Spark._currentRenderer.get(); - maybeAnnotate = renderer ? _.bind(renderer.annotate, renderer) : function(html) { - return html; - }; - html = ''; - outerRange = null; - if (itemArr.length === 0) { - html = elseFunc(); - } else { - _.each(itemArr, function(elt) { - return html += maybeAnnotate(itemFunc(elt.value), Spark._ANNOTATION_LIST_OBSERVE_ITEM, function(range) { - elt.liveRange = range; - }); - }); - } - stopped = false; - cleanup = function() { - handle.stop(); - return stopped = true; - }; - html = maybeAnnotate(html, Spark._ANNOTATION_LIST_OBSERVE, function(range) { - if (!range) { - cleanup(); - return; - } - outerRange = range; - outerRange.finalize = cleanup; - }); - if (!renderer) { - cleanup(); - return html; - } - notifyParentsRendered = function() { - var walk; - walk = outerRange; - while ((walk = findParentOfType(Spark._ANNOTATION_LANDMARK, walk))) { - walk.rendered.call(walk.landmark); - } - }; - later = function(func) { - Deps.afterFlush(function() { - if (!stopped) { - func(); - } - }); - }; - _.extend(callbacks, { - addedAt: function(val, idx) { - return later(function() { - var frag, range; - frag = Spark.render(_.bind(itemFunc, null, val)); - DomUtils.wrapFragmentForContainer(frag, outerRange.containerNode()); - range = makeRange(Spark._ANNOTATION_LIST_ITEM, frag); - if (itemArr.length === 0) { - Spark.finalize(outerRange.replaceContents(frag)); - } else { - itemArr[idx - 1].liveRange.insertAfter(frag); - } - return itemArr[idx] = { - liveRange: range, - value: val - }; - }); - }, - removedAt: function(val, idx) { - return later(function() { - var frag; - if (itemArr.length === 1) { - frag = Spark.render(elseFunc); - DomUtils.wrapFragmentForContainer(frag, outerRange.containerNode()); - Spark.finalize(outerRange.replaceContents(frag)); - } else { - Spark.finalize(itemArr[idx].liveRange.extract()); - } - itemArr.splice(idx, 1); - return notifyParentsRendered(); - }); - }, - movedTo: function(val, fromIdx, toIdx) { - return later(function() { - var elt, frag; - elt = (itemArr.splice(fromIdx, 1))[0]; - frag = elt.liveRange.extract(); - if (toIdx in itemArr) { - itemArr[toIdx].liveRange.insertBefore(frag); - } else { - itemArr[toIdx - 1].liveRange.insertAfter(frag); - } - itemArr.splice(toIdx, 0, elt); - return notifyParentsRendered(); - }); - }, - changedAt: function(val, idx) { - return later(function() { - var elt; - elt = itemArr[idx]; - if (!elt) { - throw new Error("Unknown item at index: " + idx); - } - elt.value = val; - return Spark.renderToRange(elt.liveRange, _.bind(itemFunc, null, elt.value)); - }); - } - }); - return html; - }; - - findParentOfType = function(type, range) { - while (true) { - range = range.findParent(); - if (!(range && range.type !== type)) { - break; - } - } - return range; - }; - - makeRange = function(type, start, end, inner) { - var range; - range = new LiveRange(Spark._TAG, start, end, inner); - range.type = type; - return range; - }; - -}).call(this); diff --git a/example/packages/reactive-extra/lib/observe-sequence.js b/example/packages/reactive-extra/lib/observe-sequence.js new file mode 100644 index 0000000..b7602a1 --- /dev/null +++ b/example/packages/reactive-extra/lib/observe-sequence.js @@ -0,0 +1,161 @@ +// Generated by CoffeeScript 1.7.1 +(function() { + var ObserveSequence, ObserveSequenceFetch, ObserveSequenceObserve, diffArray, idParse, idStringify; + + ObserveSequence = Package['observe-sequence'].ObserveSequence; + + idStringify = LocalCollection._idStringify; + + idParse = LocalCollection._idParse; + + ObserveSequenceFetch = ObserveSequence.fetch; + + ObserveSequence.fetch = function(seq) { + if (seq && seq instanceof ReactiveList) { + return seq; + } + return ObserveSequenceFetch.call(this, seq); + }; + + ObserveSequenceObserve = ObserveSequence.observe; + + ObserveSequence.observe = function(sequenceFunc, callbacks) { + var computation, lastSeq, lastSeqArray, observe; + observe = false; + Deps.nonreactive(function() { + return observe = sequenceFunc() instanceof ReactiveList; + }); + if (!observe) { + return ObserveSequenceObserve.call(this, sequenceFunc, callbacks); + } + lastSeq = null; + lastSeqArray = []; + computation = Deps.autorun(function() { + var activeObserveHandle, idsUsed, seq, seqArray; + seq = sequenceFunc(); + if (activeObserveHandle) { + lastSeqArray = _.map(activeObserveHandle._fetch(), function(doc) { + return { + _id: doc._id, + item: doc + }; + }); + activeObserveHandle.stop(); + activeObserveHandle = null; + } + idsUsed = {}; + seqArray = _.map(seq, function(item, index) { + var id, idString; + id = void 0; + if (typeof item === "string") { + id = "-" + item; + } else if (typeof item === "number" || typeof item === "boolean" || item === undefined) { + id = item; + } else if (typeof item === "object") { + id = (item && item._id) || index; + } else { + throw new Error("{{#each}} doesn't support arrays with " + "elements of type " + typeof item); + } + idString = idStringify(id); + if (idsUsed[idString]) { + if (typeof item === "object" && "_id" in item) { + warn("duplicate id " + id + " in", seq); + } + id = Random.id(); + } else { + idsUsed[idString] = true; + } + return { + _id: id, + item: item + }; + }); + diffArray(lastSeqArray, seqArray, callbacks); + lastSeq = seq; + return lastSeqArray = seqArray; + }); + return { + stop: function() { + computation.stop(); + } + }; + }; + + diffArray = function(lastSeqArray, seqArray, callbacks) { + var diffFn, lengthCur, newIdObjects, oldIdObjects, posCur, posNew, posOld; + diffFn = Package.minimongo.LocalCollection._diffQueryOrderedChanges; + oldIdObjects = []; + newIdObjects = []; + posOld = {}; + posNew = {}; + posCur = {}; + lengthCur = lastSeqArray.length; + _.each(seqArray, function(doc, i) { + newIdObjects.push({ + _id: doc._id + }); + posNew[idStringify(doc._id)] = i; + }); + _.each(lastSeqArray, function(doc, i) { + oldIdObjects.push({ + _id: doc._id + }); + posOld[idStringify(doc._id)] = i; + posCur[idStringify(doc._id)] = i; + }); + diffFn(oldIdObjects, newIdObjects, { + addedBefore: function(id, doc, before) { + var position; + position = (before ? posCur[idStringify(before)] : lengthCur); + _.each(posCur, function(pos, id) { + if (pos >= position) { + posCur[id]++; + } + }); + lengthCur++; + posCur[idStringify(id)] = position; + callbacks.addedAt(id, seqArray[posNew[idStringify(id)]].item, position, before); + }, + movedBefore: function(id, before) { + var position, prevPosition; + prevPosition = posCur[idStringify(id)]; + position = (before ? posCur[idStringify(before)] : lengthCur - 1); + _.each(posCur, function(pos, id) { + if (pos >= prevPosition && pos <= position) { + posCur[id]--; + } else { + if (pos <= prevPosition && pos >= position) { + posCur[id]++; + } + } + }); + posCur[idStringify(id)] = position; + callbacks.movedTo(id, seqArray[posNew[idStringify(id)]].item, prevPosition, position, before); + }, + removed: function(id) { + var prevPosition; + prevPosition = posCur[idStringify(id)]; + _.each(posCur, function(pos, id) { + if (pos >= prevPosition) { + posCur[id]--; + } + }); + delete posCur[idStringify(id)]; + lengthCur--; + callbacks.removedAt(id, lastSeqArray[posOld[idStringify(id)]].item, prevPosition); + } + }); + _.each(posNew, function(pos, idString) { + var id, newItem, oldItem; + id = idParse(idString); + if (_.has(posOld, idString)) { + newItem = seqArray[pos].item; + oldItem = lastSeqArray[posOld[idString]].item; + if (typeof newItem === "object" || newItem !== oldItem) { + callbacks.changedAt(id, newItem, oldItem, pos); + } + } + }); + }; + +}).call(this); diff --git a/example/packages/reactive-extra/lib/reactive-array-test.js b/example/packages/reactive-extra/lib/reactive-array-test.js index abc10ed..6d42ffc 100644 --- a/example/packages/reactive-extra/lib/reactive-array-test.js +++ b/example/packages/reactive-extra/lib/reactive-array-test.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { Tinytest.add("ReactiveArray - Mutator", function(test) { var arr, sortedArray; diff --git a/example/packages/reactive-extra/lib/reactive-array.js b/example/packages/reactive-extra/lib/reactive-array.js index 9fdfebf..10567b3 100644 --- a/example/packages/reactive-extra/lib/reactive-array.js +++ b/example/packages/reactive-extra/lib/reactive-array.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { this.ReactiveArray = (function() { function ReactiveArray() { diff --git a/example/packages/reactive-extra/lib/reactive-dictionary-test.js b/example/packages/reactive-extra/lib/reactive-dictionary-test.js index dea6e77..76ed2a5 100644 --- a/example/packages/reactive-extra/lib/reactive-dictionary-test.js +++ b/example/packages/reactive-extra/lib/reactive-dictionary-test.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { Tinytest.add("ReactiveDictionary - Basic", function(test) { var dict, val; diff --git a/example/packages/reactive-extra/lib/reactive-dictionary.js b/example/packages/reactive-extra/lib/reactive-dictionary.js index b368c0d..04eb371 100644 --- a/example/packages/reactive-extra/lib/reactive-dictionary.js +++ b/example/packages/reactive-extra/lib/reactive-dictionary.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, diff --git a/example/packages/reactive-extra/lib/reactive-list-test.js b/example/packages/reactive-extra/lib/reactive-list-test.js index 805bdcd..ef3d99e 100644 --- a/example/packages/reactive-extra/lib/reactive-list-test.js +++ b/example/packages/reactive-extra/lib/reactive-list-test.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { Tinytest.add("ReactiveList - added/addedAt", function(test) { var addedAtX, addedIdx, addedVal, addedX, callbacks, handle, invalidCall, invalidX, list; diff --git a/example/packages/reactive-extra/lib/reactive-list.js b/example/packages/reactive-extra/lib/reactive-list.js index 3135cb5..c64861d 100644 --- a/example/packages/reactive-extra/lib/reactive-list.js +++ b/example/packages/reactive-extra/lib/reactive-list.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var LiveHandler, __hasProp = {}.hasOwnProperty, diff --git a/example/packages/reactive-extra/lib/reactive-object-test.js b/example/packages/reactive-extra/lib/reactive-object-test.js index ccee2e4..966933e 100644 --- a/example/packages/reactive-extra/lib/reactive-object-test.js +++ b/example/packages/reactive-extra/lib/reactive-object-test.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { Tinytest.add("ReactiveObject - Basic", function(test) { var obj, prop, val; diff --git a/example/packages/reactive-extra/lib/reactive-object.js b/example/packages/reactive-extra/lib/reactive-object.js index f15be7b..d3e4a23 100644 --- a/example/packages/reactive-extra/lib/reactive-object.js +++ b/example/packages/reactive-extra/lib/reactive-object.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { this.ReactiveObject = (function() { function ReactiveObject(properties) { diff --git a/example/packages/reactive-extra/package.js b/example/packages/reactive-extra/package.js index f5cc686..4bbd88c 100644 --- a/example/packages/reactive-extra/package.js +++ b/example/packages/reactive-extra/package.js @@ -6,7 +6,7 @@ var path = Npm.require("path"); Package.on_use(function(api) { // Required packages api.use(["deps", "ejson", "underscore"], ["client", "server"]); - api.use(["templating", "handlebars"], ["client"]); + api.use(["ui", "handlebars"], ["client"]); // Server and client side code api.add_files([ @@ -17,8 +17,9 @@ Package.on_use(function(api) { ], ["client", "server"]); // Client side code + api.use('minimongo', ["client"]); // for idStringify api.add_files([ - path.join("lib","handlebars-list.js") + path.join("lib","observe-sequence.js") ], ["client"]); }); diff --git a/lib/handlebars-list.js b/lib/handlebars-list.js deleted file mode 100644 index 3998801..0000000 --- a/lib/handlebars-list.js +++ /dev/null @@ -1,177 +0,0 @@ -// Generated by CoffeeScript 1.6.3 -(function() { - var HandlebarsEach, SparkListObserve, findParentOfType, makeRange; - - HandlebarsEach = Handlebars._default_helpers.each; - - Handlebars._default_helpers.each = function(arg, options) { - var elseFunc, itemFunc; - if (!(arg && arg instanceof ReactiveList)) { - return HandlebarsEach.call(this, arg, options); - } - itemFunc = function(item) { - return Spark.labelBranch((item && item._id) || Spark.UNIQUE_LABEL, function() { - return Spark.setDataContext(item, Spark.isolate(_.bind(options.fn, null, item))); - }); - }; - elseFunc = function() { - if (options.inverse) { - return Spark.isolate(options.inverse); - } else { - return ''; - } - }; - return SparkListObserve(arg, itemFunc, elseFunc); - }; - - Spark._ANNOTATION_LIST_OBSERVE = "list_observe"; - - Spark._ANNOTATION_LIST_OBSERVE_ITEM = "list_observe_item"; - - SparkListObserve = function(observable, itemFunc, elseFunc) { - var callbacks, cleanup, handle, html, itemArr, later, maybeAnnotate, notifyParentsRendered, observerCallbacks, outerRange, renderer, stopped; - elseFunc = elseFunc || function() { - return ''; - }; - callbacks = {}; - observerCallbacks = {}; - _.each(["addedAt", "changedAt", "removedAt", "movedTo"], function(name) { - return observerCallbacks[name] = function() { - return callbacks[name].apply(null, arguments); - }; - }); - itemArr = []; - _.extend(callbacks, { - addedAt: function(val, idx) { - return itemArr[idx] = { - liveRange: null, - value: val - }; - } - }); - handle = observable.observe(observerCallbacks); - renderer = Spark._currentRenderer.get(); - maybeAnnotate = renderer ? _.bind(renderer.annotate, renderer) : function(html) { - return html; - }; - html = ''; - outerRange = null; - if (itemArr.length === 0) { - html = elseFunc(); - } else { - _.each(itemArr, function(elt) { - return html += maybeAnnotate(itemFunc(elt.value), Spark._ANNOTATION_LIST_OBSERVE_ITEM, function(range) { - elt.liveRange = range; - }); - }); - } - stopped = false; - cleanup = function() { - handle.stop(); - return stopped = true; - }; - html = maybeAnnotate(html, Spark._ANNOTATION_LIST_OBSERVE, function(range) { - if (!range) { - cleanup(); - return; - } - outerRange = range; - outerRange.finalize = cleanup; - }); - if (!renderer) { - cleanup(); - return html; - } - notifyParentsRendered = function() { - var walk; - walk = outerRange; - while ((walk = findParentOfType(Spark._ANNOTATION_LANDMARK, walk))) { - walk.rendered.call(walk.landmark); - } - }; - later = function(func) { - Deps.afterFlush(function() { - if (!stopped) { - func(); - } - }); - }; - _.extend(callbacks, { - addedAt: function(val, idx) { - return later(function() { - var frag, range; - frag = Spark.render(_.bind(itemFunc, null, val)); - DomUtils.wrapFragmentForContainer(frag, outerRange.containerNode()); - range = makeRange(Spark._ANNOTATION_LIST_ITEM, frag); - if (itemArr.length === 0) { - Spark.finalize(outerRange.replaceContents(frag)); - } else { - itemArr[idx - 1].liveRange.insertAfter(frag); - } - return itemArr[idx] = { - liveRange: range, - value: val - }; - }); - }, - removedAt: function(val, idx) { - return later(function() { - var frag; - if (itemArr.length === 1) { - frag = Spark.render(elseFunc); - DomUtils.wrapFragmentForContainer(frag, outerRange.containerNode()); - Spark.finalize(outerRange.replaceContents(frag)); - } else { - Spark.finalize(itemArr[idx].liveRange.extract()); - } - itemArr.splice(idx, 1); - return notifyParentsRendered(); - }); - }, - movedTo: function(val, fromIdx, toIdx) { - return later(function() { - var elt, frag; - elt = (itemArr.splice(fromIdx, 1))[0]; - frag = elt.liveRange.extract(); - if (toIdx in itemArr) { - itemArr[toIdx].liveRange.insertBefore(frag); - } else { - itemArr[toIdx - 1].liveRange.insertAfter(frag); - } - itemArr.splice(toIdx, 0, elt); - return notifyParentsRendered(); - }); - }, - changedAt: function(val, idx) { - return later(function() { - var elt; - elt = itemArr[idx]; - if (!elt) { - throw new Error("Unknown item at index: " + idx); - } - elt.value = val; - return Spark.renderToRange(elt.liveRange, _.bind(itemFunc, null, elt.value)); - }); - } - }); - return html; - }; - - findParentOfType = function(type, range) { - while (true) { - range = range.findParent(); - if (!(range && range.type !== type)) { - break; - } - } - return range; - }; - - makeRange = function(type, start, end, inner) { - var range; - range = new LiveRange(Spark._TAG, start, end, inner); - range.type = type; - return range; - }; - -}).call(this); diff --git a/lib/observe-sequence.js b/lib/observe-sequence.js new file mode 100644 index 0000000..b7602a1 --- /dev/null +++ b/lib/observe-sequence.js @@ -0,0 +1,161 @@ +// Generated by CoffeeScript 1.7.1 +(function() { + var ObserveSequence, ObserveSequenceFetch, ObserveSequenceObserve, diffArray, idParse, idStringify; + + ObserveSequence = Package['observe-sequence'].ObserveSequence; + + idStringify = LocalCollection._idStringify; + + idParse = LocalCollection._idParse; + + ObserveSequenceFetch = ObserveSequence.fetch; + + ObserveSequence.fetch = function(seq) { + if (seq && seq instanceof ReactiveList) { + return seq; + } + return ObserveSequenceFetch.call(this, seq); + }; + + ObserveSequenceObserve = ObserveSequence.observe; + + ObserveSequence.observe = function(sequenceFunc, callbacks) { + var computation, lastSeq, lastSeqArray, observe; + observe = false; + Deps.nonreactive(function() { + return observe = sequenceFunc() instanceof ReactiveList; + }); + if (!observe) { + return ObserveSequenceObserve.call(this, sequenceFunc, callbacks); + } + lastSeq = null; + lastSeqArray = []; + computation = Deps.autorun(function() { + var activeObserveHandle, idsUsed, seq, seqArray; + seq = sequenceFunc(); + if (activeObserveHandle) { + lastSeqArray = _.map(activeObserveHandle._fetch(), function(doc) { + return { + _id: doc._id, + item: doc + }; + }); + activeObserveHandle.stop(); + activeObserveHandle = null; + } + idsUsed = {}; + seqArray = _.map(seq, function(item, index) { + var id, idString; + id = void 0; + if (typeof item === "string") { + id = "-" + item; + } else if (typeof item === "number" || typeof item === "boolean" || item === undefined) { + id = item; + } else if (typeof item === "object") { + id = (item && item._id) || index; + } else { + throw new Error("{{#each}} doesn't support arrays with " + "elements of type " + typeof item); + } + idString = idStringify(id); + if (idsUsed[idString]) { + if (typeof item === "object" && "_id" in item) { + warn("duplicate id " + id + " in", seq); + } + id = Random.id(); + } else { + idsUsed[idString] = true; + } + return { + _id: id, + item: item + }; + }); + diffArray(lastSeqArray, seqArray, callbacks); + lastSeq = seq; + return lastSeqArray = seqArray; + }); + return { + stop: function() { + computation.stop(); + } + }; + }; + + diffArray = function(lastSeqArray, seqArray, callbacks) { + var diffFn, lengthCur, newIdObjects, oldIdObjects, posCur, posNew, posOld; + diffFn = Package.minimongo.LocalCollection._diffQueryOrderedChanges; + oldIdObjects = []; + newIdObjects = []; + posOld = {}; + posNew = {}; + posCur = {}; + lengthCur = lastSeqArray.length; + _.each(seqArray, function(doc, i) { + newIdObjects.push({ + _id: doc._id + }); + posNew[idStringify(doc._id)] = i; + }); + _.each(lastSeqArray, function(doc, i) { + oldIdObjects.push({ + _id: doc._id + }); + posOld[idStringify(doc._id)] = i; + posCur[idStringify(doc._id)] = i; + }); + diffFn(oldIdObjects, newIdObjects, { + addedBefore: function(id, doc, before) { + var position; + position = (before ? posCur[idStringify(before)] : lengthCur); + _.each(posCur, function(pos, id) { + if (pos >= position) { + posCur[id]++; + } + }); + lengthCur++; + posCur[idStringify(id)] = position; + callbacks.addedAt(id, seqArray[posNew[idStringify(id)]].item, position, before); + }, + movedBefore: function(id, before) { + var position, prevPosition; + prevPosition = posCur[idStringify(id)]; + position = (before ? posCur[idStringify(before)] : lengthCur - 1); + _.each(posCur, function(pos, id) { + if (pos >= prevPosition && pos <= position) { + posCur[id]--; + } else { + if (pos <= prevPosition && pos >= position) { + posCur[id]++; + } + } + }); + posCur[idStringify(id)] = position; + callbacks.movedTo(id, seqArray[posNew[idStringify(id)]].item, prevPosition, position, before); + }, + removed: function(id) { + var prevPosition; + prevPosition = posCur[idStringify(id)]; + _.each(posCur, function(pos, id) { + if (pos >= prevPosition) { + posCur[id]--; + } + }); + delete posCur[idStringify(id)]; + lengthCur--; + callbacks.removedAt(id, lastSeqArray[posOld[idStringify(id)]].item, prevPosition); + } + }); + _.each(posNew, function(pos, idString) { + var id, newItem, oldItem; + id = idParse(idString); + if (_.has(posOld, idString)) { + newItem = seqArray[pos].item; + oldItem = lastSeqArray[posOld[idString]].item; + if (typeof newItem === "object" || newItem !== oldItem) { + callbacks.changedAt(id, newItem, oldItem, pos); + } + } + }); + }; + +}).call(this); diff --git a/lib/reactive-array-test.js b/lib/reactive-array-test.js index abc10ed..6d42ffc 100644 --- a/lib/reactive-array-test.js +++ b/lib/reactive-array-test.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { Tinytest.add("ReactiveArray - Mutator", function(test) { var arr, sortedArray; diff --git a/lib/reactive-array.js b/lib/reactive-array.js index 9fdfebf..10567b3 100644 --- a/lib/reactive-array.js +++ b/lib/reactive-array.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { this.ReactiveArray = (function() { function ReactiveArray() { diff --git a/lib/reactive-dictionary-test.js b/lib/reactive-dictionary-test.js index dea6e77..76ed2a5 100644 --- a/lib/reactive-dictionary-test.js +++ b/lib/reactive-dictionary-test.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { Tinytest.add("ReactiveDictionary - Basic", function(test) { var dict, val; diff --git a/lib/reactive-dictionary.js b/lib/reactive-dictionary.js index b368c0d..04eb371 100644 --- a/lib/reactive-dictionary.js +++ b/lib/reactive-dictionary.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, diff --git a/lib/reactive-list-test.js b/lib/reactive-list-test.js index 805bdcd..ef3d99e 100644 --- a/lib/reactive-list-test.js +++ b/lib/reactive-list-test.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { Tinytest.add("ReactiveList - added/addedAt", function(test) { var addedAtX, addedIdx, addedVal, addedX, callbacks, handle, invalidCall, invalidX, list; diff --git a/lib/reactive-list.js b/lib/reactive-list.js index 3135cb5..c64861d 100644 --- a/lib/reactive-list.js +++ b/lib/reactive-list.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { var LiveHandler, __hasProp = {}.hasOwnProperty, diff --git a/lib/reactive-object-test.js b/lib/reactive-object-test.js index ccee2e4..966933e 100644 --- a/lib/reactive-object-test.js +++ b/lib/reactive-object-test.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { Tinytest.add("ReactiveObject - Basic", function(test) { var obj, prop, val; diff --git a/lib/reactive-object.js b/lib/reactive-object.js index f15be7b..d3e4a23 100644 --- a/lib/reactive-object.js +++ b/lib/reactive-object.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.3 +// Generated by CoffeeScript 1.7.1 (function() { this.ReactiveObject = (function() { function ReactiveObject(properties) { diff --git a/package.js b/package.js index f5cc686..4bbd88c 100644 --- a/package.js +++ b/package.js @@ -6,7 +6,7 @@ var path = Npm.require("path"); Package.on_use(function(api) { // Required packages api.use(["deps", "ejson", "underscore"], ["client", "server"]); - api.use(["templating", "handlebars"], ["client"]); + api.use(["ui", "handlebars"], ["client"]); // Server and client side code api.add_files([ @@ -17,8 +17,9 @@ Package.on_use(function(api) { ], ["client", "server"]); // Client side code + api.use('minimongo', ["client"]); // for idStringify api.add_files([ - path.join("lib","handlebars-list.js") + path.join("lib","observe-sequence.js") ], ["client"]); }); diff --git a/smart.json b/smart.json index 51bddc6..54e328a 100644 --- a/smart.json +++ b/smart.json @@ -3,7 +3,7 @@ "description": "Providing reactive classes", "homepage": "https://github.com/boekkooi/reactive-extra", "author": "Warnar Boekkooi ", - "version": "0.0.6", + "version": "0.1.0", "git": "https://github.com/boekkooi/reactive-extra.git", "packages": {} } \ No newline at end of file diff --git a/src/handlebars-list.coffee b/src/handlebars-list.coffee deleted file mode 100644 index 932cb2c..0000000 --- a/src/handlebars-list.coffee +++ /dev/null @@ -1,143 +0,0 @@ -# # Handlebars each override -HandlebarsEach = Handlebars._default_helpers.each -Handlebars._default_helpers.each = (arg, options) -> - # Only use our implementation when the arg is a ReactiveList - return HandlebarsEach.call(this, arg, options) unless arg and arg instanceof ReactiveList - - # Item & else functions (stolen from [templating/deftemplate.js](https://github.com/meteor/meteor/blob/master/packages/templating/deftemplate.js) - itemFunc = (item) -> - Spark.labelBranch (item && item._id) || Spark.UNIQUE_LABEL, () -> - Spark.setDataContext item, Spark.isolate(_.bind(options.fn, null, item)) - elseFunc = () -> if options.inverse then Spark.isolate(options.inverse) else '' - - # Call our curstom observe based SparkArrayList - SparkListObserve arg, itemFunc, elseFunc - -# # Spark listObserve -Spark._ANNOTATION_LIST_OBSERVE = "list_observe"; -Spark._ANNOTATION_LIST_OBSERVE_ITEM = "list_observe_item"; - -# Render a object with a observe function using Spark -# *Most of this code is ripped from [Spark.list](https://github.com/meteor/meteor/blob/master/packages/spark/spark.js#L899)* -SparkListObserve = (observable, itemFunc, elseFunc) -> - elseFunc = elseFunc || () -> return '' - - # Create a level of indirection around our observable callbacks so we can change them later - callbacks = {} - observerCallbacks = {} - _.each ["addedAt", "changedAt", "removedAt", "movedTo"], (name) -> - observerCallbacks[name] = -> - callbacks[name].apply null, arguments - - # Create liverange stubs for the current contents of the observable - itemArr = [] - _.extend callbacks, - addedAt: (val, idx) -> - itemArr[idx] = { liveRange: null, value: val } - - handle = observable.observe observerCallbacks - - # Get the renderer, if any - renderer = Spark._currentRenderer.get(); - maybeAnnotate = if renderer then _.bind(renderer.annotate, renderer) else (html) -> html - - # Render the initial contents. - # If we have a renderer, create a range around each item as well as around the list, and save them off for later. - html = '' - outerRange = null - if itemArr.length == 0 - html = elseFunc() - else - _.each itemArr, (elt) -> - html += maybeAnnotate itemFunc(elt.value), Spark._ANNOTATION_LIST_OBSERVE_ITEM, (range) -> - elt.liveRange = range - return - - stopped = false - cleanup = () -> - handle.stop() - stopped = true - - html = maybeAnnotate html, Spark._ANNOTATION_LIST_OBSERVE, (range) -> - if !range - cleanup() - return - outerRange = range - outerRange.finalize = cleanup - return - - # No renderer? Then we have no way to update the returned html and we can close the observer. - if !renderer - cleanup() - return html - - notifyParentsRendered = -> - walk = outerRange - walk.rendered.call walk.landmark while (walk = findParentOfType(Spark._ANNOTATION_LANDMARK, walk)) - return - - later = (func) -> - Deps.afterFlush () -> - # Spark uses withEventGuard let's just have this won't brake - func() unless stopped - return - return - - # The DOM update callbacks. - _.extend callbacks, - addedAt: (val, idx) -> - later -> - frag = Spark.render(_.bind(itemFunc, null, val)) - DomUtils.wrapFragmentForContainer frag, outerRange.containerNode() - range = makeRange(Spark._ANNOTATION_LIST_ITEM, frag) - if itemArr.length == 0 - Spark.finalize outerRange.replaceContents(frag) - else - itemArr[idx-1].liveRange.insertAfter frag - itemArr[idx] = { liveRange: range, value: val } - - removedAt: (val, idx) -> - later -> - if itemArr.length == 1 - frag = Spark.render(elseFunc) - DomUtils.wrapFragmentForContainer frag, outerRange.containerNode() - Spark.finalize outerRange.replaceContents(frag) - else - Spark.finalize itemArr[idx].liveRange.extract() - itemArr.splice idx, 1 - notifyParentsRendered() - - movedTo: (val, fromIdx, toIdx) -> - later -> - elt = (itemArr.splice fromIdx, 1)[0] - frag = elt.liveRange.extract() - if toIdx of itemArr - itemArr[toIdx].liveRange.insertBefore frag - else - itemArr[toIdx-1].liveRange.insertAfter frag - itemArr.splice toIdx, 0, elt - notifyParentsRendered() - - changedAt: (val, idx) -> - later -> - elt = itemArr[idx] - throw new Error("Unknown item at index: " + idx) unless elt - elt.value = val - Spark.renderToRange elt.liveRange, _.bind(itemFunc, null, elt.value) - - return html - -# ## findParentOfType -# Ripped from [Spark.list](https://github.com/meteor/meteor/blob/master/packages/spark/spark.js#L77)* -findParentOfType = (type, range) -> - loop - range = range.findParent() - break unless range and range.type isnt type - range - -# ## makeRange -# Ripped from [Spark.list](https://github.com/meteor/meteor/blob/master/packages/spark/spark.js#L81)* -makeRange = (type, start, end, inner) -> - range = new LiveRange(Spark._TAG, start, end, inner) - range.type = type - range \ No newline at end of file diff --git a/src/observe-sequence.coffee b/src/observe-sequence.coffee new file mode 100644 index 0000000..cc32aaf --- /dev/null +++ b/src/observe-sequence.coffee @@ -0,0 +1,138 @@ +# Extending ObserveSequence (https://github.com/meteor/meteor/blob/devel/packages/observe-sequence/) +ObserveSequence = Package['observe-sequence'].ObserveSequence + +idStringify = LocalCollection._idStringify; +idParse = LocalCollection._idParse; + +# Allow ReactiveList +ObserveSequenceFetch = ObserveSequence.fetch +ObserveSequence.fetch = (seq) -> + # Only use our implementation when the seq is a ReactiveList + return seq if seq and seq instanceof ReactiveList + + return ObserveSequenceFetch.call(@, seq) + +# Add observe support for ReactiveList +ObserveSequenceObserve = ObserveSequence.observe +ObserveSequence.observe = (sequenceFunc, callbacks) -> + # First we need to check if we should handle the sequence since there is nicer way to extend this. + observe = false + Deps.nonreactive () -> + observe = sequenceFunc() instanceof ReactiveList + + # Not a ReactiveList (aka not my problem) + return ObserveSequenceObserve.call(@, sequenceFunc, callbacks) unless observe + + # Mostly a copy from observe_sequence.js + lastSeq = null + lastSeqArray = [] + computation = Deps.autorun () -> + seq = sequenceFunc() + + if activeObserveHandle + lastSeqArray = _.map activeObserveHandle._fetch(), (doc) -> + {_id: doc._id, item: doc} + + activeObserveHandle.stop() + activeObserveHandle = null + + # create id's for the list + idsUsed = {}; + seqArray = _.map seq, (item, index) -> + id = undefined + if typeof item is "string" + # ensure not empty, since other layers (eg DomRange) assume this as well + id = "-" + item + else if typeof item is "number" or typeof item is "boolean" or item is `undefined` + id = item + else if typeof item is "object" + id = (item and item._id) or index + else + throw new Error("{{#each}} doesn't support arrays with " + "elements of type " + typeof item) + + idString = idStringify(id) + if idsUsed[idString] + warn "duplicate id " + id + " in", seq if typeof item is "object" and "_id" of item + id = Random.id() + else + idsUsed[idString] = true + return { + _id: id + item: item + } + diffArray lastSeqArray, seqArray, callbacks + + lastSeq = seq + lastSeqArray = seqArray + + return { + stop: () -> + computation.stop() + return + } + +# Direct copy of diffArray in observe_sequence.js +diffArray = (lastSeqArray, seqArray, callbacks) -> + diffFn = Package.minimongo.LocalCollection._diffQueryOrderedChanges + oldIdObjects = [] + newIdObjects = [] + posOld = {} + posNew = {} + posCur = {} + lengthCur = lastSeqArray.length + _.each seqArray, (doc, i) -> + newIdObjects.push _id: doc._id + posNew[idStringify(doc._id)] = i + return + + _.each lastSeqArray, (doc, i) -> + oldIdObjects.push _id: doc._id + posOld[idStringify(doc._id)] = i + posCur[idStringify(doc._id)] = i + return + + diffFn oldIdObjects, newIdObjects, + addedBefore: (id, doc, before) -> + position = (if before then posCur[idStringify(before)] else lengthCur) + _.each posCur, (pos, id) -> + posCur[id]++ if pos >= position + return + + lengthCur++ + posCur[idStringify(id)] = position + callbacks.addedAt id, seqArray[posNew[idStringify(id)]].item, position, before + return + + movedBefore: (id, before) -> + prevPosition = posCur[idStringify(id)] + position = (if before then posCur[idStringify(before)] else lengthCur - 1) + _.each posCur, (pos, id) -> + if pos >= prevPosition and pos <= position + posCur[id]-- + else posCur[id]++ if pos <= prevPosition and pos >= position + return + + posCur[idStringify(id)] = position + callbacks.movedTo id, seqArray[posNew[idStringify(id)]].item, prevPosition, position, before + return + + removed: (id) -> + prevPosition = posCur[idStringify(id)] + _.each posCur, (pos, id) -> + posCur[id]-- if pos >= prevPosition + return + + delete posCur[idStringify(id)] + + lengthCur-- + callbacks.removedAt id, lastSeqArray[posOld[idStringify(id)]].item, prevPosition + return + + _.each posNew, (pos, idString) -> + id = idParse(idString) + if _.has(posOld, idString) + newItem = seqArray[pos].item + oldItem = lastSeqArray[posOld[idString]].item + callbacks.changedAt id, newItem, oldItem, pos if typeof newItem is "object" or newItem isnt oldItem + return + return \ No newline at end of file diff --git a/src/reactive-list.coffee b/src/reactive-list.coffee index 28c9df3..313c6f1 100644 --- a/src/reactive-list.coffee +++ b/src/reactive-list.coffee @@ -156,7 +156,7 @@ class @ReactiveList extends ReactiveArray currentPosition++ for move in moves - this._trigger 'movedTo', @_list[move.to], move.from, move.to + @._trigger 'movedTo', @_list[move.to], move.from, move.to return @