Backbone Client Side Collection Filtering - backbone.js

Consider a backbone app that has a select list of categories, and a table of files underneath it. All files, regardless of the category they belong to are in the App.collection.files collection. When a user selects a new category, I'd like to filter the table to only show those matching files.
I'd like to do this completely on the client side. I do NOT want to fetch a new list of files from the server each time a new category is picked.
I plan to to a App.collection.files.where({category_id: xx}) within App.View.Files.render() to accomplish the filtering itself.
What is the best Backbone-y way to trigger/respond to an event to get this working?
One idea I am playing with is another collection, that contains the filtered files, but not sure if this is the best approach.
App.View.FilesPage = Backbone.View.extend({
template: App.utils.template('files-page'),
className: 'container-fluid',
initialize: function(){
this.render();
},
render: function(){
this.$el.html(this.template());
this.$el.find('.categories').append(
new App.View.Categories({collection:App.collection.categories}).el
);
this.$el.find('.files table').append(
new App.View.Files({collection:App.collection.files}).el
);
return this;
}
});
App.View.Categories = Backbone.View.extend({
tagName: 'select',
events: {
'change': 'onChange'
},
initialize: function(){
this.$el.attr('size', this.collection.length + 1);
this.render();
},
render: function(){
var option = function(value, text, isSelected){
return $('<option></option>', {'value':value, 'text':text});
};
this.$el.html(option(0, 'All Documents'));
this.$el.append(
this.collection.invoke(function(){
return option(this.get('category_id'), this.get('title'));
})
).val(0);
return this;
},
onChange: function(event){
}
});
App.View.Files = Backbone.View.extend({
tagName: 'tbody',
initialize: function(){
this.render();
},
render: function(){
this.$el.html(
this.collection.invoke(function(){
return new App.View.File({model:this}).el;
})
);
return this;
}
});

Here is one solution I came up with, that seems fine and dandy on the surface.
Code changes are called out within comments.
Thoughts on this solution, and completely different solutions are welcome.
App.View.FilesPage = Backbone.View.extend({
template: App.utils.template('files-page'),
className: 'container-fluid',
initialize: function(){
// CHANGE: Store application wide reference to 'Categories' model
App.model.categories = new App.Model.Categories();
this.render();
},
render: function(){
this.$el.html(this.template());
this.$el.find('.categories').append(
new App.View.Categories({
// CHANGE: Pass model into 'Categories' view
model: App.model.categories,
collection: App.collection.categories
}).el
);
this.$el.find('.files table').append(
new App.View.Files({collection:App.collection.files}).el
);
return this;
}
});
App.View.Categories = Backbone.View.extend({
tagName: 'select',
events: {
'change': 'onChange'
},
initialize: function(){
this.$el.attr('size', this.collection.length + 1);
this.render();
},
render: function(){
var option = function(value, text, isSelected){
return $('<option></option>', {'value':value, 'text':text});
};
this.$el.html(option(0, 'All Documents'));
this.$el.append(
this.collection.invoke(function(){
return option(this.get('category_id'), this.get('title'));
})
).val(0);
return this;
},
onChange: function(){
// CHANGE: Update attribute within 'Categories' model
this.model.set('category_id', this.$el.val());
}
});
App.View.Files = Backbone.View.extend({
tagName: 'tbody',
initialize: function(){
this.collection.on('reset', this.render, this);
// CHANGE: Listen for change on 'Categories' model, and re-render this view
App.model.categories.on('change:category_id', this.render, this);
this.render();
},
render: function(){
this.$el.html(
_.invoke(this.getFilteredFiles(), function(){
return new App.View.File({model:this}).el;
})
);
return this;
},
// CHANGE: Return a filtered set of models from collection, based on 'Categories' model attributes
getFilteredFiles: function(){
var categoryId = App.model.categories.get('category_id');
if (categoryId!=0){
return this.collection.where({'category_id': categoryId});
}
else{
return this.collection.models;
}
}
});

Related

Backbone appends to list view instead of replacing it

