Backbone model structure gets changed when returning them from web worker - backbone.js

I am trying to reset a backbone collection with an array of models. It gets reset but the model structure is changed (nested one level).
Here is a detailed explanation:
Model
var SeatModel = Backbone.Model.extend({
defaults:{
},
initialize:function () {
console.log('Model initialized');
}
});
Collection
var myCollection = Backbone.Collection.extend({
url:"",
parse:function (data) {
},
initialize:function () {
console.log('Collection initialized');
}
});
Now, I am executing some logic in a web worker, which generates an array of models. The size of the array varies depending on the url I hit.
When the array is ready, I reset the data in the collection using something like:
(Before this, I have instantiated the collection and set it in an service object)
worker.onmessage = function(e) {
newDataForCollection = e.data;
//update the collection
service.get("myCollection").reset(newDataForCollection);
};
After getting reset, the structure of the collection gets changed to something like:
models: Array[3154]
[0...99]
0:g.Model
attributes:
attributes:
price: "12"
Whereas it should be like:
models: Array[3154]
[0...99]
0:g.Model
attributes:
price: "12"
Also the number of models in the array gets reduced. (Should have been around 6100 in this case).
I am unable to figure out, what causes the internal structure to get nested by one level on invoking reset on the collection.
Updated Post
Figured it out. We cannot send objects with functions in post message, so the models in the array just have the attributes and no functions. Related Passing objects to a web worker

Figured it out. We cannot send objects with functions in post message, so the models in the array just have the attributes and no functions. This was related to issue Passing objects to a web worker

Related

Backbone model which I see in success callback and error callback is different. #Backbone save

