backbone, insert model while maintaining sort order - javascript

When a new model is added (via "set" function of the collection), I want the model be inserted at the index maintaining sort order, instead at the end.
Thanks
var Ts = (function () {
var Result = Backbone.Model.extend({
idAttribute : 'PAG_ID'
});
var ResultList = Backbone.Collection.extend({
model: Result,
comparator: function(result) {
//console.log(result);
return result.get('SORT_KEY');
},
});
var resultsCollection = new ResultList(data);
data = undefined;
var TableView = Backbone.View.extend({
tagName: 'table',
initialize : function() {
_.bindAll(this, 'render', 'renderRow');
this.collection.on("add", this.renderRow, this);
},
render: function() {
$(this.el).attr('id', 'tsTable').addClass('resulttable');
this.renderHeader(this.collection.shift());
this.collection.each(this.renderRow);
return this;
},
renderHeader : function(model) {
var col=new HeaderView({model:model});
this.$el.append(col.render().$el);
return this;
},
renderRow : function(model) {
var row=new RowView({model:model});
this.$el.append(row.render().$el);
return this;
}
});
var HeaderView = Backbone.View.extend({
tagName: 'tr',
model: resultsCollection.models,
initialize: function() {
this.model.on('change',this.render,this);
},
render: function() {
var html=_.template(colTemplate,this.model.toJSON());
this.$el.html(html);
return this;
}
});
var RowView = Backbone.View.extend({
tagName: 'tr',
initialize: function() {
this.model.on('all',this.render,this);
},
remove: function () {
debug.log("Called remove event on model");
$(this.el).remove();
},
model: resultsCollection.models,
render: function() {
var html=_.template(rowTemplate,this.model.toJSON());
this.$el.html(html);
return this;
},
attributes : function () {
return {
id : this.model.get('PAG_ID')
};
}
});
var tableView = new TableView({collection: resultsCollection});
$("body").append( tableView.render().$el );
resultsCollection.set(initialdata);
resultsCollection.set(someotherdata, {merge: true});
I have changed to as below and it works.Not sure if this is the best implementation
renderRow : function(model) {
var row = new RowView({model:model});
var index = model.get('SORT_KEY') - 1;
row.render().$el.insertAfter(this.$el.find('tr:eq('+ index +')'));
return this;
}

If you provide a comparator function on your collection, Collection.set will perform a silent sort after the new models have been spliced in.
From backbones source http://backbonejs.org/docs/backbone.html:
set: function(models, options) {
var sortable = this.comparator && (at == null) && options.sort !== false;
var sortAttr = _.isString(this.comparator) ? this.comparator : null;
...
if (toAdd.length) {
if (sortable) sort = true;
this.length += toAdd.length;
if (at != null) {
splice.apply(this.models, [at, 0].concat(toAdd));
} else {
push.apply(this.models, toAdd);
}
}
if (sort) this.sort({silent: true});
Here is a fiddle demonstrating that collection.set respects a comparator.
http://jsfiddle.net/puleos/sczV3/

Related

Backbone issues multiple ajax requests unexpectedly

I put the fetch url with deferred method and I expect it will only invoke the remote ajax request one time.
However, it calls three times when I load the page.
How could I fix it? Thanks
js scripts
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
initialize: function() {
this.fetch({
success: this.fetchSuccess,
error: this.fetchError
});
this.deferred = new $.Deferred();
},
deferred: Function.constructor.prototype,
fetchSuccess: function(collection, response) {
collection.deferred.resolve();
},
fetchError: function(collection, response) {
throw new Error("Products fetch did get collection from API");
},
var comments = new Comments();
...
comments.deferred.done(function() {
commentView.render();
emptyCommentView.render();
});
compelte js scripts
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
initialize: function() {
this.fetch({
success: this.fetchSuccess,
error: this.fetchError
});
this.deferred = new $.Deferred();
},
deferred: Function.constructor.prototype,
fetchSuccess: function(collection, response) {
collection.deferred.resolve();
},
fetchError: function(collection, response) {
throw new Error("Products fetch did get collection from API");
},
wellFormedComments: function () {
var MESSAGE_LIMIT_LENGTH = 80
var models = comments.select(function (model) {
var msg = model.get("message")
if (msg!=null) {
msg = msg.replace(/^\s+|\s+$/g, '')
if (msg.length >= MESSAGE_LIMIT_LENGTH) {
model.set("preview_message", msg.substr(0, MESSAGE_LIMIT_LENGTH/2));
} else{
};
return true
}
else{
return false
};
});
return new Comments(models);
},
emptyComments: function () {
var models = comments.select(function (model) {
var msg = model.get("message")
return false===_(msg).notBlank();
});
return new Comments(models);
}
});
var comments = new Comments();
var CommentView = Backbone.View.extend({
el: $("#comments_section"),
render: function() {
var notNullComments = comments.wellFormedComments();
if (notNullComments.length > 0) {
$("#dadasay_comments_plugin").show();
}
var html = commentsTmpl(notNullComments.toJSON());
$(this.el).append(html);
},
});
var EmptyCommentView = Backbone.View.extend({
el: $("#empty_comments_list"),
render: function() {
var source = $('#empty_comments_list_tmpl').html();
var emptyComments = comments.emptyComments();
var html = emptyCommentsTmpl(emptyComments.toJSON());
$(this.el).html(html);
},
});
var commentView = new CommentView({
collection: comments
});
var emptyCommentView = new EmptyCommentView({
collection: comments
});
comments.deferred.done(function() {
commentView.render();
emptyCommentView.render();
});
The problem is that your comments collection triggers fetch when initialized. It's methods wellFormedComments and emptyComments creates new comments collections so they triggers fetch as well.
You can fix this by manually triggering fetch when required, something like:
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
wellFormedComments: function() {
var MESSAGE_LIMIT_LENGTH = 80
var models = this.select(function(model) {
var msg = model.get("message")
if (msg != null) {
msg = msg.replace(/^\s+|\s+$/g, '')
if (msg.length >= MESSAGE_LIMIT_LENGTH) {
model.set("preview_message", msg.substr(0, MESSAGE_LIMIT_LENGTH / 2));
} else {};
return true
} else {
return false
};
});
return new Comments(models);
},
emptyComments: function() {
var models = this.select(function(model) {
var msg = model.get("message")
return false === _(msg).notBlank();
});
return new Comments(models);
}
});
var CommentView = Backbone.View.extend({
el: $("#comments_section"),
render: function() {
var notNullComments = comments.wellFormedComments();
if (notNullComments.length > 0) {
$("#dadasay_comments_plugin").show();
}
var html = commentsTmpl(notNullComments.toJSON());
$(this.el).append(html);
},
});
var EmptyCommentView = Backbone.View.extend({
el: $("#empty_comments_list"),
render: function() {
var source = $('#empty_comments_list_tmpl').html();
var emptyComments = comments.emptyComments();
var html = emptyCommentsTmpl(emptyComments.toJSON());
$(this.el).html(html);
},
});
var comments = new Comments();
var commentView = new CommentView({
collection: comments
});
var emptyCommentView = new EmptyCommentView({
collection: comments
});
comments.fetch({ // <--------- Do this manually once
success: function() {
commentView.render();
emptyCommentView.render();
},
error: function() {}
});
I think you can better structure your code as shown below, hope the comments explain the changes
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
wellFormedComments: function() {
var MESSAGE_LIMIT_LENGTH = 80
var models = this.select(function(model) {
var msg = model.get("message")
if (msg != null) {
msg = msg.replace(/^\s+|\s+$/g, '')
if (msg.length >= MESSAGE_LIMIT_LENGTH) {
model.set("preview_message", msg.substr(0, MESSAGE_LIMIT_LENGTH / 2));
}
return true
}
return false
});
return new Comments(models);
},
emptyComments: function() {
var models = this.select(function(model) {
var msg = model.get("message")
return false === _(msg).notBlank();
});
return new Comments(models);
}
});
var CommentView = Backbone.View.extend({
el: $("#comments_section"),
template: commentsTmpl, // template reference, better create it here
initialize: function() {
this.render(); // self rendering
},
render: function() {
if (this.collection.length) { // use this.collection to refer to view's collection rather than external variables
$("#dadasay_comments_plugin").show(); //This shouldn't be a global selection
}
var html = this.template(this.collection.toJSON());
this.$el.append(html);
//---^ use cached jQuery object rather than creating new one
},
});
var EmptyCommentView = Backbone.View.extend({
el: $("#empty_comments_list"),
template: emptyCommentsTmpl,
initialize: function() {
this.render();
},
render: function() {
var source = $('#empty_comments_list_tmpl').html(); // unused?
var html = this.template(this.collection.toJSON());
this.$el.html(html);
},
});
var comments = new Comments();
comments.fetch({ // <--------- Do this manually once
success: function(collection, response) {
//----------------^ comments collection, all comments
var commentView = new CommentView({
collection: collection.wellFormedComments() // pass the resuting collection
});
var emptyCommentView = new EmptyCommentView({
collection: collection.emptyComments() // pass the resuting collection
});
},
error: function() {}
});

