backbone model and collection - backbone.js

help me understand why the code is not working properly
my code:
var Link = Backbone.Model.extend({
defaults : {
groups: []
}
});
var LinkCollection = Backbone.Collection.extend({
model:Link,
url: 'item.json'
});
var Group = Backbone.Model.extend({
defaults: {
links: []
},
initialize : function() {
this.on("all" , function(event) {
console.log(event);
});
this.on("change:links" , function() {
console.log(this.get("links").length , this.id);
})
},
setLink: function(link) {
var links = this.get("links");
links.push(link);
this.set("links" , links);
this.trigger("change:links" , this);
},
removeLink : function(link) {
var links = this.get("links") , index = links.indexOf(link);
links.splice(index , 1);
this.set("links" , links);
this.trigger("change:links" , this);
}
});
var GroupCollection = Backbone.Collection.extend({
model:Group,
url: 'group.json',
setLinks : function(links) {
var self = this;
this.links = links;
this.links.on('all' , function(event) {
console.log(event);
});
this.links.on('add' , self.setLink , this);
this.links.on('remove' , this.removeLink , this);
this.links.on('reset' , this.resetLinks , this);
},
setLink : function(link) {
var self = this , test = false;
if(self.length ) {
link.get('groups').forEach(function(groupId) {
var group = self.get(groupId);
console.log(group , groupId);
if( group ) {
test = true;
group.setLink(link);
}
});
if(!test) {
self.get("notInGroup").setLink(link);
}
self.get("allObjects").setLink(link);
}
},
resetLinks : function() {
this.links.each(this.setLink);
},
initialize: function() {
var self = this;
this.on('reset' , self.resetLinks , this);
},
removeLink: function(link) {
var self = this;
link.get('groups').forEach(function(groupId) {
var group = self.get(groupId);
if(group) {
group.removeLink(link);
}
})
}
});
var LinkView = Backbone.View.extend({
tagName: 'li',
className : 'list-item',
template: _.template($("#ListView").html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this
}
});
var GroupView = Backbone.View.extend({
tagName : 'li',
className: 'group-list ',
template: _.template($("#GroupView").html()),
initialize: function() {
this.model.on('remove', function() {
console.log('remove');
});
this.model.on('reset' , function() {
console.log('reset');
});
this.model.on('destroy' , function() {
console.log('destroy')
});
},
render: function() {
var model = this.model.toJSON();
this.$el.html(this.template(model));
this.renderLinks(this.model);
this.model.on('change:links' , this.renderLinks.bind(this));
return this;
},
renderLinks : function(group) {
var self = this;
self.$el.find("ul").empty();
group.get("links").forEach(function(link) {
var view = new LinkView({model: link});
self.$el.find("ul").append(view.render().el);
});
}
})
var App = Backbone.View.extend({
el: $('#content'),
initialize: function() {
this.links = new LinkCollection();
this.groups = new GroupCollection();
this.groups.setLinks(this.links);
this.groups.bind('add', this.addGroup, this);
this.groups.bind('reset', this.addGroups, this);
this.links.fetch();
this.groups.fetch({reset: true});
return this;
},
render: function() {
this.$el.html($('#GroupListView').html())
},
addGroup: function(model) {
var view = new GroupView({model: model});
this.$("ul.group-list").append(view.render().el);
},
addGroups: function() {
this.groups.each(this.addGroup)
}
})
$(function() {
var app = new App();
app.render();
})
working example http://plnkr.co/edit/40CCIq0jt2AdmGD61uAn
but instead of the expected list of groups I get incorrect group
this list i tried to get:
All Objects
First
Second
Third
Fourth
Fifth
FirstGroup
First
Third
SecondGroup
Third
Fourth
NotInGroup
Second
Fifth
upd: Groups and Links can come at different times, so the order of their receipt is not critical. the problem is that for some reason the models groups added extra value, which should not be there

Screenshot added. Please look at the highlighted change

Related

backbone memory leak remove not working?

