Prevent multiple ajax requests onclick - javascript

I've an issue with multiple ajax requests. For example I've a form with a button, and onclick it runs a service which essentially load list of items in a table; for now it should load a single item into a table when I hit the button.
However, when I hit the button multiple times, the same item is duplicated when its loaded.
How can I prevent while there is still no callback from the first one?
current ng service
var getItems = function () {
var def = $q.defer();
Items.get().then(function (items) {
def.resolve(items);
}, function (err) {
...
});
};
Not sure if this is a solution, but when I write above code like this:
var def = false;
var getItems = function () {
def = $q.defer();
Items.get().then(function (items) {
def.resolve(items);
}, function (err) {
...
});
};
This stops the duplication when I initialize the def = false, not sure if this is the correct approach by resetting the previous/old request to false?

You can put a lock on the function to prevent the code from running multiple times at once or at all:
// your service
$scope.isRunning = false;
var getItems = function () {
if(!$scope.isRunning){
$scope.isRunning = true;
var def = $q.defer();
Items.get().then(function (items) {
def.resolve(items);
}, function (err) {
...
}).finally(function(){
//$scope.isRunning = false; // once done, reset isRunning to allow to run again. If you want it to run just once then exclude this line
});
}
};
Unsure how you want to handle the button in terms of being clicked multiple times
You can hide it on click:
<button ng-hide="isRunning">Stuff</button>
You can disable it on click:
<button ng-disabled="isRunning">Stuff</button>
if disabling, you should probably give feedback like changing opacity:
<button ng-disabled="isRunning" ng-class='{"opacity-half": isRunning}'>Stuff</button>
.opacity-half { opacity: 0.5 }

the below code should do the trick I am avoiding some angular specific syntax hope that helps;
function yourContoller(/*all injectables*/) {
var requesting = false;
$scope.buttonClick = function() {
if (!requesting) {
requesting = true;
yourService.getItems().then(function(response) {
/*your code to handle response*/
requesting = false;
});
}
};
}
if you want to disable a button in the view you can expose this variable by simply using scope ($scope.requesting = false;) with ng-disabled.

you can create a reusable directive so that on any button which is clickable, it doesnt get pressed twice
app.directive('clickAndDisable', function() {
return {
scope: {
clickAndDisable: '&'
},
link: function(scope, iElement, iAttrs) {
iElement.bind('click', function() {
iElement.prop('disabled',true);
scope.clickAndDisable().finally(function() {
iElement.prop('disabled',false);
})
});
}
};
});
This can be used on a button as follows:
<button click-and-disable="functionThatReturnsPromise()">Click me</button>

Related

Trigger dropzone cancel event by clicking modal outside

I work on a project which uses dropzonejs upload file component. But, I faced that issue while trying to make it more customize.
If you click somewhere outside of the modal window (bootstrap modal), should trigger dropzonejs cancel button event (alert/confirm box, see the attached picture). But it does not happen like this and have never seen anything related within the documents.
Any solution?
I have comeover by this very manual and dirty way described below:
I used window.onbeforeunload to make what i thought real and a flag. The point is that, after the upload process successfully ended, then unbind the onbeforeunlad to prevent always asking "Are you sure you want to cancel this upload?" even if it is completed.
var cancelConfirmation = "Are you sure you want to cancel this upload?";
var isFileInProgress = false;
var uploadZone = $("form[class=dropzone]").dropzone({
method: "post",
uploadMultiple: false,
parallelUploads: 1,
dictCancelUploadConfirmation: cancelConfirmation,
success: function (file, response) {
isFileInProgress = false;
window.onbeforeunload = function () { };
},
init: function () {
this.on("error", function (file, response) {
isFileInProgress = false;
window.onbeforeunload = function () { /* unbind */ };
});
this.on("addedfile", function () {
isFileInProgress = true;
window.onbeforeunload = function () {
return cancelConfirmation;
};
});
this.on("complete", function () {
isFileInProgress = false;
window.onbeforeunload = function () { /* unbind */ };
});
this.on("canceled", function () {
isFileInProgress = false;
window.onbeforeunload = function () { /* unbind */ };
});
}
});
$(".modal").on('hide.bs.modal', function (event) {
if (isFileInProgress && !confirm(cancelConfirmation)) {
event.preventDefault();
}
});

How to achieve a queue of object instances of jQuery modals, assuring only one instance is on per time?