Cannot retrieve collection outside of the view

I'm making a simple list of people with option when clicking on person's name the Router will take a name as a parameter 'student/:name' and find a right person's object in a collection. I instantiate collection in a GroupView class by fetching it from the server. And that's where the Error appears: to get the access to collection (so I can find right object) in my viewStudent() method in Router class, I'm making one more instance of GroupView(), and console shows an error and that's right, 'cause there're no objects in collection.
I cannot wrap my head around this, why in GroupView() I receive data from the server and my collection just works fine, but second time I instantiate GroupView() in a Router - there's no collection? Maybe there's any other way I can get access to the collection in my Router? Any help would be greatly appreciated.
var StudentModel = Backbone.Model.extend({
defaults: {
name: 'Volodya',
lastName: 'Peterson',
age: 22,
gender: 'male'
}
});
var StudentsCollection = Backbone.Collection.extend({
model: StudentModel,
url: '/students.json'
});
var StudentView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#studentTpl').html()),
events: {
'click': function () {
eventAggregator.trigger('student:selected', this.model);
}
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var GroupView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection = new StudentsCollection();
this.collection.on('update', this.render, this);
this.collection.fetch();
},
render: function () {
var self = this;
this.collection.each(function (student) {
var studentView = new StudentView({
model: student
});
self.$el.append(studentView.render().el);
});
$('body').html(this.$el);
}
});
var RouterView = Backbone.View.extend({
tagName: 'ul',
render: function () {
var self = this;
_.each(this.model.toJSON(), function (value) {
self.$el.append('<li>' + value + '</li>');
});
return this;
}
});
var GroupController = function () {
this.start = function () {
var groupView = new GroupView();
};
};
var Router = Backbone.Router.extend({
routes: {
'': 'index',
'student/:name': 'viewStudent'
},
index: function () {
groupController.start();
},
viewStudent: function (name) {
var groupView = new GroupView();
var selectedStudent = groupView.collection.find(function (student) {
return student.get('name') === name;
});
$('body').append((new RouterView({ model : selectedStudent})).render().el);
}
});
var eventAggregator= _.extend({}, Backbone.Events),
groupController;
$(function () {
var router = new Router();
groupController = new GroupController();
Backbone.history.start();
eventAggregator.on('student:selected', function (student) {
var urlpath= 'student/'+ student.get('name');
router.navigate(urlpath, {trigger: true});
});
});