In the router I do this
function test() {
self.topbarView = new TopbarView();
self.topbarView.render();
GhostviewHunter.addView(self.topbarView);
}
function clean() {
console.log(GhostviewHunter.currentViews.length);
GhostviewHunter.clean();
}
setInterval(test, 1000);
setInterval(clean, 1000);
ghostviewhunter should clean/remove the views:
define('ghostviewHunter', [], function() {
var GhostviewHunter = function() {};
GhostviewHunter.prototype.currentViews = [];
GhostviewHunter.prototype.addView = function(view) {
this.currentViews.push(view);
}
GhostviewHunter.prototype.clean = function() {
_.each(this.currentViews, function(view) {
view.remove();
});
this.currentViews.length = 0;
}
GhostviewHunter.__instance = null;
GhostviewHunter.getInstance = function() {
if( GhostviewHunter.__instance == null ) {
GhostviewHunter.__instance = new GhostviewHunter();
}
return GhostviewHunter.__instance;
}
return GhostviewHunter.getInstance();
})
TopView is fetching a model, the model is updated every 1seconde with setInterval function.
I thought that remove(); would be enough be the memory leak is very quick when I monitor the app.
Any idea ?
EDIT:
TOPBARVIEW
define('topbarView', [
'backbone',
'parameterManager',
'text!views/topbarView/topbarTemplate.html',
'drupalidModel',
'weatherModel',
'refreshTime',
'dateParser'
], function(Backbone, ParameterManager, TopbarTemplate, DrupalidModel, WeatherModel, RefreshTime, DateParser) {
var TopbarView = Backbone.View.extend({
el: '#topbar',
template: _.template(TopbarTemplate),
events: {},
initialize: function() {
var self = this;
_.bindAll(this, 'render', 'startDateRefresh');
this.dateParser = new DateParser();
self.startDateRefresh();
setInterval(self.startDateRefresh, RefreshTime.date);
this.initWeatherModel();
},
render: function() {
var self = this;
var data = {
picto_url : ParameterManager.get('WEATHER_RESOURCE_URL') + ParameterManager.get('WEATHER_PICTO_CODE') + ".png",
date: self.date
}
this.$el.html(this.template({data: data}));
},
initWeatherModel: function() {
var self = this;
var weather_url = ParameterManager.get('WEATHER_URL');
if(weather_url === null) {
this.drupalidModel = new DrupalidModel();
this.drupalidModel.fetch({
success: function(model, response) {
var center_id_num = model.get('center_id_num');
ParameterManager.set('DRUPAL_CENTER_ID_NUM', center_id_num);
ParameterManager.constructWeatherUrl();
self.model = new WeatherModel();
self.listenTo(self.model,'change', self.render);
self.startModelRefresh();
},
error: function() {
console.log("Failed to fetch center id!");
}
})
} else {
this.model = new WeatherModel();
self.listenTo(self.model,'change', self.render);
this.startModelRefresh();
};
},
startModelRefresh: function() {
var self = this;
this.modelRefresh = function() {
self.model.fetch();
}.bind(this);
self.modelRefresh();
setInterval(self.modelRefresh, RefreshTime.weather);
},
stopModelRefresh: function() {
var self = this;
clearInterval( self.modelRefresh );
},
startDateRefresh: function() {
var self = this;
this.date = this.dateParser.classicDate();
this.render();
}
});
return TopbarView;
})
As fbynite suggested, your code which is supposed to clear the interval(s) is not correct, you should pass the interval id to clearInterval.
apart from that, you're not calling stopModelRefresh() at all. You should make sure all external references are properly removed before removing the view. For example I've added a destroy method that clears the interval before removing the view:
var TopbarView = Backbone.View.extend({
el: '#topbar',
template: _.template(TopbarTemplate),
events: {},
initialize: function() {
},
render: function() {
},
modelRefresh: function() {
this.model.fetch();
},
startModelRefresh: function() {
this.modelRefresh();
this.intervalId = setInterval(_.bind(this.modelRefresh,this), RefreshTime.weather);
},
stopModelRefresh: function() {
clearInterval(this.intervalId);
},
destroy: function() {
this.stopModelRefresh();
this.remove();
}
});
Now your GhostviewHunter should call it instead of directly calling remove:
GhostviewHunter.prototype.clean = function() {
_.each(this.currentViews, function(view) {
view.destroy();
});
this.currentViews.length = 0;
}
or you can even override the remove method itself to something like:
remove: function(){
this.stopThisInterval();
this.stopThatInterval();
this.cleanUpSomethingElse();
Backbone.View.prototype.remove.call(this);
}
and have the ghost thingy call remove itself.
Note that you have other interval calling startDateRefresh which you're not even attempting to clear... You should clear all such similarly.
And as a side note, I strongly suggest to stop spamming self = this where it is totally unnecessary for eg:
stopModelRefresh: function() {
var self = this;
clearInterval( self.modelRefresh );
// Why..? Nothing here changes the context?
},
and I also suggest recursively calling modelRefresh once the current fetch succeeds/fails rather than calling it from an interval where you have no guarantee that the previous fetch is complete

Backbone remove from collection not working