I am producing a single page website with Wordpress and Backbone.js, i have come into a problem with when i fetch new data. It simply adds DOM elements onto the container el rather than replacing them. The collection updates correctly as i can see it has the right amount of elements in the console.
var PostItem = Backbone.Model.extend();
var PostItems = Backbone.Collection.extend({
model: PostItem,
url: '/wp-admin/admin-ajax.php'
});
var postItems = new PostItems();
var PostView = Backbone.View.extend({ /* Model View */
tagName : 'article',
className : 'widget',
template : _.template( $('#widgetPost').html() ),
render: function(){
var attributes = this.model.toJSON();
this.$el.html( this.template( attributes ) );
return this;
}
});
var PostListView = Backbone.View.extend({ /* Collection View */
el : '#content',
initialize: function(){
this.collection.on('add', this.addOne, this);
this.collection.on('reset', this.addAll, this);
},
addOne: function(postItem){
var postView = new PostView({ model : postItem });
this.$el.append( postView.render().el );
},
addAll: function(){
this.collection.forEach(this.addOne, this);
},
render: function(){
this.addAll();
},
});
var postListView = new PostListView({
collection : postItems
});
$(function(){
$('a#posts').click(function(){
postItems.fetch({
data: {
action: 'do_ajax',
fn: 'get_the_posts'
}
});
return false;
});
$('a#pages').click(function(){
postItems.fetch({
data: {
action: 'do_ajax',
fn: 'get_the_pages'
}
});
return false;
});
});
You need to clear out your collectionView's $el! :)
addAll: function(){
this.$el.empty();
this.collection.forEach(this.addOne, this);
}
This should do the trick.

View doesn't shows backbone models in template

