Backbone.js Click event firing multiple times - backbone.js

Is there any forum/Issue tracking website for backbone.js ?
I have an Issue that click event triggers multiple times. I had found a work around using Underscore.js , debounce method.
Is the problem addressed in latest backbone.js ?
Please suggest me on this .
Raja K
define([
'jquery', 'underscore','backbone',], function($, _, Backbone,Marionette) {
var Sample = Backbone.Marionette.ItemView.extend({
template: 'sample/sample',
model : new Model(),
render: function() {
id = utils.getStorage('some_id');
if(parseInt(id) > 0 ) {
this.model.set({some_id:id});
this.rendersome(id);
} else {
data = new Model().toJSON();
this.renderdata(data);
}
},
events: {
"click #some" : "someinfo"
},
someinfo : function() {
var self = this;
$.ajax({
url: API_URL + "sample/sampleinfo",
type: 'POST',
crossDomain: true,
cache: true,
data: JSON.stringify({ 'code1': this.model.get('code1'),
'code2' : this.model.get('code2'),
"auth" : init.auth, "user_id" : init.user_id }),
contentType: 'application/json',
success: function(data,response,jqXHR) {
if('SUCCESS' == data._meta.status && data.records.message.me == 'positive') {
self.model.set(data.records);
self.renderdata(data.records);
} else {
console.log(data.records.message);
return false;
}
},
error: function (request, status, error) {
console.log(request);
}
});
},
change: function (event) {
var target = event.target;
var change = {};
change[target.name] = target.value;
this.model.set(change);
},
});
return Sample;
});
Router Init code below,
routeaction : function() {
var Header = new HeaderView({'el': '#header'});
Header.render();
var test = new Tview({'el': '#content'});
test.render();
}
UPDATE : I am using backbone.subroute and the view got destroyed but not rendering after that. Becuase both old and current object referring the same element. But why it is not rendering again ? Can you please suggest me what I am missing here ?
render: function () {
// remove the existing header object
if(typeof gheader == "object") gheader.close();
// render the object
self.$el.html(tmpl(data));
},
UPDATE : I guess view.remove() is working fine. I have reused the container at $el and view.remove() removed the container element and stopped rendering the other views. Should I recreate the container ?
I can use "tagName" but suggest me how do I apply the stylesheet ?

undelegateEvents() Removes all of the view's delegated events. Useful if you want to disable or remove a view from
the DOM temporarily.
http://backbonejs.org/#View-undelegateEvents

Related

Refreshing FullCalendar upon saving events with Angular Resource $save