var Wine = Backbone.Model.extend({
winename: "Charles Shaw"
})
var Wines = Backbone.Collection.extend({
Model: Wine
})
var divElement = Backbone.View.extend({
initialize: function () {
this.render();
},
tagName: "ul",
render: function () {
$("#div1").empty();
$("#div1").append("<ul id='ulList'></ul>"),
wines.each(function (model) {
$("#ulList").append("<li>" + model.winename + "</li>");
});
return this;
}
});
var wine1 = new Wine();
wine1.winename = "wine1";
var wines = new Wines();
wines.add(wine1);
var wine2 = new Wine();
wine2.winename = "wine2";
wines.add(wine2);
function changewinename(model, winename) {
this.winename = winename;
}
var d = new divElement(wines);
wines.on("add", addwinename);
wines.on("remove", removewinename);
function addwinename(model, winename) {
d.initialize();
}
function removewinename(model, winename) {
d.initialize();
}
function AddWine() {
var winename = $("#wineName").val();
var wineFromUI = new Wine();
wineFromUI.winename = winename;
wines.add(wineFromUI);
$("#wineName").val("");
}
function changewinename(model, winename) {
this.winename = winename;
}
function RemoveWine() {
var wineValue = $("#wineName").val();
var wine1 = new Wine();
wine1.on({ "change:winename": changewinename });
wine1.set({ winename: wineValue });
alert(wine1.winename);
wines.remove(wine1);
$("#wineName").val("");
}
Collection remove for a model isn't working. Add works fine.
you need to add a function inside your collection like this:
var Wines = Backbone.Collection.extend({
Model: Wine,
removeElement: function(elements, options) {
return this.remove(elements, options);
}
})
and call it like this in your view:
wines.removeElement(wine1);

BackboneJS - Cannot call method 'on' of undefined

I have this simple BackboneJS app and it keeps returning this error on adding new model to collection: Cannot call method 'on' of undefined. Can someone help me. I can't see the problem in here.I have my templates defined in index.html, and I am using Slim framework and NotORM.
(function(){
window.App =
{
Models:{},
Collections: {},
Views : {}
}
window.template = function(id)
{
return _.template( jQuery('#' + id).html());
}
App.Models.Party = Backbone.Model.extend({
});
App.Collections.Partys = Backbone.Collection.extend({
model: App.Models.Party,
url: "http://localhost/BackboneJS/vjezba6/server/index.php/task"
});
App.Views.Party = Backbone.View.extend({
tagName :"div",
className: "box shadow aktivan",
template: template("boxovi"),
initialize: function()
{
this.model.on('change', this.render, this);
},
events:{
"click .izbrisi" : "izbrisi"
},
render: function()
{
var template = this.template( this.model.toJSON() );
this.$el.html(template);
return this;
},
izbrisi: function()
{
this.model.destroy();
},
ukloni: function()
{
this.remove();
}
});
App.Views.Partys = Backbone.View.extend({
tagName:"div",
id: "nosac-boxova",
initialize: function()
{
},
render: function() {
this.collection.each(this.addOne, this);
return this;
},
addOne: function(party) {
var partyView = new App.Views.Party({ model: party });
this.$el.append(partyView.render().el);
}
});
App.Views.Dodaj = Backbone.View.extend({
tagName: "div",
template : template("dodajTemp"),
events:
{
'submit' : 'submit'
},
submit : function(e)
{
e.preventDefault();
console.log(e);
var nazivPolje = $(e.currentTarget).find(".naziv").val();
var tekstPolje = $(e.currentTarget).find(".lokal").val();
var noviParty = new App.Views.Party({naziv:nazivPolje, tekst: tekstPolje});
this.collection.create(noviParty);
},
initialize: function()
{
},
render: function()
{
var template = this.template();
this.$el.html(template);
return this;
}
});
/* var kolekcijaPartya = new App.Collections.Partys([
{
naziv:"Ovo je prvi naziv",
tekst: "Ovo je prvi tekst"
},
{
naziv:"Ovo je drugi naziv",
tekst: "Ovo je drugi tekst"
}
]);*/
var kolekcijaPartya = new App.Collections.Partys;
kolekcijaPartya.fetch({
success: function()
{
var partysView = new App.Views.Partys({collection:kolekcijaPartya});
$("#content").prepend(partysView.render().el);
$("div.box").slideAj();
var dodajView = new App.Views.Dodaj({collection: kolekcijaPartya});
$("div#sidebar-right").html(dodajView.render().el);
}
});
})();
var noviParty = new App.Views.Party({naziv:nazivPolje, tekst: tekstPolje});
this.collection.create(noviParty);
so you are trying to add a View to your collection?

backbone-extend.js doesn't seem to load my method

