Push data to Firebase via polymerfire/firebase-query element - firebase

I'm a little stuck trying to push data on demand to a Firebase database using the polymerfire polymer element. I have a data binding inside a DOM element and it works flawlessly for registers that already exists. My real question is how to create new registers with unique id?
//firebase query for a specific path and a data binding
<firebase-query
id="query"
app-name="testApp"
path="/[[uid]]/messages"
data="{{data}}">
</firebase-query>
//dom repeat for each item inside the data binding
<template is="dom-repeat" items="{{data}}">
<div class="card">
<p>[[item.text]]</p>
</div>
</template>
If I modify the template to have an iron-input and a 2 way data binding, this update the register at ease and no problem in Firebase.
<template is="dom-repeat" items="{{data}}">
<div class="card">
<input is="iron-input" bind-value="{{item.text}}">
</div>
</template>
The real tricky part is how to push a new object (message) to Firebase with a unique id, something like "lasdjlkadj1978kld"?
//firebase estructure
{
"uid" : {
"messages" : {
"message1" : {
"message" : "some text",
"timestamp" : "some date"
},
"message2" : {
"message" : "some text",
"timestamp" : "some date"
}
...
...
}
}
}
I have tried updating the "data" object via JS but its only modified locally...

I'm not sure I understand your question correctly.
How to create new registers with unique id?
You can use firebase-document that provide save method that take 2 arguments parentPath and key (just leave the key).
<firebase-document id='document'
data='{{data}}'>
</firebase-document>
<script>
Polymer({
saveMessage: function () {
// path = /<uid>/messages in your case
this.$.document.save(path).then(function () {
// after document saved, path will be changed to the new ref
// any change of data will sent back up and stored
});
}
});
</script>
I had tried updating the "data" object via js but its only modified
locally...
How you update the data, Did you use this.set?

Related

"Uncaught TypeError: Cannot read property 'push' of null" with Polymer 3 and polymerfire3

I'm working in a project with Polymer 3 and polymerfire3.
Right now I have been able to use firebase-auth element successfully. But now that I'm trying to use the firebase-query element I get this on the console Uncaught TypeError: Cannot read property 'push' of null
This is my code
import { PolymerElement, html } from '#polymer/polymer/polymer-element.js';
import './shared-styles.js';
import 'polymerfire3/firebase-auth.js';
import 'polymerfire3/firebase-query.js';
class PerfilView extends PolymerElement {
static get properties() {
return {
user: Object,
uid: String,
data: {
type: Object,
observer: 'dataChanged'
}
};
}
static get template() {
return html`
<firebase-auth
id="auth" user="{{user}}">
</firebase-auth>
<firebase-query
log
id="query"
app-name="morse"
path="/notes/"
data="{{data}}">
</firebase-query>
<div class="card">
<div id="notes">
<ul id="notes-list">
<template is="dom-repeat" items="{{data}}" as="note">
<li>
<p class="content">{{note}}</p>
</li>
</template>
</ul>
<paper-input value="{{inputP::input}}" label="Take a note.."></paper-input>
<div id="notes-controls">
<paper-button id="add" on-tap="add">Add</paper-button>
</div>
</div>
</div>
`;
}
add() {
this.$.query.ref.push({
content: this.inputP
});
}
}
window.customElements.define('perfil-view', PerfilView);
Does it have something to do with the polymerfire3 elements?
You will need to add polymer-document in order to add a record. Additional to your code, something like:
<firebase-document
id="document"
app-name="morse"
path="/notes/[[key]]"
data="{{edata}}">
</firebase-document>
and pushing new data may look like ;
add() {
var key = firebase.database.ref('notes').push().key;
this.set('edata', this.inputP);
this.set('key', key);
// this new Note will be syncronised upon new key and edata properties defined. Here firebase-document will keep sysnronised with edata and path.
}
firebase-query element is basically utilized for -
combining the given properties into query options that generate a
query, a request for a filtered, ordered, immutable set of Firebase
data
Here is further to read up on.
I think, what you're trying to do, can simply be achieved as follows -
add(noteData) {
let newNoteRef = firebase.app().database().ref('notes').push();
return newNoteRef.set(noteData).then(() => {
console.log(`a new note successfully added.`);
});
}
Upon successful data upsert firebase-query will automatically update data bound-variable to reflect the new list.

