How to iterate over a range of a Backbone collection? - backbone.js

How to iterate over a range, lets say from the 3. to 10., of a Backbone collection?

By slicing the array of models and using _.each on the result
var c=new Backbone.Collection(...);
_.each( c.models.slice(3,11), function(model) {
console.log(model.get("id"));
});
slice is 0 based and the end index is excluded.

As opposed to slicing, you can be a bit more direct by using the collection's at method.
for (var idx=3;idx<=10;++idx) {
var model = collection.at(idx);
...do something...
}

Related

ng-repeat order by for specific values on a property

I am looking to order my list by a number of specific values on one property, and then some addtional properties once that is done.
The code I have is:
<tr ng-repeat-start="subcontractor in trade.SubContractors | orderBy:['Status':'PREF','Status':'APPR','Status':'LOGD','Status':'NIU','-AverageScores.AverageScore','-ProjectCount','SubcontractorName']">
Where the important bit (and the bit I can't get working) is:
'Status':'PREF','Status':'APPR','Status':'LOGD','Status':'NIU'
Is there a way of doing this in angular?
I would instead implement a handler to process data. Process subcontractors before it's set, running each element through a handler and assigning "sortValue" property to each one.
Then simply call orderBy using the sortValue property. This way you would decouple sorting data from displaying data. Though don't use a filter to do so, as it would be quite expensive resource-vise.
Something like
var statusSorting = ['PREF','APPR','LOGD','NIU'];
function sortContractors(models) {
var processed = [];
angular.forEach(models, function(model){
// logic to assign sortValue
var statusIndex = statusSorting.indexOf(model.status);
model.sortValue = statusIndex + 1;
});
return processed;
}
api.getData()
.then(function(data){
$scope.models = sortContractors(data);
});
// template
<tr ng-repeat="model in models | orderBy:'sortValue'">
You can then control priority by changing status position in the array and ordering desc/asc.
Alternately:
orderBy multiple fields in Angular
<tr ng-repeat="model in models | orderBy:['sortValue', 'contractorName']">
Your answer had a few non-required parts so I've tweaked it to give me the following:
function getStatusSortOrder(subcontractors) {
var statusSorting = ['PREF', 'APPR', 'LOGD', 'NIU'];
angular.forEach(subcontractors, function (model) {
statusIndex = statusSorting.indexOf(model.Status);
model.StatusSortValue = statusIndex;
});
}

Underscore indexOf within a Collection

I've been looking already around for the answer with no luck.
Basically I am reviewing again the Addy Osmani guide to Backbone, and it seems I can't get through this code here.
var people = new Backbone.Collection;
people.comparator = function(a, b) {
return a.get('name') < b.get('name') ? -1 : 1;
};
var tom = new Backbone.Model({name: 'Tom'});
var rob = new Backbone.Model({name: 'Rob'});
var tim = new Backbone.Model({name: 'Tim'});
people.add(tom);
people.add(rob);
people.add(tim);
console.log(people.indexOf(rob) === 0); // true
console.log(people.indexOf(tim) === 1); // true
console.log(people.indexOf(tom) === 2); // true
I don't see how people.comparator can reorder the collection even though is not called anywhere, plus how comes that returning 1 or -1 can reorder it.
Or is it implicitly called once the Collection is created or indexOf is called on the Collection itself?
From the backbone documentation:
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.
So every time you call people.add(...) the collection uses the comparator that you have set with people.comparator = function(a, b) { ... } to insert the model in an ordered position.

Calling shuffle on a filtered backbone collection

I'm trying to filter a Collection, and then shuffle the filtered values.
I was thinking of using the where method Backbone provides. Something like:
myRandomModel = #.where({ someAttribute: true }).shuffle()[0]
However, where returns an array of all the models which match the attributes; and apparently shuffle needs a list to work with:
shuffle_ .shuffle(list)
Returns a shuffled copy of the list
http://documentcloud.github.com/underscore/#shuffle
Is there a way to turn my array of models into a 'list'? Or should I write some logic myself to get this done?
When the Underscore docs say list, they mean array. So you can use _.shuffle like this:
shuffled = _([1, 2, 3, 4]).shuffle()
Or in your case:
_(#where(someAttribute: true)).shuffle()
However, since you're just grabbing a single model, you could simply generate a random index instead of shuffling:
matches = #where(someAttribute: true)
a_model = matches[Math.floor(Math.random() * matches.length)]
The shuffle() and where() method are just a proxy in Backbone collections to the underscore method. Underscore methods still work on their own, with arrays as argument. Here is what I would do:
myRandomModel = _.shuffle(#.where({ someAttribute: true }))[0]
Reference: http://documentcloud.github.com/underscore/#shuffle
PS: #"mu is too short" is right however, to get a single model I would go the Math.random() way myself.
I put the following in my application.js file (using Rails 3):
Array.prototype.shuffleArray = function() {
var i = this.length, j, tempi, tempj;
if ( i === 0 ) return false;
while ( --i ) {
j = Math.floor( Math.random() * ( i + 1 ) );
tempi = this[i];
tempj = this[j];
this[i] = tempj;
this[j] = tempi;
}
return this;
};
and now I can call shuffleArray() on an array of arrays. Leaving this unanswered for now though, since I would like to know if there is a better way to do it with Underscore/Backbone.
First in your collection you should have a filtered function
Like
var MyCollection = Backbone.Collection.extend ({
filtered : function ( ) {
Normally you will use _.filter to get only the models you wanted but you may also use the suffle as a replacement use this.models to get the collection models
Here shuffle will mix the models
var results = _ .shuffle( this.models ) ;
Then use underscore map your results and transform it to JSON
like so
results = _.map( results, function( model ) { return model.toJSON() } );
finally returning a new backbone collection with only results you may return only the json if that's what you are looking for
return new Backbone.Collection( results ) ;
note if you don't want to keep all data in collection for later uses you may use the following and ignore the view below ;
this.reset( results ) ;
}
});

Backbone.marionette compositeview ignores the index for addItemView

I am using Backbone.Marionette's CompositeView and I want to insert a new model at index 0 of the collection and have it appear as the first view in the composite.
I have this code to insert the element:
PlansApp.Plans.collection.unshift new PlansApp.Plan data
The problem is that the CompositeView ignores the index of the newly inserted item in the collection.
in Backbone.Marionette, the appendHtml method looks like this:
appendHtml: function(collectionView, itemView, index){
collectionView.$el.append(itemView.el);
},
The index argument is never used.
The newly inserted view will always be placed at the end of the composite.
How can I get it to appear at position 0?
This is by design, due to the number of edge cases that have been identified in supporting sorted collections directly.
The index parameter was added so that developers could individually add support in their applications. There's a wiki page that shows an example of how to do this: https://github.com/derickbailey/backbone.marionette/wiki/Adding-support-for-sorted-collections
MySortedView = Backbone.Marionette.CompositeView.extend({
// ...,
appendHtml: function(collectionView, itemView, index){
var childrenContainer = $(collectionView.childrenContainer || collectionView.el);
var children = childrenContainer.children();
if (children.size() === index) {
childrenContainer.append(itemView.el);
} else {
childrenContainer.children().eq(index).before(itemView.el);
}
}
});
The original Derick's answer is a bit dated for the current version of Marionette, and the wiki contains only an example for a collectionview.
Here's what I use for compositeviews currently:
appendHtml: (collectionView, itemView, index) ->
$container = #getItemViewContainer(collectionView)
if index is 0
$container.prepend itemView.el
else
childAtIndex = $container.children().eq(index)
if childAtIndex.length
childAtIndex.before itemView.el
else
$container.append itemView.el

google like search of backbone collection

I'd like to be able to search model attributes contained within a backbonejs collection. This is how I do it now...
wherePartial: function(attrs) {
// this method is really only tolerant of string values. you can't do partial
// matches on objects, but you can compare a list of strings. If you send it a list
// of values; attrs={keyA:[1,2,3],keyB:1}, etc the code will loop through the entire
// attrs obj and look for a match. strings are partially matched and if a list is found
// it's expected that it contains a list of string values. The string values should be considered
// to be like an OR operation in a query. Non-list items are like an AND.
if (_.isEmpty(attrs)) return [];
var matchFound = false;
return this.filter(function(model) {
// this is in the outer for loop so that a function isn't created on each iteration
function listComparator(value, index, list){
return model.get(key).toLowerCase().indexOf(value.toLowerCase()) >= 0;
}
for (var key in attrs) {
if (_.isArray(attrs[key])){
matchFound = _.any(attrs[key],listComparator);
if (matchFound !== true) return false;
} else {
matchFound = model.get(key).toLowerCase().indexOf(attrs[key].toLowerCase()) >= 0;
if (matchFound === false) return false;
}
}
return true;
});
}
Assume "C" is an instantiated collection, this is how I use it:
name:joe (nickname:joe the man nickname:joe cool nickname:joey)
is typed into a textbox and converted into this:
C.wherePartial({name:"joe",nicknames:["joe the man","joe cool","joey"]})
The above method returns all models that have the name joe and within that scope, any of the models that have the name joe and any of the nicknames. It works well for what I use it for. However, I'd really like to make a search that doesn't require the key:value pattern. I'd like to do this in a search box like when using a search engine on the web. I thought about just looking at every attribute on each model, but that takes awhile when you have a large collection (160k+ models).
Has anyone come across a need like this in the past? If so, how did you solve it? I'd like to keep the search contained on the client and not use any ajax calls to the backend. The reason for this is that the entire collection is already loaded on the client.
I thought of a way to do it. Serialize the attributes to a string during model instantiation. Listen for updates and update the serialization.
serializeAttr: function(){
this.serializedAttr = "";
var self = this;
_.each(this.toJSON(),function(value, key, list){
self.serializedAttr += value;
});
}
Then I can do simple searches on that cached value:
cc.serializedAttr.toLowerCase().indexOf("joe") >= 0

Resources