I added this to my backbone-extend.js file which resides in the same folder as backbone-min.js...
_.extend(Backbone.View.prototype, {
getFormData: function(form) {
var unindexed_array = form.serializeArray();
var indexed_array = {};
$.map(unindexed_array, function(n, i){
indexed_array[n['name']] = n['value'];
});
return indexed_array;
}
});
However, when I call this.getFormData in my view code, I get a method not defined error. What am I missing? Thanks for your help!
Edit: Here is my view. I have to uncomment the getFormData method to make it work. It can't see the getFormData otherwise...
define([
'jquery',
'underscore',
'backbone',
'models/Member',
'text!templates/memberEditTemplate.html'
], function($, _, Backbone, Member, memberEditTemplate) {
var MemberEditView = Backbone.View.extend({
el: $("#page"),
model: 'member',
initialize: function(args) {
this.member = new Member({ id: args.id });
this.member.on('error', this.eventSyncError, this);
this.member.on('sync', this.eventSyncModelLoaded, this);
this.member.fetch();
},
events: {
"click #bttnMemberSave": "bttnClickMemberSave"
},
eventSyncError: function(model,response,options) {
console.log('Sync error='+response.statusText);
$('#server-message').css({'color':'red', 'font-weight':'bold'}).text(response.statusText);
//$('#server-message').text(response.statusText);
},
eventSyncModelLoaded: function(model,response,options) {
this.render();
},
eventSyncModelSaved: function(model,response,options) {
console.log("Member saved!");
$('#server-message').css({'color':'green', 'font-weight':'bold'}).text("Member saved!");
//$('#server-message').text('Member saved!');
var to = setTimeout(function() { Backbone.history.navigate('members', true); }, 2000);
},
bttnClickMemberSave: function() {
var data = this.getFormData($('#member-form').find('form'));
this.member.save(data, { success: this.eventSyncModelSaved });
},
// getFormData: function(form) {
// var unindexed_array = form.serializeArray();
// var indexed_array = {};
// $.map(unindexed_array, function(n, i){
// indexed_array[n['name']] = n['value'];
// });
// return indexed_array;
// },
render: function() {
this.member.toJSON();
var compiledTemplate = _.template( memberEditTemplate, { member: this.member } );
this.$el.html( compiledTemplate );
return this;
}
});
return MemberEditView;
});
Ok, I added backbone-extend.js to the RequireJS required files array in my app.js, now it's working.

Backbone/Marionette ItemView not rendering on model change

Already a couple of hours struggle trying to solve this...
Although the model gets fetched correctly and I can verify it as the view gets informed of the model's 'change' event, it just does not render.
At startup, the default model data ('Test Project'), is correctly displayed in the view, but after the model is refreshed, the view is not refreshed.
I tried to show a new view in the layout after model refresh but it did not change much...
Any idea or opinion about this ?
App.Project = function () {
var Project = {};
var ProjectModel = Backbone.Model.extend({
defaults:{
id: 0,
name: "Test Project",
intro: "",
desc: ""
},
initialize: function () {
// Flag fetch status to avoid multiple simultaneous calls
this.loading = false;
var self = this;
App.vent.on("project:display", function (_id) { self.fetchProject(_id); });
},
fetchProject: function (_id) {
if (this.loading)
return true;
this.loading = true;
var self = this;
var id = _id;
this.url = 'data.project_'+id+'.json';
this.fetch({
success: function (_data) {
self.loading = false;
},
error: function () {
self.loading = false;
}
});
}
});
Project.Details = new ProjectModel();
var Layout = Backbone.Marionette.Layout.extend({
template: "#project-layout",
regions: { details: "#project_details" }
});
Project.initializeLayout = function () {
Project.layout = new Layout();
App.content.show(App.Project.layout);
};
App.addInitializer(function () {
App.Project.initializeLayout();
});
Project.display = function () {
App.Project.Views.showDetails(Project.Details);
App.vent.trigger("project:display", 1);
}
return Project;
}();
App.Project.Views = function () {
var Views = {};
var DetailView = Backbone.Marionette.ItemView.extend({
template: "#project-details-template",
tagName: "div",
initialize: function () {
//this.listenTo(this.model, "change", this.render, this);
},
modelEvents: {
'change': "modelChanged"
},
modelChanged: function() {
console.log(this.model);
this.render();
}
});
Views.showDetails = function (_project) {
var projectView = new DetailView({model: _project});
App.Project.layout.details.show(projectView);
};
return Views;
}();
App.ProjectRouting = function () {
var ProjectRouting = {};
ProjectRouting.Router = Backbone.Marionette.AppRouter.extend({
initialize: function (_options) {
this.route('project/', "displayProject", _options.controller.display);
}
});
App.vent.on("project:display", function (_id) {
App.navigate("project/");
});
App.addInitializer(function (_options) {
ProjectRouting.router = new ProjectRouting.Router({
controller: App.Project
});
});
return ProjectRouting;
}();

Resources