Backbone Collection method return subset of self - backbone.js

Hi I am using Backbonejs, I have a example collection
var MyCollection=Backbone.Collection.extend({
init:function(options){xxx},
getPageNumber:function(){this.length/100},
myfilter:function(){},
});
The problem is I want to add this myfilter function, that filter out the collection, and return the same type "myCollection", so that I can then call getPageNumber(). like this:
collection.myfilter(cb).getPageNumber();
The default filter function in backbone return a simple array, not the Backbone.Collection Object. so is the underscorejs.filter.(which essensially is the same as Backbone.collection.filter). I am wondering if there is an easy way to do it.
Thanks

you can refresh your original collection using reset
myfilter:function(){
var result = yourFilterLogicHere...
this.reset(result);
return this;
}

Related

Backbone collection.reset function returns array

I have the following code in backbone view:
getAccounts: function() {
var filteredCollection = this.view.collection.reset(this.view.collection.where({ AccountStatus: 'Open' }));
return filteredCollection;
}
And I assume that this code returns me collection according to doc link http://backbonejs.org/#Collection-reset
But it returns an array. What is wrong here?
The documentation says
Returns the newly-set models
This means you get an array containing newly set models. It doesn't say it returns the collection itself. There is no reason to return the collection itself because you just performed this action on the collection and you already have access to it.
You can just do return this.view.collection instead.

How to extend an object with angular.extend?

I'm trying to extend an object in angularJs using multiple ajax calls results. However only the last results extends my empty object.
$rootScope.progress = {}; // the objkect to extend
$http.get('data.json').
then(function(result){angular.extend($rootScope.progress,result.data)}),
$http.get('error.json').
then(function(result){angular.extend($rootScope.progress,result.data)}),
$http.get('data2.json').
then(function(result){angular.extend($rootScope.progress,result.data)}),
$http.get('data3.json').
then(function(result) {angular.extend($rootScope.progress,result.data)})
You could see a plunker here:http://plnkr.co/edit/iDmsTpDpFUnvrv1pCUU9?p=preview
It is because all your data return is array instead of object.
Changed all the data.json to object.
Refer to http://plnkr.co/edit/Gq22dsksxlPYCryM1BOs?p=preview
If your json always return array, then your $scope.result should changed to array.
you can use $scope.result = $scope.result.concat(value.data);

Marionette.js - hijacking CompositeView functions to create streaming pagination

