ethstats-server/web-app/.meteor/local/build/programs/server/packages/observe-sequence.js

385 lines
31 KiB
JavaScript

(function () {
/* Imports */
var Meteor = Package.meteor.Meteor;
var Tracker = Package.tracker.Tracker;
var Deps = Package.tracker.Deps;
var LocalCollection = Package.minimongo.LocalCollection;
var Minimongo = Package.minimongo.Minimongo;
var _ = Package.underscore._;
var Random = Package.random.Random;
/* Package-scope variables */
var ObserveSequence, seqChangedToEmpty, seqChangedToArray, seqChangedToCursor;
(function () {
///////////////////////////////////////////////////////////////////////////////////
// //
// packages/observe-sequence/observe_sequence.js //
// //
///////////////////////////////////////////////////////////////////////////////////
//
var warn = function () { // 1
if (ObserveSequence._suppressWarnings) { // 2
ObserveSequence._suppressWarnings--; // 3
} else { // 4
if (typeof console !== 'undefined' && console.warn) // 5
console.warn.apply(console, arguments); // 6
// 7
ObserveSequence._loggedWarnings++; // 8
} // 9
}; // 10
// 11
var idStringify = LocalCollection._idStringify; // 12
var idParse = LocalCollection._idParse; // 13
// 14
ObserveSequence = { // 15
_suppressWarnings: 0, // 16
_loggedWarnings: 0, // 17
// 18
// A mechanism similar to cursor.observe which receives a reactive // 19
// function returning a sequence type and firing appropriate callbacks // 20
// when the value changes. // 21
// // 22
// @param sequenceFunc {Function} a reactive function returning a // 23
// sequence type. The currently supported sequence types are: // 24
// 'null', arrays and cursors. // 25
// // 26
// @param callbacks {Object} similar to a specific subset of // 27
// callbacks passed to `cursor.observe` // 28
// (http://docs.meteor.com/#observe), with minor variations to // 29
// support the fact that not all sequences contain objects with // 30
// _id fields. Specifically: // 31
// // 32
// * addedAt(id, item, atIndex, beforeId) // 33
// * changedAt(id, newItem, oldItem, atIndex) // 34
// * removedAt(id, oldItem, atIndex) // 35
// * movedTo(id, item, fromIndex, toIndex, beforeId) // 36
// // 37
// @returns {Object(stop: Function)} call 'stop' on the return value // 38
// to stop observing this sequence function. // 39
// // 40
// We don't make any assumptions about our ability to compare sequence // 41
// elements (ie, we don't assume EJSON.equals works; maybe there is extra // 42
// state/random methods on the objects) so unlike cursor.observe, we may // 43
// sometimes call changedAt() when nothing actually changed. // 44
// XXX consider if we *can* make the stronger assumption and avoid // 45
// no-op changedAt calls (in some cases?) // 46
// // 47
// XXX currently only supports the callbacks used by our // 48
// implementation of {{#each}}, but this can be expanded. // 49
// // 50
// XXX #each doesn't use the indices (though we'll eventually need // 51
// a way to get them when we support `@index`), but calling // 52
// `cursor.observe` causes the index to be calculated on every // 53
// callback using a linear scan (unless you turn it off by passing // 54
// `_no_indices`). Any way to avoid calculating indices on a pure // 55
// cursor observe like we used to? // 56
observe: function (sequenceFunc, callbacks) { // 57
var lastSeq = null; // 58
var activeObserveHandle = null; // 59
// 60
// 'lastSeqArray' contains the previous value of the sequence // 61
// we're observing. It is an array of objects with '_id' and // 62
// 'item' fields. 'item' is the element in the array, or the // 63
// document in the cursor. // 64
// // 65
// '_id' is whichever of the following is relevant, unless it has // 66
// already appeared -- in which case it's randomly generated. // 67
// // 68
// * if 'item' is an object: // 69
// * an '_id' field, if present // 70
// * otherwise, the index in the array // 71
// // 72
// * if 'item' is a number or string, use that value // 73
// // 74
// XXX this can be generalized by allowing {{#each}} to accept a // 75
// general 'key' argument which could be a function, a dotted // 76
// field name, or the special @index value. // 77
var lastSeqArray = []; // elements are objects of form {_id, item} // 78
var computation = Tracker.autorun(function () { // 79
var seq = sequenceFunc(); // 80
// 81
Tracker.nonreactive(function () { // 82
var seqArray; // same structure as `lastSeqArray` above. // 83
// 84
if (activeObserveHandle) { // 85
// If we were previously observing a cursor, replace lastSeqArray with // 86
// more up-to-date information. Then stop the old observe. // 87
lastSeqArray = _.map(lastSeq.fetch(), function (doc) { // 88
return {_id: doc._id, item: doc}; // 89
}); // 90
activeObserveHandle.stop(); // 91
activeObserveHandle = null; // 92
} // 93
// 94
if (!seq) { // 95
seqArray = seqChangedToEmpty(lastSeqArray, callbacks); // 96
} else if (seq instanceof Array) { // 97
seqArray = seqChangedToArray(lastSeqArray, seq, callbacks); // 98
} else if (isStoreCursor(seq)) { // 99
var result /* [seqArray, activeObserveHandle] */ = // 100
seqChangedToCursor(lastSeqArray, seq, callbacks); // 101
seqArray = result[0]; // 102
activeObserveHandle = result[1]; // 103
} else { // 104
throw badSequenceError(); // 105
} // 106
// 107
diffArray(lastSeqArray, seqArray, callbacks); // 108
lastSeq = seq; // 109
lastSeqArray = seqArray; // 110
}); // 111
}); // 112
// 113
return { // 114
stop: function () { // 115
computation.stop(); // 116
if (activeObserveHandle) // 117
activeObserveHandle.stop(); // 118
} // 119
}; // 120
}, // 121
// 122
// Fetch the items of `seq` into an array, where `seq` is of one of the // 123
// sequence types accepted by `observe`. If `seq` is a cursor, a // 124
// dependency is established. // 125
fetch: function (seq) { // 126
if (!seq) { // 127
return []; // 128
} else if (seq instanceof Array) { // 129
return seq; // 130
} else if (isStoreCursor(seq)) { // 131
return seq.fetch(); // 132
} else { // 133
throw badSequenceError(); // 134
} // 135
} // 136
}; // 137
// 138
var badSequenceError = function () { // 139
return new Error("{{#each}} currently only accepts " + // 140
"arrays, cursors or falsey values."); // 141
}; // 142
// 143
var isStoreCursor = function (cursor) { // 144
return cursor && _.isObject(cursor) && // 145
_.isFunction(cursor.observe) && _.isFunction(cursor.fetch); // 146
}; // 147
// 148
// Calculates the differences between `lastSeqArray` and // 149
// `seqArray` and calls appropriate functions from `callbacks`. // 150
// Reuses Minimongo's diff algorithm implementation. // 151
var diffArray = function (lastSeqArray, seqArray, callbacks) { // 152
var diffFn = Package.minimongo.LocalCollection._diffQueryOrderedChanges; // 153
var oldIdObjects = []; // 154
var newIdObjects = []; // 155
var posOld = {}; // maps from idStringify'd ids // 156
var posNew = {}; // ditto // 157
var posCur = {}; // 158
var lengthCur = lastSeqArray.length; // 159
// 160
_.each(seqArray, function (doc, i) { // 161
newIdObjects.push({_id: doc._id}); // 162
posNew[idStringify(doc._id)] = i; // 163
}); // 164
_.each(lastSeqArray, function (doc, i) { // 165
oldIdObjects.push({_id: doc._id}); // 166
posOld[idStringify(doc._id)] = i; // 167
posCur[idStringify(doc._id)] = i; // 168
}); // 169
// 170
// Arrays can contain arbitrary objects. We don't diff the // 171
// objects. Instead we always fire 'changedAt' callback on every // 172
// object. The consumer of `observe-sequence` should deal with // 173
// it appropriately. // 174
diffFn(oldIdObjects, newIdObjects, { // 175
addedBefore: function (id, doc, before) { // 176
var position = before ? posCur[idStringify(before)] : lengthCur; // 177
// 178
if (before) { // 179
// If not adding at the end, we need to update indexes. // 180
// XXX this can still be improved greatly! // 181
_.each(posCur, function (pos, id) { // 182
if (pos >= position) // 183
posCur[id]++; // 184
}); // 185
} // 186
// 187
lengthCur++; // 188
posCur[idStringify(id)] = position; // 189
// 190
callbacks.addedAt( // 191
id, // 192
seqArray[posNew[idStringify(id)]].item, // 193
position, // 194
before); // 195
}, // 196
movedBefore: function (id, before) { // 197
if (id === before) // 198
return; // 199
// 200
var oldPosition = posCur[idStringify(id)]; // 201
var newPosition = before ? posCur[idStringify(before)] : lengthCur; // 202
// 203
// Moving the item forward. The new element is losing one position as it // 204
// was removed from the old position before being inserted at the new // 205
// position. // 206
// Ex.: 0 *1* 2 3 4 // 207
// 0 2 3 *1* 4 // 208
// The original issued callback is "1" before "4". // 209
// The position of "1" is 1, the position of "4" is 4. // 210
// The generated move is (1) -> (3) // 211
if (newPosition > oldPosition) { // 212
newPosition--; // 213
} // 214
// 215
// Fix up the positions of elements between the old and the new positions // 216
// of the moved element. // 217
// // 218
// There are two cases: // 219
// 1. The element is moved forward. Then all the positions in between // 220
// are moved back. // 221
// 2. The element is moved back. Then the positions in between *and* the // 222
// element that is currently standing on the moved element's future // 223
// position are moved forward. // 224
_.each(posCur, function (elCurPosition, id) { // 225
if (oldPosition < elCurPosition && elCurPosition < newPosition) // 226
posCur[id]--; // 227
else if (newPosition <= elCurPosition && elCurPosition < oldPosition) // 228
posCur[id]++; // 229
}); // 230
// 231
// Finally, update the position of the moved element. // 232
posCur[idStringify(id)] = newPosition; // 233
// 234
callbacks.movedTo( // 235
id, // 236
seqArray[posNew[idStringify(id)]].item, // 237
oldPosition, // 238
newPosition, // 239
before); // 240
}, // 241
removed: function (id) { // 242
var prevPosition = posCur[idStringify(id)]; // 243
// 244
_.each(posCur, function (pos, id) { // 245
if (pos >= prevPosition) // 246
posCur[id]--; // 247
}); // 248
// 249
delete posCur[idStringify(id)]; // 250
lengthCur--; // 251
// 252
callbacks.removedAt( // 253
id, // 254
lastSeqArray[posOld[idStringify(id)]].item, // 255
prevPosition); // 256
} // 257
}); // 258
// 259
_.each(posNew, function (pos, idString) { // 260
var id = idParse(idString); // 261
if (_.has(posOld, idString)) { // 262
// specifically for primitive types, compare equality before // 263
// firing the 'changedAt' callback. otherwise, always fire it // 264
// because doing a deep EJSON comparison is not guaranteed to // 265
// work (an array can contain arbitrary objects, and 'transform' // 266
// can be used on cursors). also, deep diffing is not // 267
// necessarily the most efficient (if only a specific subfield // 268
// of the object is later accessed). // 269
var newItem = seqArray[pos].item; // 270
var oldItem = lastSeqArray[posOld[idString]].item; // 271
// 272
if (typeof newItem === 'object' || newItem !== oldItem) // 273
callbacks.changedAt(id, newItem, oldItem, pos); // 274
} // 275
}); // 276
}; // 277
// 278
seqChangedToEmpty = function (lastSeqArray, callbacks) { // 279
return []; // 280
}; // 281
// 282
seqChangedToArray = function (lastSeqArray, array, callbacks) { // 283
var idsUsed = {}; // 284
var seqArray = _.map(array, function (item, index) { // 285
var id; // 286
if (typeof item === 'string') { // 287
// ensure not empty, since other layers (eg DomRange) assume this as well // 288
id = "-" + item; // 289
} else if (typeof item === 'number' || // 290
typeof item === 'boolean' || // 291
item === undefined) { // 292
id = item; // 293
} else if (typeof item === 'object') { // 294
id = (item && item._id) || index; // 295
} else { // 296
throw new Error("{{#each}} doesn't support arrays with " + // 297
"elements of type " + typeof item); // 298
} // 299
// 300
var idString = idStringify(id); // 301
if (idsUsed[idString]) { // 302
if (typeof item === 'object' && '_id' in item) // 303
warn("duplicate id " + id + " in", array); // 304
id = Random.id(); // 305
} else { // 306
idsUsed[idString] = true; // 307
} // 308
// 309
return { _id: id, item: item }; // 310
}); // 311
// 312
return seqArray; // 313
}; // 314
// 315
seqChangedToCursor = function (lastSeqArray, cursor, callbacks) { // 316
var initial = true; // are we observing initial data from cursor? // 317
var seqArray = []; // 318
// 319
var observeHandle = cursor.observe({ // 320
addedAt: function (document, atIndex, before) { // 321
if (initial) { // 322
// keep track of initial data so that we can diff once // 323
// we exit `observe`. // 324
if (before !== null) // 325
throw new Error("Expected initial data from observe in order"); // 326
seqArray.push({ _id: document._id, item: document }); // 327
} else { // 328
callbacks.addedAt(document._id, document, atIndex, before); // 329
} // 330
}, // 331
changedAt: function (newDocument, oldDocument, atIndex) { // 332
callbacks.changedAt(newDocument._id, newDocument, oldDocument, // 333
atIndex); // 334
}, // 335
removedAt: function (oldDocument, atIndex) { // 336
callbacks.removedAt(oldDocument._id, oldDocument, atIndex); // 337
}, // 338
movedTo: function (document, fromIndex, toIndex, before) { // 339
callbacks.movedTo( // 340
document._id, document, fromIndex, toIndex, before); // 341
} // 342
}); // 343
initial = false; // 344
// 345
return [seqArray, observeHandle]; // 346
}; // 347
// 348
///////////////////////////////////////////////////////////////////////////////////
}).call(this);
/* Exports */
if (typeof Package === 'undefined') Package = {};
Package['observe-sequence'] = {
ObserveSequence: ObserveSequence
};
})();
//# sourceMappingURL=observe-sequence.js.map