Polymer and Polymerfire: how to loop through the data from an observer?

I have a simple <firebase-query> tag, and I'd like to manipulate some of the data before having it displayed through a <dom-repeat>. For example, I need to turn some fields into links, and also parse some dates.
So, I need to get the data once it's ready, loop through each item, and change some of the values.
To do that, I have an observer on the data to detect when it's ready. However, I can't figure out how to loop through the data from that JavaScript function. For some reason, for(var i in items) doesn't work, although the items do exist.
Here is the component:
<dom-module id="cool-stuff">
<template>
<firebase-query id="query" path="/items" data="{{items}}"></firebase-query>
<template is="dom-repeat" items="{{items}}" as="item">
[[item.name]]<br />
[[item.date]]<br />
</template>
</template>
<script>
Polymer({
is: 'ix-table',
properties: {
items: {type: Object, observer: "_itemsChanged"},
}
itemsChanged: function(data) {
// how do I loop through the data received from firebase-query?
console.log(data);
}
});
</script>
</dom-module>
Ideally, all I'd want to do in the observer function is something like:
for(var i in data) {
obj = data[i];
obj.name = '<a href="/item/"+obj.key>'+ojb.name+'</a>';
}
But I can't seem to be able to loop through the data.
Inside the observer function, console.log(data) returns some weird stuff like this:
Array[o]
0: Object (which contains a proper item)
1: Object (same)
2: Object (same)
Update:
Here is a screenshot of what console.log(data) returns (from inside the observer):
The array seems to be populated with all the objects, but it shows as Array[0]. So it won't let me loop through them.
Update 2:
Thanks to arfost here is the solution:
<script>
Polymer({
is: 'ix-table',
properties: {
items: {type: Object},
}
observers: [
'_itemsChanged(items.splices)'
],
_itemsChanged: function(changeRecord) {
if (changeRecord) {
changeRecord.indexSplices.forEach(function(s) {
for (var i=0; i<s.addedCount; i++) {
var index = s.index + i;
var item = s.object[index];
console.log('Item ' + item.name + ' added at index ' + index);
// do whatever needed with the item here:
this.items[index].name = "New name";
}
}, this);
}
},
});
</script>
<firebase-query> results
Note that <firebase-query> results in an array of objects. Let's say your database contained the following items under /notes/<USER_ID>/:
Your <firebase-query> would look similar to this:
<firebase-query
id="query"
app-name="notes"
path="/notes/[[user.uid]]"
data="{{notes}}">
</firebase-query>
(where user is bound to <firebase-auth>.user).
Assuming the user is logged in, <firebase-query> would then populate its data property (i.e., bound to notes) with the following array:
Note how each object contains a $key property, which corresponds to the item's key seen in the Firebase console's Database view.
You could then iterate notes directly with <dom-repeat>:
<template is="dom-repeat" items="[[notes]]">
<li>
<div>key: [[item.$key]]</div>
<div>body: [[item.body]]</div>
<div>title: [[item.title]]</div>
</li>
</template>
Binding to HTML strings
You should be aware that the string data bindingsĀ are rendered literally in this case, so attempting to set name to obj.name = '<a href="...">' would render the literal string instead of an anchor. Instead, you should declare the tags in your template, and bind the key and name properties inside those tags. So, your observer could be replaced with this:
<template is="dom-repeat" items="{{items}}" as="item">
<a href$="/item/[[item.key]]">[[item.name]]</a><br />
[[item.date]]<br />
</template>
Iterating an array
The following note is only relevant if you prefer to mutate the data before displaying it...
When iterating an array, you should avoid for..in because it doesn't guarantee order of iteration, and because it may iterate over enumerable properties you might not necessarily care about. Instead, you could use for..of (assuming ES6 is available to your app):
for (let note of notes) {
note.title += ' ...';
}
or Array.prototype.forEach():
notes.forEach(function(note) {
note.title += ' ...';
});
I thinks I have run into the same issue as you.
It come from the way firebase query is getting the array, the way polymer obersvers works, and is hidden by the fact that the javascript console is reference based when it show the objects.
In fact what really happen here, is that firebase query is creating an empty array, which trigger your polymer observer.
So your function is called as soon as the array is created, but still empty and you can't iterate through, since it's empty. You then log it, where the primitives sub-properties are correctly displayed (array[0])
Then firebase begin to populate the array with the datas. The arrays reference stay the same, so polymer won't fire the observer again, and in the console, when it try to display the array it display the array referenced in the log, which now contains the datas.
I recommend that you use a array mutation observer in place of your simple one as follow
`properties: {
items: {type: Object},
},
,
observers: [
'_itemsChanged(items.splices)'
],`
It will fire every time an object is added to your array, and you would be able to do the work you need :)
I had the link for the documentation on array mutation observer :)
polymer array mutation observer
I hope this will solve your issue,
have a good day.
i don't think i can think of a scenario where you'd need to mutate the data by looping through the array rather than just using computed bindings. like this:
<template is="dom-repeat" items="{{items}}" as="item">
<child-el date="{{_computeDate(item.date)}}"></child-el><br />
<child-el attr1="{{_someOtherConversion(item.prop1)}}"></child-el><br />
<child-el attr2="{{_iPromiseAnyConversionCanBeDoneLikeThis(item.prop2)}}"></child-el><br />
</template>
<script>
_computeDate: function(item) {
//do date converstion
}

