From 844dcfd0c04091e9a5fa16f25befa8075c7b2e50 Mon Sep 17 00:00:00 2001 From: christophe-g Date: Sat, 17 Jun 2017 19:13:13 +0200 Subject: [PATCH 01/13] added exists attribute, reflecting whether we have data at a given path. An "empty-result" event is fired by firebase-document or firebase-query to notify that the path does not hold any data. --- firebase-database-behavior.html | 23 ++++++++++++++++++++++- firebase-document.html | 8 +++++++- firebase-query.html | 9 ++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/firebase-database-behavior.html b/firebase-database-behavior.html index 0cf7108..6ccdcd9 100644 --- a/firebase-database-behavior.html +++ b/firebase-database-behavior.html @@ -14,6 +14,12 @@ (function() { 'use strict'; + /** + * the element is notified that the path does not hold any data (fired by firebase-document or firebase query) + * + * @event empty-result + */ + /** @polymerBehavior Polymer.FirebaseDatabaseBehavior */ Polymer.FirebaseDatabaseBehaviorImpl = { properties: { @@ -46,7 +52,20 @@ disabled: { type: Boolean, value: false - } + }, + + /** + * `exists` is set to `true` when the data actually exists for the specified path; `false` otherwise. + * When we are unable to determine whether data exists or not (e.g. first round trip to the server not yet performed) the value is `null` + */ + exists: { + type: Boolean, + notify: true, + value: null, + readOnly: true, + reflectToAttribute: true + }, + }, observers: [ @@ -95,6 +114,8 @@ }, __pathChanged: function(path, oldPath) { + this._setExists(null); + if (!this.disabled && !this.valueIsEmpty(this.data)) { this.syncToMemory(function() { this.data = this.zeroValue; diff --git a/firebase-document.html b/firebase-document.html index 039e712..e8f6fa2 100644 --- a/firebase-document.html +++ b/firebase-document.html @@ -160,11 +160,13 @@ __onFirebaseValue: function(snapshot) { var value = snapshot.val(); + var exists = true; if (value == null) { value = this.zeroValue; this.__needSetData = true; - } + exists = false; + } if (!this.isNew) { this.async(function() { @@ -188,6 +190,10 @@ } } }); + this._setExists(exists); + if(!exists) { + this.fire('empty-result'); + } }); } } diff --git a/firebase-query.html b/firebase-query.html index 41246bb..64286d2 100644 --- a/firebase-query.html +++ b/firebase-query.html @@ -287,6 +287,7 @@ this.syncToMemory(function() { this.set('data', this.zeroValue); + this._setExists(null); }); } @@ -298,7 +299,8 @@ query.off('child_moved', this.__onFirebaseChildMoved, this); } - this._onOnce = true; + this._setExists(null); + this._onOnce = true; query.on('child_added', this.__onFirebaseChildAdded, this.__onError, this); query.on('child_removed', this.__onFirebaseChildRemoved, this.__onError, this); query.on('child_changed', this.__onFirebaseChildChanged, this.__onError, this); @@ -327,6 +329,7 @@ value = this.__snapshotToValue(snapshot); this.__map[key] = value; + this._setExists(true); this.splice('data', previousChildIndex + 1, 0, value); }, @@ -342,6 +345,10 @@ this.syncToMemory(function() { this.splice('data', this.__indexFromKey(key), 1); }); + if (this.data.length === 0) { + this._setExists(false); + this.fire('empty-result'); + } }); } }, From 91d7793f101dc2a0d972385d3a14959b89683ffb Mon Sep 17 00:00:00 2001 From: Ionescu Marian Date: Sat, 7 Oct 2017 02:00:53 +0300 Subject: [PATCH 02/13] Update README.md Add project-id in the example firebase-app to be able to use firebase.firestore(). Implementation for projectId already merged in firebase/polymerfire#273 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b50fd57..95ac926 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,8 @@ Example Usage: database-url="https://polymerfire-test.firebaseio.com/" api-key="AIzaSyDTP-eiQezleFsV2WddFBAhF_WEzx_8v_g" storage-bucket="polymerfire-test.appspot.com" - messaging-sender-id="544817973908"> + messaging-sender-id="544817973908" + project-id="polymerfire-test"> From f6df335bc3871a097f29eb4d99b8d2083f8c7974 Mon Sep 17 00:00:00 2001 From: Alexey Rodionov Date: Sat, 4 Nov 2017 15:56:16 +0300 Subject: [PATCH 03/13] Fix typo in code --- firebase-storage-multiupload.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firebase-storage-multiupload.html b/firebase-storage-multiupload.html index d1790b5..31a4422 100644 --- a/firebase-storage-multiupload.html +++ b/firebase-storage-multiupload.html @@ -53,7 +53,7 @@ * file-task * * * Date: Sat, 13 Jan 2018 12:13:20 +0100 Subject: [PATCH 04/13] integrate @merlinnot comments --- firebase-database-behavior.html | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/firebase-database-behavior.html b/firebase-database-behavior.html index 6ccdcd9..60c38af 100644 --- a/firebase-database-behavior.html +++ b/firebase-database-behavior.html @@ -14,12 +14,6 @@ (function() { 'use strict'; - /** - * the element is notified that the path does not hold any data (fired by firebase-document or firebase query) - * - * @event empty-result - */ - /** @polymerBehavior Polymer.FirebaseDatabaseBehavior */ Polymer.FirebaseDatabaseBehaviorImpl = { properties: { @@ -55,8 +49,11 @@ }, /** - * `exists` is set to `true` when the data actually exists for the specified path; `false` otherwise. - * When we are unable to determine whether data exists or not (e.g. first round trip to the server not yet performed) the value is `null` + * `exists` is set to `true` when the data actually exists for the + * specified path; `false` otherwise. + * When we are unable to determine whether data exists or not + * (e.g. first round trip to the server not yet performed) the value + * is `null` */ exists: { type: Boolean, From 58ed184931dd7afdb39172d453d1236889840ee0 Mon Sep 17 00:00:00 2001 From: christophe geiser Date: Sat, 13 Jan 2018 12:15:56 +0100 Subject: [PATCH 05/13] integrate @merlinnot comment --- firebase-query.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/firebase-query.html b/firebase-query.html index 9b6f94e..4b31e6b 100644 --- a/firebase-query.html +++ b/firebase-query.html @@ -300,7 +300,8 @@ query.off('child_changed', this.__onFirebaseChildChanged, this); query.off('child_moved', this.__onFirebaseChildMoved, this); } - + + this._setExists(null); this._onOnce = true; /* We want the value callback to batch load the initial data, @@ -312,8 +313,7 @@ * the callback if the query changes */ query.on('value', this.__onFirebaseValue, this.__onError, this); - this._setExists(null); - + query.on('child_added', this.__onFirebaseChildAdded, this.__onError, this); query.on('child_removed', this.__onFirebaseChildRemoved, this.__onError, this); query.on('child_changed', this.__onFirebaseChildChanged, this.__onError, this); From c8e301c18cfe909be91397ec84d852098aa7ee64 Mon Sep 17 00:00:00 2001 From: christophe geiser Date: Sat, 13 Jan 2018 12:21:42 +0100 Subject: [PATCH 06/13] integrate @merlinnot comments --- firebase-query.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/firebase-query.html b/firebase-query.html index 4b31e6b..1c00526 100644 --- a/firebase-query.html +++ b/firebase-query.html @@ -354,7 +354,6 @@ var key = snapshot.key; if (this.__initialLoadDone) { - var value = snapshot.val(); var previousChildIndex = this.__indexFromKey(previousChildKey); @@ -363,8 +362,8 @@ value = this.__snapshotToValue(snapshot); this.__map[key] = value; - this._setExists(true); this.splice('data', previousChildIndex + 1, 0, value); + this._setExists(true); } }, @@ -382,7 +381,6 @@ }); if (this.data.length === 0) { this._setExists(false); - this.fire('empty-result'); } }); } From 7290611c0da89652f12f209768a97128d6ee9916 Mon Sep 17 00:00:00 2001 From: christophe-g Date: Sat, 13 Jan 2018 14:01:02 +0100 Subject: [PATCH 07/13] added tests --- firebase-query.html | 1 + test/firebase-document.html | 53 +++++++++++++++++++++++++++++++++++++ test/firebase-query.html | 17 ++++++++++++ 3 files changed, 71 insertions(+) diff --git a/firebase-query.html b/firebase-query.html index 117cf4a..466d0d3 100644 --- a/firebase-query.html +++ b/firebase-query.html @@ -340,6 +340,7 @@ }.bind(this)) this.set('data', data); + this._setExists(true); } const query = this.query diff --git a/test/firebase-document.html b/test/firebase-document.html index b97adcf..3fb00e8 100644 --- a/test/firebase-document.html +++ b/test/firebase-document.html @@ -65,6 +65,59 @@ }); } }); + + function pushFirebaseValue(path, value) { + return firebase.app('test').database().ref(path).push(value); + } + + function clearFirebaseValue(path) { + return firebase.app('test').database().ref(path).set(null); + } + + var makeObject; + var root; + + setup(function() { + var objectId = 0; + makeObject = function(value) { + return { + val: value || objectId++ + }; + }; + + return pushFirebaseValue('/test', { ignore: 'me' }).then(function(snapshot) { + root = '/test/' + snapshot.key; + }); + }); + + suite('exists attribute', function() { + var query; + + setup(function() { + query = fixture('BasicStorage'); + query.path = root + '/list'; + return query.transactionsComplete; + }); + + test('exists is null when we change the path', function() { + query.path = '/myNewPath'; + expect(query.exists).to.be.equal(null); + }); + + test('exists is true when we have data false when we remove it.', function() { + var object = makeObject(); + + return pushFirebaseValue(query.path, object).then(function() { + expect(query.exists).to.be.equal(true); + }).then(function(){ + clearFirebaseValue(query.path); + }).then(function() { + expect(query.exists).to.be.equal(false); + }); + }); + + }); + }); diff --git a/test/firebase-query.html b/test/firebase-query.html index 777e7b5..fb9e059 100644 --- a/test/firebase-query.html +++ b/test/firebase-query.html @@ -176,6 +176,7 @@ var object = makeObject(); return pushFirebaseValue(query.path, object).then(function() { + expect(query.exists).to.be.equal(true); expect(query.data.length).to.be.equal(1); expect(query.data[0]).to.be.ok; expect(query.data[0].val).to.be.equal(object.val); @@ -241,6 +242,22 @@ expect(query.data[0].foo).to.be.eql(undefined); }); }); + + test('exists is null when template is stamped', function() { + expect(query.exists).to.be.equal(null); + }) + + test('exists is true when we have data false when we remove it.', function() { + var object = makeObject(); + + return pushFirebaseValue(query.path, object).then(function() { + expect(query.exists).to.be.equal(true); + }).then(function(){ + clearFirebaseValue(query.path); + }).then(function() { + expect(query.exists).to.be.equal(false); + }); + }); }); suite('querying against leaf node collections', function() { From a7e445eacc53051e8dd9a8b1ce95097e6488411d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Sat, 13 Jan 2018 23:25:56 +0000 Subject: [PATCH 08/13] Fix indentation in test files. --- test/firebase-auth.html | 8 ++++---- test/firebase-common-behavior.html | 22 +++++++++++----------- test/firebase-database-behavior.html | 8 ++++---- test/firebase-document.html | 8 ++++---- test/firebase-query.html | 8 ++++---- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/test/firebase-auth.html b/test/firebase-auth.html index 5e4f5f1..927fb5b 100644 --- a/test/firebase-auth.html +++ b/test/firebase-auth.html @@ -22,10 +22,10 @@ + name="test" + api-key="AIzaSyDTP-eiQezleFsV2WddFBAhF_WEzx_8v_g" + auth-domain="polymerfire-test.firebaseapp.com" + database-url="https://polymerfire-test.firebaseio.com"> diff --git a/test/firebase-common-behavior.html b/test/firebase-common-behavior.html index 8eba2b9..268ca2e 100644 --- a/test/firebase-common-behavior.html +++ b/test/firebase-common-behavior.html @@ -23,23 +23,23 @@ + name="test" + api-key="AIzaSyDTP-eiQezleFsV2WddFBAhF_WEzx_8v_g" + auth-domain="polymerfire-test.firebaseapp.com" + database-url="https://polymerfire-test.firebaseio.com"> + name="alt" + api-key="AIzaSyDTP-eiQezleFsV2WddFBAhF_WEzx_8v_g" + auth-domain="polymerfire-test.firebaseapp.com" + database-url="https://polymerfire-test.firebaseio.com"> + api-key="AIzaSyDTP-eiQezleFsV2WddFBAhF_WEzx_8v_g" + auth-domain="polymerfire-test.firebaseapp.com" + database-url="https://polymerfire-test.firebaseio.com"> diff --git a/test/firebase-database-behavior.html b/test/firebase-database-behavior.html index f3efa47..fe2053b 100644 --- a/test/firebase-database-behavior.html +++ b/test/firebase-database-behavior.html @@ -23,10 +23,10 @@ + name="test" + api-key="AIzaSyDTP-eiQezleFsV2WddFBAhF_WEzx_8v_g" + auth-domain="polymerfire-test.firebaseapp.com" + database-url="https://polymerfire-test.firebaseio.com"> diff --git a/test/firebase-document.html b/test/firebase-document.html index b97adcf..fadb86f 100644 --- a/test/firebase-document.html +++ b/test/firebase-document.html @@ -24,10 +24,10 @@ + name="test" + api-key="AIzaSyDTP-eiQezleFsV2WddFBAhF_WEzx_8v_g" + auth-domain="polymerfire-test.firebaseapp.com" + database-url="https://polymerfire-test.firebaseio.com"> diff --git a/test/firebase-query.html b/test/firebase-query.html index 777e7b5..791df06 100644 --- a/test/firebase-query.html +++ b/test/firebase-query.html @@ -21,10 +21,10 @@ + name="test" + api-key="AIzaSyBzKhxNa2k9pA3m9_Ji3POFAKyGGFnyshI" + auth-domain="note-app-firebase-3a483.firebaseapp.com" + database-url="https://note-app-firebase-3a483.firebaseio.com"> From 7ea2df285f78238fe502a6c72df5a2a0aaece658 Mon Sep 17 00:00:00 2001 From: Westbrook Johnson Date: Wed, 21 Feb 2018 17:02:22 -0500 Subject: [PATCH 09/13] Race condition prevention. --- firebase-firestore-mixin.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/firebase-firestore-mixin.html b/firebase-firestore-mixin.html index a7bcc07..0846d23 100644 --- a/firebase-firestore-mixin.html +++ b/firebase-firestore-mixin.html @@ -169,10 +169,11 @@ this._firestoreProps = {}; this._firestoreListeners = {}; - this.db = this.constructor.db || firebase.firestore(); } connectedCallback() { + super.connectedCallback(); + this.db = this.constructor.db || firebase.firestore(); if (this[CONNECTED_CALLBACK_TOKEN] !== true) { this[CONNECTED_CALLBACK_TOKEN] = true; @@ -188,8 +189,6 @@ } } } - - super.connectedCallback(); } _firestoreBind(name, options) { From 7ff8a378125037c01e113c2a182c17037d0d90e8 Mon Sep 17 00:00:00 2001 From: Westbrook Johnson Date: Fri, 23 Feb 2018 16:15:16 -0500 Subject: [PATCH 10/13] Prevent collection paths with multiple arguments from triggering multiple splices --- firebase-firestore-mixin.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firebase-firestore-mixin.html b/firebase-firestore-mixin.html index 0846d23..81d0fc8 100644 --- a/firebase-firestore-mixin.html +++ b/firebase-firestore-mixin.html @@ -210,9 +210,9 @@ const observer = `_firestoreUpdateBinding('${name}', ${args.join(',')})` this._createMethodObserver(observer); + } else { + this._firestoreUpdateBinding(name, ...args.map(x => this[x])); } - - this._firestoreUpdateBinding(name, ...args.map(x => this[x])); } _firestoreUpdateBinding(name, ...args) { From 63cbcaa975e3176a38578eaf698ed92226dea679 Mon Sep 17 00:00:00 2001 From: Westbrook Johnson Date: Sun, 25 Feb 2018 00:08:23 -0500 Subject: [PATCH 11/13] `_firestoreUpdateBinding` every `_firestoreBind` But, make the `_createMethodObserver` after so that the first time doesn't loop back into `_firestoreUpdateBinding` a second time when `_firestoreUnlisten` zeros out the properties. --- firebase-firestore-mixin.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/firebase-firestore-mixin.html b/firebase-firestore-mixin.html index 81d0fc8..3236351 100644 --- a/firebase-firestore-mixin.html +++ b/firebase-firestore-mixin.html @@ -204,14 +204,13 @@ this._firestoreProps[name] = config; const args = config.props.concat(config.observes); + this._firestoreUpdateBinding(name, ...args.map(x => this[x])); if (args.length > 0) { // Create a method observer that will be called every time // a templatized or observed property changes const observer = `_firestoreUpdateBinding('${name}', ${args.join(',')})` this._createMethodObserver(observer); - } else { - this._firestoreUpdateBinding(name, ...args.map(x => this[x])); } } From fd337f1dd25d7c05bdeb99ace020230f0b4b03ba Mon Sep 17 00:00:00 2001 From: Westbrook Johnson Date: Sun, 25 Feb 2018 00:18:39 -0500 Subject: [PATCH 12/13] Lazily unlisten and create DB If you can't add a listener till all `propArgsReady && observesArgsReady` no need to remove the previous listener till then. Create the DB then too. --- firebase-firestore-mixin.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firebase-firestore-mixin.html b/firebase-firestore-mixin.html index 3236351..c6095c0 100644 --- a/firebase-firestore-mixin.html +++ b/firebase-firestore-mixin.html @@ -173,7 +173,6 @@ connectedCallback() { super.connectedCallback(); - this.db = this.constructor.db || firebase.firestore(); if (this[CONNECTED_CALLBACK_TOKEN] !== true) { this[CONNECTED_CALLBACK_TOKEN] = true; @@ -215,7 +214,6 @@ } _firestoreUpdateBinding(name, ...args) { - this._firestoreUnlisten(name); const config = this._firestoreProps[name]; const isDefined = (x) => x !== undefined; @@ -227,6 +225,8 @@ observesArgs.length === config.observes.length; if (propArgsReady && observesArgsReady) { + this._firestoreUnlisten(name); + this.db = this.db || firebase.firestore(); const collPath = stitch(config.literals, propArgs); const assigner = this._firestoreAssigner(name, config); From 24e2d647b9e74ed9ff3f1e59eae7b84836ed47ce Mon Sep 17 00:00:00 2001 From: Westbrook Johnson Date: Tue, 27 Feb 2018 21:35:13 -0500 Subject: [PATCH 13/13] Move unlisten back to before the "ready" check Allows listening to be unbound if one of the propArgs becomes un"ready" later in the lifecycle. --- firebase-firestore-mixin.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/firebase-firestore-mixin.html b/firebase-firestore-mixin.html index c6095c0..8598ad4 100644 --- a/firebase-firestore-mixin.html +++ b/firebase-firestore-mixin.html @@ -214,7 +214,7 @@ } _firestoreUpdateBinding(name, ...args) { - + this._firestoreUnlisten(name); const config = this._firestoreProps[name]; const isDefined = (x) => x !== undefined; const propArgs = args.slice(0, config.props.length).filter(isDefined); @@ -225,7 +225,6 @@ observesArgs.length === config.observes.length; if (propArgsReady && observesArgsReady) { - this._firestoreUnlisten(name); this.db = this.db || firebase.firestore(); const collPath = stitch(config.literals, propArgs); const assigner = this._firestoreAssigner(name, config);