I have a backbone model which has Backbone Collections in it. When I save the model and if it is success then my model object is properly structured as it was. But when error occurs (say validation error), in error callback the model object is modified (Collections inside model object are converted into Array). As a result all my functions defined for that Collections are now "undefined" and gives me error.
save : function() {
this.model.save(_.extend(originalModel.toJSON() || {}, this.model
.toJSON()), {
success : this.onSaveSuccess,
error: this.onSaveError,
include : []
});
},
onSaveSuccess : function(model) {
//Here the model is properly structured
},
onSaveError : function(model, response) {
// Here the model is modified, all collections are now array
//I have to explicitly call my parse method to re structure it.
model = model.parse(model.attributes);
}
I would like to know why is this happening. Am I doing something wrong here ?
For the sake of this example, let's assume the attribute of the model that holds the collection is called "people". It isn't clearly documented, but model.save(attributes) actually behaves like:
model.set(attributes);
model.save();
Here's the relevant annotated source of save(...). What your code is doing is first setting the "people" attribute to the array of people, then attempting to save it. When the save fails, your model has the array, not the collection, as the value of "people".
I suspect your end point is returning the full representation of the model on success, and your model is correctly parsing that representation & re-building the Collection at that point. But your error handler won't do that automatically.
As an aside, in my experience Models that contain Collections are hard to manage & reason about. I've had better luck having a Model that contains an array of data, and then having a method on that Model to build a Collection on the fly. Something like:
var MyModel = Backbone.Model.extend({
// ...
getPeople: function() {
// See if we've previously built this collection
if (!this._peopleCollection) {
var people = this.get('people');
this._peopleCollection = new Backbone.Collection(people);
}
return this._peopleCollection;
}
});
This removes the Collection concept from the server communication (where it's pretty unnecessary), while also providing a smarter data layer of your application (smart Models are a good thing).
The solution for this is passing wait:true in options. This will not modify until and unless server returns a valid response.
save : function() {
this.model.save(_.extend(originalModel.toJSON() || {}, this.model
.toJSON()), {
success : this.onSaveSuccess,
error: this.onSaveError,
**wait:true**
include : []
});
},

Backbone Collection get property

Got a server returning a JSON object like so:
{
'key1':'value'
'key2':{
'key2_0':'value'
}
}
And a collection:
var Collection = Backbone.Collection.extend({
url:api.url//which returns the object above
});
var collection = new Collection();
collection.fetch({
success:function(data){
//do something
}
});
Now i need to use certain properties of the collection throughout my application, but say i need key1, i always have to do collection.at(0).get('key1');//returns 'value', because the data returned is stored within the collection, in a new Array at key 0.
Question:
How to directly... collection.get('key1')//now returns undefined... because it is.
I know i could expose an object to the global scope in the collection success function some_other_var = data.toJSON()[0] and access the some_other_var properties directly, but that's not what i'm looking for;
In order to use the get() function from a Backbone.Collection you need to know the model id or cid wanted.
For instance, lets say your data coming from the server is like follow:
[{
id: '123',
name: 'Alex'
}, {
id: '456',
name: 'Jhon'
}]
In that case you can do this:
this.collection.get('123').get('name') // Return "Alex"
Keep in mind that collection is just a set of model, so behind the scenes by doing collection.get() you are getting a model
Tip: If you don't have any kind of id in your server data, there is always the option of using underscore methods:
find
filter
some
contains
etc
It seems like you're trying to ascribe attributes to a collection, but a collection is merely a set of models. Having additional data that is constant throughout the collection suggests that it should be wrapped inside another Model, which is demonstrated here: Persisting & loading metadata in a backbone.js collection

Cannot set priority on $asObject()'s child keys using Angularfire 0.8

According to the Angularfire docs, when working with an object returned through $asObject(), you can set priority for said object by defining a $priority property on the object and then using $save().
My code works great, but $priority isn't doing anything. Here's some code with complete explanations in the comments:
app.factory('MyService', function($rootScope, $firebase) {
// The complete Firebase url
var ref = *obfuscated*;
// Returning the dataset as an object containing objects
var data = $firebase(ref).$asObject;
// This object is what's returned by MyService
var Data = {
// Method to create a new object within the data set,
// keyed by myId. Attempting to set priority for the
// record via $priority. returnData.uid is a valid string.
create: function(returnData, myId) {
data[myId] = {
myId: myId,
$priority: returnData.uid
};
// No need to explain the stuff between here and the
// $rootScope listener below, just added for context
data.$save().then(function() {
setMyId(myId);
});
},
findByMyId: function(myId) {
if (myId) {
return data[myId];
}
}
};
function setMyId(myId) {
$rootScope.myId = User.findByMyId(myId);
}
// This event listener works fine, fires
// at user login and returns data
$rootScope.$on('$firebaseSimpleLogin:login', function(e, returnData) {
// returnData.uid has the correct value - this
// function should return the object(aka record) with
// a priority matching returnData.uid
var query = $firebase(ref.startAt(returnData.uid).endAt(returnData.uid)).$asObject();
// console shows an object with the normal $firebase
// properties, but no records. If I define query without
// limiting the set ($firebase(ref).$asObject()), it returns
// the full set just fine. The priority on all records is still
// null.
console.log(query);
query.$loaded(function() {
setData(query.myId);
});
});
return Data;
});
Yes, I'm following Thinkster.io's tutorial and I'm in Chapter 7. No, this is not a duplicate of the other questions about that chapter, I already found my way around the pre-Angularfire 0.8 code present in their examples, just can't set $priority, and I've spent about 5 hours so far trying to find a solution through my own efforts and on the web.
Any takers?
When viewed in the light of how JavaScript works with objects (i.e. unordered), how JSON handles objects (i.e. unordered), and in light of the expectation that AngularFire's $asObject() method is intended for storing key/value pairs, and singular records that are not used as a collection, this starts to make some sense.
Internally, the synchronize'd object's $save method calls Firebase's setWithPriority. In set or setWithPriority calls, the child nodes are replaced. Any meta data like priorities on those children are replaced.
In AngularFire, $asArray is intended to handle ordered collections, and provides the ability to set $priority on child nodes (only one level deep, of course, as it treats its children as singular records that are not themselves collections).
Since, in your case, you want to work with fixed keys rather than push ids, you'll probably want to override the $add method using $extendFactory and do something like the following:
angular.module(MY_APP).factory('FixedKeysFactory', function($FirebaseArray, $firebaseUtils) {
return $FirebaseArray.$extendFactory({
$add: function(data) {
this._assertNotDestroyed('$add');
if( angular.isObject(data) && typeof data.$id === 'string' ) {
return this.$inst().$set(data.$id, $firebaseUtils.toJSON(data));
}
else {
return this.$inst().$push($firebaseUtils.toJSON(data));
}
}
});
});
You could then pass this into your $firebase instance in place of the default factory:
var list = $firebase(ref, {arrayFactory: FixedKeysFactory}).$asArray();
A simpler but less awesomatic™ solution would be to manually add your objects to the array, manually giving them a $id, then call $save:
var list = $firebase(ref).$asArray();
var i = list.length;
list.push({ foo: 'bar', $id: 'kato' });
list.$save(i);
Some notes on the future: It will soon be possible to use any field as sort criteria and there will be no need to set priorities (yay!). It will probably be possible to set your own $id before calling $add on a synchronized array in AngularFire as soon as I clear that with the other devs (like the 0.8.3 release).

Will calling fetch on my Collection instantiate all of the Models it receives in Backbone?

I have this code in my Backbone application:
app.Collections.quotes = new app.Collections.Quotes();
app.Collections.quotes.fetch();
And I can see an array of Objects returned in the network tab but when I expand out the Collection, the Models array inside is 0. Do they get instantiated as Models when fetch() is ran on a new Collection?
This is my Collection:
app.Collections.Quotes = Backbone.Collection.extend({
model: app.Models.Quote,
url: function() {
return app.Settings.apiUrl() + '/quotes';
}
});
EDIT:
app.Collections.quotes.fetch({
success: function(){
app.Utils.ViewManager.swap('section', new app.Views.section({section: 'quotes'}));
}
});
And in my Model:
idAttribute: 'Number',
This was the fix! Thanks for help. Dan kinda pointed me in the right direction amongst the comments...
Calling fetch() on a Collection attempts to populate the JSON response into Models: Collection#fetch.
Is your server definitely returning a valid JSON array of objects?
Do you have any validation on your Quote Model? I'm pretty sure Backbone validates each models before populating the collection, only populating with the models which pass. So if it exists, check that your Model#validate method is working correctly.
You shouldn't need an ID (although it's obviously required if you want to edit them).