Polymerfire getting data for each element in a list

Using a similar example to that on the polymerfire website, how can I get the information for each of the notes.
If I get a list of notes avaliable like:
<firebase-document
path="/teams/[[teamid]]/notes"
data="{{notes}}"
>
</firebase-document>
The result of that would be a object
f738hfno3oibr39rhr: true
adfagsg35ugho84hoh: true
... etc
What would be the accepted way to get data for each of these notes?
This is what I've got so far but its not working when things get removed or added.
<template is="dom-repeat" items="{{notesComputed}}">
<div>
<!-- Get the data needed for this card -->
<firebase-document
path="/notes/[[item.noteid]]/meta"
data="{{item.meta}}"
></firebase-document>
note that I've converted the notes object direct from firebase to an array so I can use the dom-repeat
Thanks any feedback is appreciated
Get Data:
Use firebase-query, this will return an array of items at the specified path.
<firebase-query
path="/teams/[[teamid]]/notes"
data="{{notes}}"
>
</firebase-query>
no need for extra conversion
can be use in dom-repeat
Save Data
Use the firebase-document to save data to a certain location.
<firebase-document id="doc"></firebase-document>
then in the function where you want to add the data do the following
this.$.doc.path = null; //makes sure to not override any other data
this.$.doc.data = {}; //data that you want to save
this.$.doc.save('path'); //path in firebase where you want to save your data
Example file structure
,<dom-module id="el-name">
<template>
<styel></style>
<firebase-document id="doc"></firebase-document>
<firebase-query
id = "query"
path = "/users"
data = "{{usersData}}">
</firebase-query>
<template id="dom-repeat" items="{{usersData}}" as="user">
<div>[[user.name]]</div>
</template>
<paper-button on-tap="add"></paper-button>
</template>
<script>
Polymer({
is: 'el-name',
properties: {},
add: function() {
this.$.doc.path = null;
this.$.doc.data = {name: 'Some Name'};
//save(path, key), by not giving a key firebase will generate one itself
this.$.doc.save('/users');
}
});
</script>
</dom-module>

Displaying items from collection in a template

