How to overwrite/cancel previous promise called one by one? - javascript

I have jQuery code that respond to user keyboard press and I'm checking if element is in view. The plugin return promise if user press the key very fast or hold it (right now it's too fast for my code) it create promise in kind of loop. I want to cancel previous promise before I call new one.
I came up with this code that ignores previous promise:
var scroll_callback_counter = 0;
function move_cursor_visible() {
var cursor = self.find('.cursor');
var i = scroll_callback_counter++;
return cursor.is_fully_in_viewport(self).then(function(visible) {
if (i === scroll_callback_counter && !visible) {
var offset = cursor.offset();
var container_offset = self.offset();
self.scrollTop(offset.top - container_offset.top - 5);
return true;
}
});
}
Is there a better way? It works but I don't know if this is correct way to create code that cancel/ignore promise callback.
EDIT:
Here is is_fully_in_viewport function:
function jquery_resolve(value) {
var defer = jQuery.Deferred();
defer.resolve(value);
return defer.promise();
}
$.fn.is_fully_in_viewport = (function() {
function is_visible(node, container) {
var box = node.getBoundingClientRect();
var viewport = container[0].getBoundingClientRect();
var top = box.top - viewport.top;
var bottom = box.bottom - viewport.top;
var height = container.height();
return bottom > 0 && top <= height;
}
if (window.IntersectionObserver) {
return function(container) {
var node = this[0];
var defer = jQuery.Deferred();
var item_observer = new window.IntersectionObserver(function(entries) {
defer.resolve(entries[0].isIntersecting);
item_observer.unobserve(node);
}, {
root: container[0]
});
item_observer.observe(node);
return defer.promise();
};
} else {
return function(container) {
return jquery_resolve(is_visible(this[0], container));
};
}
})();

Creates a debounced function that delays invoking func until after
wait milliseconds have elapsed since the last time the debounced
function was invoked.
You can use debounce method of loadash,
_.debounce(func, [wait=0], [options={}])
The debounced function comes with a cancel method to cancel delayed
func invocations and a flush method to immediately invoke them

Related

Send event when module was executed

I'm really stuck on this.. I need to send an event when both Load module and Hide module code was executed, and only then send the event. Ideas on how to achieve this?
// Load module
(
function() {
var s=document.createElement('script');
s.type='text/javascript';
s.async=true;
s.src='https://example.com/bundles.js';
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}
)();
// Hide module
var inverval = setInterval(hideClass, 100);
function hideClass () {
if ($(".class").hide().length > 0) clearInterval(inverval);
}
// When both happend = Send a event to Google Analytics
DigitalData.push({'event':Module, 'eventLabel':'Page'});
If this is your only option, then perhaps there's something you are going about wrongly. Anyway, let's see ... Only when both events have taken place.
var HandleTwoEvents = function (key1, key2) {
this.count = 0;
this.pack = [];
$self = this;
this.startListening = function(fn) {
fn = fn || function () {}
window.addEventListener(key1, function (ev) {
if ($self.pack.indexOf(key1) < 0) {
$self.pack.push(key1);
$self.count++;
if ($self.count == 2) {
fn();
$self.count = 0;
}
}
console.log(key1, ev);
});
window.addEventListener(key2, function (ev) {
if ($self.pack.indexOf(key2) < 0) {
$self.pack.push(key2);
$self.count++;
if ($self.count == 2) {
fn();
$self.count = 0;
}
}
console.log(key2, ev);
});
}
}
Forgive me, i always use this function to create events
function createEvent(name, obj) {
var evt = document.createEvent("Event");
evt.initEvent(name, true, true);
evt.data = obj;
dispatchEvent(evt);
}
Now, to log both events ...
var both = new HandleTwoEvents("EventKeyOne", "EventKeyTwo");
both.startListening(function () {console.log("This means that both Events have taken place")});
Now, let's test ...
createEvent("EventKeyOne", {});
//key, data are the arguments ... function defined in startListening above does not execute, and upon inspection, both.count is seen to be 1
createEvent("EventKeyTwo", {});
//Now, function executes.
//It also works if "EventKeyTwo" is raised before "EventKeyOne"
Happy Coding!
PS: I'm sure there's a better way to handle the use of the $self variable, with some function binding, i guess. I've never been able to learn it.

Stoping jquery .then chain via user input

