Fetch a backbone collection with only the models with specified value - backbone.js

I have a dictionary of type {name: value}
A = {
name: x,
name: y,
name: z
}
I want to fetch a collection (consisting of models having one of their attribute as 'name'), but to be optimal I want to fetch such that the value of the attribute 'name' exists in my dictionary.
Is there a way to do specific filtering like that?

If you're doing the filtering client-side, overriding the filter method is really NOT the way to go.
Now you no longer have it available, should you need it later. Also, modifying the collection itself from within the filter method is an undesirable sideeffect.
Instead you should be using the parse method, which will automatically be called when fetching the collection.
Now as I understand it, you want to limit the fetched set to models with names matching the keys in your dictionary.
If so, I would do the following:
parse: function(response, options) {
// Do we want to filter the response?
if (options.filterNames) {
// Filter
response = _.filter(response, function(obj) {
// Check if this model name is one of the allowed names
return _.contains(options.filterNames, obj.name);
});
}
// Backbone will use the return value to create the collection
return response;
}
And then call fetch using
someCollection.fetch({filterNames: _.keys(someDictionary)});
If you're certain, you will always be filtering the collection on fetch, you can omit passing the option and just use the dictionary within parse.
Alternatively you could create a fetchFiltered() method on the collection, which would then invoke the line above.

After investigations and trials, here are the two ways this can be resolved:
1. Client side filtering after fetching the collection from the server. This is a less optimal method, especially when the collection is huge. In situations when you really want 5 models out of a 1000 model collection, it can be an overkill. But if the server side has no logic of accepting and using the filtering client side filtering should look something like:
Overload the collection filter code something like:
var filter = {
filter: function() {
var results = _.filter(this.models, function(model) {
// Perform the check on this model, like compare it to your local dict
if (checkPassed) {
return true;
}
return false;
});
results = _.map(results, function(model) {
return model.toJSON();
});
// Reset the existing collection to filtered models
this.reset(results) ;
};
var ExtendedCollection = OriginalCollection.extend(filter);
Pass a filter option in the fetch ajax call to the server, and the server should understand the filter and return the collection based off that.

Related

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).

backbone.js modify specific fields of model after fetch the collection

var items=[{"endsAt": "2013-05-26T07:00:00Z","id": 1,"name": "Niuniu1"},
{"endsAt": "2013-05-26T07:00:00Z","id": 2,"name": "Niuniu2"}]
ItemModel=Backbone.Model.extend({});
ItemCollection=Backbone.Collection.extend({
model:ItemModel,
url: '...',
parse: function(response) {
return response.items;
}
})
If I have a series of data like items, when I build model, for each model, it's endAt will be "2013-05-26T07:00:00Z". Where can I modify the model or data process so it will actually be "2013-05-26"?
I could do a foreach loop inside collection to process the date, but I'm wondering if there is a better pracitce like to do a parse inside the model?
Thanks!
The practice I use is the one you said you've thought about - implementing a custom parse on the model. As the documentation states, it will be called for you after a sync. See here: http://backbonejs.org/#Model-parse
ItemModel = Backbone.Model.extend({
parse: function(response,options) {
//perform your work on 'response',
// return the attributes this model should have.
};
})
As far as I know, you have 2 options here
Implement a custom parse method inside your model
Implement the initialize method inside your model
Both of them don't have any problems, I did 2 ways in several projects, and they work well

Make backbone model fetch discard missing fields

Let's say I have a Backbone model that when fetch() is called (for that specific model, not the entire collection) it gets this from the server:
{ a: "val-1", b: "val-2" }
Later, I call fetch() on the model again, and this time the server returns:
{ b: "val-x", c: "val-y" }
At this point, I would like my model to have this state, because that's the latest state provided by the server:
{ b: "val-x", c: "val-y" }
However, my Backbone model has this state instead:
{ a: "val-1", b: "val-x", c: "val-y" }
How do I fetch() a model in Backbone so that the resulting state is exactly what is returned from the server, and doesn't include old obsolete fields?
I discovered that one way to get the desired behavior is by adding this parse function to the model in question:
parse: function(resp) {
for (var key in this.attributes) {
if (resp[key] === undefined) {
resp[key] = undefined;
}
}
return resp;
}
If the response from the server lacks an attribute that's in the local model, it explicitly adds that key to the response with an undefined value. This causes fetch() to behave correctly and remove it from the model.
If you want fetch to discard your local state and take the server data without merging then use the {reset: true} option.
From the docs: http://backbonejs.org/#Collection-fetch
When the model data returns from the server, it uses set to
(intelligently) merge the fetched models, unless you pass {reset:
true}, in which case the collection will be (efficiently) reset.

How can I persist custom attributes over a collection fetch

I have a an "Asset" backbone model that has a custom attribute called "selected". Its custom in the sense that it is not part of the object on the server side. I use to represent which of the list of assets the user has currently selected.
var Asset = Backbone.Model.extend({
defaults: {
selected: false
},
idAttribute: "AssetId"
});
This model is part of a backbone collection that I fetch periodically to get any changes from the server.
The problem I have is that every time I fetch the collection, the collection is doing a reset (I can tell by the listening for the reset event) and hence the value of the selected attribute is wiped out by the data coming in from the ajax request.
The backbone.js documentation seems to suggest that there is a intelligent merge that will solve this problem. I believe I'm doing this in my fetch methods
allAssets.fetch({ update: true ,cache: false});
And I have also set the "idAttribute" field in the model so that the ids of the object coming in can be compared with the objects in the collection.
The way I have solved this is by writing my own Parse method in my collection object
parse: function (response) {
// ensure that the value of the "selected" for any of the models
// is persisted into the model in the new collection
this.each(function(ass) {
if (ass.get("selected")) {
var newSelectedAsset = _.find(response, function(num) { return num.AssetId == ass.get("AssetId"); });
newSelectedAsset.selected = true;
}
});
return response;
}
Is there a better way to do this?
Collection.update (introduced in Backbone 0.9.9) does indeed try to merge existing models, but does so by merging all set attributes in the new model into the old model. If you check Backbone source code, you'll see
if (existing || this._byCid[model.cid]) {
if (options && options.merge && existing) {
existing.set(model.attributes, options);
needsSort = sort;
}
models.splice(i, 1);
continue;
}
All attributes, including defaults, are set, that's why your selected attribute is reset to false. Removing the default value for selected will work as intended: compare http://jsfiddle.net/nikoshr/s5ZXN/ to http://jsfiddle.net/nikoshr/s5ZXN/3/
That said, I wouldn't rely on a model property to store my app state, I would rather move it to a controller somewhere else.

Resources