Backbone nested view

I'm trying to make a nested view. Backendview call ListPostView and ListPostView call SinglePostView. ListPostview and SinglePostView recursively creates a list by a collection.
BackendView is used only to wrap the list in a page html.
The collection passed to BackendView is retrieved by fetch and by method reset.
The problem is I can't render my collection and error is "undefined" inside SinglePostView.
If I call directly ListPostView it works perfectly.
I think maybe depends by event "bind" inside initialize function.
This is collection:
var Attori = Backbone.Collection.extend({
model:Attore,
idAttribute: "id",
fetch: function(options) {
var collection = this;
var cb = new Codebird;
cb.setConsumerKey("1Cx*mfA", "YedD*4s");
cb.__call(
"oauth2_token",
{},
function (reply) {
var bearer_token = reply.access_token;
console.log(bearer_token);
cb.setBearerToken(bearer_token);
}
);
console.log(options);
cb.setToken("259**g4ONJYi2","z8LLm52M**PS");
var params = {
q: "jim carrey"
//screen_name:"brad"
};
cb.__call(
"users/search",
params,
function (reply) {
console.log(reply);
collection.reset(reply);
}
);
}
});
return Attori;
});
this is Backendview:
var BackendView = Backbone.View.extend({
tagName: "ul",
id: "list",
events: {
"touchend": "goToDetails"
},
template: Handlebars.compile(template),
initialize: function () {
this.collection.bind("reset", this.render, this);
},
render: function (eventName) {
console.log(this.collection.length);
/* _.each(this.collection.models, function (ad) {
$(this.el).append(new ListPostView({
collection: ad
}).render().el);
}, this);*/
/* $(this.el).append(new ListPostView({
collection: this.collection
}).render().el);*/
if (typeof this.collection !== 'undefined' && this.collection.length > 0) {
// the array is defined and has at least one element
var List=new ListPostView({collection:this.collection});
//List.render();
}
//console.log(List);
return this;
},
goToDetails: function () {
Parse.history.navigate("ads/" + this.model.cid, {trigger: true});
}
});
return BackendView;
});
this is ListpostView:
var ListPostView = Backbone.View.extend({
tagName: "ul",
id: "list",
template: Handlebars.compile(template),
initialize: function () {
console.log(this.collection);
this.collection.bind("reset", this.render, this);
},
render: function (eventName) {
console.log(this.collection.models);
$(this.el).empty();
_.each(this.collection.models, function (a) {
$(this.el).append(new SinglePostView({
model: a
}).render().el);
}, this);
return this;
}
});
return ListPostView;
});
and this is SinglePostView:
var SinglePostView = Backbone.View.extend({
tagName: "li",
events: {
"touchend": "goToDetails"
},
template: Handlebars.compile(template),
initialize: function () {
console.log(this.model);
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
render: function (eventName) {
var ad = this.model.toJSON();
ad.cid = this.model.cid;
$(this.el).html(this.template(ad));
return this;
},
goToDetails: function () {
Parse.history.navigate("ads/" + this.model.cid, {trigger: true});
}
});
return SinglePostView;
});