This is probably a simple question, but i'm totally lost.
I have this function.
m.util.genericSwipeVertFunc = function (
ajaxRequest,
swipeOutTarget,
swipeInTarget
) {
var stage1, stage2, failStage, dfd = $.Deferred(), finalStage, functionPromise;
// Swipe of screen wait for ajax request
stage1 = function () {
return $.when(
ajaxRequest, // Returns $.Deferred()
m.util.animateDeffered(swipeOutTarget, "fadeOutDown", true) // Returns $.Deferred()
);
};
// Swipe and Show
stage2 = function () {
swipeInTarget.show();
return m.util.animateDeffered(swipeInTarget, "fadeInDown"); // Returns $.Deferred()
};
finalStage = function () {
dfd.resolve();
}
failStage = function () {
console.log("fail!");
swipeInTarget.hide();
};
functionPromise = stage1()
.then(stage2)
.then(finalStage);
$.when(functionPromise,dfd)
.fail(failStage);
return dfd;
};
Basically it does some fancy animations to fade in and out different response outputs from ajax functions. This all works fine, except when the user tries to change between targets very fast(before one chain finishes they start another) I get crazy animation all over the place.
I want to be able to reject the chain at any point by doing something like this.
// called on script load.
var currentAction = $.Deferred();
// Called everytime someone starts animation chain.
currentAction.reject();
currentAction = m.util.genericSwipeVertFunc(dfd, swipeOutTarget, swipeInTarget);
);
With my current code the failFunction is hit correctly but it doesn't stop the execution of stage2. So it hides then shows it and continues breaking things.
So to the question. How do I put a deferred in a chain that i can reject at any time during the chains execution ? :)
Example fiddle
http://jsfiddle.net/ff3jojbo/
Update for clarification
I am using animate.css for my animations. Not jquery animation.
I am more interested in how to stop the chain from starting the next stage at any point from user input.
Answer fiddle
http://jsfiddle.net/aefkwa8a/
Try using .queue() , .promise()
// array of functions to add to queue
var arr = [];
var swipeInTarget = $("#stage1");
var swipeOutTarget = $("#stage2");
// pseudo `ajax` call
var ajaxRequest = function ajaxRequest(next) {
return $.Deferred(function(d) {
setTimeout(function() {
d.resolve("ajaxRequest")
}, Math.random() * 5000)
}).promise()
// Note `.then(function() {console.log(this)})` for example ,
// can be removed
.then(function(data) {
console.log(data)
}).then(next)
}
var stage1 = function stage1(next) {
return swipeOutTarget.fadeTo(Math.random() * 5000, Math.random())
.promise()
// Note `.then(function() {console.log(this)})` for example ,
// can be removed
.then(function() {
console.log(this)
})
.then(next)
}
var stage2 = function stage2(next) {
return swipeInTarget
.show(Math.random() * 5000, function() {
return $(this).fadeTo(Math.random() * 2000, Math.random())
})
.promise()
// Note `.then(function() {console.log(this)})` for example ,
// can be removed
.then(function() {
console.log(this)
})
.then(next)
}
// do stuff when queue cleared
var failStage = function failStage() {
return swipeInTarget.hide(Math.random() * 2000)
.promise().then(function() {
console.log("m processes stopped")
})
}
// always do stuff when queue cleared,
// or all functions in queue complete
var finalStage = function finalStage() {
console.log("complete", this)
}
// create jQuery object
var m = $({
m: arr
});
// add function to `"stages"` queue
m.queue("stages", [stage1, stage2, finalStage]);
// do stuff when all functions complete , or queue cleared
m.promise("stages")
.then(finalStage);
// dequque `"stages"` queue
m.dequeue("stages");
// clear `"stages"` queue
$("button").click(function() {
m.queue("stages", [])
.promise("stages").always(failStage)
})
#stage2 {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<button>stop m processes</button>
<div id="stage1">stage1</div>
<div id="stage2">stage2</div>
OP's own solution here can fail after several clicks. In particular, if button is clicked while a section is flying in, then the latest demanded section may fly in, then disappear.
This solution is completely different.
Instead of using jQuery's queue/dequeue, it uses a regular stage1().then(stage2) promise chain, and stops progress down that chain by removing the CSS animation classes from the animated element and detaching its animationend handler, thus ensuring the promise associated with completion never resolves.
As you will see, much of the functionality is factored as jQuery plugins, which makes for convenient, compact syntax.
$(function () {
// **************************
// *** Various outer vars ***
// **************************
var $sections = $('#TabSection>div').hide();
var ajaxPromise;
var str = {
//various strings
'animationend': 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend',
'fadeOutClasses': 'fadeOutDown animated',
'fadeInClasses': 'fadeInDown animated',
'allClasses': 'fadeOutDown fadeInDown animated'
};
// ***********************************************
// *** Utilities in the form of jQuery plugins ***
// ***********************************************
jQuery.fn.killAnim = function(animation) {
/* jQuery plugin :
* Remove all the animation classes from all possible targets, and
* detach any currently attached animationend handlers.
* Depends on: str (object).
*/
return this.off(str.animationend).removeClass(str.allClasses);
};
jQuery.fn.cssAnimate = function (animation) {
/* jQuery plugin :
* Perform CSS animation and return promise.
* Depends on: str (object); killAnim (plugin).
*/
var that = this;
return $.Deferred(function(dfd) {
// if no target or target not visible, resolve;
if(that.length == 0 || !that.is(':visible')) {
dfd.resolve();
}
that.addClass(animation).one(str.animationend, dfd.resolve);
}).then(function() {
that.killAnim();
});
};
jQuery.fn.genericSwipeVertFunc = function () {
/* jQuery plugin :
* Sequence two CSS animations - fadeOut then fadeIn.
* Depends on: str (object); killAnim (plugin); cssAnimate (plugin).
*/
var that = this; // swipeInTarget
var swipeOutTarget = $sections.filter(':visible()').eq(0);
function stage1() {
$sections.killAnim().not(swipeOutTarget).hide();
return swipeOutTarget.cssAnimate(str.fadeOutClasses).then(function() {
swipeOutTarget.hide();
});
};
function stage2() {
$sections.not(that).killAnim().hide();
return that.show().cssAnimate(str.fadeInClasses);
};
return stage1().then(stage2);
};
// **********************
// *** Event handlers ***
// **********************
$('button').on('click', function (event) {
var inTarget = $($(this).data('tar'));
if(ajaxPromise) {
ajaxPromise.abort('aborted');
}
// *** start: emulate AJAX ***
ajaxPromise = $.Deferred(function(dfrd) {
setTimeout(dfrd.resolve, 1000);
});
ajaxPromise.abort = ajaxPromise.reject;
// *** end: emulate AJAX ***
ajaxPromise.then(function() {
return inTarget.genericSwipeVertFunc();
}).fail(function(e) {
$sections.killAnim().hide();
console.log(e);
});
});
});
I believe this solution to be more reliable. Even with lots of manic clicking, I could not defeat it.
Try it here