Backbone.js firing Collection change event multiple times

In one of by Backbone.js views I am updating the attribute "read" of the current model (instance of Message) by using this.model.set( { read: true } );. I verified that this command is only executed once (I know about "ghost events"). As you can see below I configured the Collection to fire an update event in which the whole Collection gets saved into a variable.
Unfortunately the saveToVar function gets called 3 times instead of one! Also, the first time saveToVar is called, this correctly consists of all the collection's models, whilst the 2nd and 3rd time this only has one model, namely the one I did the update on.
I tracked everything down piece by piece but I have no clue why this happens.
window.Message = Backbone.Model.extend({
});
window.MessageCollection = Backbone.Collection.extend({
model: Message,
initialize: function()
{
this.on("change", this.saveToVar);
},
saveToVar: function(e)
{
App.Data.Messages = this.toJSON();
return;
}
});
In your jsfiddle, you're doing this:
App.Collections.message = new MessageCollection([ ... ]);
var elements = App.Collections.message.where({ id: 4 });
var item = new MessageCollection(elements);
Your where call will return models that are in the message collection, not copies of those models but exactly the same model objects that are in message. Now you have two references to your id: 4 model:
The original one buried inside App.Collections.message.
The one in elements[0].
Both of those references are pointing at the same object. Then you add elements to another MessageCollection. Now you have something like this:
App.Collections.message.models[3] item.models[0]
| |
+--> [{id: 4}] <--+
Both of those collections will be notified about change events on the id: 4 model since collections listen to all events on their members:
Any event that is triggered on a model in a collection will also be triggered on the collection directly, for convenience.
And your collection listens for "change" events in itself:
initialize: function()
{
this.on("change", this.saveToVar);
}
So when you do this:
this.model.set({ read: true });
in your view, both collections will be notified since that model happens to be in both collections.
If we alter your event handler to look like this:
saveToVar: function() {
console.log(_(this.models).pluck('cid'));
}
then you'll see that the same cid (a unique identifier that Backbone generates) appears in both collections. You can also attach a random number to each collection and see what you get in saveToVar: http://jsfiddle.net/ambiguous/mJvJJ/1/
You probably shouldn't have one model in two collections. You probably shouldn't have two copies of the same model kicking around either so cloning elements[0] before creating item might not be a good idea either. You might need to reconsider your architecture.

Resources