How to pass filtered data into view from collection in backbone

I know Im pretty close to figuring this out. Im trying to filter out my collection based on if favorite eq true. If I console.log - I can see it's doing its job. But it's not updating my view.
Anyone have any idea what I'm missing or doing wrong?
Here is my code:
var Products = Backbone.Model.extend({
// Set default values.
defaults: {
favorite: false
}
});
var ProductListCollection = Backbone.Collection.extend({
model: Products,
url: '/js/data/wine_list.json',
parse: function(data) {
return data;
},
comparator: function(products) {
return products.get('Vintage');
},
favoritesFilter1: function(favorite) {
return this.filter(function(products) {
return products.get('favorite') == true;
});
},
favoritesFilter: function() {
return this.filter(function(products) {
return products.get('favorite') == true;
});
},
});
var products = new ProductListCollection();
var ProductListItemView = Backbone.View.extend({
el: '#wine-cellar-list',
initialize: function() {
products.bind('reset', this.render, this);
products.fetch();
this.render();
},
render: function() {
console.log(this.collection);
var source = $('#product-template').html();
var template = Handlebars.compile(source);
var html = template(this.collection.toJSON());
this.$el.html(html);
return this;
},
});
// Create instances of the views
var productView = new ProductListItemView({
collection: products
});
var CellarRouter = Backbone.Router.extend({
routes: {
'': 'default',
"favorites": "showFavorites",
"purchased": "showPurchased",
"top-rated": "showTopRated",
},
default: function() {
productView.render();
},
showFavorites: function() {
console.log('Favorites');
productView.initialize(products.favoritesFilter());
},
showPurchased: function() {
console.log('Purchased');
},
showTopRated: function() {
console.log('Top Rated');
}
});
$(function() {
var myCellarRouter = new CellarRouter();
Backbone.history.start();
});
There's many mistakes in your code, I'll try to clarify the most I can :
Your collection should be just like this :
var ProductListCollection = Backbone.Collection.extend({
model: Products,
url: '/js/data/wine_list.json',
comparator: 'Vintage' // I guess you want to sort by this field
});
Your view like this :
var ProductListItemView = Backbone.View.extend({
el: '#wine-cellar-list',
initialize: function() {
this.collection.bind('reset', this.full, this);
this.collection.fetch();
},
full: function() {
this.render(this.collection.models);
},
favorites: function(favorite) {
this.render(this.collection.where(favorite)); // here's the answer to your question
},
render: function(models) {
console.log(models);
var source = $('#product-template').html();
var template = Handlebars.compile(source);
var html = template(models.toJSON()); // You may have to change this line
this.$el.html(html);
return this;
},
});
And in your router :
showFavorites: function() {
console.log('Favorites');
productView.favorites(true); // or false, as you like
}