I want to not be able to run the same command twice very quickly

So I have this chunk of code here:
lockskipCommand = (function(_super) {
__extends(lockskipCommand, _super);
function lockskipCommand() {
return lockskipCommand.__super__.constructor.apply(this, arguments);
}
lockskipCommand.prototype.init = function() {
this.command = '/lockskip';
this.parseType = 'exact';
return this.rankPrivelege = 'bouncer';
};
lockskipCommand.prototype.functionality = function() {
data.lockBooth();
new ModerationForceSkipService();
return setTimeout((function() {
return data.unlockBooth();
}), 4500);
};
return lockskipCommand;
})(Command);
I want to be able to let it has some sort of cool down, so it can't be used quickly in a row. The reason I want this is to prevent from people being skipped, because that's what this chunk of code is for skipping people.
I hope this is enough information to get some help. Thanks!
You can use Underscore's debounce() method (with true as the third argument).
If you don't want to include Underscore for this simple task, you could do...
var debounceFn = function (fn, delay) {
var lastInvocationTime = Date.now();
delay = delay || 0;
return function () {
(Date.now() - delay > lastInvocationTime) && (lastInvocationTime = Date.now()) && fn && fn();;
};
};
jsFiddle.
What I need is a way to not be able to execute the command more than once in a row.
You could do something similar...
var onceFn = function (fn) {
var invoked = false;
return function () {
! invoked && (invoked = true) && fn && fn();
};
};
jsFiddle.

Throttle event calls in jQuery

