How does map work with a Backbone collection? - javascript

Goal: I am trying to create a case insensitive search that goes through my collection and tries to match the user query against model's name attribute. Right now if I want to find a specific model, the search query must be exact.
It seems there is no easy way to do something so simple in Backbone, not out of the box. The function map came to mind. What if I could go through the entire collection and change model's name attribute to lower case, then change user query to lower case as well and voila!
But the problem is I have no idea how to use Backbone Collection and map function. There is no documentation on map in Backbone docs, other than a link that leads you to underscore documentation with a super primitive example code using an array of three numbers.
This does not work...why?
this.collection.map(function(model) {
return model.get('name').toLowerCase();
});

Actually, all of underscore's collection methods are proxied on Backbone.Collection objects. when you do a collection.map(... it returns an array of objects returned by the mapped function. The solution presented by raina77ow does not work since a Backbone.Collection is not an array and assigning the result of the map to this.collection will destroy the collection itself.
If you want to filter a collection, I would recommend using the filter method. ( I assume you are working from a Backbone.View:
var filter = this.$('#search-field').val(),
filteredModels = this.collection.filter( function( model ) {
return model.get('name').toLowerCase() === filter;
};
this.collection.reset( filteredModels );
Note that any of underscore's proxied methods on collections will return an array of models. If you then want to use them, you can reset the collection with these models or, equivalently, set the collection's models attribute to the filtered results: this.collection.models = filteredModels. The first form has the advantage of triggering a reset event on the collection to which you can listen to and, for example re-render your view.

Related

Can we use underscore's "where" to filter collection by CID

I am simply wondering if we can only filter Backbone collections by attributes, but not by other properties such as CID
is this correct:
_.where(collection,{cid:'xyz'}) // filters by cid property?
_.where(collection,{attributes:{firstName:'foo'}}) // filters by attributes.firstName?
I hope someone can understand my confusion about how to use where to filter on nested properties.
Can someone please explain is if it is possible to filter by a top level property like CID or if Backbone collections are configured to just filter by attributes.
Normally you'd use _.where to search an array of objects for matching properties. So saying _.where(collection, ...) doesn't make a lot of sense, you'd want to search the collection's models array:
var a = _.where(collection.models, { cid: 'xyz' })
That will do more or less the same thing as:
var a = [ ], i;
for(i = 0; i < collection.models.length; ++i)
if(collection.models[i].cid === 'xyz')
a.push(collection.models[i])
_.where doesn't know about the special structure of a Backbone collection so it doesn't know that it should be looking at collection.models or that there are attributes inside the models unless you tell it. Since cid is a property of models rather than an attribute, the above _.where works.
If you want to search the model attributes then you'd want to use the Underscore methods that are mixed into collections as they are adjusted to look at the collection's models or the special Collection#where that knows about the attributes object inside models. So if you wanted to look for a firstName attribute then you'd say:
collection.where({ firstName: 'foo' })
There's no support for searching nested objects out of the box. However, all the wheres are just wrappers for _.filter so you can write your own predicate function that does whatever you want:
collection.filter(function(model) {
// Do whatever you want to model, return true if you like
// it and false otherwise.
});
For the most part you should try to ignore the Backbone.Collection#models and Backbone.Model#attributes properties and use the various methods instead. There's usually a better way to deal with models and attributes that will be sure to take care of any internal bookkeeping.

Meteor: Create a new collection from one attribute of an existing collection

In my Meteor app, I already have a collection fullList = new Mongo.Collection('fullList'); that is an array of objects, and each object has several attributes, such as Color, Factor, and Tot.
I want to create a new collection - or at least just a new array - that is, an array of all of the Tot values. The pseudo-code would be something like newList = fullList.Color, if that makes sense.
I know how to display one attribute in html using {{Color}}, but I can't seem to do anything with it in JavaScript.
In case it's relevant, the reason I want this array is I'd like to use D3.js to represent that data.
It sounds like your collection is a set of documents (in Mongo terminology), with each document being a serialised object, rather than actually a single-document collection that stores an array. In that case, you should be able to use the built-in map function on your collection cursor. Documentation here:
http://docs.meteor.com/#/full/map
This would look something like (using only the document argument in the callback):
fullList = new Mongo.Collection('fullList');
newlist = fullList.find().map(function(document) {
return document.Tot;
});
map() will iterate over all the documents in the collection - as no arguments are passed to find() - and for each document add an item to an array (assigned to newList) that is the value returned by the callback function, in this case Tot.

save multiple record in backbone model?

Am trying to save model in backbone. This code is working
myModel.save({'title':title},{changed:'title'});
But am not sure why it is not working without changed attribute. myModel.save({'title':title});
Also how do I save multiple records using this?
To save multiple attributes for a single model, include all the changed attributes and their corresponding values in a single object {}.
For example:
myModel.save({
attributeA: valueA,
attributeB: valueB,
attributeC: valueC
});
It may help to read through backbone's annotated source regarding the save function.
To save attributes on all models in a collection, you could iterate over the collection, possibly using underscore's each function.
For example:
myCollection.each(function (myModel) {
myModel.save({
attributeA: valueA,
attributeB: valueB,
attributeC: valueC
});
})
Note: Since these examples are out of context, unique values for the attributes would have to be handled with additional logic.

Ember GroupableMixin by using groupProperty

I am trying to implement GroupableMixin (https://github.com/ahawkins/ember.js/blob/groupable-mixin/packages/ember-runtime/lib/mixins/groupable.js) on EmberJS but I have trouble using it.
I want to specify a groupProperty in my ArrayController (just like how we have sortProperties) and I want my groupings to be calculated based on that property.
I did try http://emberjs.jsbin.com/cawaq/3/edit and this is what I got:
App.UsersIndexController = Ember.ArrayController.extend({
sortProperties: ['created_at'],
groupProperty: 'role',
groupedContent: groupBy('role'), //How do it reference groupProperty in groupBy?
//How can groupedContent be updated whenever groupProperty is changed?
});
I have seen http://discuss.emberjs.com/t/ember-enumerable-no-group-by/3594/6 and http://jsbin.com/ukagip/2/edit but still cannot quite figure out how to make that work properly.
The intent and behavior of Mr. Hawkins' GroupableMixin is very different from the one that's used discussed in Ember's Discourse forum. It seems that you're confusing the two methods.
Mixin Approach (a la Mr. Hawkins)
Note this is the approach I'd recommend
The GroupableMixin is an instance of Ember.Mixin, which is used to extend an object's prototype. For a more in depth explanation of mix-in's see Coding Value's explanation.
We can determine the mix-ins requirements and behavior from reading the tests for the mix-in:
the class must behave like an ArrayProxy (so ArrayController's are fine)
a groupBy property must be set for the class
So we can include this mix-in in an array controller as follows:
App.UsersIndexController = Ember.ArrayController.extend(Ember.GroupableMixin, {
groupBy: 'role'
});
Now that we've satisfied the mix-in's requirements, we'll have access to a few new computed properties and functions such as groupedContent for free. Without adding anything else to the controller, we could write something like this in the template to access the groups:
{{#each group in groupedContent}}
<h1>{{group.name}}</h1>
<ul>
{{#each user in content}}
<li>{{user.name}}</li>
{{/each}}
</ul>
{{/each}}
Here's an example that groups words in an array controller by first letter.
Computed Helper Approach (a la Ember Sherpa)
This method creates a helper which defines a computed property based on the key provided to the function. We can create a similar function which maps to the group interface of the mix-in:
Sherpa = {};
Sherpa.groupBy = function (groupBy) {
var dependentKey = 'content.#each.' + groupBy;
return Ember.computed(dependentKey, function(){
var result = [];
this.get('content').forEach(function(item){
var hasGroup = !!result.findBy('name', get(item, groupBy));
if (!hasGroup) {
result.pushObject(Ember.Object.create({
name: get(item, groupBy),
content: []
}));
}
result.findBy('name', get(item, groupBy)).get('content').pushObject(item);
});
return result;
});
};
Now we can use this function to create a groupedContent computed property: groupedContent: Sherpa.groupBy('role')
Here's an example which uses the same template as the previous example and has only swapped the mix-in with this method.
Recommendation
Use the GroupableMixin, it's far more robust than the method discussed in the forum:
The method discussed in the forum...
is simple and easy to understand
is inefficient
Recomputes the entire groups array whenever a change is made to the array or relevant property
Doesn't implement map structures for lookups and instead searches iteratively through the array
Nukes all itemControllers on any computed change: If you check some boxes in the second example and then add a new word, all of the checkboxes will be cleared
The mix-in...
is a much more robust, albeit complex, implementation
handles inserts and removals to groups instead of recomputing the entire map
backs the groups with a map for better performance and efficiency
behaves as expected when using an itemController: just tick of checkboxes and add some words in the first example, and you'll notice that the checkboxes are never cleared

For Loop over Backbone Collection

Fairly new to backbone, so this is a really basic question. I have a Backbone collection passed into a function and I can prove that it has been passed and that the models in the collection have ids.
Here's how I'm setting the ids -
convertToMapObjects: (results) =>
objectList = new ObjectList()
results.each(result)->
testObj = new TestObject()
testObj.set
id = result.get("id")
objectList.add(testObj)
And in another function ( accessed through making the model trigger an event) -
getIds: (objects) =>
ids = (object.id for object in objects)
I think the issue may be because of how I'm iterating through the collection because when I tried doing
for object in objects
console.log(object)
I saw two undefineds. Is this correct? If so, why can't I use a for loop to go through a backbone collection? Also, is there a way I could do so?
A Backbone collection is not an array so for ... in won't produce the results you're expecting. You want to look at the collection's models property if you want to use a simple loop.
However, Backbone collections have various Underscore methods mixed in:
Underscore Methods (28)
Backbone proxies to Underscore.js to provide 28 iteration functions on Backbone.Collection. They aren't all documented here, but you can take a look at the Underscore documentation for the full details…
forEach (each)
...
So you can use map or pluck if you'd like to avoid accessing the models property:
ids = objects.map (m) -> m.id
ids = objects.pluck 'id'
The pluck method is, more or less, just a special case of map but collections implement a native version rather than using the Underscore version so that they can pluck model attributes rather than simple object properties.
You want to loop over the models property of the collection, not the collection object itself.
for object in object.models
This will give you a model in the collection

Categories

Resources