Update a collections view dynamically after removing the models from it - javascript

I have the following code:
// Create the Backbone view to display a list of vehicles
var Vehicle = Backbone.Model.extend({
idAttribute: "registrationNumber",
urlRoot: "/api/vehicles",
validate: function(attrs) {
if (!attrs.registrationNumber)
return "Registration number is required!";
}
});
var Vehicles = Backbone.Collection.extend({
model: Vehicle
});
// View for the model
var VehicleView = Backbone.View.extend({
tagname: "li",
attributes: {
"data-color" : "Rainbow"
},
events: {
"click .deleteButton" : "deleteVehicle"
},
render: function() {
var template = _.template($("#vehicleTemplate").html());
var html = template(this.model.toJSON());
this.$el.html(html);
return this;
},
deleteVehicle: function() {
console.log("Model destroyed");
this.model.destroy();
}
});
var VehiclesView = Backbone.View.extend({
tagName : "ul",
initialize: function () {
this.model.on("change", this.render, this);
this.model.on("add", this.render, this);
this.model.on("remove", this.render, this);
},
render: function() {
var self = this;
this.model.each(function(vehicle){
var vehicleView = new VehicleView({model: vehicle});
self.$el.append(vehicleView.render().$el);
});
}
});
// Add cars to the collection
var car1 = new Vehicle({registrationNumber: "XLI887", color: "blue"});
var car2 = new Vehicle({registrationNumber: "ZNP123", color: "blue"});
var car3 = new Vehicle({registrationNumber: "XUV456", color: "grey"});
var vehicles = new Vehicles([car1, car2, car3]);
var vehiclesView = new VehiclesView({el: "#container", model: vehicles});
vehiclesView.render();
The rendering looks good, but when I click delete, the new collection (with one less element) is appended to the existing view of the older collection. How do I replace the old collection view with the new one?

I modified the "remove" handler as below and it dynamically removed the "li" element from the list and showed the new collection.
initialize: function () {
this.model.on("change", this.render, this);
this.model.on("add", this.render, this);
this.model.on("remove", this.onDelete, this);
},
onDelete : function(vehicle) {
console.log("Vehicle deleted");
this.$el.find("li#" + vehicle.id).remove();
}

Related

Toggle class on mouse click event