Bootstrapping data in backbone.js?

I've manage to put together the below code through various examples, which seems to work okay, but it doesn't seem to preload my data, can anyone please tell me what I'm missing?
App = (function(Backbone, _){
var Note = Backbone.Model.extend(
{
defaults:
{
part1: 'hello',
part2: 'world'
}
});
var TableList = Backbone.Collection.extend({
model: Note
});
var ListRow = Backbone.View.extend(
{
tagName: 'li',
initialize: function()
{
_.bindAll(this, 'render');
},
render: function()
{
$(this.el).html('<span>'+this.model.get('part1')+' '+this.model.get('part2')+'</span>');
return this;
}
});
var ListView = Backbone.View.extend(
{
el: $('#layout_content'),
events:
{
'click button#add': 'addItem'
},
initialize: function()
{
_.bindAll(this, 'render', 'addItem', 'appendItem');
this.collection = new TableList();
this.collection.bind('add', this.appendItem);
this.counter = 0;
this.render();
},
render: function()
{
var self = this;
$(this.el).append("<button id='add'>Add list item</button>");
$(this.el).append("<ul></ul>");
_(this.collection.models).each(function(item){ // in case collection is not empty
self.appendItem(item);
}, this);
},
addItem: function()
{
this.counter++;
var note = new Note();
note.set({part2: note.get('part2') + this.counter});
this.collection.add(note);
},
appendItem: function(item)
{
var listRow = new ListRow({
model: item
});
$('ul', this.el).append(listRow.render().el);
}
});
var app = function(initialModels)
{
this.start = function()
{
this.tableList = new TableList();
this.listView = new ListView({collection: this.tableList});
this.tableList.reset(initialModels);
};
};
return app;
})(Backbone, _);
then init the app with:
<script language="javascript">
var app = new App([{"id":"95","note_title":"can we find the title"},{"id":"93","note_title":"some title"}]);
app.start();
</script>
okay, there are a few issues with your code,
there are 2 issues in your start method,
a) you throw away your collection
this.start = function()
{
this.tableList = new TableList();
this.listView = new ListView({collection: this.tableList});
this.tableList.reset(initialModels);
};
and then in intialize is where you overwrite the collection you pass along
initialize: function()
{
_.bindAll(this, 'render', 'addItem', 'appendItem');
this.collection = new TableList(); // this one gets overwritten, remove this line
}
b) you trigger a collection reset with the models you want to populate it with, but don't listen to an event, either add a listener like this:
this.collection.bind('reset', this.appendAllItems, this);
or create your collection like this:
this.start = function()
{
this.tableList = new TableList(initialModels);
this.listView = new ListView({collection: this.tableList});
};

Categories