Collection of certain length - backbone.js

Is there anyway if we can just pass a collection of certain length to a view?
I am trying to implement pagination thats why i don't want to pass entire collection.
var <new View> = new App.Views.photos({ collection: <Collection of certain length> });
Is this possible? I don't mind adding another method that returns me a collection of certain length.
Update:
I created a another collection with same model and using it as a buffer everytime i paginate.
Let me know if this is a good idea?

You could implement a paging method to your collection, that returns a subset (a "page") of the collection.
Something like this (not tested):
var PhotoCollection = Backbone.Collection.extend({
model:TestModel,
getPage: function(pageNumber, pageLength) {
pageLength = pageLength || 10;
var first = (pageNumber - 1) * pageLength;
var last = Math.min(pageNumber * pageLength, this.length);
var page = [];
if(first <= this.length) {
for(var i=first;i<last;i++) {
page.push(this.at(i));
}
}
return new PhotoCollection(page);
}
});
So you can retrieve any "page" from the collection:
//first page using default size of 10
photos.getPage(0);
//second page with page size 25
photos.getPage(1, 25);

Related

backbone.js set in model initialize not effecting models in collection

While performing a fetch() on my backbone collection, and instantiating models as children of that collection, I want to add one more piece of information to each model.
I thought that I could do this using set in the model initialize. (My assumption is that fetch() is instantiating a new model for each object passed into it. And therefore as each initialize occurs the extra piece of data would be set.
To illustrate my problem I've pasted in four snippets, first from my collection class. Second the initialize function in my model class. Third, two functions that I use in the initialize function to get the needed information from the flickr api. Fourth, and finally, the app.js which performs the fetch().
First the collection class:
var ArmorApp = ArmorApp || {};
ArmorApp.ArmorCollection = Backbone.Collection.extend({
model: ArmorApp.singleArmor,
url: "https://spreadsheets.google.com/feeds/list/1SjHIBLTFb1XrlrpHxZ4SLE9lEJf4NyDVnKnbVejlL4w/1/public/values?alt=json",
//comparator: "Century",
parse: function(data){
var armorarray = [];
var entryarray = data.feed.entry;
for (var x in entryarray){
armorarray.push({"id": entryarray[x].gsx$id.$t,
"Filename": entryarray[x].gsx$filename.$t,
"Century": entryarray[x].gsx$century.$t,
"Date": entryarray[x].gsx$date.$t,
"Country": entryarray[x].gsx$country.$t,
"City": entryarray[x].gsx$city.$t,
"Type": entryarray[x].gsx$type.$t,
"Maker": entryarray[x].gsx$maker.$t,
"Recepient": entryarray[x].gsx$recipient.$t,
"Flickrid": entryarray[x].gsx$flickrid.$t,
"FlickrUrl": "", //entryarray[x].gsx$flickrurl.$t,
"FlickrUrlBig": ""//entryarray[x].gsx$flickrurlbig.$t,
});
}
return armorarray;
}
});
Second, the initialization in my model.
initialize: function(){
//console.log("A model instance named " + this.get("Filename"));
item = this;
var flickrapi = "https://api.flickr.com/services/rest/?&method=flickr.photos.getSizes&api_key=<my_apikey>&photo_id=" + this.get("Flickrid") + "&format=json&jsoncallback=?";
sources = getFlickrSources(flickrapi);
sources.then(function(data){
sourceArray = parseFlickrResponse(data);
FlickrSmall = sourceArray[0].FlickrSmall;
console.log (FlickrSmall);
item.set("FlickrUrl", FlickrSmall);
console.log(item);
});
Notice here how I'm getting the "Flickrid" and using to get one more piece of information and then trying to add it back into the model with item.set("FlickrUrl", FlickerSmall);
console.log confirms that the property "FlickrUrl" has been set to the desired value.
Third, these are the functions my model uses to get the information it needs for the flicker api.
var getFlickrSources = function(flickrapi){
flickrResponse = $.ajax({
url: flickrapi,
// The name of the callback parameter, as specified by the YQL service
jsonp: "callback",
// Tell jQuery we're expecting JSONP
dataType: "jsonp"})
return flickrResponse;
}
var parseFlickrResponse = function(data){
flickrSourceArray = []
if (data.stat == "ok"){
sizeArray = data.sizes.size;
for (var y in sizeArray){
if (sizeArray[y].label == "Small"){
flickrSourceArray.push({"FlickrSmall": sizeArray[y].source});
}
else if (sizeArray[y].label == "Large"){
flickrSourceArray.push({"FlickrLarge": sizeArray[y].source});
}
}
}
return flickrSourceArray
}
But, fourth, when I try to perform the fetch and render the collection, I only get objects in my collection without the FlickrUrl property set.
//create an array of models and then pass them in collection creation method
var armorGroup = new ArmorApp.ArmorCollection();
armorGroup.fetch().then(function(){
console.log(armorGroup.toJSON());
var armorGroupView = new ArmorApp.allArmorView({collection: armorGroup});
$("#allArmor").html(armorGroupView.render().el);
});
var armorRouter = new ArmorApp.Router();
Backbone.history.start();
The console.log in this last snippet prints out all the objects/models supposedly instantiated through the fetch. But none of them include the extra property that should have been set during the initialization.
Any ideas what is happening?
What is this function ? getFlickrSources(flickrapi)
Why are you using this.get in the initialize function. Honestly it looks over-complicated for what you are trying to do.
If you want to set some parameter when you instantiate your model then do this var model = new Model({ param:"someparam", url:"someurl",wtv:"somewtv"});
If the point is to update your model just write an update function in your model something like update: function (newparam) { this.set;... etc and call it when you need it.
If I read you well you just want to set some params when your model is instantiated, so just use what I specified above. Here is some more doc : http://backbonejs.org/#Model-constructor
I hope it helps.
edit:
Put your call outside your model, you shouldn't (imo) make call inside your model this way it seems kinda dirty.
Sources.then(function(flickrdata) {
var mymodel = new Model({flicker:flickrdata.wtv});
});
It would be cleaner in my opinion.

get nested object length in an angularFire array in Firebase

I have a Firebase structure like this:
user {
uid {
Lessons {
lid1 {
Title: ...
}
lid2 {
Title: ...
}
}
}
}
I want to use AngularFire to convert user as array so I can filter them using Angular like this:
var usersRef = new Firebase($rootScope.baseUrl + "users");
var userListfb = $firebase(usersRef).$asArray();
The problem is, I also need the number of child of the Lessons object. When I log the userListfb, it is an array. But inside the array, the Lessons node still an object. I can not user length to get its length. What is the correct way to find out the number of child of the Lessons Node with Firebase AngularFire?
Edit 1
According to Frank solution, I got an infinite loop (digest circle error from AngularJS).
The problem is, I will not know the "uid" key. I need to loop it in the first array to get the uid into the second firebaseArray.
Let's say I have a ng-repeat="user in users" in the view and call this on view level in each repeat:
{{getLessonLength(user.uid)}}
Then in the controller, I have this function:
$scope.users = $firebaseArray($scope.usersRef);
$scope.getLessonLength = function (uid) {
var userRef = $rootScope.baseUrl + "users/" + uid + "/lessons/";
var lessonsNode = $firebaseArray(new Firebase(userRef));
return lessonsNode.length;
}
}
And it throw this error: Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []
All I want it is something like var lessonsCount = snapshot.child('lessons').numChildren() in regular Firebase .on('child_added' ...), the numChildren() function in FirebaseArray. Please help!
AngularFire contains quite some code to ensure that an ordered collection in your Firebase maps correctly to a JavaScript array as Angular (and you) expect it.
If you have a reference to a specific user, you can just create a new sync ($firebase) and call $asArray on that.
var usersRef = new Firebase($rootScope.baseUrl + "users");
var userListfb = $firebase(usersRef).$asArray();
var uid1LessonsRef = userRef.child('uid1').child('Lessons');
var uid1LessonsArray = $firebase(uid1LessonsRef).$asArray();
uid1LessonsArray.$loaded().then(function(arr) {
console.log('Loaded lessons, count: '+arr.length);
});
The data will only be synchronized once, no matter how many references you create to it.

How to define a Backbone.Collection to be a property but not an attribute of a Backbone.Model?

Say I have a web application which allows to filter a set of items and say that I'm using backbone.js in the web frontend.
Quite naturally I ended up creating a Filter which extends Backbone.Model and a SearchResultList extending Backbone.Collection. The Filter has some attributes like searchTerm, dataFrom, dateTo... Filter also has a method called applyFilter. Filter.applyFilter should call searchResultList.fetch, thus updating/filtering the search results.
The question is, how to best initialize searchResultList as a property of Filter instead of being an attribute in Backbone terms?
I would not like to create the SearchResultList in Filter.initialize as SearchResultList is not necessarily "owned" by Filter.
For now I ended up passing the searchResultList object as an option to Filter.initialize, but that feels a little bit awkward to me.
I doubt that it is a good idea to let Filter.applyFilter call SearchResultList.fetch. However, there needs to be a method of Filter which when called triggers a new request somehow. (Listening to change events of Filter is not an option either as the user is supposed to change filter options in multiple steps and decides to apply the filter manually via a button click)
I have a Collection for performing searches. Most of my collections extend this Search Collection.
It has an object in it holding parameters for searching. The user of the collection can call setSearchParam, and I have a method buildSearchString that will build the search part of the URL to call (I use the Matrix parameters in my REST urls).
window.SearchCollection = Backbone.Collection.extend({
initialize : function() {
this.params = {}; // holds parameters for searching
},
// Set a search param
setSearchParam: function(param, value) {
if (typeof value === 'undefined') return;
if (typeof this.params === 'undefined') this.params = {};
this.params[param] = value;
},
setSearchParams : function(paramMap) {
_.extend(this.params, paramMap);
},
// Build Matrix params for search. This could just as easily be a standard URL string
buildSearchString: function() {
var search = [];
var i = 0;
for (var key in this.params) {
var value = this.params[key];
if (utils.isEmpty(value)) continue;
search[i++] = ";";
search[i++] = key;
search[i++] = "=";
search[i++] = value;
}
return search.join('');
}
});
Then when I create a collection, I extend it:
window.BookingCollection = window.SearchCollection.extend({
model: Booking,
url: function(){
var urlString = BASE_REST_URL + "bookings/";
// Build the matrix params for the REST URL
urlString += this.buildSearchString();
return urlString;
}
});
Then, a user of this collection can use it like so:
var bookings = new BookingCollection();
bookings.setSearchParam("name", "hello");

Initializing nested collections with Backbone

I am trying to nest a Collection View into a Model View.
In order to do so, I used Backbone's Marionnette Composite View and followed that tutorial
At the end he initializes the nested collection view like this:
MyApp.addInitializer(function(options){
var heroes = new Heroes(options.heroes);
// each hero's villains must be a backbone collection
// we initialize them here
heroes.each(function(hero){
var villains = hero.get('villains');
var villainCollection = new Villains(villains);
hero.set('villains', villainCollection);
});
// edited for brevity
});
How would you go doing the same without using the addInitalizer from Marionette?
In my project I am fectching data from the server. And when I try doing something like:
App.candidatures = new App.Collections.Candidatures;
App.candidatures.fetch({reset: true}).done(function() {
App.candidatures.each(function(candidature) {
var contacts = candidature.get('contacts');
var contactCollection = new App.Collections.Contacts(contacts);
candidature.set('contacts', contactCollection);
});
new App.Views.App({collection: App.candidatures});
});
I get an "indefined options" coming from the collection:
App.Collections.Contacts = Backbone.Collection.extend({
model: App.Models.Contact,
initialize:function(models, options) {
this.candidature = options.candidature;
},
url:function() {
return this.candidature.url() + "/contacts";
}
)};
That's because when you're creating the contactCollection, you're not providing a candidatures collections in an options object. You do need to modify your contact collection initialization code to something like:
initialize:function(models, options) {
this.candidature = options && options.candidature;
}
That way the candidature attribute will be set to the provided value (and if not provided, it will be undefined).
Then, you still need to provide the info when you're instanciating the collection:
App.candidatures.each(function(candidature) {
var contacts = candidature.get('contacts');
var contactCollection = new App.Collections.Contacts(contacts, {
candidature: candidature
});
candidature.set('contacts', contactCollection);
});
P.S.: I hope you found my blog post useful!

Multiple collections tied to one base collection with filters and eventing

I have a complex model served from my back end, which has a bunch of regular attributes, some nested models, and a couple of collections.
My page has two tables, one for invalid items, and one for valid items. The items in question are from one of the nested collections. Let's call it baseModel.documentCollection, implementing DocumentsCollection.
I don't want any filtration code in my Marionette.CompositeViews, so what I've done is the following (note, duplicated for the 'valid' case):
var invalidDocsCollection = new DocumentsCollection(
baseModel.documentCollection.filter(function(item) {
return !item.isValidItem();
})
);
var invalidTableView = new BookIn.PendingBookInRequestItemsCollectionView({
collection: app.collections.invalidDocsCollection
});
layout.invalidDocsRegion.show(invalidTableView);
This is fine for actually populating two tables independently, from one base collection. But I'm not getting the whole event pipeline down to the base collection, obviously. This means when a document's validity is changed, there's no neat way of it shifting to the other collection, therefore the other view.
What I'm after is a nice way of having a base collection that I can have filter collections sit on top of. Any suggestions?
I fleshed out my previous attempt and have come up with an extension to Backbone.Collection that does what I need.
collections.FilteredCollection = Backbone.Collection.extend({
initialize: function(items, options) {
if (_.isUndefined(options.baseCollection))
throw "No base collection to watch";
if (!_.isFunction(options.filterFunc)) {
throw "No filter to apply";
}
_.extend(this, options);
this.listenTo(this.baseCollection, 'all', this.reraise);
},
reraise: function (event) {
this.reset(this.baseCollection.filter(this.filterFunc), { silent: true });
var args = [].slice.call(arguments, 1);
this.trigger(event, args);
}
});
The one small issue I have with this is that I have to manually apply filterFunc to the baseCollection, then pass that in as the items parameter when instantiating a FilteredCollection, but that's something I can live with.
The below code is what I'm using to instantiate. Note that there's another almost-exact copy which is for the collection of ONLY VALID items, but any filters can be applied.
var allDocs = theModel.get('Documents');
var invalidOptions = {
baseCollection: allDocs,
filterFunc: function(item) {
return !item.isValidItem();
}
};
var invalidDocs = allDocs.filter(invalidOptions.filterFunc);
var invalidDocsCollection = new collections.FilteredCollection(
invalidDocs, invalidOptions
);

Resources