i'm starting with Backbone and Laravel, and i have severals questions because i don`t find anything in Spanish (maybe i don't know how to search, therefore it's easier to ask).
Here are my Models:
window.mMateria = Backbone.Model.extend({
defaults: {
nombremateria: ""
},
});
window.cMaterias = Backbone.Collection.extend({
url: "materias",
model: mMateria,
initialize: function() {
this.fetch();
}
});
Here are my Views:
window.vMaterias = Backbone.View.extend({
tagName: 'ul',
model: cMaterias,
className:'list-materias',
initialize: function () {
_.bindAll(this, "render");
},
render: function(){
$(this.el).append("Renderizando!"); //It appears
_.each(this.model.models, function (aMater) {
console.log(aMater); //HERE IT DOESN'T ENTER, doesn't show anything
$(this.el).append(new vMateria({model:aMater}).render().el);
}, this);
return this;
},
el: $(".container-fluid")
});
window.vMateria = Backbone.View.extend({
initialize:function () {
_.bindAll(this, "render");
this.model.bind("change", this.render(), this);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
className: "item-materia",
el: $(".container-fluid"),
template: _.template($('#pl_materia').val()),
});
Then initialize:
cmaterias = new cMaterias();
console.log(cmaterias); //it returns 41 signatures
vmaterias = new vMaterias({model: cmaterias});
console.log(vmaterias); //Shows child {cid: "view1", model: child, ...
vmaterias.render().el;
Please help me and excuse my English, I dont know if Laravel with the
return Response::eloquent(Materia::all()); is the problem. Be as especific as possible. Dios los bendiga.
Try these changes.
window.vMaterias = Backbone.View.extend({
tagName: 'ul',
className:'list-materias',
render: function(){
this.$el.empty();
this.$el.append("Renderizando!"); //It appears
this.collection.each(function (aMater) {
console.log(aMater); //HERE IT DOESN'T ENTER, doesn't show anything
this.$el.append(new vMateria({model:aMater}).render().el);
}, this);
return this;
},
el: $(".container-fluid")
});
cmaterias = new cMaterias();
console.log(cmaterias); //it returns 41 signatures
vmaterias = new vMaterias({collection: cmaterias});

Render not being called in Backbone view

Here is my Backbone:
App.Models.Count = Backbone.Model.extend({
url: this.url,
initialize: function() {
this.fetch({
success: function(data, response) {
this.count = data.get('count');
console.log(this.count); // 9, correct answer
}
});
}
});
App.Views.Count = Backbone.View.extend({
tagName: 'span',
initialize: function(options) {
this.count = this.options.count;
console.log(options); // returns correctly
this.model.on('reset', this.render, this);
},
render: function() {
console.log('test'); // not called
this.$el.html(this.model.toJSON());
return this;
}
});
And in my route:
var mc = new (App.Models.Count.extend({'url' : 'main-contact-count'}))();
var mcv = new (App.Views.Count.extend({ model: mc }))();
console.log(mcv); // 9, correct answer
$('#contactCount').html(mcv);
As you can see, my render method is never called. Also, it seems that my view is being called before my model, based on what I see console.log'd in Firebug. Is that because of the async? Why isn't render being called?
You're using Backbone in a funky way. Here's the more standard way to do this:
App.Models.Count = Backbone.Model.extend({
urlRoot: "main-contact-count"
});
App.Views.Count = Backbone.View.extend({
tagName: 'span',
initialize: function(options) {
this.model.on('change', this.render, this);
},
render: function() {
console.log('test');
this.$el.html(this.model.toJSON());
return this;
}
});
And in the router:
var mc = new App.Models.Count();
var mcv = new App.Views.Count({model: mc});
mc.fetch();
$('#contactCount').html(mcv.el);
EDIT
It turns out you're listening to "reset" on a Backbone model. This will never happen. Try listening on "change" instead of reset:
this.model.on('change', this.render, this);

detect select status of options in a multiselect with backbone.picky

I have a multiselect dropdown that I'm rendering with Backbone. As user selects or deselects options, I'd like those (de)selections to be saved asynchronously via Backbone.
I found Backbone.Picky, and thought it might be helpful in my endeavor, but I can't seem to get it to detect selects.
In my FieldView's clicked function below, console.log(this.model.selected); always writes undefined to the log. Why?
var Field = Backbone.Model.extend({
initialize: function(){
var selectable = new Backbone.Picky.Selectable(this);
_.extend(this, selectable);
}
});
var FieldView = Backbone.View.extend({
tagName: "option",
initialize: function(){
_.bindAll(this, 'render');
},
events: {
"click":"clicked"
},
clicked: function(e) {
var data_type = this.model.get("DATA_TYPE");
console.log(this.model.selected); // why is this undefined?
console.log("it's a " + data_type);
},
render: function(){
this.$el.attr('value', this.model.get('COLUMN_NAME')).html(this.model.get('display_name'));
return this;
}
});
Here's a jsfiddle http://jsfiddle.net/EAZCt/2/ for more context.
Using Backbone, how can I asynchronously save the select-status of options in a multiselect list?
Your model object doesn't ever have "selected" property because you never select the model. I have never used Backbone.Picky but it seems that you could try:
var FieldView = Backbone.View.extend({
tagName: "option",
initialize: function(){
this.model.on('selected', this.selected, this);
},
events: {
"click":"clicked"
},
clicked: function() {
this.model.select();
},
selected: function() {
var data_type = this.model.get("DATA_TYPE");
console.log(this.model.selected);
console.log("it's a " + data_type);
},
render: function(){
this.$el.attr('value', this.model.get('COLUMN_NAME')).html(this.model.get('display_name'));
return this;
}
});
http://jsfiddle.net/hGEYL/

Backbone.js not rendering

My collection is not rendering for some reason. Cannot find out why.
TreeItem = Backbone.Model.extend({
});
TreeList = Backbone.Collection.extend({
model: TreeItem,
url: "/get_tree_list"
});
window.tree_list = new TreeList();
// VIEW
window.TreeItemView = Backbone.View.extend({
tagName: 'li',
initialize: function(){
_.bindAll(this, 'render');
},
render: function(){
$(this.el).html('<span>'+this.model.get('title')+'</span>');
return this;
}
});
window.TreeListView = Backbone.View.extend({
el: "#tree-structure",
events: {
},
initialize: function() {
_.bindAll(this, 'appendItem', 'render');
tree_list.bind('add', this.appendItem);
tree_list.fetch();
this.render();
},
render: function() {
tree_list.each(this.appendItem);
return this;
},
appendItem: function(item){
var tree_item_view = new TreeItemView({
model: item
});
$(this.el).append(tree_item_view.render().el);
}
});
var tree_list_view = new TreeListView;
Backbone.js provides a lot to be interpreted that's where people new go wrong. Your mistake is fundamental in nature. You tie the View directly to the model
see initialize function where a instance of collection is rendered!!
Always and anywhere you create model, collection pass then as parameters to the constructor of views. Check my fiddle
Never call render inside model, view or collection. They must be inside application file
JsFiddle
http://jsfiddle.net/35QGM/

Resources