diff --git a/firebase-firestore-mixin.html b/firebase-firestore-mixin.html index b4e9a19..8036f33 100644 --- a/firebase-firestore-mixin.html +++ b/firebase-firestore-mixin.html @@ -6,10 +6,6 @@ { const PROPERTY_BINDING_REGEXP = /{([^{]+)}/g; - const TRANSFORMS = { - doc: function(snap) { return iDoc(snap); }, - collection: function(snap) { return snap.empty ? [] : snap.docs.map(doc => iDoc(doc)) } - } const isOdd = (x) => x & 1 === 1; @@ -31,14 +27,14 @@ return whole; } - const collect = (what, which) => { - let res = {}; - while (what) { - res = Object.assign({}, what[which], res); // Respect prototype priority - what = Object.getPrototypeOf(what); - } - return res; - }; + const collect = (what, which) => { + let res = {}; + while (what) { + res = Object.assign({}, what[which], res); // Respect prototype priority + what = Object.getPrototypeOf(what); + } + return res; + }; const iDoc = (snap) => { if (snap.exists) { @@ -157,18 +153,17 @@ this._firestoreProps[name] = config; - // Create a method observer that will be called every time a templatized or observed property changes - let args = config.props.concat(config.observes).join(','); - if (args.length) { args = ',' + args; } - this._createMethodObserver(`_firestoreUpdateBinding('${type}','${name}'${args})`); - if (!config.props.length && !config.observes.length) { - this._firestoreUpdateBinding(type,name); + this._firestoreUpdateBinding(name, type); + } else { + // Create a method observer that will be called every time a templatized or observed property changes + let args = config.props.concat(config.observes).join(','); + this._createMethodObserver(`_firestoreUpdateBinding('${name}', '${type}', ${args})`); } } - _firestoreUpdateBinding(type, name) { - this._firestoreUnlisten(name); + _firestoreUpdateBinding(name, type) { + this._firestoreUnlisten(name, type); const config = this._firestoreProps[name]; const propArgs = Array.prototype.slice.call(arguments, 2, config.props.length + 2).filter(arg => arg); @@ -182,10 +177,7 @@ } const collPath = stitch(config.literals, propArgs); - const assigner = snap => { - this[name] = TRANSFORMS[type](snap); - this[name + 'Ready'] = true; - } + const assigner = this._firestoreAssigner(name, type); let ref = this.db[type](collPath); this[name + 'Ref'] = ref; @@ -203,11 +195,57 @@ } } - _firestoreUnlisten(name) { + _firestoreUnlisten(name, type) { if (this._firestoreListeners[name]) { this._firestoreListeners[name](); delete this._firestoreListeners[name]; } + + this[name] = type === 'collection' ? [] : null; + } + + _firestoreAssigner(name, type) { + if (type === 'doc') { + return (snap) => this._firestoreAssignDocument(name, snap); + } else if (type === 'collection') { + return (snap) => this._firestoreAssignCollection(name, snap); + } else { + throw new Error('Unknown listener type.'); + } + } + + _firestoreAssignDocument(name, snap) { + this[name] = iDoc(snap); + this[name + 'Ready'] = true; + } + + _firestoreAssignCollection(name, snap) { + const propertyValueIsArray = Array.isArray(this[name]) + const allDocumentsChanged = snap.docs.length === snap.docChanges.length; + if (propertyValueIsArray && allDocumentsChanged === false) { + snap.docChanges.forEach((change) => { + switch (change.type) { + case 'added': + this.splice(name, change.newIndex, 0, iDoc(change.doc)); + break; + case 'removed': + this.splice(name, change.oldIndex, 1); + break; + case 'modified': + if (change.oldIndex === change.newIndex) { + this.splice(name, change.oldIndex, 1, iDoc(change.doc)); + } else { + this.splice(name, change.oldIndex, 1); + this.splice(name, change.newIndex, 0, iDoc(change.doc)); + } + break; + default: + throw new Error(`Unhandled document change: ${change.type}.`); + } + }); + } else { + this[name] = snap.docs.map(iDoc); + } } } }