I'm building a calendar inspired by this example http://plnkr.co/edit/pIDltQRV6TQGD4KQYnj7?p=preview and adding $save to add new events using RESTful server connection.
I'm trying to find a way to make the calendar show the new events when they are $saved without manually refreshing the browser.
My attempt to get this to work was to add (or remove) the event data to the event array (gDataService.events). Although it does change the content of the array, the change is not shown in the calendar. (e.g., if I change the date of the event, the event won't move to the new date.)
Can someone point me in the right direction? Thanks!
HTML
<div ui-calendar="uiConfig.calendar" class="span8 calendar" ng-model="eventSources"></div>
Controller1 ... This saves new event.
$scope.ok = function () {
$scope.entry = new calFactory();
$scope.entry.data = data
$scope.entry.$save( function(){
// data saved. do something here.
toaster.pop('success','Message','Update successfully completed.');
});
};
Controller2 ... Main controller that defines eventSource
myApp.controller("MainCtrl", function($scope,$compile,uiCalendarConfig, calFactory,eventFactory, gDataService) {
gDataService.events = function(start, end, callback) {
var d = new Date(start);
var events;
events = calFactory.query({
start: start,
end: end
});
events.$promise.then(function(value){
gDataService.events = events;
//have to call the callback as well to keep the calendar happy
callback(gDataService.events);
$scope.statusTxt = $scope.statusTxt + " ... Event loading completed at " + moment(new Date()).format("HH:mm:ss");
}
);
};
/* event sources array*/
$scope.eventSources = [gDataService.events]; /*, $scope.eventSource, $scope.eventsF*/
})
Factory
myApp.factory("calFactory",['$resource','$filter', function($resource, $filter) {
return $resource("/griddata/", {}, {
get: {
method: 'GET'
},
save: {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
transformRequest: function(data, headersGetter) {
data = angular.toJson(data);
data = $.parseJSON(data);
return $.param(data.data);
}
}
});
}]);
gDataService ... This stores event data and make it available to other parts of the program
myApp.factory("gDataService", function ($rootScope, calFactory) {
var service = {};
service.events = [];
service.addData = function(object, no_broadcast) {
this.events.push({
__id: object.task_id, title: object.task, start: object.duedates,
backgroundColor: bColor, textColor: bTextColor, borderColor: bColor
});
if (!no_broadcast) {$rootScope.$broadcast("gDataUpdated")};
};
service.removeData = function(task_id, no_broadcast) {
var arr_index = _.findIndex(this.events, {'__id': task_id});
this.events.splice(arr_index, 1);
if (!no_broadcast) {$rootScope.$broadcast("gDataUpdated")};
};
return service;
});
Nobody would answer to this much sophisticated question.
You have some typos:
Missed closing with ";", "]"...
What's inside 'function calFactory'? What is '$broadcast'?
Why do you put '$' leading the JavaScript object? Is this meaning "private var"?
"if (!no_broadcast) ..." is not coding, but commenting.
In "$scope.entry.$save( function(){",
Why entry doesn't have '$', but scope and save have it?
Here is the answer from chris-rock https://github.com/angular-ui/ui-calendar/issues/200.
Changed scope.init in calendar.js like this:
scope.init = function(){
calendar.fullCalendar(options);
window.calendar = calendar; /// This is the key
};
Now I can add or remove events dynamically using window.calendar.fullCalendar('removeEvents' or 'renderEvent')!!
Here is how I changed my code.
gDataService
service.addData = function(object, no_broadcast) {
//add additional project
this.added_event = {
__id: object.task_id, title: object.task, start: object.duedates,
backgroundColor: bColor, textColor: bTextColor, borderColor: bColor
};
this.events.push(this.added_event);
if (!no_broadcast) {$rootScope.$broadcast("gDataUpdated")};
};
service.removeData = function(_id, no_broadcast) {
var arr_index = _.findIndex(this.events, {'_id': _id});
this.delete_id = _id;
this.events.splice(arr_index, 1);
if (!no_broadcast) {$rootScope.$broadcast("gDataUpdated")};
};
return service;
Controller
$scope.$on('gDataUpdated', function(){
if (gDataService.delete_id) {
window.calendar.fullCalendar('removeEvents',gDataService.delete_id); // This removes this event from the calendar
gDataService.delete_id = null;
};
if (gDataService.added_event) {
window.calendar.fullCalendar('renderEvent',gDataService.added_event,false); // This adds this event to the calendar
gDataService.added_event = null;
};

Backbone passing a model to Router

I'm developing an application for android using require js and Backbone. I have to pass a model taken from a collection via touchend event to the router. How can I do it?
define(["jquery", "underscore","backbone","handlebars", "views/CinemaView", "models/CineDati", "text!templates/listacinema.html"],
function($,_,Backbone,Handlebars,CinemaView, CineDati, template){
var ListaCinemaView = Backbone.View.extend({
template: Handlebars.compile(template),
events: {
"touchend" : "Details"
},
initialize : function (){
var self = this;
$.ajax({
url: 'http://www.cineworld.com/api/quickbook/cinemas',
type: 'GET',
data: {key: 'BwKR7b2D'},
dataType: 'jsonp', // Setting this data type will add the callback parameter for you
success: function (response, status) {
// Check for errors from the server
if (response.errors) {
$.each(response.errors, function() {
alert('An error occurred. Please Try Again');
});
} else {
$.each(response.cinemas, function() {
var cinema = new CineDati();
cinema.set({ id : this.id, name : this.name , cinema_url : this.cinema_url, address: this.address, postcode : this.postcode , telephone : this.telephone });
self.model.add([cinema]);
});
self.render();
}}
});
},
events : {
"#touchend" : Dettagli
},
render : function(){
$(this.el).empty();
$(this.el).html(template).append(
_.each(this.model.models, function (cinema) {
$("#lista").append(new CinemaView({
model: cinema
}).render().el); }, this));
return this;
},
Dettagli : function(){
Backbone.history.navigate( this.model , {trigger: "true"});
}
});
return ListaCinemaView;
});
You need to override the Backbone's navigate function as following:
var Router = Backbone.Router.extend({
routeParams: {},
routes: {
"home": "onHomeRoute"
},
/*
*Override navigate function
*#param {String} route The route hash
*#param {PlainObject} options The Options for navigate functions.
* You can send a extra property "params" to pass your parameter as following:
* {
* params: 'data'
* }
**/
navigate: function(route, options) {
var routeOption = {
trigger: true
},
params = (options && options.params) ? options.params : null;
$.extend(routeOption, options);
delete routeOption.params;
//set the params for the route
this.param(route, params);
Backbone.Router.prototype.navigate(route, routeOption);
},
/*
*Get or set parameters for a route fragment
*#param {String} fragment Exact route hash. for example:
* If you have route for 'profile/:id', then to get set param
* you need to send the fragment 'profile/1' or 'profile/2'
*#param {Any Type} params The parameter you to set for the route
*#return param value for that parameter.
**/
param: function(fragment, params) {
var matchedRoute;
_.any(Backbone.history.handlers, function(handler) {
if (handler.route.test(fragment)) {
matchedRoute = handler.route;
}
});
if (params !== undefined) {
this.routeParams[fragment] = params;
}
return this.routeParams[fragment];
},
/*
* Called when hash changes to home route
**/
onHomeRoute: function() {
console.log("param =", this.param("home"));
}
})
Here I have written a custom function "param" for doing the get/set of parameters you want to send.
Now to send a custom parameter you can send it like the following:
var router = new Router();
Backbone.history.start({});
router.navigate("home", {
params: 'Your data'
});
To retrieve the data in get data inside the callback function you can write code like this:
this.param("home"); //this will return the parameter you set while calling navigate function or else will return null
Router.navigate() doesn't pass any data. It sets the url fragment and takes a couple options. See the docs here: http://backbonejs.org/#Router-navigate
My suggestion:
Use Router.navigate() to change the URL.
Use the Backbone.Events aggregator to trigger (or publish) your event and data.
So say you have a list of movies, and you have a view button. The view button publishes the model it wants shown and changes the URL fragment.
var vent = _.extend( {}, Backbone.Events ); // Create your app specific event aggregator
var ListaCinemaView = Backbone.View.extend({
...
Dettagli : function(){
vent.trigger('movie:show:request', this.model);
Backbone.history.navigate( this.model.get('id') );
}
}
Somewhere else in your app add a handler for movie:view:request.
vent.on('movie:show:request', showMovieDetails);
var showMovieDetails = function(model) { ... }
Lastly, check out MarrionetteJS. It uses the publish/subscribe pattern to handle communication between parts of an app. It's a really nice framework since you basically opt-in to the parts that you want to use. It's very well documented and supported. Also, its creator, Derick Bailey, is very active on Stackoverflow, so you'll get help quick.

Auto update backbone collection

I'm having a problem re rendering a simple collection in backbone, the render event is never fired from the listeners... I'm not sure of where is the mistake, could please someone help me?
File with models:
window.MetricDevice = Backbone.Model.extend({
defaults: {
ip: null,
framesReceived: null,
framesOutOfOrder: null,
framesLost: null
}
});
window.MetricDevicesCollection = Backbone.Collection.extend({
model: MetricDevice,
value: null,
url: function(){
return hackBase + "/wm/iptv/metric/devices/json";
},
initialize:function () {
this.fetch({ reset: true });
console.log("data fetched");
},
});
Render page:
window.MetricItemView = Backbone.View.extend({
events: {
"click input[type=button]" : "removeDevice",
},
initialize:function(){
this.template = _.template(tpl.get('metric-devices-item'));
this.render();
},
removeDevice:function(){
$.ajax({
url:hackBase + '/wm/iptv/metric/disable/' + this.model.get("ip") + '/0/json',
dataType:"json",
success:function (data) {
if ( data.return == 1 ){
alert(data.error);
}else{
alert("Metric disabled in " + this.model.get("ip"));
}
},
});
},
render:function(){
var ip = this.model.get("ip");
console.log("rendering item in view " + ip);
},
});
window.MetricView = Backbone.View.extend({
events: {
"click input[type=button]" : "add",
"click input[type=img]" : "updateAll",
},
clicked:function(e){
},
updateAll:function(e){
this.render();
},
initialize:function () {
this.template = _.template(tpl.get('metric-devices-list'));
this.model.bind("change", function(){
console.log("metricView data change detected");
this.render();
});
this.model.bind("reset", this.render());
},
add:function(e){
if($(e.currentTarget).attr("name") == "add" ){
var ip = document.getElementById('vaddress').value;
var threshold = document.getElementById('vthreshold').value;
$.ajax({
url:hackBase + '/wm/iptv/metric/enable/' + ip + '/' + threshold + '/json',
dataType:"json",
success:function (data) {
if ( data.return == 1 ){
alert(data.error);
}else{
alert("Metric enabled in device");
}
},
});
}else if($(e.currentTarget).attr("name") == "cancel"){
document.getElementById('vaddress').value = "";
document.getElementById('vthreshold').value = "";
}
},
render:function (eventName) {
$(this.el).html(this.template());
var list = $(this.el).find('#tableData');
console.log("On render!");
var subviews = [];
console.log("looping on models");
_.each(this.model.models, function (sw) {
console.log("model loop " + sw.get("ip"));
var m = new MetricItemView({model:sw, tagName: 'tbody', el: $(this.el).find('#tableData')});
list.append(m.template(sw.toJSON()));
}, this);
return this;
},
});
The problem is that the render method in MetricView is called just when the page is loaded for the first time, and after this I've the impression that the JSON stay cached, and the content just change if I close the browser clean the cache and run again...
The console output is:
On render! metricView.js:86
looping on models metricView.js:88
model loop 10.0.0.1 metricView.js:92
rendering item in view 10.0.0.1 metricView.js:26
And I'm instantiating MetricView like this
var metricdevices = new MetricDevicesCollection();
$('#content').html(new MetricView({model:metricdevices}).render().el);
Am i forgetting something?
The problem you are facing is that you are not using Backbone models and collection at their full potential. When you are calling manual $.ajax(...), no Backbone event will fire. Here are a couple suggestions that would make your code integrate with Backbone.
First, you should instantiate your view with the proper reserved keyword: collection
var metricdevices = new MetricDevicesCollection();
$('#content').html(new MetricView({ collection : metricdevices }).render().el);
Backbone events in collection are intended to work with precise REST API practices. A Model that belongs to a Collection will inherit it's url parameter. It expects your REST API to map to the following scheme:
model.save() --> model.id is present ? PUT collection.url/model.id : POST collection.url
model.delete() --> DELETE collection.url/model.id
model.fetch() --> GET collection.url/model.id
The idea is that you can manipulate models individually and use collection to fetch all the relevant models when needed. Your API does not seem to be adapted for that kind of workflow.
A monky patch that would keep your data updated is to trigger a fetch of the collection when an operation succeeds.
var collection = this.collection;
$.ajax({
//...
success: {
collection.fetch();
}
}
Since you are already listening to the Backbone events, it will trigger the reset event and render the view. Note that this is not a good way of doing things. What you should do is refactor your server access points to conform to standard good REST practice if you have access to it. If you don't, use proper Object Oriented patterns and implement model behavior in the model. For example:
window.MetricDevice = Backbone.Model.extend({
defaults: {
ip: null,
framesReceived: null,
framesOutOfOrder: null,
framesLost: null
},
enable : function() {
var device = this,
ip = this.ip,
treshold = this.treshold;
$.ajax({
url:hackBase + '/wm/iptv/metric/enable/' + ip + '/' + threshold + '/json',
dataType:"json",
success:function (data) {
if ( data.return == 1 ){
alert(data.error);
} else {
device.trigger('change');
alert("Metric enabled in device");
}
}
});
return this;
}
});
And then you can properly call the object from a view:
var ip = document.getElementById('vaddress').value;
var threshold = document.getElementById('vthreshold').value;
var metricDevice = new MetricDevice({ ip : ip, treshold : treshold });
this.collection.add(metricDevice.enable());

Fetching items with Backbone seems to ignore the id passed in

I have these routes in my webservice and I can hit either of them directly through the browser and I return the correct value.
app.get('/repairs', repair.findAll);
app.get('/repairs/:id', repair.findById);
When I ask Backbone to do this I am unexpectedly getting a call to
app.get('/repairs', repair.findAll);
when I expect it to reach
app.get('/repairs/:id', repair.findById);
The piece of code that appears to be calling "/repairs" rather than "/repairs/:id" is
var EditRepair = Backbone.View.extend({
el : '.page',
render : function(options) {
var scope = this;
var repair = new Repair({id: options.id});
//This has the correct id
console.log(options.id);
//I would expect this to call /repairs/12344312
//However it calls /repairs
repair.fetch({
success : function(repair){
var template = _.template($('#edit-repair-template').html(), {repair : repair});
scope.$el.html(template);
}
});
}
});
var Repair = Backbone.Model.extend({
urlRoot : 'repairs'
});
var Router = Backbone.Router.extend({
routes: {
'edit/:id' : 'editRepair'
}
});
var editRepair = new EditRepair();
var router = new Router();
router.on('route:editRepair', function(id) {
console.log('Edit Id : ' + id);
editRepair.render({id:id});
});
The options.id can be console.logged and shows the correct id of the item. I've had a few issues so far with the difference between _id in mongodb and id in backbone which I have worked around but for the life of me I cannot see why this is issuing a call to repairs and not repairs/id.
Any help appreciated.
My fault, I had an ajax prefilter that was encoding the uri components.
This was messing up the requests being issued.
$.ajaxPrefilter( function( options, originalOptions, jqXHR ) {
options.url = "http://localhost:3000/" + encodeURIComponent( options.url );
console.log(options.url);
});
Changed to
$.ajaxPrefilter( function( options, originalOptions, jqXHR ) {
options.url = "http://localhost:3000/" + options.url;
console.log(options.url);
});

Backbone on('add') not being called

I defined the add handler on my collection:
var BookmarksCollection = Backbone.Collection.extend({
initialize: function() {
this.on('add', function(obj) {
console.log(obj.get('title') + ' added');
});
},
url: '/bookmarks',
model: Bookmark
});
And it's being called from one of my views:
var AddView = Backbone.View.extend({
el: $('#entry'),
events: {
'click #submit': 'submitHandler'
},
submitHandler: function(event) {
if($('input').val()) {
var newBookmark = new Bookmark;
newBookmark.set({url: $('input').val()});
console.log('Save!');
newBookmark.save({
success: function(newBookmark) {
console.log('Success!');
myBookmarks.add(newBookmark);
myBookmarks.trigger('add', newBookmark);
}
});
}
}
});
However the add handler on my collection is never triggered. In fact, not even the success function in the view seems to be called.
But the newBookmark gets saved anyway, so no errors happening.
What am I missing?
NodeJS respond with 201 (Created) when a new bookmark is posted.
Maybe backbone expects a 200 (OK) to trigger success?
EDIT: I tried triggering the event from outside the success function and it works.
How can I tweak backbone to react to a 201 (Created)?
I think the issue is that he first argument of save should be the data you are trying to save. The second is the options which can include the success and error callbacks.
newBookmark.save({dataiwanttosave: "hi"}, {
success: function(newBookmark) {
console.log('Success!');
myBookmarks.add(newBookmark);
myBookmarks.trigger('add', newBookmark);
}
});

Resources