Dynamic sort in backbone collection not working - backbone.js

So I have a very basic backbone collection and model. I currently do not have a view as I'm rendering the collection through a custom template. What I'd like to do is sort the collection through an event (clicking on a column header). The event sets a new comparator, and then fires the .sort() method on the collection. However, when I dump the collection data after the .sort(), the collect is in the same order. I'm new to backbone and collections, so perhaps I'm missing something. Here's my code:
var TicketCollection = Backbone.Collection.extend({
model : TicketModel,
initialize : function() {
},
fetch : function(options) {
options = options ? options : {};
var self = this;
$.ajax({
url : "/getTickets",
data : {},
method : "POST",
cache : false,
dataType : "json",
success : function(Json) {
self.reset(Json);
},
complete : options.complete
});
},
render : function() {
var self = this;
Base.renderTemplate({el : $("#ticketListContainer"), Template : "ticketList", data : {tickets : this.toJSON()}});
$("#ticketList").find("#tdHeadCompany").click(function() {
self.comparator = function(ticket) {
ticket.get("company");
};
self.sort();
console.log(JSON.stringify(self.toJSON()));
});
},
comparator : function(ticket) {
return ticket.get("number");
}
});
The console.log shows the collection is still in its original order, and not being ordered by "company" as I'd like when the company header is clicked. Any advice? Thanks!

And I was missing a return in my comparator function. Thanks for pointing that out, Andrew!

Related

Loading Backbone model by custom attribute

Lets say, I have the following Backbone model :
var Meal = Backbone.Model.extend({
defaults: {
"appetizer": "caesar salad",
"entree": "ravioli",
"dessert": "cheesecake"
},
urlRoot : api/meals,
idAttribute : id,
// some other stuff
});
Assuming that I have a backend Spring MVC conroller that intercept GET requests, so when I fetch my model, using
myMeal.fetch();
my model gets loaded from the server.
for now everything is clear, my question is, what if I have another method on the backend that takes a string as parameter and return as responsebody, the right json element.
how can I make that call from my model ?
I'm looking for something like this :
var meal = new Meal({'entree': 'value'});
meal.fetch({
// if there is no id, and 'entree' is given, I want to call /
// api/findByEntree passing this.entree as parameter.
});
I want to make an Ajax call from the object itself to the backend, by specifying the url inside the Backbone model.
urlRoot can be a function so no need to override fetch. I believe you could just do something like this:
var Meal = Backbone.Model.extend({
defaults: {
"appetizer": "caesar salad",
"entree": "ravioli",
"dessert": "cheesecake"
},
urlRoot : function() {
return 'api/' + this.get('id') ? 'meals' : 'findByEntree';
},
idAttribute : id,
// some other stuff
});
You can override the default fetch, intercept the call, do some verification and then pass onto the original fetch:
var Meal = Backbone.Model.extend({
fetch: function(options) {
if(this.has('id')) {
Backbone.Model.prototype.fetch.call(this, options);
} else {
this.findByEntree(options);
}
},
fetchByEntree: function(options) {
...
}
});
however, keep in mind that you'll need some extra logic to deal with the case of trying to fetch a new Meal, which won't have neither id nor entree.

Backbone, getting parameters others than data in collection

Given the following json:
{
"admin": false,
"data": [
{
value: key,
value :key
},
{
value: key,
value :key
}
]
}
I defined my collection like this:
var myCollection = Backbone.Collections.extend({
url: myurl.com,
parse : function (response) {
return response.data;
}
});
It works like charm, it fill my collection with the data array, however, into the tamplate, I need to render some content when admin is equal true. But I cannot find a way to pass that value to the template.
Any chance any of u kind guys can point it into the right direction to solve this?
You could save the admin flag as a property of the collection in the parse method:
var myCollection = Backbone.Collection.extend({
model: myModel,
isAdmin: false,
...
parse : function (response) {
this.isAdmin = response.admin; //save admin flag from response
return response.data;
}
});
Then you could retrieve it and pass it to your template or use it in any other way in the view render method:
var myView = Backbone.View.extend({
collection: new myCollection(),
...
render: function(){
//retrieve admin flag from collection:
var isAdmin = this.collection.isAdmin;
//you could add it into the json you pass to the template
//or do anything else with the flag
}
});
You can try this fiddle with a very basic render function.

Backbone filter collection and rendering it

