How to delete a dependent model from the same collection in backbone.js - backbone.js

I have a model which has both navid and subnavid .While destroying a model i need to check in the entire collection , for other models which have navid as same as subnavid of the model i'am trying to delete . Please help me out . Thanks in advance . Heregoes my sample code.
Model:
var Node = Backbone.Model.extend({
defaults: {
NavId: '',
SubNavId: ''.
ItemName:''
} }
Collection:
var NodeCollection = Backbone.Collection.extend({ model:Node }
And i have two view one for the Node(i am building tr) and other for
the collection(I need to build table) var NodeCollectionView =
Backbone.View.extend({
initialize: function (options) {
var self = this; self.collection = new NodeCollection({ NavigationId: options.NavigationId });
self.collection.fetch({
success: function () {
/*I am getting hte proper collection from my restful api and iam able to bind it properly
self.render();
}
});
},
render: function () {
var that = this;
_.each(this.collection.models, function (item) {
that.RenderEachNode(item);
}, this);
},
RenderEachNode: function (item) {
var TempJsonNode = item.toJSON();
var self = this;
var nodeView = new NodeView({
tagName: 'tr',
id: 'NavId_' + TempJsonNode.NavItemId,
model: item
});
} });
var ItemTemplate = ""; ItemTemplate += " <td>"; ItemTemplate += " <a><%= ItemName %></a>"; ItemTemplate +=" </td>"; ItemTemplate
+=" <td>"; ItemTemplate +=" <a href='#' original-title='Delete ' class='tip_north Delete'>X</a>"; ItemTemplate +=" </td> ";
var NavigationItemView = Backbone.View.extend({
template: ItemTemplate,
render: function () {
var self = this;
var tmpl = _.template(this.template);
this.$el.html(tmpl(this.model.toJSON()));
return this;
},
events: {
"click .Delete": "DeleteBtnClick"
},
DeleteBtnClick: function () {
var self = this;
self.model.destroy({
success: function (status, data) {
var RetData = JSON.parse(data);
if (RetData.Status == 'Success') {
$(self.el).remove()
}
},
error: function () {
alert('Error In Deleting The Record');
}
});
return false;
} });
I am able to build the table properly but while destroying a model , i am not figuring out a way to destroy the dependent models.My Api is restricted in such a way that i cannot get a nested json ( if so i would have done with backbone relation). so i need to figure out some way that the other models and views which has the NavId of the model am deleting.
Please help me out.

How about something like:
var NodeView = Backbone.View.extend({
initialize: function() {
//when the model gets destroyed, remove the view
this.listenTo(this.model, 'destroy', this.remove);
},
//..clip
DeleteBtnClick: function () {
var self = this;
var collection = self.model.collection;
var navId = self.model.get('NavId');
self.model.destroy({
success: function (status, data) {
var RetData = JSON.parse(data);
if (RetData.Status == 'Success') {
//if parent was part of a collection
if (collection) {
//find related models
var related = collection.filter(function (model) {
return model.get('SubNavId') === navId;
});
//call destroy for each related model.
var promises = _.invoke(related, 'destroy');
//optional: if you want to do something when all the children
//are destroyed:
$.when.apply($, promises).then(function () {
console.log('all destroyed');
});
}
}
},
error: function () {
console.log(arguments);
alert('Error In Deleting The Record');
}
});
return false;
}
});
Edit: JSFiddle here

Related

Fetch data did not render in template

This is one of question from a Backbone newbie.
So, I was trying to use this.model.bind and this.model.on('change', this.render), but it could not work for my model. I checked my console & my model function getStoreByName. This is returning an array, but render function is rendering before the fetch array, that's why I want to bind model to view when model change.
Here is how far I have gotten so far.
This is Backbone view:
var storeTemplate = Backbone.View.extend({
initialize: function () {
console.log('view inti is running');
this.template = template;
this.model = new storeModel();
this.model.getStoreByName();
this.stores = this.model.get('stores');
this.model.on('change', this.render);
console.log(this.stores);
console.log('ready to call this.render()');
this.render();
console.log('end to call this.render()');
console.log('view init end');
},
render: function () {
console.log('render is start');
this.logger.log(Logger.LOG_LEVELS.TRACE, "Entering render.");
console.log(this.model.stores);
this.$el.html(_.template(this.template, { stores: this.model.get('stores') }));
return this;
}
});
return storesTemplate;
});
and this is my Backbone Model
var store= Backbone.Model.extend({
initialize: function () {
console.log('init models is running');
this.stores = [];
this.set({ 'stores': this.stores});
console.log(this.get('stores'));
this.service = Service;
},
getStoreByName: function () {
console.log('getting store');
stores = [];
this.service.getStoreByName(function (xml) {
$(xml).find("element").each(
function () {
var store = {
"storeID": $(this).find("ID").text(),
"storeType": $(this).find("Type").text(),
"storeName": $(this).find("Name").text(),
};
if (xml !== null) {
stores.push(store);
}
else {
this.model.set({ stores: [] });
}
}
);
that.set('stores', store)
},
);
},
})
return store;
});
Try this.listenTo(this.model,'change:stores', this.render);
If that doesn't work use promises. Update model like this:
getStoreByName: function() {
var deferred = $.Deferred();
var stores = [];
return this.service.getStoreByName(function(xml) {
if (xml === null) {
return Deferred.resolve([]);
}
$(xml).find("element").each(function() {
var store = {
"storeID": $(this).find("ID").text(),
"storeType": $(this).find("Type").text(),
"storeName": $(this).find("Name").text(),
};
stores.push(store);
});
return Deferred.resolve(stores);
});
this.model.set('stores', stores);
return deferred.promise();
}
and in the view you can do this.model.getStoreByName().then(this.render);

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

Adding model to a collection after save method in backbone

I am using the save method when my data is submitted. On success callback of the save method, the collection should be updated with the model which i have saved since i want to get the id of the model from my server. My code is as below
var app = app || {};
app.AllDoneView = Backbone.View.extend({
el: '#frmAddDone',
events:{
'click #addDone':'addDone'
},
addDone: function(e ) {
e.preventDefault();
var formData = {
doneHeading: this.$("#doneHeading").val(),
doneDescription: this.$("#doneDescription").val(),
};
var donemodel = new app.Done();
donemodel.save(formData,
{
success :function(data){
/*my problem is here how do i listen to collection event add that has been
instantiated in intialize property to call renderDone . My tried code is
var donecollection = new app.AllDone();
donecollection.add(donemodel);
and my response from server is
[{id:145, doneHeading:heading , doneDescription:description,
submission_date:2014-08-27 03:20:12}]
*/
},
error: function(data){
console.log('error');
},
});
},
initialize: function() {
this.collection = new app.AllDone();
this.collection.fetch({
error: function () {
console.log("error!!");
},
success: function (collection) {
console.log("no error");
}
});
this.listenTo( this.collection, 'add', this.renderDone );
},
renderDone: function( item ) {
var doneView = new app.DoneView({
model: item
});
this.$el.append( doneView.render().el );
}
});
Collection is
var app = app || {};
app.AllDone = Backbone.Collection.extend({
url: './api',
model: app.Done,
});
Model is
var app = app || {};
app.Done = Backbone.Model.extend({
url: "./insert_done",
});
View is
var app = app || {};
app.DoneView = Backbone.View.extend({
template: _.template( $( '#doneTemplate' ).html() ),
render: function() {
function
this.$el.html( this.template( this.model.attributes ) );
return this;
}
});
In your success callback you create an entirely new collection, which doesn't have any listeners registered. This is the reason why the renderDone isn't triggered.
The model you receive from the server should be added to the collection which is attached directly to your view, this.collection:
var self = this,
donemodel = new app.Done();
donemodel.save(formData, {
success :function(data){
// this is the collection you created in initialize
self.collection.add(donemodel);
},
error: function(data){
console.log('error');
}
});

Backbone.js Fuse.js render filtered collection

Im trying to add a fuzzy search feature to filter objects in a collection. The console is showing that the Fuse is working correctly and returning the correct objects. Now the question is how do I pass the filtered collection to my view to be rendered.
Here is the collection:
define(["jquery", "backbone", "models/MachineModel"],
function($, Backbone, Model) {
var MachineCollection = Backbone.Collection.extend({
model: Model,
url: '/api/machines',
searchablefields: ['name', 'type', 'ips', 'dataset', 'cpus', 'datacenter', 'state'],
rebuildIndex: function(options) {
var _ref;
if (options == null) {
options = {
keys: this.searchablefields
};
}
return this._fuse = new Fuse(_.pluck(this.models, 'attributes'), options);
},
search: function(query) {
this.rebuildIndex();
var result = this._fuse.search(query);
console.log(result);
this.trigger('reset');
}
});
return MachineCollection;
});
and here is my view
define(["jquery", "backbone", "views/cloud/machines/SingleMachineView", "text!templates/cloud/machines/allMachines.html"],
function($, Backbone, SingleMachineView, template){
var AllMachinesView = Backbone.View.extend({
el: "#magic",
initialize: function() {
// Calls the view's render method
this.collection.on('add', this.addMachine, this);
this.collection.on('reset', this.onCollectionReset, this);
this.render();
},
// View Event Handlers
events: {
'keyup #filter': 'fuzzySearch'
},
// SUBVIEWS
// ========
onCollectionReset: function(collection) {
console.log('collection reset');
var that = this;
$(collection).each(function (model) {
that.addMachine(model);
});
},
addMachine: function(model) {
var machineHTML = (new SingleMachineView({ model: model })).render().el;
$(machineHTML).prependTo('#machine-container');
},
// FUZZY SEARCH
// ============
fuzzySearch: function(e) {
var query = $(e.target).val();
this.collection.search(query);
},
// RENDER
// ======
render: function() {
this.template = _.template(template);
this.$el.html(this.template);
return this;
}
});
return AllMachinesView;
});
any insight would be greatly appreciated.

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