You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1 lines
15 KiB
1 lines
15 KiB
{"version":3,"sources":["observe-sequence/observe_sequence.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,wB;AACA,0C;AACA,wC;AACA,U;AACA,uD;AACA,6C;;AAEA,sC;AACA,G;AACA,E;;AAEA,+C;AACA,uC;;AAEA,mB;AACA,uB;AACA,qB;;AAEA,oE;AACA,wE;AACA,4B;AACA,I;AACA,mE;AACA,mE;AACA,oC;AACA,I;AACA,8D;AACA,6C;AACA,oE;AACA,qE;AACA,mC;AACA,I;AACA,+C;AACA,mD;AACA,0C;AACA,0D;AACA,I;AACA,sE;AACA,kD;AACA,I;AACA,wE;AACA,2E;AACA,0E;AACA,8D;AACA,oE;AACA,+C;AACA,I;AACA,0D;AACA,2D;AACA,I;AACA,oE;AACA,6D;AACA,gE;AACA,oE;AACA,oE;AACA,oC;AACA,+C;AACA,uB;AACA,mC;;AAEA,iE;AACA,gE;AACA,iE;AACA,8B;AACA,M;AACA,qE;AACA,iE;AACA,M;AACA,gC;AACA,qC;AACA,4C;AACA,M;AACA,wD;AACA,M;AACA,oE;AACA,iE;AACA,+C;AACA,sE;AACA,mD;AACA,+B;;AAEA,uC;AACA,gE;;AAEA,kC;AACA,gF;AACA,qE;AACA,gE;AACA,6C;AACA,a;AACA,qC;AACA,qC;AACA,S;;AAEA,mB;AACA,gE;AACA,0C;AACA,qE;AACA,wC;AACA,4D;AACA,iE;AACA,+B;AACA,0C;AACA,gB;AACA,mC;AACA,S;;AAEA,qD;AACA,sB;AACA,gC;AACA,S;AACA,O;;AAEA,Y;AACA,yB;AACA,2B;AACA,gC;AACA,qC;AACA,O;AACA,M;AACA,I;;AAEA,yE;AACA,mE;AACA,+B;AACA,yB;AACA,e;AACA,gB;AACA,sC;AACA,iB;AACA,oC;AACA,yB;AACA,Y;AACA,+B;AACA,K;AACA,G;AACA,E;;AAEA,oC;AACA,wD;AACA,wD;AACA,E;;AAEA,uC;AACA,wC;AACA,+D;AACA,E;;AAEA,wD;AACA,+D;AACA,oD;AACA,8D;AACA,0E;AACA,wB;AACA,wB;AACA,iD;AACA,2B;AACA,kB;AACA,sC;;AAEA,sC;AACA,sC;AACA,qC;AACA,K;AACA,0C;AACA,sC;AACA,qC;AACA,qC;AACA,K;;AAEA,4D;AACA,kE;AACA,gE;AACA,sB;AACA,sC;AACA,6C;AACA,sE;;AAEA,mB;AACA,+D;AACA,kD;AACA,2C;AACA,8B;AACA,yB;AACA,W;AACA,O;;AAEA,kB;AACA,yC;;AAEA,wB;AACA,W;AACA,+C;AACA,iB;AACA,gB;AACA,M;AACA,wC;AACA,wB;AACA,e;;AAEA,gD;AACA,yE;;AAEA,8E;AACA,2E;AACA,kB;AACA,iC;AACA,iC;AACA,wD;AACA,4D;AACA,yC;AACA,sC;AACA,sB;AACA,O;;AAEA,+E;AACA,8B;AACA,Q;AACA,6B;AACA,6E;AACA,0B;AACA,gF;AACA,2E;AACA,sC;AACA,mD;AACA,uE;AACA,uB;AACA,6E;AACA,uB;AACA,S;;AAEA,2D;AACA,4C;;AAEA,wB;AACA,W;AACA,+C;AACA,oB;AACA,oB;AACA,gB;AACA,M;AACA,4B;AACA,iD;;AAEA,yC;AACA,gC;AACA,uB;AACA,S;;AAEA,qC;AACA,kB;;AAEA,0B;AACA,W;AACA,mD;AACA,sB;AACA,K;AACA,K;;AAEA,2C;AACA,+B;AACA,kC;AACA,kE;AACA,mE;AACA,mE;AACA,sE;AACA,2D;AACA,oE;AACA,0C;AACA,uC;AACA,wD;;AAEA,6D;AACA,yD;AACA,O;AACA,K;AACA,E;;AAEA,wD;AACA,Y;AACA,E;;AAEA,+D;AACA,mB;AACA,sD;AACA,W;AACA,mC;AACA,+E;AACA,sB;AACA,0C;AACA,2C;AACA,oC;AACA,gB;AACA,0C;AACA,uC;AACA,Y;AACA,gE;AACA,yD;AACA,K;;AAEA,mC;AACA,4B;AACA,oD;AACA,kD;AACA,uB;AACA,Y;AACA,+B;AACA,K;;AAEA,mC;AACA,K;;AAEA,kB;AACA,E;;AAEA,iE;AACA,mE;AACA,oB;;AAEA,sC;AACA,mD;AACA,oB;AACA,8D;AACA,6B;AACA,4B;AACA,yE;AACA,6D;AACA,c;AACA,mE;AACA,O;AACA,M;AACA,6D;AACA,oE;AACA,mC;AACA,M;AACA,gD;AACA,iE;AACA,M;AACA,8D;AACA,wB;AACA,4D;AACA,K;AACA,K;AACA,kB;;AAEA,mC;AACA,E","file":"/packages/observe-sequence.js","sourcesContent":["var warn = function () {\n if (ObserveSequence._suppressWarnings) {\n ObserveSequence._suppressWarnings--;\n } else {\n if (typeof console !== 'undefined' && console.warn)\n console.warn.apply(console, arguments);\n\n ObserveSequence._loggedWarnings++;\n }\n};\n\nvar idStringify = LocalCollection._idStringify;\nvar idParse = LocalCollection._idParse;\n\nObserveSequence = {\n _suppressWarnings: 0,\n _loggedWarnings: 0,\n\n // A mechanism similar to cursor.observe which receives a reactive\n // function returning a sequence type and firing appropriate callbacks\n // when the value changes.\n //\n // @param sequenceFunc {Function} a reactive function returning a\n // sequence type. The currently supported sequence types are:\n // 'null', arrays and cursors.\n //\n // @param callbacks {Object} similar to a specific subset of\n // callbacks passed to `cursor.observe`\n // (http://docs.meteor.com/#observe), with minor variations to\n // support the fact that not all sequences contain objects with\n // _id fields. Specifically:\n //\n // * addedAt(id, item, atIndex, beforeId)\n // * changedAt(id, newItem, oldItem, atIndex)\n // * removedAt(id, oldItem, atIndex)\n // * movedTo(id, item, fromIndex, toIndex, beforeId)\n //\n // @returns {Object(stop: Function)} call 'stop' on the return value\n // to stop observing this sequence function.\n //\n // We don't make any assumptions about our ability to compare sequence\n // elements (ie, we don't assume EJSON.equals works; maybe there is extra\n // state/random methods on the objects) so unlike cursor.observe, we may\n // sometimes call changedAt() when nothing actually changed.\n // XXX consider if we *can* make the stronger assumption and avoid\n // no-op changedAt calls (in some cases?)\n //\n // XXX currently only supports the callbacks used by our\n // implementation of {{#each}}, but this can be expanded.\n //\n // XXX #each doesn't use the indices (though we'll eventually need\n // a way to get them when we support `@index`), but calling\n // `cursor.observe` causes the index to be calculated on every\n // callback using a linear scan (unless you turn it off by passing\n // `_no_indices`). Any way to avoid calculating indices on a pure\n // cursor observe like we used to?\n observe: function (sequenceFunc, callbacks) {\n var lastSeq = null;\n var activeObserveHandle = null;\n\n // 'lastSeqArray' contains the previous value of the sequence\n // we're observing. It is an array of objects with '_id' and\n // 'item' fields. 'item' is the element in the array, or the\n // document in the cursor.\n //\n // '_id' is whichever of the following is relevant, unless it has\n // already appeared -- in which case it's randomly generated.\n //\n // * if 'item' is an object:\n // * an '_id' field, if present\n // * otherwise, the index in the array\n //\n // * if 'item' is a number or string, use that value\n //\n // XXX this can be generalized by allowing {{#each}} to accept a\n // general 'key' argument which could be a function, a dotted\n // field name, or the special @index value.\n var lastSeqArray = []; // elements are objects of form {_id, item}\n var computation = Tracker.autorun(function () {\n var seq = sequenceFunc();\n\n Tracker.nonreactive(function () {\n var seqArray; // same structure as `lastSeqArray` above.\n\n if (activeObserveHandle) {\n // If we were previously observing a cursor, replace lastSeqArray with\n // more up-to-date information. Then stop the old observe.\n lastSeqArray = _.map(lastSeq.fetch(), function (doc) {\n return {_id: doc._id, item: doc};\n });\n activeObserveHandle.stop();\n activeObserveHandle = null;\n }\n\n if (!seq) {\n seqArray = seqChangedToEmpty(lastSeqArray, callbacks);\n } else if (seq instanceof Array) {\n seqArray = seqChangedToArray(lastSeqArray, seq, callbacks);\n } else if (isStoreCursor(seq)) {\n var result /* [seqArray, activeObserveHandle] */ =\n seqChangedToCursor(lastSeqArray, seq, callbacks);\n seqArray = result[0];\n activeObserveHandle = result[1];\n } else {\n throw badSequenceError();\n }\n\n diffArray(lastSeqArray, seqArray, callbacks);\n lastSeq = seq;\n lastSeqArray = seqArray;\n });\n });\n\n return {\n stop: function () {\n computation.stop();\n if (activeObserveHandle)\n activeObserveHandle.stop();\n }\n };\n },\n\n // Fetch the items of `seq` into an array, where `seq` is of one of the\n // sequence types accepted by `observe`. If `seq` is a cursor, a\n // dependency is established.\n fetch: function (seq) {\n if (!seq) {\n return [];\n } else if (seq instanceof Array) {\n return seq;\n } else if (isStoreCursor(seq)) {\n return seq.fetch();\n } else {\n throw badSequenceError();\n }\n }\n};\n\nvar badSequenceError = function () {\n return new Error(\"{{#each}} currently only accepts \" +\n \"arrays, cursors or falsey values.\");\n};\n\nvar isStoreCursor = function (cursor) {\n return cursor && _.isObject(cursor) &&\n _.isFunction(cursor.observe) && _.isFunction(cursor.fetch);\n};\n\n// Calculates the differences between `lastSeqArray` and\n// `seqArray` and calls appropriate functions from `callbacks`.\n// Reuses Minimongo's diff algorithm implementation.\nvar diffArray = function (lastSeqArray, seqArray, callbacks) {\n var diffFn = Package.minimongo.LocalCollection._diffQueryOrderedChanges;\n var oldIdObjects = [];\n var newIdObjects = [];\n var posOld = {}; // maps from idStringify'd ids\n var posNew = {}; // ditto\n var posCur = {};\n var lengthCur = lastSeqArray.length;\n\n _.each(seqArray, function (doc, i) {\n newIdObjects.push({_id: doc._id});\n posNew[idStringify(doc._id)] = i;\n });\n _.each(lastSeqArray, function (doc, i) {\n oldIdObjects.push({_id: doc._id});\n posOld[idStringify(doc._id)] = i;\n posCur[idStringify(doc._id)] = i;\n });\n\n // Arrays can contain arbitrary objects. We don't diff the\n // objects. Instead we always fire 'changedAt' callback on every\n // object. The consumer of `observe-sequence` should deal with\n // it appropriately.\n diffFn(oldIdObjects, newIdObjects, {\n addedBefore: function (id, doc, before) {\n var position = before ? posCur[idStringify(before)] : lengthCur;\n\n if (before) {\n // If not adding at the end, we need to update indexes.\n // XXX this can still be improved greatly!\n _.each(posCur, function (pos, id) {\n if (pos >= position)\n posCur[id]++;\n });\n }\n\n lengthCur++;\n posCur[idStringify(id)] = position;\n\n callbacks.addedAt(\n id,\n seqArray[posNew[idStringify(id)]].item,\n position,\n before);\n },\n movedBefore: function (id, before) {\n if (id === before)\n return;\n\n var oldPosition = posCur[idStringify(id)];\n var newPosition = before ? posCur[idStringify(before)] : lengthCur;\n\n // Moving the item forward. The new element is losing one position as it\n // was removed from the old position before being inserted at the new\n // position.\n // Ex.: 0 *1* 2 3 4\n // 0 2 3 *1* 4\n // The original issued callback is \"1\" before \"4\".\n // The position of \"1\" is 1, the position of \"4\" is 4.\n // The generated move is (1) -> (3)\n if (newPosition > oldPosition) {\n newPosition--;\n }\n\n // Fix up the positions of elements between the old and the new positions\n // of the moved element.\n //\n // There are two cases:\n // 1. The element is moved forward. Then all the positions in between\n // are moved back.\n // 2. The element is moved back. Then the positions in between *and* the\n // element that is currently standing on the moved element's future\n // position are moved forward.\n _.each(posCur, function (elCurPosition, id) {\n if (oldPosition < elCurPosition && elCurPosition < newPosition)\n posCur[id]--;\n else if (newPosition <= elCurPosition && elCurPosition < oldPosition)\n posCur[id]++;\n });\n\n // Finally, update the position of the moved element.\n posCur[idStringify(id)] = newPosition;\n\n callbacks.movedTo(\n id,\n seqArray[posNew[idStringify(id)]].item,\n oldPosition,\n newPosition,\n before);\n },\n removed: function (id) {\n var prevPosition = posCur[idStringify(id)];\n\n _.each(posCur, function (pos, id) {\n if (pos >= prevPosition)\n posCur[id]--;\n });\n\n delete posCur[idStringify(id)];\n lengthCur--;\n\n callbacks.removedAt(\n id,\n lastSeqArray[posOld[idStringify(id)]].item,\n prevPosition);\n }\n });\n\n _.each(posNew, function (pos, idString) {\n var id = idParse(idString);\n if (_.has(posOld, idString)) {\n // specifically for primitive types, compare equality before\n // firing the 'changedAt' callback. otherwise, always fire it\n // because doing a deep EJSON comparison is not guaranteed to\n // work (an array can contain arbitrary objects, and 'transform'\n // can be used on cursors). also, deep diffing is not\n // necessarily the most efficient (if only a specific subfield\n // of the object is later accessed).\n var newItem = seqArray[pos].item;\n var oldItem = lastSeqArray[posOld[idString]].item;\n\n if (typeof newItem === 'object' || newItem !== oldItem)\n callbacks.changedAt(id, newItem, oldItem, pos);\n }\n });\n};\n\nseqChangedToEmpty = function (lastSeqArray, callbacks) {\n return [];\n};\n\nseqChangedToArray = function (lastSeqArray, array, callbacks) {\n var idsUsed = {};\n var seqArray = _.map(array, function (item, index) {\n var id;\n if (typeof item === 'string') {\n // ensure not empty, since other layers (eg DomRange) assume this as well\n id = \"-\" + item;\n } else if (typeof item === 'number' ||\n typeof item === 'boolean' ||\n item === undefined) {\n id = item;\n } else if (typeof item === 'object') {\n id = (item && item._id) || index;\n } else {\n throw new Error(\"{{#each}} doesn't support arrays with \" +\n \"elements of type \" + typeof item);\n }\n\n var idString = idStringify(id);\n if (idsUsed[idString]) {\n if (typeof item === 'object' && '_id' in item)\n warn(\"duplicate id \" + id + \" in\", array);\n id = Random.id();\n } else {\n idsUsed[idString] = true;\n }\n\n return { _id: id, item: item };\n });\n\n return seqArray;\n};\n\nseqChangedToCursor = function (lastSeqArray, cursor, callbacks) {\n var initial = true; // are we observing initial data from cursor?\n var seqArray = [];\n\n var observeHandle = cursor.observe({\n addedAt: function (document, atIndex, before) {\n if (initial) {\n // keep track of initial data so that we can diff once\n // we exit `observe`.\n if (before !== null)\n throw new Error(\"Expected initial data from observe in order\");\n seqArray.push({ _id: document._id, item: document });\n } else {\n callbacks.addedAt(document._id, document, atIndex, before);\n }\n },\n changedAt: function (newDocument, oldDocument, atIndex) {\n callbacks.changedAt(newDocument._id, newDocument, oldDocument,\n atIndex);\n },\n removedAt: function (oldDocument, atIndex) {\n callbacks.removedAt(oldDocument._id, oldDocument, atIndex);\n },\n movedTo: function (document, fromIndex, toIndex, before) {\n callbacks.movedTo(\n document._id, document, fromIndex, toIndex, before);\n }\n });\n initial = false;\n\n return [seqArray, observeHandle];\n};\n"]} |