I have a keyup event bound to a function that takes about a quarter of a second to complete.
$("#search").keyup(function() {
//code that takes a little bit to complete
});
When a user types an entire word, or otherwise presses keys rapidly, the function will be called several times in succession and it will take a while for them all to complete.
Is there a way to throttle the event calls so that if there are several in rapid succession, it only triggers the one that was most recently called?
Take a look at jQuery Debounce.
$('#search').keyup($.debounce(function() {
// Will only execute 300ms after the last keypress.
}, 300));
Here is a potential solution that doesn't need a plugin. Use a boolean to decide whether to do the keyup callback, or skip over it.
var doingKeyup = false;
$('input').keyup(function(){
if(!doingKeyup){
doingKeyup=true;
// slow process happens here
doingKeyup=false;
}
});
You could also use the excellent Underscore/_ library.
Comments in Josh's answer, currently the most popular, debate whether you should really throttle the calls, or if a debouncer is what you want. The difference is a bit subtle, but Underscore has both: _.debounce(function, wait, [immediate]) and _.throttle(function, wait, [options]).
If you're not already using Underscore, check it out. It can make your JavaScript much cleaner, and is lightweight enough to give most library haters pause.
Here's a clean way of doing it with JQuery.
/* delayed onchange while typing jquery for text boxes widget
usage:
$("#SearchCriteria").delayedChange(function () {
DoMyAjaxSearch();
});
*/
(function ($) {
$.fn.delayedChange = function (options) {
var timer;
var o;
if (jQuery.isFunction(options)) {
o = { onChange: options };
}
else
o = options;
o = $.extend({}, $.fn.delayedChange.defaultOptions, o);
return this.each(function () {
var element = $(this);
element.keyup(function () {
clearTimeout(timer);
timer = setTimeout(function () {
var newVal = element.val();
newVal = $.trim(newVal);
if (element.delayedChange.oldVal != newVal) {
element.delayedChange.oldVal = newVal;
o.onChange.call(this);
}
}, o.delay);
});
});
};
$.fn.delayedChange.defaultOptions = {
delay: 1000,
onChange: function () { }
}
$.fn.delayedChange.oldVal = "";
})(jQuery);
Two small generic implementations of throttling approaches. (I prefer to do it through these simple functions rather than adding another jquery plugin)
Waits some time after last call
This one is useful when we don't want to call for example search function when user keeps typing the query
function throttle(time, func) {
if (!time || typeof time !== "number" || time < 0) {
return func;
}
var throttleTimer = 0;
return function() {
var args = arguments;
clearTimeout(throttleTimer);
throttleTimer = setTimeout(function() {
func.apply(null, args);
}, time);
}
}
Calls given function not more often than given amount of time
The following one is useful for flushing logs
function throttleInterval(time, func) {
if (!time || typeof time !== "number" || time < 0) {
return func;
}
var throttleTimer = null;
var lastState = null;
var eventCounter = 0;
var args = [];
return function() {
args = arguments;
eventCounter++;
if (!throttleTimer) {
throttleTimer = setInterval(function() {
if (eventCounter == lastState) {
clearInterval(throttleTimer);
throttleTimer = null;
return;
}
lastState = eventCounter;
func.apply(null, args);
}, time);
}
}
}
Usage is very simple:
The following one is waiting 2s after the last keystroke in the inputBox and then calls function which should be throttled.
$("#inputBox").on("input", throttle(2000, function(evt) {
myFunctionToThrottle(evt);
}));
Here is an example where you can test both: click (CodePen)
I came across this question reviewing changes to zurb-foundation. They've added their own method for debounce and throttling. It looks like it might be the same as the jquery-debounce #josh3736 mentioned in his answer.
From their website:
// Debounced button click handler
$('.button').on('click', Foundation.utils.debounce(function(e){
// Handle Click
}, 300, true));
// Throttled resize function
$(document).on('resize', Foundation.utils.throttle(function(e){
// Do responsive stuff
}, 300));
Something like this seems simplest (no external libraries) for a quick solution (note coffeescript):
running = false
$(document).on 'keyup', '.some-class', (e) ->
return if running
running = true
$.ajax
type: 'POST',
url: $(this).data('url'),
data: $(this).parents('form').serialize(),
dataType: 'script',
success: (data) ->
running = false

JQuery: How to call RESIZE event only once it's FINISHED resizing?

