I have a backbone/backgrid editable grid, and everytime I edit a cell the "change" event is fired up twice and I end up making two separate PUT requests.
I know that this happens because the "change" event fires once when I edit it in the cell, and another when the data comes back from the server; and that the behaviour could be avoided by passing {wait: true} to the save method, but I don't know where I need to overload it.
My model declaration is like this:
var Redirects = Backbone.Model.extend({
urlRoot: '/global/redirects',
initialize: function () {
Backbone.Model.prototype.initialize.apply(this, arguments);
this.on("change", function (model, options) {
if (options && options.save === false) return;
model.save({
error: alertMe
});
});
this.on('fetch request', function (e) {
loadingOn(e);
});
this.on('sync error', function (e) {
loadingOff(e);
});
this.on('error', function(e, resp){
alertMe(e, resp);
});
}
});
You shouldn't receive a 2nd change event when a Model is synced from the server, regardless of the wait option.
The problem in your save call is that you haven't specified the attributes hash, i.e. the first parameter. If you don't have any attributes to modify, which considering you are firing save for a change event, that's probably the case, you will need the following:
this.on("change", function (model, options) {
if (options && options.save === false) return;
model.save(null, {error: alertMe});
});
What was actually happening was that you were setting error as an attribute on the model, and that triggered the change event.
Related
$(".getDetails").click(function() {
// some stuff like fetching response from server
})
when user clicks getDetails button on UI multiple times within fraction of second , jquery generates two calls for click function and my logic fails.
I think solution to this will be to disable the button on first click itself(so that use can't click multiple times). Once i get the response or just before returning
from click method i make it enable. Is there any better solution ?
If no, how can i make button disable as soon as user click button first time. I think it needs to be done before calling click method or some where in html element ?
Java provides synchronized keyword so that only one thread enters at time inside method , i am not sure is similar thing exist in javascript or not ?
Assuming the click handler executes an AJAX request you can set the button as disabled before making the request, then enable it again once the request completes. Try this:
$(".getDetails").click(function(){}
var $btn = $(this).prop('disabled', true);
$.ajax({
url: '/foo'
success: function() {
console.log('It worked!');
},
error: function() {
console.log('It failed!');
},
complete: function() {
$btn.prop('disabled', false);
}
});
});
you can try unbinding click event and after ajax call again bind click to that class
$(".getDetails").click(function(){}
$(".getDetails").unbind('click');
// some stuff like fetching response from server
)
You can use simple flag to prevent firing your logic multiple times:
var flag = true
$(".getDetails").click(function() {
if (flag) {
flag = false;
//your logic...
//when your code ends (in after-AJAX callback for example)
flag = true;
}
});
$(".getDetails").click(function(e){
var $target = $(e.currentTarget);
// assuming the click listener is on the button
$target.prop('disabled',true);
// request, stuff...and when done:
$target.prop('disabled',false);
})
try Prevent Default and return false to avoid any other event propagation
This is solution is like semaphore or monitor
var progress = false;
$(".getDetails").on('click', function(e) {
if(!progress){
progress = true;
// some stuff like fetching response from server
//also after sucessfull fetch make true to false again
}else{
console.log('something in progress');
}
e.preventDefault();
return false;
})
This should make sure that your button will not fire the async request twice, until you have a response.
function doAjaxReq() {
/*
Add your ajax operation here
as a return value of doAjaxReq
like so:
return $.ajax({
url: '/foo',
type: 'POST',
data: data
})
Since i can't use ajax here let's smilulate
it useing a promise.
*/
promise = new Promise(function(res, rej) {
setTimeout(function(){
res({foo: "bar"});
}, 1000)
})
return promise;
}
/*
Inside here you add the click handlder
only once use `elem.one('click'...`
*/
function addClickHandler(elem) {
elem.one('click', function() {
// do your ajax request and when its
// done run `addClickHanlder` again
// i'm using `.then` because of the promise,
// you should be using `.done`.
doAjaxReq().then(function(data) {
console.log(data);
addClickHandler(elem);
});
})
}
addClickHandler($(".getDetails"));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class="getDetails">Get Details</button>
I tried to create a simple solution for ordering problem in async calls on a project I'm working on.
The best solution I found was this:
I attach an event that check if the pre-requirements are done, if so I remove the event listener and perform the function.
Each function calls the event once its done and that way everyone will be waiting until the can run.
This is the code:
$(function() {
$(document).on('bmadone',function() {
if(beepmeapp.done_arr['fb-init']){
$(document).off('bmadone','#bma-root',this);
getBMAUserRoutes();
}
});
});
The function (for the test) is doing this:
function getBMAUserRoutes(){
$.ajax({
url : '/bma/users/fb',
type : 'GET',
data : {
access_token: 'abc'
},
success : function( data ) {
console.log('getBMAUser: success');
beepmeapp.done_arr['user-init'] = true;
$('#bma-root').trigger('bmadone');
},
error : function( xhr, err ) {
console.log('getBMAUser: error');
}
});
}
It works great but the problem I have is that it gets into a loop that never ends.
Don't know why but it looks like:
$(document).off('bmadone','#bma-root',this);
Doesn't remove the listener...
I'm a true newbie in JS / Jquery and all of this client side development so I guess I'm probably missing something basic.
You have to pass the same (sub)set of parameters to .off that you passed to .on. I.e. in your case it would be $(document).off('bmadone').
You never passed '#bma-root' to .on and this does not refer to the event handler (the function you pass to .on), so by passing those you won't remove the correct event handler.
If you need to remove only that specific handler, you can use a named function instead:
function handler() {
if(beepmeapp.done_arr['fb-init']){
$(document).off('bmadone', handler);
getBMAUserRoutes();
}
}
$(document).on('bmadone', handler);
// or as a named function expression
$(document).on('bmadone', function handler() {
if(beepmeapp.done_arr['fb-init']){
$(document).off('bmadone', handler);
getBMAUserRoutes();
}
});
or use namespaced events.
What I am trying
On Save or Delete of my form a simple notification is poped-up to user
What I have done
events :{
'#save-button click' : 'onSaveBUttonClick',
'#delete-button click' : 'onDeleteButtonClick'
};
onDeleteButtonClick = function(){
//popup appears to confirm delete
this.model.on('sync',function(model){
model.off('sync');
alert("project deleted");
},this);
this.model.destroy();
}
onSaveBUttonClick = function(){
//popup appears to confirm delete
this.model.on('sync',function(){
model.off('sync');
alert("project Saved");
},this);
this.model.save();
}
The problem
I click on the delete button and say , select , cancel. Here the model.on('sync') is bound to the model.
Now when I click save , and confirm , the model.on('sync') is called twice (one bound by delete button and one bound by save button).
So I am getting 2 pop-ups Project deleted first and project saved after it.
How can I avoid this?
You can use the success options in model.save and model.destroy
destroy model.destroy([options])
Destroys the model on the server by delegating an HTTP DELETE request to Backbone.sync. Returns
a jqXHR object, or false if the model isNew. Accepts success and error
callbacks in the options hash.
save model.save([attributes], [options])
[...]
save accepts success and error callbacks in the options hash.
Your methods could look like
onDeleteButtonClick = function(){
this.model.destroy({
success: function() {
alert("project deleted");
}
});
}
onSaveBUttonClick = function(){
this.model.save(null, {
success: function() {
alert("project saved");
}
});
}
I am trying to use template.find to make my life easier.
But in the javascript console I get: undefined is not a function
Here's what I have. It is getting tripped up on template.find(...)
Template.superuserHUD.events =
{
'click input.new_device': function (template) {
var id = template.find(".new_device_id").value;
Device_IPs.insert({ID: id, IP: "Not Connected"});
}
}
Any ideas?
The Event handler function receives two arguments: event, an object with information about the event, and template, a template instance for the template where the handler is defined.
The second parameter is optional, but it needs to be received in handler when you want to use template instance functions like find(), findAll(), firstNode() and lastNode().
So, to use the template.find() in your event handler, you need to pass both the arguments as:
'click input.new_device': function (event, template) {
// your event handling code
}
use a second arugment please like
Template.superuserHUD.events
'click input.new_device': (event, template) ->
options =
id: template.find('.new_device_id').value
IP: 'Not Connected'
Device_IPs.insert options
and sometimes use template itself like
Template.superuserHUD.events
event: (event, template) ->
#find('.new_device_id').value
Here's the same in javascript for the coffee illiterate...
Template.superuserHUD.events({
'click input.new_device': function(event, template) {
var options;
options = {
id: template.find('.new_device_id').value,
IP: 'Not Connected'
};
return Device_IPs.insert(options);
}
});
Template.superuserHUD.events({
event: function(event, template) {
return this.find('.new_device_id').value;
}
});
I have a single html page with multiple div elements on it. Each time a user clicks on on a div, it is replaced with an CKEditor on the fly:
$('.editable').click(function() {
editor = CKEDITOR.replace(this);
});
Now, if the CKEditor instance loses its focus (blur event), I need to post the content to a separate script via ajax (if something changed) and destroy this instance:
$('.editable').click(function() {
editor = CKEDITOR.replace(this);
editor.on('blur', function(e)
{
if (e.editor.checkDirty())
// get data with e.editor.getData() and do some ajax magic
e.editor.destroy();
});
});
But this example won't work because, I don't know why, destory() will be called before checkDirty(). How can I get this working?
How about if you put the destroy() inside the if() statement? You could have an else clause that invokes destroy if nothing has changed. If something has changed, you can invoke destroy() within the if clause once the data has been transfered.
$('.editable').click(function() {
editor = CKEDITOR.replace(this);
editor.on('blur', function(e)
{
if (e.editor.checkDirty()) {
// get data with e.editor.getData() and do some ajax magic
if ( dataTransferComplete ) {
e.editor.destroy();
}
} else {
e.editor.destroy();
}
});
});
Or you could check a variable before invoking destroy(). Set that variable to true after the data transfer has been completed and in the else clause, that way destroy() won't be invoked until you've checked for changes and transfered any updated data.
$('.editable').click(function() {
editor = CKEDITOR.replace(this);
editor.on('blur', function(e)
{
var okToDestroy = false;
if (e.editor.checkDirty()) {
// get data with e.editor.getData() and do some ajax magic
okToDestroy = true;
} else {
okToDestroy = true;
}
if (okToDestroy )
e.editor.destroy();
});
});
This is an outline, I haven't tested the code, but if shows the concept.
Be Well,
Joe