I've got a Backbone.View that renders a collection and filters it on mouse click. I need to add class active to the button that I click, but the problem is that buttons are the part of this view and whenever I try to addClass or toggleClass it just renders again with default class. Here's my view:
var ResumeList = Backbone.View.extend({
events: {
'click #active': 'showActive',
'click #passed': 'showPassed'
},
initialize: function () {
this.collection = new ResumeCollection();
},
render: function (filtered) {
var self = this;
var data;
if (!filtered) {
data = this.collection.toArray();
} else {
data = filtered.toArray();
}
this.$el.html(this.template({ collection: this.collection.toJSON() });
_.each(data, function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
return this;
},
showActive: function () {
this.$('#active').toggleClass('active');
// a function that returns a new filtered collection
var filtered = this.collection.filterActive();
this.render(filtered);
}
});
But as I've already told, the class I need is toggled or added just for a moment, then the view is rendered again and it is set to default class. Is there any way to handle this?
I simplified the rendering and added some optimizations.
Since we don't have your template, I changed it to enable optimization:
<button id="active" type="button">Active</button>
<button id="passed" type="button">Passed</button>
<div class="list"></div>
Then your list view could be like this:
var ResumeList = Backbone.View.extend({
events: {
'click #active': 'showActive',
'click #passed': 'showPassed'
},
initialize: function() {
this.childViews = [];
this.collection = new ResumeCollection();
},
render: function() {
this.$el.html(this.template());
// cache the jQuery element once
this.elem = {
$list: this.$('.list'),
$active: this.$('#active'),
$passed: this.$('#passed')
};
this.renderList(); // default list rendering
return this;
},
renderList: function(collection) {
this.elem.$list.empty();
this.removeChildren();
collection = collection || this.collection.models;
// Underscore's 'each' has a argument for the context.
_.each(collection, this.renderItem, this);
},
renderItem: function(model) {
var view = new ResumeView({ model: model });
this.childViews.push(view);
this.elem.$list.append(view.render().el);
},
showActive: function() {
this.elem.$active.toggleClass('active');
var filtered = this.collection.filterActive();
this.renderList(filtered);
},
/**
* Gracefully call remove for each child view.
* This is to avoid memory leaks with listeners.
*/
removeChildren: function() {
var view;
while ((view = this.childViews.pop())) {
view.remove();
}
},
});
Additional information:
Managing Views and Memory Leaks
Underscore's each (notice the third argument)
Try to avoid callback hell, make the callbacks reusable (like renderItem)
I have edited the snippet can you try this.
var ResumeList = Backbone.View.extend({
events: {
'click #active': 'filterActive',
'click #passed': 'showPassed'
},
toggleElement: undefined,
initialize: function () {
this.collection = new ResumeCollection();
},
render: function (filtered) {
var self = this;
var data;
if (!filtered) {
data = this.collection.toArray();
} else {
data = filtered.toArray();
}
this.$el.html(this.template({ collection: this.collection.toJSON() });
_.each(data, function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
return this;
},
filterActive: function (evt) {
this.toggleElement = this.$el.find(evt.currentTarget);
// a function that returns a new filtered collection
var filtered = this.collection.filterActive();
this.render(filtered);
this.toggleActive();
},
toggleActive: function() {
if(this.toggleElement.is(':checked')) {
this.$el.find('#active').addClass('active');
} else {
this.$el.find('#active').removeClass('active');
}
}
});
Please note: I have taken checkbox element instead of button.

Getting a Paragraph that changes with dropdown using Backbone.js

I've gotten my dropdown to be populate with lessons, but then I am stuck trying to figure out how I can get the text beneath my dropdown to change based on what the user selects by using Backbone.js
I am populating a select by adding options containing my Lessons and having the title show. Now I am just stuck on where should I insert the text so that it will change based on the selection.
Here is my HTML:
<script type="text/template" id="lesson-template">
<span class="lesson-title"><%= title %></span>
//How should I insert the text?
</script>
<script type="text/template" id="library-template">
<h1> Lesson Library </h1>
<select class="lessons"></select>
</script>
Here is my JSON file where I pull the information, I want to show the title now:
[{
"title": "Intro",
"text":"Do this now"
},
{
"title": "Second",
"text":"And then this"
}]
This is what is included in my javascript file:
window.Lesson = Backbone.Model.extend({});
window.Lessons = Backbone.Collection.extend({
model: Lesson,
url: './lessons.json'
});
window.library = new Lessons();
window.LessonView = Backbone.View.extend({
tagName: 'option',
className: 'lesson',
initialize: function() {
_.bindAll(this,'render');
this.model.bind('change',this.render);
this.template = _.template($('#lesson-template').html());
},
render: function() {
var renderedContent = this.template(this.model.toJSON());
$(this.el).html(renderedContent);
return this;
}
});
window.LibraryLessonView = LessonView.extend({
});
window.LibraryView = Backbone.View.extend({
tagName: 'section',
className: 'library',
initialize: function() {
_.bindAll(this, 'render');
this.template = _.template($('#library-template').html());
this.collection.bind('reset', this.render);
},
render: function() {
var $lessons,
collection = this.collection;
$(this.el).html(this.template({}));
$lessons = this.$('.lessons');
this.collection.each(function(lesson) {
var view = new LibraryLessonView({
model: lesson,
collection: collection
});
$lessons.append(view.render().el);
});
return this;
}
});
First give an id to each lesson.
window.LessonView = Backbone.View.extend({
...
render: function() {
var renderedContent = this.template(this.model.toJSON());
$(this.el).html(renderedContent);
// Then put the id as the option's value
$(this.el).val(this.model.get('id'));
return this;
}
...
});
window.LibraryView = Backbone.View.extend({
...
// bind the select's onchange event to this.onSelect
events: {
'change select': 'onSelect'
},
...
...
onSelect: function(e) {
// Grab the id of the select lesson
var lessonId = $(e.target).val();
// And get the lesson data back
var lesson = _.indexBy(this.collection.toJSON(), 'id')[lessonId];
// Then you could render you lesson view with something like this :
$('#target-container').html(
_.template($('#lesson-template').html(), lesson)
);
},
...
});
You could also go for a more elaborate views / collections construct but I'd suggest you get this working first.

TypeError: Cannot call method 'replace' of null - Backbone.js

I'm trying to do a tutorial backbone project with peepcode, but I got stuck.
I am trying to render a view from the console by creating a new view from a collection. Here's my code
(function($) {
window.Album = Backbone.Model.extend({
isFirstTrack: function(index) {
return index == 0;
},
isLastTrack: function(index) {
return index >= this.get('tracks').length - 1;
},
trackUrlAtIndex: function(index) {
if (this.get('tracks').length >= index) {
return this.get('tracks')[index].url;
}
return null;
}
});
window.Albums = Backbone.Collection.extend({
model: Album,
url: '/albums'
});
window.Album = Backbone.Model.extend({});
window.AlbumView = Backbone.View.extend({
tagName: 'li',
className: 'album',
initialize: function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.template = _.template($('#album-template').html());
},
render: function() {
var renderedContent = this.template(this.model.toJSON());
$(this.el).html(renderedContent);
return this;
}
});
window.LibraryAlbumView = AlbumView.extend({
});
window.LibraryView = Backbone.View.extend({
tagName: 'section',
className: 'library',
initialize: function () {
_.bindAll(this, 'render');
this.template = _.template($('#library-template').html());
this.collection.bind('reset', this.render);
},
render: function() {
var $albums,
collection = this.collection;
$(this.el).html(this.template({}));
$albums = this.$(".albums");
collection.each(function(album) {
var view = new LibraryAlbumView({
model: album,
collection: collection
});
$albums.append(view.render().el);
});
return this;
}
});
})(jQuery);
When I type libraryView = new LibraryView({ collection: library })
In the console I get this response:
TypeError: Cannot call method 'replace' of null
Can anyone help me out?
Most likely the element for the library view doesn't exist in your markup.
This should be true
$('#library-template').length == 1;
This error can happen if you do:
_.template(null)
_.template(undefined);
Calling .html() on an element that doesn't exist returns undefined.
What exactly is the library variable (that you're using as the value in the constructor argument to LibraryView) set to? It's not defined globally, so it's probably null. You'd need to first declare that variable:
var library = new Albums(),
libraryView = new LibraryView({ collection: library })

backbone view render not creating

Just beginning with backbone and after few hours can't seem to get even a view render working correctly. I've included all appropriate JavaScript files in HTML. Here is my script:
(function($) {
// MODELS
var Paper = Backbone.Model.extend ({
defaults : {
title : null,
author: null,
}
});
// COLLECTIONS
var PaperCollection = Backbone.Collection.extend({
model : Paper,
initialize : function() {
console.log("We've created our collection");
}
});
// VIEWS
var PaperView = Backbone.View.extend({
tagName:'li',
className: 'resultTable',
events: {
'click .ptitle':'handleClick'
},
initialize: function() {
_.bindAll(this, 'render', 'handleClick');
},
render: function() {
$(this.el).html('<td>'+this.model.get('title')+'</td>');
return this; // for chainable calls
},
handleClick: function() {
alert('Been clicked');
}
});
var ListView = Backbone.View.extend({
events: {
//"keypress #new-todo": "createOnEnter",
},
initialize : function() {
console.log('Created my app view');
_.bindAll(this, 'render', 'addOne', 'appendOne');
this.collection = new PaperCollection();
this.collection.bind('add', this.appendOne); // collection event binder
this.counter = 0;
this.render();
},
render : function() {
console.log('Render app view');
$(this.el).append("<button id='add'>Add list item</button>");
$(this.el).append("<p>More text</p>");
// $(this.el).append("<ul></ul>");
/*
_(this.collection.models).each(function(item){ // in case collection is not empty
appendOne(item);
}, this); */
},
addOne: function() {
this.counter++;
var p = new Paper();
p.set({
title: "My title: " + this.counter // modify item defaults
});
this.collection.add(p);
},
appendOne: function(p) {
var paperView = new PaperView({
model: p
});
$('ul', this.el).append(paperView.render().el);
}
});
var App = new ListView({el: $('paper_list') });
// App.addOne();
})(jQuery);
Note not getting any errors in console on FF - but still not displaying any of the render outputs in AppView). Appreciate any help. Simple HTML:
<body>
<div class="container_16">
<div class="grid_16">
<div id="paper_list">
Text...
<ul class="thelist"></ul>
</div>
</div>
<div class="clear"></div>
</div>
</body>
This will at least get you rendering the ListView...
// MODELS
var Paper = Backbone.Model.extend ({
defaults : {
title : null,
author: null,
}
});
// COLLECTIONS
var PaperCollection = Backbone.Collection.extend({
model : Paper,
initialize : function() {
console.log("We've created our collection");
}
});
// VIEWS
var PaperView = Backbone.View.extend({
tagName:'li',
className: 'resultTable',
events: {
'click .ptitle':'handleClick'
},
initialize: function() {
_.bindAll(this, 'render', 'handleClick');
},
render: function() {
$(this.el).html('<td>'+this.model.get('title')+'</td>');
return this; // for chainable calls
},
handleClick: function() {
alert('Been clicked');
}
});
var ListView = Backbone.View.extend({
el: '#paper_list',
events: {
"click #add": "createOnEnter",
},
initialize : function() {
console.log('Created my app view');
_.bindAll(this, 'render', 'addOne', 'appendOne');
this.collection = new PaperCollection();
this.collection.bind('add', this.appendOne); // collection event binder
this.counter = 0;
this.render();
},
render : function() {
console.log(this);
$(this.el).append("<button id='add'>Add list item</button>");
$(this.el).append("<p>More text</p>");
// $(this.el).append("<ul></ul>");
/*
_(this.collection.models).each(function(item){ // in case collection is not empty
appendOne(item);
}, this); */
},
addOne: function() {
this.counter++;
var p = new Paper();
p.set({
title: "My title: " + this.counter // modify item defaults
});
this.collection.add(p);
},
appendOne: function(p) {
var paperView = new PaperView({
model: p
});
$('ul', this.el).append(paperView.render().el);
}
});
$(function(){
var App = new ListView();
});
A couple of things...First, I initialized your ListView inside of a document.ready to make sure that the DOM was ready to go, second, I made the el in the listview simply #paper_list then you can do $(this.el) later.
I at least got the button and "more text" to show up...Let me know if that helps!

Loading initial data in Backbone.js

I'm new to backbone.js and MVC so apologise if this is a silly question...
I have been experimenting with some of the backbone.js tutorials out there and am trying to work out how to load an initial set of data onto the page.
If anyone could point me in the right direction or show me the what I'm missing below, it would be greatly appreciated!
Thanks!
The code is below or at: http://jsfiddle.net/kiwi/kgVgY/1/
The HTML:
Add list item
The JS:
(function($) {
Backbone.sync = function(method, model, success, error) {
success();
}
var Item = Backbone.Model.extend({
defaults: {
createdOn: 'Date',
createdBy: 'Name'
}
});
var List = Backbone.Collection.extend({
model: Item
});
// ------------
// ItemView
// ------------
var ItemView = Backbone.View.extend({
tagName: 'li',
// name of tag to be created
events: {
'click span.delete': 'remove'
},
// `initialize()` now binds model change/removal to the corresponding handlers below.
initialize: function() {
_.bindAll(this, 'render', 'unrender', 'remove'); // every function that uses 'this' as the current object should be in here
this.model.bind('change', this.render);
this.model.bind('remove', this.unrender);
},
// `render()` now includes two extra `span`s corresponding to the actions swap and delete.
render: function() {
$(this.el).html('<span">' + this.model.get('planStartDate') + ' ' + this.model.get('planActivity') + '</span> <span class="delete">[delete]</span>');
return this; // for chainable calls, like .render().el
},
// `unrender()`: Makes Model remove itself from the DOM.
unrender: function() {
$(this.el).remove();
},
// `remove()`: We use the method `destroy()` to remove a model from its collection.
remove: function() {
this.model.destroy();
}
});
// ------------
// ListView
// ------------
var ListView = Backbone.View.extend({
el: $('body'),
// el attaches to existing element
events: {
'click button#add': 'addItem'
},
initialize: function() {
_.bindAll(this, 'render', 'addItem', 'appendItem'); // every function that uses 'this' as the current object should be in here
this.collection = new List();
this.collection.bind('add', this.appendItem); // collection event binder
this.render();
},
render: function() {
_(this.collection.models).each(function(item) { // in case collection is not empty
appendItem(item);
}, this);
},
addItem: function() {
var item = new Item();
var planStartDate = $('#planStartDate').val();
var planActivity = $('#planActivity').val();
item.set({
planStartDate: planStartDate,
planActivity: planActivity
});
this.collection.add(item);
},
appendItem: function(item) {
var itemView = new ItemView({
model: item
});
$('ul', this.el).append(itemView.render().el);
}
});
var listView = new ListView();
})(jQuery);
Thanks.
Here's the modified example: http://jsfiddle.net/kgVgY/2/
You create the collection first with the data you want
var list = new List([{
createdOn: 'Jan',
createdBy: 'John',
planStartDate: "dfd",
planActivity: "dfdfd"
}]);
and then pass the collection to the view you want
var listView = new ListView({collection: list});
That's about all you had wrong in this code. Few minor unrelated notes:
You were using _(this.collection.models).each. Backbone collections use underscore to expose all those functions on themselves, so that is equivalent to this.collection.each
You don't really need the "unrender" method on the ItemView but since you aren't using that I'm guessing you're using it for debugging.

Categories

Resources