{"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"]}