I am creating a streaming paginated list of views. We start the app with an empty collection and add items to the collection at regular intervals. When the size of the collection passes a page_size attribute then the rest of the models should not get rendered, but the compositeView should add page numbers to click on.
I am planning on creating a render function for my compositeView that only renders items based on the current page# and page size by have a function in my collection that returns a list of models like this:
get_page_results: function(page_number){
var all_models = this.models;
var models_start = page_number * this.page_size;
var models_end = models_start + this.page_size;
//return array of results for that page
return all_models.slice(models_start,models_end);
}
My question is, should I even be using Marionette's composite view for this? It seems like im overwriting most of the functionality of Marionette's collectionView to get what I want.
Every time the number of items in my collection changes two things need to be updated:
The itemViews in the collection view
The page numbers at the bottom of the composite view
My strong recommendation is not to do this in the view layer. You're going to add a ton of code to your views, and you're going to end up duplicating a lot of this code between multiple views (one for displaying the data, one for page list and counts, one for ...).
Instead, use a decorator pattern to build a collection that knows how to handle this. I do this for filtering, sorting and paging data, and it works very well.
For example, here's how I set up filtering (running in a JSFdiddle here: http://jsfiddle.net/derickbailey/vm7wK/)
function FilteredCollection(original){
var filtered = Object.create(original);
filtered.filter = function(criteria){
var items;
if (criteria){
items = original.where(criteria);
} else {
items = original.models;
}
filtered.reset(items);
};
return filtered;
}
var stuff = new Backbone.Collection();
var filtered = FilteredCollection(stuff);
var view = Backbone.View.extend({
initialize: function(){
this.collection.on("reset", this.render, this);
},
render: function(){
var result = this.collection.map(function(item){ return item.get("foo"); });
this.$el.html(result.join(","));
}
});
In your case, you won't be doing filtering like this... but the idea for paging and streaming would be the same.
You would track what page # you are on in your "PagingCollection", and then when your original collection is reset or has new data added to it, the PagingCollection functions would re-calculate which data needs to be in the final pagedCollection instance, and reset that collection with the data you need.
Something like this (though this is untested and incomplete. you'll need to fill in some detail and flesh it out for your app's needs)
function PagingCollection(original){
var paged = Object.create(original);
paged.currentPage = 0;
paged.totalPages = 0;
paged.pageSize = 0;
paged.setPageSize = function(size){
paged.pageSize = size;
};
original.on("reset", function(){
paged.currentPage = 0;
paged.totalPages = original.length / paged.pageSize;
// get the models you need from "original" and then
// call paged.reset(models) with that list
});
original.on("add", function(){
paged.currentPage = 0;
paged.totalPages = original.length / paged.pageSize;
// get the models you need from "original" and then
// call paged.reset(models) with that list
});
return paged;
}
Once you have the collection decorated with the paging info, you pass the paged collection to your CollectionView or CompositeView instance. These will properly render the models that are found in the collection that you pass to it.
Regarding CollectionView vs CompositeView ... a CompositeView is a CollectionView (it extends directly from it) that allows you to render a model / template around the collection. That's the majority difference... they both deal with collections, and render a list of views from that collection.
We have built a set of mixins for bakcbone.marionette that you may find usefull (https://github.com/g00fy-/marionette.generic/)
You could use PaginatedMixin, that allows a Backbone collection to be paginated + a PrefetchMixin, so you don't have to pass a prefetched collection to a view.
the only code you would have to do is:
YourListView = Generic.ListView.mixin(PaginatedMixin,LoadingMixin,PrefetchMixin).extend({
paginateBy:10,
template:"#your-list-template",
itemViewOptions:{template:"#your-itemview-template"},
fetchPage:function(page){
this.page = page;
return this.collection.refetch({data:{page:page}}); // your code here
},
hasNextPage:function(){
return true; // your code here
},
});
For a working example see https://github.com/g00fy-/stack.reader/blob/master/js/views.js

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

Why does the backbone.js where function return an array of models?

When I use the Backbone.Collection.where function to filter the collection I get an array of models as return value but not an other filtered collection object. So I can't use other collection functions with that.
What is the purpose of such behavior?
where isn't the only method that returns an Array. where returns a new Array because you definitely don't want it mutating your existing Collection automatically. Also, many times you may want the result in Array form.
For whatever reason, the BB devs decided that it was better to return a new Array rather than a new Collection. One thought could be that, perhaps the returned data would be used in a different type of Collection. Another reason could be so that you always know what is returned from one of these methods. 2+ types of collections will ALWAYS return Arrays from these types of methods rather than having to try and inspect via instanceof or something else that isn't very reliable.
Edit
In addition, you COULD make your collections behave in a manner where you return new Collections. Create a base Collection to do something like this:
// Override the following methods
var override = ["where","find",...];
var collectionProto = Backbone.Collection.prototype;
BaseCollection = Backbone.Collection.extend({});
for (var key in collectionProto) {
if (collectionProto.hasOwnProperty(key) && override.indexOf(key) > -1) {
BaseCollection.prototype[key] = function () {
return new this.constructor(collectionProto[key].apply(this, arguments);
};
}
}
Instead over extending off Backbone.Collection, extend off BaseCollection.
Note that you can still use most of the underscore utilities on arrays. Here's how to use each() after a filter()
_.each( MyCollection.filter( filter_fn() {} ), each_fn() {} )

Resources