Filtering Backbone.js collection - javascript

I'm working with several backbone collections and sometimes I need to access parts of them based on some criteria.
METHOD 1
As already stated in this question, using filter() on the collection itself returns an array of models and not another collection. This can work in simple cases, but it has the effect of losing collection's method concatenation as a plain array of models won't have all methods defined in the collection.
METHOD 2
The answer to that question suggested creating a new collection passing the array of models to the constructor. This works but has the side effect of calling the collection's constructor every time, so any event binding that might be defined there gets stacked on every time you filter the collection.
So what's the correct way to create a sub-collection based on some filter criteria?
Should I use method 1 and create more filtering methods instead on relying on method chaining?
Should I go with method 2 and avoid binding events in the collection's constructor?

Personally I would create more filtering methods on the collection, because it has the additional benefit of encapsulating logic inside the collection.
You could also try to reuse the existing collection. I was toying around with the idea, and arrived at something like this:
var Collection = Backbone.Collection.extend({
//Takes in n arrays. The first item of each array is the method you want
//to call and the rest are the arguments to that method.
//Sets the collection.models property to the value of each successive filter
//and returns the result of the last. Revers the collection.models to its original value.
chainFilters: function(/*args..*/) {
var models = this.models;
try {
filters = _.toArray(arguments);
_.each(filters, function(filter) {
this.models = filter[0].apply(this, _.rest(filter));
}, this);
} catch(err) {
this.models = models;
throw err;
}
var filtered = this.models;
this.models = models;
return filtered;
}
});
Usage:
var results = collection.chainFilters(
[ collection.filter, function(model) { return model.get('name') === 'foo'; } ],
[ collection.someMethod, 'someargument' ],
[ collection.someOtherMethod ]
);
Here's a working sample. It's a bit peculiar, I know.

It depends on the use case. If you want those models to update a view then you probably want a new collection as otherwise you don't get the nice reactive template updates. If you simply wanted the models to iterate through or manipulate data without worrying about the data updating then use the array + underscore.js.
Try it with the arrays and if you find yourself writing a lot of boiler plate code with features already in a collection but not in underscore.js, just start using a collection.

Related

map relationship between object in javascript

I am using meteor for development which using mongo db as store. Thats means that all operations are just display object value (frontend) or operate object (backend). For example, from [user] collection "copy" value (userId) to [message] collection but different key name. is there any way better to
describe the relationship between two object rather than using
message.userId = user._id
maybe using a object to describe
{"userId","_id"}
There are many ways to skin a cat. This is how I would do it.
I would first create a mapping object with key-value pairs which represent the field relations between the two objects. The keys are the keys from the first object and the values are the keys from the second object.
{
"userId":"_id",
"userName":"name"
//...
}
Then I would use a function like this to apply the mapping object to two objects:
function applyMapping(fromObj, toObj, mappingObj) {
for (fromKey in mappingObj) {
var toKey = mappingObj[fromKey];
toObj[toKey] = fromObj[fromKey];
}
}

How to perform a foreach in an associative array/object in a MongoDB schema instantiation using mongoose without selecting schema config parameters?

Been Googling for hours and can't really find a solid answer. Maybe I'm using the wrong search parameters? Anyways here I go:
I'm new to MongoDB and am trying to change the values of a schema instantiation before I save it. I know there are a lot of posts on using the 'save' middleware, but I don't think it will solve my problem.
I have a schema design like this:
var fooSchema = new mongoose.Schema({
data:{
type1: {
objectfoo: [],
objectbar: []
};
type2: {
objectxxx: [],
objectyyy: []
};
}
});
This is not an infinite object, so I didn't want to do fancy stuff like link it to another schema or collection.
I want to do something like:
for(i in data){
for(j in data[i]){
data[i][j].push(val);
}
}
However, it will give me an "undefined error" or will say object does not exist and error out. I did a console.log within the first for loop and it turns out that not only does for(...in...) selects the data fields, but it also selects the configuration fields within the schema instantiation like:
$__delta
$__version
increment
$__where
remove
model
$__buildDoc
init
$__storeShard
toJSON
[Function]
[Function]
So I guess the code works, it just breaks when it hits one of these other properties and tries to push a value in.
Note that "type1" and "objectfoo" need to be in an associative type of array because I need to refer to them by name, so I can't use a normal array and loop through array.length. I'm probably going about this completely wrong as I'm new to MongoDB so if there is a much more efficient way to do this, please do tell.
I'm thinking there's a way to refer to only the data field of the object without selecting the other configuration stuff but I can't find it after searching for a few hours.
Thanks in advance.
EDIT: For clarity: I've instantiated it already with:
var Foo = mongoose.model('foo', fooSchema);
var newfoo = new Foo();
So when I do:
for(i in newfoo.data){
console.log(newfoo.data[i]);
}
It will print out all the configuration stuff.
Call toObject() on your model data when you need to convert it to a plain object without any extra fields or functions:
for(i in data.toObject()){
for(j in data[i].toObject()){
data[i][j].push(val);
}
}

Sorting collections using Backbone.js