I've a task of building a modal prompt, that's been simple so far describing its methods like "show", "hide" when it comes down just to DOM manupulation.
Now comes the hardship for me... Imagine we have a page on which there are several immediate calls to construct and show several modals on one page
//on page load:
$("browser-deprecated-modal").modal();
$("change-your-city-modal").modal();
$("promotion-modal").modal();
By default my Modal (and other libraries i tried) construct all of these modals at once and show them overlapping each other in reverse order -
i.e $(promotion-modal) is on the top, while the
$("browser-deprecated-modal") will be below all of them. that's not what i want, let alone overlapping overlays.
I need each modal to show up only when the previous one (if there'are any) has been closed. So, first we should see "browser-deprecated-modal" (no other modals underneath), upon closing it there must pop up the second one and so on.
I've been trying to work it out with this:
$.fn.modal = function(options) {
return this.each(function() {
if (Modal.running) {
Modal.toInstantiateLater.push({this,options});
} else {
var md = new Modal(this, options);
}
});
}
destroy :function () {
....
if (Modal.toInstantiateLater.length)
new Modal (Modal.toInstantiateLater[0][0],Modal.toInstantiateLater[0][1]);
}
keeping a track of all calls to construct a Modal in a array and in the "destroy" method make a check of this array is not empty.
but it seems awkward and buggy me thinks.
i need a robust and clear solution. I've been thinking about $.Callbacks or $.Deferred,
kinda set up a Callback queue
if (Modal.running) { //if one Modal is already running
var cb = $.Callbacks();
cb.add(function(){
new Modal(this, options);
});
} else { //the road is clear
var md = new Modal(this, options);
}
and to trigger firing cb in the destroy method, but i'm new to this stuff and stuck and cannot progress, whether it's right or not, or other approach is more suitable.
Besides, I read that callbacks fire all the functions at once (if we had more than one extra modal in a queue), which is not right, because I need to fire Modal creation one by one and clear the Callback queue one by one.
Please help me in this mess.
My code jsfiddle
I got rid of the counter variable, as you can use toInstantiateLater to keep track of where you are, and only had to make a few changes. Give this a try...
Javscript
function Modal(el, opts){
this.el = $(el);
this.opts = opts;
this.overlay = $("<div class='overlay' id='overlay"+Modal.counter+"'></div>");
this.wrap = $("<div class='wrap' id='wrap"+Modal.counter+"'></div>");
this.replace = $("<div class='replace' id='replace"+Modal.counter+"'></div>");
this.close = $("<span class='close' id='close"+Modal.counter+"'></span>")
if (Modal.running) {
Modal.toInstantiateLater.push(this);
}
else {
Modal.running = true;
this.show();
}
}
Modal.destroyAll = function() {
Modal.prototype.destroyAll();
};
Modal.prototype = {
show: function() {
var s = this;
s.wrap.append(s.close);
s.el.before(s.replace).appendTo(s.wrap).show();
$('body').append(s.overlay).append(s.wrap);
s.bindEvents();
Modal.currentModal = s;
},
bindEvents: function() {
var s = this;
s.close.on("click.modal",function(e){
s.destroy.call(s,e);
});
},
destroy: function(e) {
var s = this;
s.replace.replaceWith(s.el.hide());
s.wrap.remove();
s.overlay.remove();
if (Modal.toInstantiateLater.length > 0) {
Modal.toInstantiateLater.shift().show();
}
else {
Modal.running = false;
}
},
destroyAll: function(e) {
Modal.toInstantiateLater = [];
Modal.currentModal.destroy();
}
}
Modal.running = false;
Modal.toInstantiateLater = [];
Modal.currentModal = {};
$.fn.modal = function(options) {
return this.each(function() {
var md = new Modal(this, options);
});
}
$("document").ready(function(){
$("#browser-deprecated-modal").modal();
$("#change-your-city-modal").modal();
$("#promotion-modal").modal();
$("#destroy-all").on("click", function() {
Modal.destroyAll();
});
});
jsfiddle example
http://jsfiddle.net/zz9ccbLn/4/

Passing functions between RequireJS files

I've got a file which needs to run on page load (randomise_colors.js), but also needs to be called by another file as part of a callback function (in infinite_scroll.js). The randomise_colors script just loops through a list of posts on the page and assigns each one a color from an array which is used on the front-end.
Infinite Scroll loads new posts in to the DOM on a button click, but because the randomise_colors.js file has already ran on page load, new content loaded is not affected by this so I need it to run again. I'm open to other suggestions if it sounds like I could be tackling the problem in a different way, I'm no JS expert.
Currently I'm getting Uncaught ReferenceError: randomise_colours is not defined referring this line of infinite_scroll.js:
randomise_colours.init();
I'm calling all files that need be loaded on document.ready in app.js
require(['base/randomise-colours', 'base/infinite-scroll'],
function(randomise_colours, infinite_scroll) {
var $ = jQuery;
$(document).ready(function() {
infinite_scroll.init();
randomise_colours.init();
});
}
);
This is infinite_scroll.js which initialises Infinite Scroll and features the callback. The callback function runs whenever new items are loaded in via AJAX using the Infinite Scroll jQuery plugin. I've put asterix around the area where I need to run the randomise_colors.init() function from randomise_colors.js.
define(['infinitescroll'], function() {
var $ = jQuery,
$loadMore = $('.load-more-posts a');
function addClasses() {
**randomise_colours.init();**
};
return {
init: function() {
if($loadMore.length >= 1) {
this.setUp();
} else {
return false;
}
},
setUp: function() {
this.initInfiniteScroll();
},
initInfiniteScroll: function() {
$('.article-listing').infinitescroll({
navSelector : '.load-more-posts',
nextSelector : '.load-more-posts a',
itemSelector : '.standard-post'
}, function(newItems) {
addClasses();
});
//Unbind the standard scroll-load function
$(window).unbind('.infscr');
//Click handler to retrieve new posts
$loadMore.on('click', function() {
$('.article-listing').infinitescroll('retrieve');
return false;
});
}
};
});
And this is my randomise_colors.js file which runs fine on load, but needs to be re-called again after new content has loaded in.
define([], function() {
var $ = jQuery,
$colouredSlide = $('.image-overlay'),
colours = ['#e4cba3', '#867d75', '#e1ecb9', '#f5f08a'],
used = [];
function pickRandomColour() {
if(colours.length == 0) {
colours.push.apply(colours, used);
used = [];
}
var selected = colours[Math.floor(Math.random() * colours.length)];
var getSelectedIndex = colours.indexOf(selected);
colours.splice(getSelectedIndex, 1);
used.push(selected);
return selected;
};
return {
init: function() {
if($colouredSlide.length >= 1) {
this.setUp();
} else {
return false;
}
},
setUp: function() {
this.randomiseColours();
},
randomiseColours: function() {
console.log('randomise');
$colouredSlide.each(function() {
var newColour = pickRandomColour();
$(this).css('background', newColour);
});
}
};
});
You would have to reference randomiseColours inside the infiniteScroll file. So you need to change your define function to the following:
define(['infinitescroll', 'randomise-colours'], function(infiniteScroll, randomise_colours)
Remember that when using require you need to reference all variables through the define function, otherwise they will not be recognised.

pjax/ajax and browser back button issues

I use pjax to ajaxify my menu links. This works fine until I use the browser back button. In my javascript file I use Common Script files (to load all the necessary js files when the user hits the url) and Script files with respect to each menu links (when navigated through pjax)
function myFunction(){
/*All the script files */
}
$(document).ready(function(){
myFunction();
/*pjax menu loading block*/
$(document).on('click', 'a[data-pjax]', function(event) {
$.pjax.click(event, '#pjax-container');
$(document).on('pjax:end', function() {
myFunction();
});
});
});
Now when I navigate to a menu item and try to come back by clicking the browser back button, the script files are getting duplicated (eg: slider images getting duplicated and table sorting not working).How to overcome this issue?
You can implement the url specific loading this way, create a queue of functions which you want to load and unload on pjax complete
The solution is based on js prototyping
// create queue for load and unload
var onLoad = new PjaxExecQueue();
var onUnload = new PjaxExecQueue();
// way to add functions to queue to run on pjax load
onLoad.queue(function() {
someFunction();
});
// way to add functions to queue to unload on pjax load
onUnload.queue(function() {
someOtherFunction();
});
// load function if url contain particular path name
onLoad.queue_for_url(function_name, 'url_section');
// check for url specific function
var URLPjaxQueueElement = function(exec_function, url) {
this.method = exec_function;
if(url) {
this.url = new RegExp(url);
} else {
this.url = /.*/;
}
};
// create a queue object
var PjaxExecQueue = function () {
this.url_exec_queue = [];
this.id_exec_queue = [];
this.fired = false;
this.indicating_loading = false;
this.content = $('#content');
};
PjaxExecQueue.prototype = {
queue: function (exec_function) {
this.url_exec_queue.unshift(new URLPjaxQueueElement(exec_function));
},
queue_for_url: function (exec_function, url_pattern) {
this.url_exec_queue.unshift(new URLPjaxQueueElement(exec_function, url_pattern));
},
queue_if_id_present: function(exec_function, id) {
this.id_exec_queue.unshift(new IDPjaxQueueElement(exec_function, id));
},
fire: function () {
if(this.indicating_loading) {
this.content.removeClass("indicate-loading");
this.indicating_loading = false;
}
if(!this.fired) {
var match_loc = window.location.pathname;
var i = this.url_exec_queue.length;
while(i--) {
this.url_exec_queue[i].fire(match_loc);
}
i = this.id_exec_queue.length;
while(i--) {
this.id_exec_queue[i].fire(match_loc);
}
}
this.fired = true;
},
reset: function() {
this.fired = false;
},
loading: function () {
this.content.addClass("indicate-loading");
this.indicating_loading = true;
this.reset();
},
count: function () {
return exec_queue.length;
},
show: function (for_url) {
for (var i=0; i < exec_queue.length; i++) {
if(for_url) {
if(exec_queue[i].url.test(for_url)) {
console.log("" + exec_queue[i].method);
}
} else{
console.log(exec_queue[i].url + " : " + exec_queue[i].method);
}
}
}
};
// before send
$(document).on('pjax:beforeSend', function() {
onLoad.loading();
onUnload.fire();
});
// after pjax complete
$(document).on('pjax:complete', function() {
onLoad.fire();
onUnload.reset();
});

Jquery confirmation box

I'm looking to create a generic confirmation box that can be used by multiple widgets easily, but I'm running into problems with scope and was hoping for a clearer way of doing what I'm trying to do.
Currently I have the following -
(function() {
var global = this;
global.confirmationBox = function() {
config = {
container: '<div>',
message:''
}
return {
config: config,
render: function(caller) {
var jqContainer = $(config.container);
jqContainer.append(config.message);
jqContainer.dialog({
buttons: {
'Confirm': caller.confirm_action,
Cancel: caller.cancel_action
}
});
}
}
} //end confirmationBox
global.testWidget = function() {
return {
create_message: function(msg) {
var msg = confirmationBox();
msg.message = msg;
msg.render(this);
},
confirm_action: function() {
//Do approved actions here and close the confirmation box
//Currently not sure how to get the confirmation box at
//this point
},
cancel_action: function() {
//Close the confirmation box and register that action was
//cancelled with the widget. Like above, not sure how to get
//the confirmation box back to close it
}
}
}//end testWidget
})();
//Create the widget and pop up a test message
var widget = testWidget();
widget.create_message('You need to confirm this action to continue');
Currently I'm just looking to do something as simple as close the box from the within the widget, but I think I've wrapped my own brain in circles in terms of what knows what.
Anyone want to help clear my befuddled brain?
Cheers,
Sam
The resulting code:
I thought it might be useful for people who find this thread in later days looking for a solution to a similar problem to see the code that resulted from the helpful answers I got here.
As it turns out it was pretty simple in the end (as most of the frustrating mind-tangles are).
/**
* Confirmation boxes are used to confirm a request by a user such as
* wanting to delete an item
*/
global.confirmationBox = function() {
self = this;
config = {
container: '<div>',
message: '',
}
return {
set_config:config,
render_message: function(caller) {
var jqContainer = $(config.container);
jqContainer.attr('id', 'confirmation-dialog');
jqContainer.append(config.message);
jqContainer.dialog({
buttons: {
'Confirm': function() {
caller.confirm_action(this);
},
Cancel: function() {
caller.cancel_action(this);
}
}
});
}
}
} // end confirmationBox
global.testWidget = function() {
return {
create_message: function(msg) {
var msg = confirmationBox();
msg.message = msg;
msg.render(this);
},
confirm_action: function(box) {
alert('Success');
$(box).dialog('close');
},
cancel_action: function(box) {
alert('Cancelled');
$(box).dialog('close');
}
}
}//end testWidget
You could pass jqContainer to the confirm/cancel functions.
Alternately, assign jqContainer as a property of caller. Since the confirm/cancel functions are called as methods of caller, they will have access to it via this. But that limits you to tracking one dialog per widget.
Try something like this:
(function() {
var global = this;
/*****************This is new****************/
var jqContainer;
global.confirmationBox = function() {
config = {
container: '<div>',
message:''
}
return {
config: config,
render: function(caller) {
// store the container in the outer objects scope instead!!!!
jqContainer = $(config.container);
jqContainer.append(config.message);
jqContainer.dialog({
buttons: {
'Confirm': caller.confirm_action,
Cancel: caller.cancel_action
}
});
}
}
} //end confirmationBox
global.testWidget = function() {
return {
create_message: function(msg) {
var msg = confirmationBox();
msg.message = msg;
msg.render(this);
},
confirm_action: function() {
//Do approved actions here and close the confirmation box
//Currently not sure how to get the confirmation box at this point
/*******Hopefully, you would have access to jqContainer here now *****/
},
cancel_action: function() {
//Close the confirmation box and register that action was
//cancelled with the widget. Like above, not sure how to get
//the confirmation box back to close it
}
}
}//end testWidget
})();
//Create the widget and pop up a test message
var widget = testWidget();
widget.create_message('You need to confirm this action to continue');
If that doesn't work, try defining your callbacks (confirm_action, cancel_action) as private members of your object. But they should be able to access the outer scope of your main object.

Categories