The below code is expected to insert a document of 3 pairs in the FooterButtons collection, then those values are shown as labels on 3 buttons in the footer template.
But when the "click .menuItem" is called, it only insert "YES" in the collection. Any idea why it is broker and best way to fix it? Thanks
Server and client code
FooterButtons = new Mongo.Collection('footerButtons');
Server code
Meteor.publish('footerButtons', function(){
return FooterButtons.find();
});
Client code
Meteor.subscribe('footerButtons');
//---main_menu.js--------------------
Template.mainMenu.events({
'click .menuItem': function (event) {
FooterButtons.insert({button:"NO", button:"EXTRA", button:"YES"});
}
});
//---footer.html---------------
<template name="footer">
{{#each footerButtons}}
<h1>
<button class="col-xs-4" type="button">{{button}}</button>
</h1>
{{/each}}
</template>
//---footer.js---------------
Template.footer.helpers({
footerButtons: function(){
return FooterButtons.find();
}
});
This command is totally wrong for the mongo insertion
FooterButtons.insert({button:"NO", button:"EXTRA", button:"YES"});
If you create a javascript object,
var obj = {button:"NO", button:"EXTRA", button:"YES"};
since all the keys is duplicated, your object will only has one key which has the value of the last one: Yes
You need to insert one by one
FooterButtons.insert({button:"NO"});
FooterButtons.insert({button:"EXTRA"});
FooterButtons.insert({button:"YES"});

Template is re-rendered even though there is no data change

I'm struggling with an issue that I will explain giving a simple demo.
There's following very simple document in People Collection.
{
"_id" : "vxmLRndHgNocZouJg",
"fname" : "John" ,
"nicks" : [ "Johnny" , "Jo"]
}
Now let's consider following templates. Basically I display username and a list of nicknames with input field for adding more nicknames.
<head>
<title>test</title>
</head>
<body>
{{> name}}<br/>
{{> nicks}}
</body>
<template name="name">
<input type="text" value="{{fname}}"/>
</template>
<template name="nicks">
{{#each nicks}}
<div>{{this}}</div>
{{else}}
no nicks yet
{{/each}}
<input type="text" name="nicks"/>
<input type="submit"/>
</template>
My client javascript code is as follows:
Template.name.fname = function() {
return People.findOne({"fname" : "John"},{
transform : function(doc) {
return doc.fname;
}
});
}
Template.name.rendered = function() {
console.log('Template "name" rendered!');
}
Template.nicks.nicks = function() {
var john = People.findOne({"fname" : "John"});
if(john) return john.nicks;
}
Template.nicks.events({
'click input[type="submit"]' : function () {
var johnId = People.findOne({"fname" : "John"})._id; // demo code
People.update(johnId,{
$addToSet : {
nicks : $('input[name="nicks"]').val()
}
})
}
});
My problem is that after adding nickname (update of nicks field in a document) template name is re-rendered (I know because I console.log it). When I query People collection to provide data for name template I use transform option so changes in nicks field shouldn't have impact on name template.
Meteor docs supports this:
Cursors are a reactive data source. The first time you retrieve a cursor's documents with fetch, map, or forEach inside a reactive computation (eg, a template or autorun), Meteor will register a dependency on the underlying data. Any change to the collection that changes the documents in a cursor will trigger a recomputation.
Why template name is re-rendered then?
The template is re-rendered because you change the People collection.
When you alter the People collection, Meteor automatically assumes that everything that it provides data to needs to be recalculated. (Which your name template does via Template.name.fname.
Even though you transform the output of the cursor, the People collection has changed in some way. The query is done before the transform is used, in other words, its not the transform that is looked at but the whole collection.
Meteor thinks that perhaps your document with {'fname':'John'} may have some other field that might have changed and it needs to requery it to check (which the nicks field has been altered). The transform is then applied after the requery.
Your HTML might not actually change at this point, only if the cursor returns something different will the html be changed.
If it becomes an issue in any scenario (i.e forms being cleared or DOM being changed when it shouldn't be) you can use the {{#isolate}} {{/isolate}} blocks to ensure that only everything inside them is re-rendered and nothing outside.

Resources