I'm still quite new to backbone, so I'm sorry if there is any gross error in what I'm doing.
What I'm trying to do seems very simple: getting a collection of models from the db and do some filters on it. Let's say we are trying to filter hotels. Once I have my main collection, I would like to filter them for price, stars, and so on (pretty much what you can find in yelp or tripadvisor or so) - and of course, I want to "reset" the filters once the user uncheck the different checkboxes.
So far, I have 3 views:
- one view based on the panel where the results will be displayed
- one view based on a template that represents each item (each hotel)
- one view will all the filters I want to use.
The problem I am having is that I am bot able to keep my collection in such a state that I am able to revert my filters or to refresh the view with the new collection.
Can you please help me to understand where my problem is? And what should I do?
<script>
// model
var HotelModel = Backbone.Model.extend({});
// model view
var ItemView = Backbone.View.extend({
tagName : 'div',
className : 'col-sm-4 col-lg-4 col-md-4',
template : _.template($('#hotelItemTemplate').html()),
initialize : function() {
this.model.bind('change', this.render, this);
this.model.bind('remove', this.remove, this);
},
render : function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
//view list
var HotelListView = Backbone.View.extend({
el : '#paginated-content',
events : {
"scroll" : "fetch"
},
initialize : function(options) {
var items = this.collection;
items.on('add', this.add, this);
items.on('all', this.render, this);
},
add : function(item) {
var view = new ItemView({
model : item
});
$('#paginated-content').append(view.render().el);
},
fetch : function() {
this.collection.getNextPage();
}
});
// filterign menu
var FilteringView = Backbone.View.extend({
el : '#filtering',
// just examples of one of the filters that user can pick
events : {
'click #price_less_100 ' : 'updateValue',
'click #five_stars ' : 'updateStars',
},
template : _.template($('#filteringTemplate').html()),
initialize : function() {
this.collection.on('reset', this.render, this);
this.collection.on('sync', this.render, this);
},
render : function() {
this.$el.html(this.template);
return this;
},
updateValue : function(e) {
//this is something I'm not using at the moment but that it contains a copy of the collection with the filters
var filtered = new FilteredCollection(coursesPaginated);
// if is checked
if (e.currentTarget.checked) {
var max = 100;
var filtered2 = _.filter(this.collection.models, function(item) {
return item.get("price") < max;
});
//not used at the moment
//filtered.filterBy('price', function(item) {
// return item.get('price') < 100;
//});
//this does not work
//this.collection.reset(filtered2);
//so I do this
this.collection.set(filtered2);
} else{
// here, i would like to have something to put the collection in a state before this filter was applied
//something that I do not use at the moment
//filtered.removeFilter('price');
}
},
updateStars : function(e) {
//do something later
}
});
// collection
var HotelCollection = Backbone.PageableCollection.extend({
model : HotelModel,
// Enable infinite paging
mode : "server",
url : '{{url("/api/hotels")}}',
// Initial pagination states
state : {
pageSize : 15,
sortKey : "updated",
order : 1
},
// You can remap the query parameters from `state` keys from
// the default to those your server supports
queryParams : {
totalPages : null,
totalRecords : null,
sortKey : "sort"
},
parse : function(response) {
$('#hotels-area').spin(false);
this.totalRecords = response.total;
this.totalPages = Math.ceil(response.total / this.perPage);
return response.data;
}
});
$(function() {
hotelsPaginated = new HotelCollection();
var c = new HotelListView({
collection : hotelsPaginated
});
$("#paginated-content").append(c.render().el);
hotelsPaginated.fetch();
});
It seems to me that it is not so easy to do filtering like this using backbone. If someone has other suggestion,please do.
Thank you!
My solution for this:
Main Collection which fetched from server periodically.
Filtered Collection which resets each time you use filtering.
Main view which used to render filtered collection (example new MainView({collection: filteredCollection};)) Also there must be
handler for collection 'reset' event.
Filter view which have a model containing filter values and which triggers Filtered Collection reset with new values.
Everithing easy.
Sorry for no code examples, not on work)

Backbone.js - fetch method does not fire reset event

I'm beginning with Backbone.js and trying to build my first sample app - shopping list.
My problem is when I fetch collection of items, reset event isn't probably fired, so my render method isn't called.
Model:
Item = Backbone.Model.extend({
urlRoot : '/api/items',
defaults : {
id : null,
title : null,
quantity : 0,
quantityType : null,
enabled : true
}
});
Collection:
ShoppingList = Backbone.Collection.extend({
model : Item,
url : '/api/items'
});
List view:
ShoppingListView = Backbone.View.extend({
el : jQuery('#shopping-list'),
initialize : function () {
this.listenTo(this.model, 'reset', this.render);
},
render : function (event) {
// console.log('THIS IS NEVER EXECUTED');
var self = this;
_.each(this.model.models, function (item) {
var itemView = new ShoppingListItemView({
model : item
});
jQuery(self.el).append(itemView.render().el);
});
return this;
}
});
List item view:
ShoppingListItemView = Backbone.View.extend({
tagName : 'li',
template : _.template(jQuery('#shopping-list-item').html()), // set template for item
render : function (event) {
jQuery(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
Router:
var AppRouter = Backbone.Router.extend({
routes : {
'' : 'show'
},
show : function () {
this.shoppingList = new ShoppingList();
this.shoppingListView = new ShoppingListView({
model : this.shoppingList
});
this.shoppingList.fetch(); // fetch collection from server
}
});
Application start:
var app = new AppRouter();
Backbone.history.start();
After page load, collection of items is correctly fetched from server but render method of ShoppingListView is never called. What I am doing wrong?
Here's your problem:
" When the model data returns from the server, it uses set to (intelligently) merge the fetched models, unless you pass {reset: true}" Backbone Docs
So, you want to fire the fetch with the reset option:
this.shoppingList.fetch({reset:true}); // fetch collection from server
As an aside, you can define a collection attribute on a view:
this.shoppingList = new ShoppingList();
this.shoppingListView = new ShoppingListView({
collection : this.shoppingList // instead of model: this.shoppingList
});
Are you using Backbone 1.0? If not, ignore this, otherwise, you may find what the doc says about the Collection#fetch method interesting.
To quote the changelog:
"Renamed Collection's "update" to set, for parallelism with the similar model.set(), and contrast with reset. It's now the default updating mechanism after a fetch. If you'd like to continue using "reset", pass {reset: true}"
So basically, you're not making a reset here but an update, therefore no reset event is fired.

backbone.js getting data into collection and template

i am new to backbone.js and need a little help sending data to a template. Im using a model with fetch, and a collection. here is the code :
(function($) {
var UserModel = Backbone.Model.extend({
urlRoot : '/users',
defaults : {
name : '',
email : ''
},
initialize : function() {
_.bindAll(this);
this.fetch();
},
parse : function(res) {
return JSON.stringify(res);
},
});
var users_coll = Backbone.Collection.extend({
//model: UserModel
initialize : function() {
var u = new UserModel();
this.model = u;
}
});
var displayView = Backbone.View.extend({
initialize : function() {
this.collection = new users_coll();
//_.each(this.collection.models, alert);
//console.log(this.collection);
//alert(JSON.stringify(this.collection.models));
this.render();
},
render : function() {
var tmpl = _.template($("#data-display-tpl").html());
this.$el.html(tmpl);
}
});
var view = new displayView({
el : $("#data-display")
});
})(jQuery);
it's working fine upto the model part. In the parse function of the model, i have used console.log() and everything seems fine. i get a properly formated json, and the fetch works fine too.
however in my collection i get nothing when i try console.log(user_coll.models).
i think i am probably missing something really small. not sure what, maybe the flow of things is all wrong.
I tried to modify your code just a bit to get poin trough...hope it helps clarify few basics.
I also didn't try provided example, but in theory it should work ;)
Here is how his example should be done...
Let's imagine Twitter app for example. Twitter app has only one model that represents one user in system. That's UserModel
var UserModel = Backbone.Model.extend({
urlRoot : '/user', // this is just for modifying one specific user
defaults : {
name : '',
email : ''
},
initialize : function() {
_.bindAll(this);
//this.fetch(); // WRONG: This call was "wrong" here
// fetch() should be done on Collection not model
},
parse : function(res) {
return JSON.stringify(res);
},
});
Now, you can have many lists of users on Twitter right. So you have two lists. In one list you have Friends users, and in other Family users
var UsersFriendsCollection = Backbone.Collection.extend({
model: UserModel // you tell Collection what type ob models it contains
url: '/users/friends',
initialize : function() {
// jabadaba whatever you need here
}
});
var UsersFamilyCollection = Backbone.Collection.extend({
model: UserModel // you tell Collection what type ob models it contains
url: '/users/family',
initialize : function() {
// jabadaba whatever you need here
}
});
...
var displayView = Backbone.View.extend({
initialize : function() {
this.collection = new UsersFriendsCollection();
this.collection.fetch(); // so you call fetch() on Collection, not Model
console.log(this.collection); // this should be populated now
//_.each(this.collection.models, alert);
//alert(JSON.stringify(this.collection.models));
this.render();
},
render : function() {
// collection data is avail. in templating engine for iteration now
var tmpl = _.template($( "#data-display-tpl" ).html(), this.collection);
this.$el.html(tmpl);
}
});
A collection's model attribute is meant for specifying what type of model the collection will contain and if specified you can pass the collection an array of raw objects and it will add and create them. From the docs
Override this property to specify the model class that the collection
contains. If defined, you can pass raw attributes objects (and arrays)
to add, create, and reset, and the attributes will be converted into a
model of the proper type
So when in your code you have
var u = new UserModel();
this.model = u;
You aren't actually adding the model to the collection. Instead you can use the collections add or fetch methods.

Resources