I am trying to add a dynamic sort by to my collection using backbone.js.
At initialization the collection has the default sorting and the view is rendered. I made a button to test how to change the sorting. It calls the following function:
app.productList.comparator = function(product) {
return parseFloat(product.get("price"));
};
app.productList.sort();
If I understand correctly the Collection should now be sorted but the view still needs to be refreshed. I read in the documentation and in this topic to listen for the sort event
window.ProductCollection = Backbone.Collection.extend({
model:Product,
localStorage: new Backbone.LocalStorage("ProductCollection"),
events:{
"sort":"test"
},
test:function(){
alert('test');
}
});
For testing purposes I added a simple alert but this is not displayed. So it seems the sort event was not triggered.
Any ideas what I'm doing wrong here?
Backbone.Collection doesn't take into account a events hash like Backbone.View does, you have to bind events yourself. You could use the initialize method to do so, something like
var ProductCollection = Backbone.Collection.extend({
model:Product,
initialize: function() {
this.on('sort', this.test);
},
test: function(){
console.log('test');
}
});
And a demo http://jsfiddle.net/nikoshr/fTwpf/
By default there is no comparator for a collection. If you define a comparator, it will be used to maintain the collection in sorted order. This means that as models are added, they are inserted at the correct index in collection.models. A comparator can be defined as a sortBy (pass a function that takes a single argument), as a sort (pass a comparator function that expects two arguments), or as a string indicating the attribute to sort by.
"sortBy" comparator functions take a model and return a numeric or string value by which the model should be ordered relative to others. "sort" comparator functions take two models, and return -1 if the first model should come before the second, 0 if they are of the same rank and 1 if the first model should come after.
Note how even though all of the chapters in this example are added backwards, they come out in the proper order:
var Chapter = Backbone.Model;
var chapters = new Backbone.Collection;
chapters.comparator = function(chapter) {
return chapter.get("page");
};
chapters.add(new Chapter({page: 9, title: "The End"}));
chapters.add(new Chapter({page: 5, title: "The Middle"}));
chapters.add(new Chapter({page: 1, title: "The Beginning"}));
alert(chapters.pluck('title'));
Collections with a comparator will not automatically re-sort if you later change model attributes, so you may wish to call sort after changing model attributes that would affect the order.
Force a collection to re-sort itself. You don't need to call this under normal circumstances, as a collection with a comparator will sort itself whenever a model is added. To disable sorting when adding a model, pass {sort: false} to add. Calling sort triggers a "sort" event on the collection.

retrieve element from Backbone.Collection.remove(n)

I'm taking my first steps with Backbone.js, and one of those involves being able to remove an item from a collection, and more importantly, retrieve that item. The Backbone.Collection.remove method simply returns the original collection with the item removed, so at the moment I'm obtaining a reference to the desired item prior to removal:
var Collection = Backbone.Collection.extend(...array of Backbone.Models...),
removedItem = Collection.get(3);
console.log(Collection.remove(3));//same collection sans #3
My question is if there is a short hand method for retrieving the remove item?
Edit: JFTR, I've read a fair bit of the source, and know that the original method returns a reference to the collection -
remove: function(models, options) {
// <snip for brevity>
// chain pattern incoming
return this;
},
It seemed odd to me that it didn't return the removed item., so I was just wondering if there was another method I'm missing, or a common way of achieving this pattern. Wouldn't be the first time I've used a long workaround when the API had some secret doohickey up it's sleeve...as it is I'll probably extend the class.
You could add a function to the Backbone.Collection 'type' and use removeModel on every collection you create.
Backbone.Collection.prototype.removeModel(model) {
var _model = this.get(model);
this.remove(item);
return _model;
}
var removedModel = collection.removeModel(model);

Collection doesn't work in template

I send a model to a template. The model has a collection. In the template I echo some variables and functions:
console.log(comments);
console.log(_.size(comments));
console.log(comments instanceof App.Collections.Comments);
console.log(_.pluck(comments, 'created'));
_.each(comments, function(com) {
console.log(com);
});
The first three work, but the last two underscore functions don't. Pluck gives 3x undefined and each doesn't iterate.
Object { length=3, models=[3], _byId={...}, more...}
3
true
[undefined, undefined, undefined]
How do I get the underscore functions to work?
Backbone collections have some Underscore methods mixed in so you can use the Underscore methods directly on the collection instance:
console.log(comments.pluck('created'));
comments.each(function(com) { console.log(com) });
Demo: http://jsfiddle.net/ambiguous/3jRNX/
This one:
console.log(_.size(comments));
works fine for you because _.size looks like this:
_.size = function(obj) {
return _.toArray(obj).length;
};
and _.toArray calls the collection's toArray:
// Safely convert anything iterable into a real, live array.
_.toArray = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return slice.call(iterable);
if (_.isArguments(iterable)) return slice.call(iterable);
return _.values(iterable);
};
which unwraps the collection's data to give you the correct length. The above is taken from the 1.3.1 source, the current Github master version's _.size has a different implementation so your _.size call is likely to break during an upgrade.
You'll want to call pluck directly on the collection, as the Collection class supports it:
http://documentcloud.github.com/backbone/#Collection-pluck
So instead of:
_.pluck(comments, 'created')
You chould call:
comments.pluck('created');

Categories