How do I call a function once the browser windows has FINISHED resizing?
I'm trying to do it like so, but am having problems. I'm using the JQuery Resize event function:
$(window).resize(function() {
... // how to call only once the browser has FINISHED resizing?
});
However, this function is called continuously if the user is manually resizing the browser window. Which means, it might call this function dozens of times in short interval of time.
How can I call the resize function only a single time (once the browser window has finished resizing)?
UPDATE
Also without having to use a global variable.
Here is an example using thejh's instructions
You can store a reference id to any setInterval or setTimeout. Like this:
var loop = setInterval(func, 30);
// some time later clear the interval
clearInterval(loop);
Debounce.
function debouncer( func , timeout ) {
var timeoutID , timeout = timeout || 200;
return function () {
var scope = this , args = arguments;
clearTimeout( timeoutID );
timeoutID = setTimeout( function () {
func.apply( scope , Array.prototype.slice.call( args ) );
} , timeout );
}
}
$( window ).resize( debouncer( function ( e ) {
// do stuff
} ) );
Note, you can use this method for anything you want to debounce (key events etc).
Tweak the timeout parameter for optimal desired effect.
You can use setTimeout() and clearTimeout() in conjunction with jQuery.data:
$(window).resize(function() {
clearTimeout($.data(this, 'resizeTimer'));
$.data(this, 'resizeTimer', setTimeout(function() {
//do something
alert("Haven't resized in 200ms!");
}, 200));
});
Update
I wrote an extension to enhance jQuery's default on (& bind)-event-handler. It attaches an event handler function for one or more events to the selected elements if the event was not triggered for a given interval. This is useful if you want to fire a callback only after a delay, like the resize event, or else.
https://github.com/yckart/jquery.unevent.js
;(function ($) {
var methods = { on: $.fn.on, bind: $.fn.bind };
$.each(methods, function(k){
$.fn[k] = function () {
var args = [].slice.call(arguments),
delay = args.pop(),
fn = args.pop(),
timer;
args.push(function () {
var self = this,
arg = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(self, [].slice.call(arg));
}, delay);
});
return methods[k].apply(this, isNaN(delay) ? arguments : args);
};
});
}(jQuery));
Use it like any other on or bind-event handler, except that you can pass an extra parameter as a last:
$(window).on('resize', function(e) {
console.log(e.type + '-event was 200ms not triggered');
}, 200);
http://jsfiddle.net/ARTsinn/EqqHx/
var lightbox_resize = false;
$(window).resize(function() {
console.log(true);
if (lightbox_resize)
clearTimeout(lightbox_resize);
lightbox_resize = setTimeout(function() {
console.log('resize');
}, 500);
});
Just to add to the above, it is common to get unwanted resize events because of scroll bars popping in and out, here is some code to avoid that:
function registerResize(f) {
$(window).resize(function() {
clearTimeout(this.resizeTimeout);
this.resizeTimeout = setTimeout(function() {
var oldOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
var currHeight = $(window).height(),
currWidth = $(window).width();
document.body.style.overflow = oldOverflow;
var prevUndefined = (typeof this.prevHeight === 'undefined' || typeof this.prevWidth === 'undefined');
if (prevUndefined || this.prevHeight !== currHeight || this.prevWidth !== currWidth) {
//console.log('Window size ' + (prevUndefined ? '' : this.prevHeight + "," + this.prevWidth) + " -> " + currHeight + "," + currWidth);
this.prevHeight = currHeight;
this.prevWidth = currWidth;
f(currHeight, currWidth);
}
}, 200);
});
$(window).resize(); // initialize
}
registerResize(function(height, width) {
// this will be called only once per resize regardless of scrollbars changes
});
see jsfiddle
Underscore.js has a couple of great methods for this task: throttle and debounce. Even if you're not using Underscore, take a look at the source of these functions. Here's an example:
var redraw = function() {'redraw logic here'};
var debouncedRedraw = _.debounce(redraw, 750);
$(window).on('resize', debouncedRedraw);
This is my approach:
document.addEventListener('DOMContentLoaded', function(){
var tos = {};
var idi = 0;
var fn = function(id)
{
var len = Object.keys(tos).length;
if(len == 0)
return;
to = tos[id];
delete tos[id];
if(len-1 == 0)
console.log('Resize finished trigger');
};
window.addEventListener('resize', function(){
idi++;
var id = 'id-'+idi;
tos[id] = window.setTimeout(function(){fn(id)}, 500);
});
});
The resize-event-listener catches all incoming resize calls, creates a timeout-function for each and saves the timeout-identifier along with an iterating number prepended by 'id-' (to be usable as array key) in the tos-array.
each time, the timout triggers, it calls the fn-function, that checks, if that was the last timeout in the tos array (the fn-function deletes every executed timout). if true (= if(len-1 == 0)), the resizing is finished.
jQuery provides an off method to remove event handler
$(window).resize(function(){
if(magic == true) {
$(window).off('resize', arguments.callee);
}
});

Categories

Resources