consider this data structure referenced on the Firebase quick start guide (here)
{"name": {"first": "Fred","last": "Flintstone"}
The docs say that one can access the datasnapshot location of each child object of "name" returned from a query using:
var ref = new Firebase("https://docs-examples.firebaseio.com/samplechat/users/fred");
ref.once("value", function(snapshot) {
var nameSnapshot = snapshot.child("name");
var name = nameSnapshot.val();
name === { first: "Fred", last: "Flintstone"}
var firstNameSnapshot = snapshot.child("name/first");
var firstName = firstNameSnapshot.val();
firstName === "Fred"
var lastNameSnapshot = snapshot.child("name").child("last");
var lastName = lastNameSnapshot.val();
lastName === "Flintstone"
var ageSnapshot = snapshot.child("age");
var age = ageSnapshot.val();
age === null (because there is no "age" child in the data snapshot)
});
But what's a little weird about this is when the following lines are processed.
var nameSnapshot = snapshot.child("name");
var name = nameSnapshot.val();
name.first, and name.last are also retrieved. So why would one use this snapshot method "child()"? Or rather when would it be beneficial to use this method, since when you pull the parent object, Firebase pulls all children, or is there a way to retrieve a parent node/object without pulling some of it's children? Then this method to me would make sense.
Any information would be gratefully appreciated! Thanks
is there a way to retrieve a parent node/object without pulling some of it's children?
The Firebase JavaScript API always retrieves the complete node. So: no, there isn't a way in the JavaScript API to get a shallow result/
why would one use this snapshot method child()?
If we compare snapshot.child("property") with snapshot.val().property. The DataSnapshot.child() method returns a DataSnapshot, from which you can get a ref again. The val() method deserializes the snapshot's value into JSON. So you'll have to construct your own ref if you'd need one. But the value of each depends highly on your use-case, so "why" is not something I can answer for you.
The snapshot is a exact picture of everything in the node at the time of the call. What it contains however, will vary depending on how you get the snapshot.
The example provided in the guide is slightly one-dimensional. In general you would not have a node called name with just one person listed.
A better example would be a node called users with data as such
users
user_id_0
firstName:
lastName:
age:
user_id_1
firstName:
lastName:
age:
When used with the Value parameter, the snapshot of the users node contains all of the node's children and all of the data within each child (in this case, all of the users and their data), and the block that handles it is called once. We use this to read, for example, all of the users in the users node and then iterate over the users for some specific data. We also use it to do multi-parameter queries, which are not supported directly by firebase. So for example, we want to get all users named Elmo, age 20.
The Add parameter reads each child, one at a time, calling the block once for child, which would be each user in this case. Typically we use this to keep UI tableView's updated (ObjC) so when a new child data is added to Firebase, all of the apps who are observing will be notified, so we can then update our UI table.
You cannot retrieve a parent object without also retrieving the children. However, you can directly access a child if you know the parent object if you are looking for a specific piece of data. So you could retrieve users/user_id_0/age
Related
I've been trying to figure out how to do this, but can't seem to get it to work. I have created a function that is being called when I click a component using v-on:click. I am also trying to pass in a value that I can then use to access a particular array that is coming in the form of data from a backend.
Here is the function in the component:
<v-card
#click="getContent('topSellers')"
>
I am then passing the 'topSellers' value as an "id" into the function that is being used to get access the exact array that I am looking for:
getContent(id) {
this.accordion = this.data.id;
console.log("data", id);
}
First of all, this.data.topSellers works. And I am seeing that the id value is correct in the console. Obviously, this is not working because id is currently a string, but I don't know how to fix that issue. Any help would be appreciated!
You need this.data[id] instead of this.data.id.
See property accessors on MDN for reference.
data[id] will access the property of data with the name of the value of id, in your case topSellers. So data.topSellers is equivalent to data["topSellers"]
[] allows you to access objects with variables.
It is recommended to handle exceptions because the variable received is not safe.
getContent(id) {
const accordion = this.data[id] || null;
if(accordion){
console.log("data", accordion);
this.accordion = accordion;
}
}
I am new to firebase and am struggling a little bit.
Currently, I am trying to update an array within a user, within a document. However, I cannot match the user to current user using the unique ID, as each users unique ID is their username, and it may have changed since creation.
I figured the best way to match the documents user to the current user would be to use a .where().get() and then use an "update()" to update the array.
Now, this is where I am getting stuck. In the firebase documents, their example of using .update is attached to a .doc
var washingtonRef = db.collection("cities").doc("DC");
//Atomically add a new region to the "regions" array field.
washingtonRef.update({
regions: firebase.firestore.FieldValue.arrayUnion("greater_virginia")
});
However, as I am using a .where, I assume I have to use references and snapshots. But, I am not quite sure how references work in this scenario and, with that, how to update properly.
Here is the code I have after a while of messing round, but no matter my variations i cannot figure it out. (essentially, I want to add a new project (in this case called "new project" to the users array of postedProjects.)
db.collection('users').where('user_id', '==', this.userInfo.user_id)
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.data().update({
postedProjects: firebase.firestore.FieldValue.arrayUnion("new project")
})
})
})
This gives me an error of ".update() is not a function".
Is anyone able to help me with my solution to show me how references should properly be used in this scenario?
You're almost there. You can't update the data of DocumentSnapshot though, since that is the in-memory representation of the document data. Instead you need to get the DocumentReference and call update on that.
doc.ref.update({
postedProjects: firebase.firestore.FieldValue.arrayUnion("new project")
})
You need a DocumentReference in order to update() a document. Nothing else will work.
In your code, doc is a QueryDocumentSnapshot type object. If you want the DocumentReference object that refers to the document from that snapshot, use its ref property.
doc.ref.update({
postedProjects: firebase.firestore.FieldValue.arrayUnion("new project")
})
GOOD SOLUTION HERE
In the case below I am trying to get data from one location and then find the related data in a different location (the firebase joins).
I am able to retrieve the appropriate data and display in the my console but I kind of stuck when it comes to storing them in one of my properties to then loop over them with a dom-repeat template. In addition I am not entirely sure if this should be done with plane JavaScript or the PolymerFire components.
//Key value for a course that I retrieved from a URL Query
var ckey = KSH456YU789;
//Get the Videos that belong to that course key
firebase.database().ref('/courseVideos/' + ckey).on('child_added', snap => {
//Get the video data of each video that belongs to the course
let videoRef = firebase.database().ref('videos/' + snap.key);
videoRef.once('value', function(snapShot) {
this.set('courseVidObj', snapShot.val());
//console.log() the data works
console.log(this.courseVidObj);
}.bind(this));
});
As it can be seen above I am able to log the data that is stored in my property 'courseVidData' which is from type Array. However, this is run for each request which basically overwrites the previous stored value.
This makes it impossible to use my property inside a dom-repeat template. As shown below:
<template is="dom-repeat" items="[[courseVidData]]" as="vid">
<my-card
card-img="[[vid.img]]"
card-name="[[vid.title]]"
card-text="[[vid.description]]"
card-key="[[vid.$key]]">
</my-card>
</template>
Second Attempt
In my second attempt I used a forEach() to store the returned data insie an array which I then added to my 'courseVidData' property.
This returns me as expected an array with three objects. Unfortunately the dom-repeat is doing nothing.
firebase.database().ref('/courseVideos/' + ckey).once('value', function(snap) {
var vidData = [];
snap.forEach(function(childSnapshot) {
let videoRef = firebase.database().ref('videos/' + childSnapshot.key);
videoRef.once('value', function(snapShot) {
vidData.push(snapShot.val());
this.courseVidData = vidData;
console.log(this.courseVidData); //returns array with the object's
}.bind(this));
});
});
So I found a way of doing it after reading through the documentation of the Polymer Template repeater (dom-repeat) And the way Arrays are handled in Polymer.
This might be not the cleanest approach but it works for now. If somebody is pointing out what could be improved I am happy to change my answer or accept a different one.
//Retrieve course videos and their details
firebase.database().ref('/courseVideos/' + ckey).on('child_added', snap => {
let videoRef = firebase.database().ref('videos/' + snap.key);
videoRef.once('value', function(snapShot) {
if (!this.courseVidData) this.set('courseVidData', []);
this.push('courseVidData', {video: snapShot.val()});
}.bind(this));
});
I can't really explain why but I had to add a if statement to check for the array and set it if not existing. Then I place the value of my snapshot inside the 'courseVidData' Property which is declared inside my Properties and from type Array.
Because the key of each returned object is now 'video' it is necessary to use [[item.video.title]] to access the object values (code below).
<template is="dom-repeat" items="[[courseVidData]]">
<h1>[[index]]</h1>
<h1>[[item.video.title]]</h1>
</template>
Update
Although this method works the unique key Firebase creates gets lost in the array. To keep the key for each object I store both key and object inside another object and append this to my array.
I know this isn't pretty and as mentioned above I am still looking for a better solution. However, it does the trick for me as a bloody beginner.
firebase.database().ref('/courseVideos/' + ckey).on('child_added', snap => {
let videoRef = firebase.database().ref('videos/' + snap.key);
videoRef.once('value', function(snapShot) {
var vidKeyObj = {key:snapShot.key, value:snapShot.val()};
if (!this.courseVidData) this.set('courseVidData', []);
this.push('courseVidData', {video: vidKeyObj});
}.bind(this));
I'm trying to write functions for storing and retrieving window state but cannot figure out how to do that. The idea is that user could make at any time a "snapshot" of the screen and with next login to the app he could retrieve it back, also he can store as many snapshots as he want.
For example: on the page I have 4 different closed panels with some kind of filters and 6 different tabs with grid inside (by default the first tab is opened). Now let's say, I have opened 2 of 4 panels, set some filters and worked with 5th tab. I want to be able to store whole window state (For example "My state 1"), and when I logged in at next time, just choose "My state 1" and get back my window state.
I already store and retrieve all grid properties in DB with next functions:
Store:
$scope.state = {}
$scope.saveStateString = function(store) {
$scope.state = JSON.stringify($scope.gridApi.saveState.save(store));
// console.log("function save state string")
};
Retrieve
if(objNode.folderId){
eventService.singleFilter(nodeId)
.then(function (res) {
if (res.body){
$scope.restoreStateString(JSON.parse(res.body));
}
});
}
else if (typeof objNode.folderId === "undefined"){
return false
}
$scope.restoreStateString = function(restore) {
$scope.gridApi.saveState.restore( $scope, restore );
};
For now I'm trying to store window state in localstorage and do next:
var storeValue = null;
var keyName = null;
var _window = {};
$scope.storeWorkspace = function (){
for (prop in window)
_window[prop] = window[prop];
storeValue = JSON.stringify(_window)
keyName = prompt("put your key name");
localStorage.setItem(keyName, storeValue);
};
but I get this error
angular.js:13708 TypeError: Converting circular structure to JSON
at Object.stringify (native)
I clearly understand, why I'm getting this error, it cause JSON doesn't accept circular objects - objects which reference themselves also I see from
console.log(_window) how the "window" has many objects inside, so I decided to ask:
How to store and retrieve window state?
Don't mix application data and resources to store, its huge, hard to reuse and will lead to running into other issues.
Keep it simple!
Construct appState object with what you required to reload the views
var appState ={config:{}, data:{}};
Store it in internalStorage / sessionStorage based on how long you to retain forever vs per session
localStorage.setItem("appState", appState);
On initial app start logic, load data from internalStorage / sessionStorage or server and you may modify existing controller code for binding it to the view.
getApplicationData(){
var appState = localStorage.getItem("appState");//get it from browser storage
if(!appState)
//get it from server
return appState;
}
This is more robust and performant approach.
The vast majority of values stored on the window object are simply not serializable. You will not be able to use JSON for this. You should instead track all changes you make to window and store those in a separate JSON object as POJOs. Alternatively, you can copy the initial window object when your application starts and then only store the differences between the current window object and the original copy.
In any case, this is probably going to be a hunt of trial and error, depending on what libraries you are using, and how they are using global variables. You will probably find you need to manually filter out some stuff when you serialize. Best practices would suggest nothing should write to the window object. If you have things writing to the window object, you're probably dealing with badly behaving code.
Do not try to store the whole window. Store your application's state in a separate object, e.g. state, which you can then attach to the global object if you absolutely have to:
window.state = {}; // your application's state goes here
var serializedState = JSON.stringify(window.state); // To be put into localStorage
Make sure that all the information you need to rebuild your app during the next launch is contained in this object, and nothing more than that. Avoid global state where possible.
Also make sure that your state object only contains serializable data. E.g. functions or symbols cannot be serialzied to a JSON string and will get lost in the process.
I'm working my way with BreezeJS in combination with Durandal. This all works fine but I have a question about creating a new entity that has a relation with another entity.
Lets say we have an entity customer and order. When I create a new order which relies on customer it has 2 fields one for customerID and one for Customer it self! The customerID is given correctly but it crashes for not having a Customer object! How do I solve this?
My Dataservice.createT is:
// This function creates an entity for the specific table (entity)
// that is passed as an parameter. The entity is the same as the table name
//in the map folders. So for creating a new Memo entity should be tblMemo
Dataservice.prototype.createT = function (initialValues, entity) {
return this.manager.createEntity(entity, initialValues);
};
var repairOrderLineN = dataservice.createT({
RepairOrderID: '1', CustomerID: result[0].CustomerId,
Description: result[0].Description, InternalCode: result[0].Code,
Hours: result[0].Hours, Tariff: result[0].Tariff,
Costs: result[0].Costs, Quantity: result[0].Qty,
DamageCode: result[0].Dam, RepairCode: result[0].Rep,
MaterialCode: result[0].Mat, LocationCode: result[0].Location,
IsAgreement: result[0].IsAgreement, IsAuthorized: result[0].IsAuthorized,
DoRepair: result[0].DoRepair
}, 'tblRepairOrderLine');
if (repairOrderLineN.entityAspect.validateEntity()) {
extendItem(repairOrderLineN);
repairorderlines.push(new repairorderline(repairOrderLineN));
dataservice.saveChanges();
updateFinance();
}
A JSON is given from a modal to create the entity and off course I understand the customer object isn't in it! damn i'm lost :S Help :(
I need more information about your case and specifically what you mean by "crashes".
I don't know what your dataservice.createT does. It's actually a little too complex for easy answer here. Can you simplify a test case for us?
Meanwhile, here is a passing test that I'm adding to "entityTests.js" of the DocCode sample that demonstrates your scenario with Northwind.
/*********************************************************
* Add an Order with initializer that set its parent Customer by Id
*********************************************************/
test("add Customer created using initializer with parent Customer Id", 4, function () {
var em = newEm();
// create a new parent Customer
var parentCustomer = em.createEntity("Customer", {
CustomerID: breeze.core.getUuid(),
CompanyName: 'TestCo'
});
// a new Order which is a child of the parent Customer
var newOrder = em.createEntity("Order", { CustomerID: parentCustomer.CustomerID() });
ok(newOrder.entityAspect.entityState.isAdded(), "newOrder should be 'added'");
ok(parentCustomer.entityAspect.entityState.isAdded(), "parentCustomer should be 'added'");
var orderCustomer = newOrder.Customer();
ok(orderCustomer, "newOrder's parent 'Customer' property should return a Customer entity");
ok(orderCustomer === parentCustomer,
"newOrder's parent Customer should be " + parentCustomer.CompanyName());
});
It would work just as well if the parent Customer previously existed (e.g., had been fetched).
Btw, I had to initialize parentCustomer.CustomerID because it is defined in metadata as a client-generated key. In most of our examples, the entity key is store-generated (e.g., Order.OrderID) and would not be specified.
Update 4/30/2013
I suspect that somewhere in your initializer object is a result[0].something where something is an entity rather than a simple data value (e.g,. an integer id).
As I write, the createEntity blows up if any initializer value is a Breeze entity. That will be fixed in v.1.3.2 which should be out soon. Note that this new feature will be beta at first. The API won't change but there are many edge conditions and it's hard to cover them all. It will take time to drive out lingering bugs.
No matter what, you have alternatives today. You would be fine if you just initialized using the foreign key ids for the related entities instead of initializing with the related entities themselves. Or you could assign the related entities after first creating the entity as in:
...
var thing = manager.createEntity('Thing', {
... initialize with simple data values
}
var thing.relatedEntityA = relatedEntityA;
var thing.relatedEntityB = relatedEntityB;
...