In my web application, I have a model that is mixture of normal standard attributes (strings, booleans, etc) and the collections. In the application a user can create a group and add a projects to that group. The group is collection with the model. I can add and remove projects to the group absolutely fine, in so much that i can see the projects being added or removed in my logging, and also in the database.
To add projects to a group I open a modal and clicking a button in that model triggers model.save() and this triggers a change event. However if I add user and then remove without reloading the page, the change is not fired, why would this be? Here is the modal view,
Modal View
Views.OrganisastionEditView = Backbone.View.extend({
className : 'modal-body',
template: _.template( $('#tpl-edit-organisation').html() ),
events: {
"click .js-add-member" : "addMember",
"click .js-add-client" : "addClient",
"click .js-add-project" : "addProject",
"click .js-add-team" : "addTeam",
"click .search-results a" : "selectSearchResult",
"click .js-remove-pill" : "removeAttribute",
"submit .edit-organisation" : "saveOrganisation",
"click .js-remove-delete" : "deleteOrganisation",
"click .js-make-admin" : "changeAdmin"
},
initialize: function() {
//this.listenTo(this.model, 'change', this.snyc);
this.listenTo(this.model.get('members'), 'change', this.changeMember);
this.render();
},
render: function() {
var self = this;
// this.model.initialize();
$('#myModal').on('hidden.bs.modal', function () {
self.remove();
Pops.Routes.Application.navigate('/groups', { trigger: false } );
});
this.$el.html( this.template({
organisation: this.model.toJSON()
})).insertAfter('.modal-header');
var organisationProjectsView = new Views.GroupsProjectsViews({
collection: this.model.get('projects')
});
var organisationClientsView = new Pops.Views.GroupsClientsViews({
collection: this.model.get('clients')
});
var organisationMembersView = new Views.GroupsMembersAdminViews({
collection: this.model.get('members')
});
var organisationTeamsView = new Views.GroupsTeamsViews({
collection: this.model.get('teams')
});
$("#myModal").modal();
},
deleteOrganisation: function(e) {
e.preventDefault();
this.model.destroy();
$("#myModal").modal('hide');
this.remove();
},
removeAttribute: function(e) {
e.preventDefault();
var element = $(e.currentTarget);
switch(element.data('type')) {
case "project":
console.log(this.model.get('projects'));
this.model.get('projects').remove(element.data('id'));
element.parents('.avatar-pill').remove();
break;
case "client":
this.model.get('clients').remove(element.data('id'));
element.parents('.avatar-pill').remove();
break;
}
},
addMember: function(e) {
e.preventDefault();
var element = $(e.currentTarget);
this.$('.search').parent().children().show();
this.$('.search').first().remove();
//element.parent().children().hide();
var search = new Views.SearchView({
collection: new Collections.Users,
type : "users",
merge: false
});
element.parent().append(search.render().el);
},
addProject: function(e) {
e.preventDefault();
var element = $(e.currentTarget);
this.$('.search').parent().children().show();
this.$('.search').first().remove();
//element.parent().children().hide();
var search = new Views.SearchView({
collection: new Collections.Projects,
type : "projects",
merge: false
});
element.parent().append(search.render().el);
},
selectSearchResult: function(e) {
e.preventDefault();
var element = $(e.currentTarget),
self = this;
switch( element.data('type')) {
case "project":
var project = new Models.Project({ id: element.data('id')});
project.fetch({
success: function() {
self.model.get('projects').add(project);
console.log(self.model.get('projects'));
var model = self.model;
self.$('.search').hide();
self.$('button').show();
var projectsDetails = new Views.ProjectNamePillView({
model : project
});
self.$('.search').parent().append( projectsDetails.render().el );
self.$('.search').remove();
}
});
break;
}
},
saveOrganisation: function(e) {
e.preventDefault();
var element = $(e.currentTarget);
var data = element.serializeJSON();
this.model.set(data);
this.model.save();
},
});
Single Model View
Views.OrganisationView = Backbone.View.extend({
tagName: 'div',
className:'group group--panel col-sm-3',
template : _.template( $('#tpl-single-group').html() ),
events: {
"click a[data-type=organisation], button[data-type=organisation]" : "edit",
"click .js-delete-group" : "removeOrganisation",
},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
//this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.removeView);
},
render: function() {
console.log('getting fired');
this.$el.html( this.template({
group: this.model.toJSON()
}));
return this;
},
removeView: function() {
this.remove();
},
removeOrganisation: function(e) {
this.model.destory();
this.remove();
},
edit: function(e) {
e.preventDefault();
Routes.Application.navigate('/organisation/edit/' + this.model.get('id'), { trigger: false } );
var editClient = new Views.OrganisastionEditView({
model: this.model
});
}
});
Change events are triggered on the model itself not its attributes. You can however listen to a particular attribute change by using the change:[attribute] event naming format.
In your example you would change:
this.listenTo(this.model.get('members'), 'change', this.changeMember);
To look like:
this.listenTo(this.model, 'change:members', this.changeMember);
Check the Backbonejs event docs.
Related
I am building a view at the moment, and when I click on a .organisation link I want to fire my edit event, however on clicking this element, nothing is fired, and I cannot understand why.
Here is the code that builds my view,
App.Views.groupsView = Backbone.View.extend({
el: '.app',
template: _.template( $('#tpl-groups-base').html() ),
events: {
},
initialize: function(options) {
this.render();
},
render: function() {
this.$el.html( this.template() );
var orgTab = new App.Views.OrganisationsTab({
collection : new App.Collections.Organisations
});
},
});
App.Views.OrganisationsTab = Backbone.View.extend({
el : '#organisations',
initialize: function() {
App.Collections.OrganisationCollection = this.collection;
this.collection.fetch();
this.collection.on('sync', this.render, this);
},
render: function() {
this.addAll();
return this;
},
addAll: function() {
App.Collections.OrganisationCollection.each(this.addOne, this);
},
addOne: function(organisation) {
var view = new App.Views.OrganisationView({
model : organisation
});
this.$el.append( view.render().el );
}
});
App.Views.OrganisationView = Backbone.View.extend({
tagName: 'a',
className:'group group--panel col-sm-3 organisation',
template : _.template( $('#tpl-single-group').html() ),
events: {
"click body" : "edit",
},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destory', this.remove);
},
render: function() {
this.$el.html( this.template({
group: this.model.toJSON()
}));
return this;
},
edit: function(e) {
e.preventDefault();
console.log(this.model);
}
});
Why would I not be able to click on the organisation a that is created in the final view, and trigger my edit function?
If you want to attach a click event listener to a child element of a view's el
events: {
"click .organisation" : "edit",
},
If you want to attach it to the el
events: {
"click" : "edit",
},
i am working on learn backbone.js, so i decided to make mi own Todo app using backbone.js and a localstorage plugin. I already have the Todo app in wich you can add and remove todos, now I am working on make them sortable in done and not done tasks. But the problem is that i can't find a way to do it. i created a method in the view called sort_done()
sort_done: function(){
var done = this.collection.where({done: true});
console.log(done);
}
But don't know how could i udate the view in order to just show thedone or not done tasks, i appreciate if someone could give me some advice in how i can manage this kind of ´problem. I also leave the model, collection and views js so you can take a look.
Model:
var Task = Backbone.Model.extend({
defaults:{
title: "An empyt task..",
done: false
},
validate: function(attrs){
if(! $.trim(attrs.title)){
return "The task has no title"
}
}
});
var task = new Task;
Collection:
var Tasks = Backbone.Collection.extend({
model: Task,
localStorage: new Backbone.LocalStorage("todos-collection")
});
var tasks = new Tasks();
Views:
var TaskView = Backbone.View.extend({
tagName: "li",
template: _.template( $('#task').html() ),
initialize: function(){
this.model.on('change', this.render, this);
this.model.on('destroy', this.remove, this);
},
render: function(){
var template = this.template( this.model.toJSON() );
this.$el.html( template );
return this;
},
events: {
'click .icon-checkbox': 'toggleState',
'click .task_title': 'editTask',
'keypress .edit': 'updateOnEnter',
'click .close_btn': 'clear'
},
toggleState: function(e){
var $checkbox = $(e.target);
this.model.save('done', !this.model.get('done'));
},
editTask: function(e){
this.task = $(e.target);
this.editBox = this.task.next();
this.editInput = this.editBox.find('.edit');
$(".task_title").removeClass("display__none");
$(".editBox").removeClass("edit_box__editing");
this.task.addClass("display__none")
this.editBox.addClass("edit_box__editing");
this.editInput.attr('placeholder', this.task.text()).focus();
},
updateOnEnter: function(e){
if(e.keyCode === 13){
this.close();
}
},
close: function(){
var value = this.editInput.val();
if(!value){
this.task.removeClass("display__none")
this.editBox.removeClass("edit_box__editing");
}else{
this.model.save({title: value});
this.task.removeClass("display__none")
this.editBox.removeClass("edit_box__editing");
}
},
clear:function(){
this.model.destroy();
}
});
var TasksView = Backbone.View.extend({
el: '#tasks',
initialize: function(){
this.render();
this.collection.on('add', this.addOne, this);
this.collection.on()
},
render: function(){
this.collection.each(this.addOne, this);
return this;
},
addOne: function(task){
var taskView = new TaskView({ model: task });
this.$el.append( taskView.render().el );
}
});
var AddTask = Backbone.View.extend({
el: '#todos',
initialize: function(){
this.collection.fetch();
},
events:{
'click #add': 'addTask',
'click #filter_done': 'sort_done',
'keypress #inputTask': 'updateOnEnter'
},
addTask: function(){
var taskTitle = $('#inputTask'). val();
$('#inputTask').val(""); //clear the input
if($.trim(taskTitle) === ''){//check if the input has some text in it
this.displayMessage("Todo's can not be empty");
}else{
var task = new Task( {title: taskTitle} ); // create the task model
this.collection.create(task); //add the model to the collection
}
},
displayMessage: function(msg){
$('#inputTask').focus().attr("placeholder", msg);
},
updateOnEnter: function(e){
if(e.keyCode === 13){
this.addTask();
}
},
sort_done: function(){
var done = this.collection.where({done: true});
console.log(done);
}
});
var tasksView = new TasksView( {collection: tasks} );
var addTask = new AddTask( {collection: tasks} );
Thank yoou very much!
When you call collection.where your collection is not filtered, it just returning the filtered models (not changing the initial collection), so for your problem you have to do like this :
initialize: function(){
this.render();
this.collection.on('add', this.addOne, this);
this.collection.on('reset', this.render, this); // here I change the event to reset
},
...
sort_done: function(){
var done = this.collection.where({done: true});
this.collection.reset(done);
console.log(done);
}
I make a simple todo app:
var Todo = Backbone.Model.extend({
});
var Todos = Backbone.Collection.extend({
model: Todo
});
var todos = new Todos();
var ItemView = Backbone.View.extend({
tagName: "li",
template: _.template($("#item-template").html()),
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
initialize: function () {
this.listenTo(todos, 'remove', this.remove);
},
events: {
"click .delete": "clear"
},
clear: function () {
todos.remove(this.model);
}
});
var AppView = Backbone.View.extend({
el: $("body"),
initialize: function () {
this.listenTo(todos, 'add', this.addOne);
},
addOne: function(todo) {
var view = new ItemView({
model: todo
});
this.$("#list").append(view.render().el);
},
events: {
"click #create": "create"
},
create: function () {
var model = new Todo({
title: this.$("#input").val()
});
todos.add(model);
}
})
var app = new AppView();
and DEMO online is here: http://jsfiddle.net/JPL94/1/
I can add item correctly, but when I want delete some item, all of them been removed;
I found it related to the bind event in ItemView, when I click one delete button, all of them are triggered.
But how can I solve this problem?
You are listening to remove events from the collection, and if my memory serves me right a collection will dispatch a remove event whenever a model is removed, so when you remove a model from the collection, all the views will see the event.
I changed your initialize in the view to
initialize: function () {
this.listenTo(this.model, 'remove', this.remove);
},
And it seems to work.
http://jsfiddle.net/JPL94/5/
I am trying to update my view whenever I add a new model to my collection. My first question is do I automatically add a model to my collection when I save that model, like:
PostsApp.Views.Form = Backbone.View.extend({
template: _.template($('#form-template').html()),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
},
events:{
'click button' : 'save'
},
save: function(e){
console.log("is this working");
e.preventDefault();
var newname = this.$('input[name=name-input]').val();
var newadress = this.$('input[name=adress-input]').val();
this.model.save({name: newname, adress : newadress});
}
});
or do I still have to do collection.add()
Other than that to see the new model in my view I am trying to add an 'add' event listener like this:
PostsApp.Views.Posts = Backbone.View.extend({
initialize: function(){
this.collection.on('add', this.addOne, this);
},
render: function(){
this.collection.forEach(this.addOne, this);
},
addOne: function(post){
var postView = new PostsApp.Views.Post({model:post});
postView.render();
this.$el.append(postView.el);
}
});
This not only doesnt work, but when I add the initialize method, it just duplicates everything in my model when the page is first loaded.
Nope.. When you do a model.save , it will just create a zombie model ( If it not already a part of the collection .i.e If a New model is saved) which is not a part of any collection.
So your add event will not be triggered for the collection.
If you want the add event to be triggered , Use the create method of collection , which then will know on which collection the new model has to be added..
collection.create({model});
Then it would internally add the model to the collection and fire the add event
Also it is a better idea to use listenTo instead of attaching events using on
this.listenTo(this.collection, 'add', this.addOne);
Code
PostsApp.Views.Form = Backbone.View.extend({
template: _.template($('#form-template').html()),
render: function () {
this.$el.html(this.template(this.model.toJSON()));
},
events: {
'click button': 'save'
},
save: function (e) {
console.log("is this working");
e.preventDefault();
var newname = this.$('input[name=name-input]').val();
var newadress = this.$('input[name=adress-input]').val();
this.collection.create({
name: newname,
adress: newadress
});
}
});
PostsApp.Views.Posts = Backbone.View.extend({
initialize: function () {
this.listenTo(this.collection, 'add', this.addOne);
},
render: function () {
this.collection.forEach(this.addOne, this);
},
addOne: function (post) {
var postView = new PostsApp.Views.Post({
model: post,
collection : this.collection
});
postView.render();
this.$el.append(postView.el);
}
});
I have created the following code and i'm unable to destroy a model from the backend.
I get a 'model is not defined error'.
I don't know why it cannot find a model? what parameters am i missing? am i wrong in adding this in this list view? should i add it in the models view? and if that is the case i added the save new model in the list view so can't see why i can't add it her.
window.LibraryView = Backbone.View.extend({
tagName: 'section',
className: 'mynotes',
events: {
"keypress #new-title": "createOnEnter",
//"keypress #new-content": "createOnEnter"
"click .mybutton": "clearCompleted"
},
initialize: function() {
_.bindAll(this, 'render');
this.template = _.template($('#library-template').html());
this.collection.bind('reset', this.render);
this.collection.bind('add', this.render);
this.collection.bind('remove', this.render);
},
render: function() {
var $mynotes,
collection = this.collection;
$(this.el).html(this.template({}));
$mynotes = this.$(".mynotes");
collection.each(function(mynote) {
var view = new LibraryMynoteview({
model: mynote,
collection: collection
});
$mynotes.append(view.render().el);
});
return this;
},
createOnEnter: function(e) {
var input = this.$("#new-title");
var input2 = this.$("#new-content");
//var msg = this.model.isNew() ? 'Successfully created!' : "Saved!";
if (!input || e.keyCode != 13) return;
// this.model.save({title: this.$("#new-title").val(), content: this.$("#new-content").val() }, {
var newNote = new Mynote({title: this.$("#new-title").val(), content: this.$("#new-content").val()});
this.collection.create(newNote);
},
clearCompleted: function() {
this.model.destroy();
this.collection.remove();
}
});
You have to bind your clearCompleted method to this:
_.bindAll(this, 'render', 'clearCompleted');