Callback after $.each with animations - javascript

So I'm running some animations to bring in some divs in my first jQuery plugin:
$.fn.turnstile = function (options, callback) {
if (!this.length) {
return this;
}
count = this.length;
var opts = $.extend(true, {}, $.fn.turnstile.defaults, options)
var delayIt = 100;
if (opts.direction == "in") {
opts.deg = '0deg';
opts.trans = '0,0';
opts.opacity = 1;
} else if (opts.direction == "out") {
opts.deg = '-90deg';
opts.trans = "-100px, 200px";
opts.opacity = 0;
} else if (opts.direction == "back") {
opts.deg = '0deg';
opts.trans = '-2000px,0px';
opts.opacity = 0;
} else {
opts.deg = direction;
}
this.each(function (index) {
delayIt += opts.delayer;
$(this).show().delay(delayIt).transition({
perspective: '0px',
rotateY: opts.deg,
opacity: opts.opacity,
translate: opts.trans
}, 400, 'cubic-bezier(0.33,0.66,0.66,1)', function () {
if (opts.direction == "back" || opts.direction == "out") {
$(this).hide();
}
if (!--count && callback && typeof (callback) === "function") {
if ($(":animated").length === 0) {
callback.call(this);
}
}
});
});
return this;
};
Now, I'd like to call my callback when all animations are completed, which should mathematically be (delayIt+400)*count - but I can't get the callback to run when all of the animations are complete. As you might be able to tell from its current state, I've attempted to check for :animated, used the !--count condition, and even tried setting a timeout equal to the duration of the animations, but all seem to fire asynchronously. What is the best way to animate these and call something back when done?
Here's the info on the .transition() plugin.

Not tested with your code (i'm not familiar with .transition), but try this:
...
this.promise().done(function(){
alert('all done');
});
return this;
};

Related

How to set a delay for L.Tooltip display?

I am wondering how to set a delay for tooltip visualization on mouse hover. I haven't found such a feature in options description in docs.
My map is crowded with many markers, so when mouse is moving around, all the time some tooltips appear. My idea is to set some delay, so that for example after 1 second of hovering, tooltip for this particular marker to be displayed.
Thank you!
I also needed to have the delay function, so I grabed it from the old Tooltip plugin and made a quick workaround, it probably can be done way better, but I have no time to improve it.
I extended the bindtooltip, open and close methods and added the 'showDelay' and 'hideDelay' properties to the L.Layer class. I added a click event to close the tooltip at the "initTooltipInteractions' too, because it made sense in my specific situation, but you can take it off.
Just add this to a javascript file and load it after leaflet:
L.Layer.include({
showDelay: 1200,
hideDelay: 100,
bindTooltipDelayed: function (content, options) {
if (content instanceof L.Tooltip) {
L.setOptions(content, options);
this._tooltip = content;
content._source = this;
} else {
if (!this._tooltip || options) {
this._tooltip = new L.Tooltip(options, this);
}
this._tooltip.setContent(content);
}
this._initTooltipInteractionsDelayed();
if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
this.openTooltipWithDelay();
}
return this;
},
_openTooltipDelayed: function (e) {
var layer = e.layer || e.target;
if (!this._tooltip || !this._map) {
return;
}
this.openTooltipWithDelay(layer, this._tooltip.options.sticky ? e.latlng : undefined);
},
openTooltipDelayed: function (layer, latlng) {
if (!(layer instanceof L.Layer)) {
latlng = layer;
layer = this;
}
if (layer instanceof L.FeatureGroup) {
for (var id in this._layers) {
layer = this._layers[id];
break;
}
}
if (!latlng) {
latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
}
if (this._tooltip && this._map) {
this._tooltip._source = layer;
this._tooltip.update();
this._map.openTooltip(this._tooltip, latlng);
if (this._tooltip.options.interactive && this._tooltip._container) {
addClass(this._tooltip._container, 'leaflet-clickable');
this.addInteractiveTarget(this._tooltip._container);
}
}
layer.fireEvent('mousemove', lastMouseEvent);
return this;
},
openTooltipWithDelay: function (t, i) {
this._delay(this.openTooltipDelayed, this, this.showDelay, t, i);
},
closeTooltipDelayed: function () {
if (this._tooltip) {
this._tooltip._close();
if (this._tooltip.options.interactive && this._tooltip._container) {
removeClass(this._tooltip._container, 'leaflet-clickable');
this.removeInteractiveTarget(this._tooltip._container);
}
}
return this;
},
closeTooltipWithDelay: function () {
clearTimeout(this._timeout);
this._delay(this.closeTooltipDelayed, this, this.hideDelay);
},
_delay: function (func, scope, delay, t, i) {
var me = this;
if (this._timeout) {
clearTimeout(this._timeout)
}
this._timeout = setTimeout(function () {
func.call(scope, t, i);
delete me._timeout
}, delay)
},
_initTooltipInteractionsDelayed: function (remove$$1) {
if (!remove$$1 && this._tooltipHandlersAdded) { return; }
var onOff = remove$$1 ? 'off' : 'on',
events = {
remove: this.closeTooltipWithDelay,
move: this._moveTooltip
};
if (!this._tooltip.options.permanent) {
events.mouseover = this._openTooltipDelayed;
events.mouseout = this.closeTooltipWithDelay;
events.click = this.closeTooltipWithDelay;
if (this._tooltip.options.sticky) {
events.mousemove = this._moveTooltip;
}
if (L.touch) {
events.click = this._openTooltipDelayed;
}
} else {
events.add = this._openTooltipDelayed;
}
this[onOff](events);
this._tooltipHandlersAdded = !remove$$1;
}
});
And then to use it:
layer.showDelay = 800; //use 0 for no delay behavior
layer.hideDelay = 2000; //use 0 for normal behavior
layer.bindTooltipDelayed("text", tooltipOptions);

JavaScript Worker : how to check if a message was received while running expensive task

I have a very expensive task running on a Worker, similar as this
for(var i = 0; i < 1000000000; i++)
//operations using i...
How can I make is so that, in that loop, I can check if a message was received from the Worker owner asking it to stop? I would like to have something like this
for(var i = 0; i < 1000000000; i++)
if(windowDidNotAskToStop())
//operations using i...
Right now I have a onmessage function registered so I can start listen to message coming from the owner, but it is blocked while my loop is running (obviously).
I imagine that the postMessage calls from the owner are queued somewhere, so I would simply have to access that in order to process the calls from inside my loop.
You’ll have to handle the events as usual and set a flag, but make sure to leave time for the event to be received in your loop, probably using setTimeout:
var exitLoop = false;
(function loop(i) {
if (exitLoop || i >= 1000000000) {
return;
}
// …
setTimeout(function () {
loop(i + 1);
}, 0);
})(0);
onmessage = function (e) {
if (e.data === 'stop') {
exitLoop = true;
}
};
Or, as a general utility function:
function asyncIterateRange(start, end, callback) {
var exitLoop = false;
var doneCallbacks = [];
(function loop(i) {
if (exitLoop) {
return;
}
if (i >= end) {
doneCallbacks.forEach(function (callback) {
callback();
});
return;
}
callback(function () {
setTimeout(function () {
loop(i + 1);
}, 0);
});
})(start);
return {
abort: function abort() {
exitLoop = true;
},
done: function addDoneCallback(callback) {
doneCallbacks.push(callback);
}
};
}
Use it like so:
var innerIterator;
var outerIterator = asyncIterateRange(0, 1000000, function outerLoop(next) {
innerIterator = asyncIterateRange(0, 1000, function innerLoop(next) {
// …
next();
}).done(next);
});
// To stop:
if (innerIterator) {
innerIterator.abort();
}
outerIterator.abort();

Javascript: Check if function is TRUE

i have a function, which should return TRUE, when IF condition inside FOR cyklus is met. (i tested that condition, it works)
createBtn.addEventListener('click',function (e){
var ch = function check(){
var url = "http://hotel.010.sk/skyfit/read.php";
var json, poc, vypis;
var i=0;
var xhr = Ti.Network.createHTTPClient({
onload: function() {
json = JSON.parse(this.responseText);
for (i = 0; i < json.poc.length; i++) {
prnt = json.poc[i];
if(win.xtra_id == prnt.id_cv && picker.getSelectedRow(0).title == prnt.datum && prnt.capacity <= prnt.cnt ){
return true;
}
};
}
});
xhr.open("GET", url);
xhr.send();
};
...
But when i call function here to check it turns me always true, unless IF condition in check() is not met!
if(ch){
alert('Something');
}
How to fix my function to get TRUE only when my condition is met?
Thanks.
This expression
if (ch) {
is always true, because ch is a function and ToBoolean(function) === true.
However even if you changed it to if (ch()) {..} it would not work anyway, because inside of the function you perform asynchronous operation. Function just returns without waiting for it to finish. In this case you should use callbacks or promise patters. Simplest is callback.
var ch = function check(callback) {
// ...
var xhr = Ti.Network.createHTTPClient({
onload: function () {
json = JSON.parse(this.responseText);
var status = false;
for (i = 0; i < json.poc.length; i++) {
prnt = json.poc[i];
if (win.xtra_id == prnt.id_cv && picker.getSelectedRow(0).title == prnt.datum && prnt.capacity <= prnt.cnt) {
status = true;
}
};
callback(status);
}
});
// ...
};
ch(function(status) {
if (status) {
alert('Something')
}
});
Please try with following thing to check condition should always true
while(true){
alert('something');
}
Your function, ch has no return value! It launches the Ti.Network.createHTTPClient function where you define an anonymous callback function (onload: function(){...). That anonymous function is executed after the ch has already returned. You'll need to either check your conditional after the callback:
...
if (win.xtra_id == prnt.id_cv && picker.getSelectedRow(0).title == prnt.datum && prnt.capacity <= prnt.cnt) {
alert('something') //DO YOUR STUFF HERE!
return true;
}
...
or wait for the callback to happen before you check the conditional using some global flags:
var MYFLAG=-1
...
if (win.xtra_id == prnt.id_cv && picker.getSelectedRow(0).title == prnt.datum && prnt.capacity <= prnt.cnt) {
MYFLAG=1
return true;
}
else
{
MYFLAG=0
}
...
//use a timer to check the flag every 500ms
var myInterval = setInterval(function()
{
if (MYFLAG != -1) {
if (MYFLAG==1) {
alert('returned true :)');
}
else {
alert('did not return true :(');
}
clearInterval(myInterval);
}
}, 500);

Javascript callback managment

I'm having trouble with designing a class which exposes its actions through callbacks. Yes my approach works for me but also seems too complex.
To illustrate the problem I've drawn the following picture. I hope it is useful for you to understand the class/model.
In my approach, I use some arrays holding user defined callback functions.
....
rocket.prototype.on = function(eventName, userFunction) {
this.callbacks[eventName].push(userFunction);
}
rocket.prototype.beforeLunch = function(){
userFunctions = this.callbacks['beforeLunch']
for(var i in userFunctions)
userFunctions[i](); // calling the user function
}
rocket.prototype.lunch = function() {
this.beforeLunch();
...
}
....
var myRocket = new Rocket();
myRocket.on('beforeLunch', function() {
// do some work
console.log('the newspaper guys are taking pictures of the rocket');
});
myRocket.on('beforeLunch', function() {
// do some work
console.log('some engineers are making last checks ');
});
I'm wondering what the most used approach is. I guess I could use promises or other libraries to make this implementation more understandable. In this slide using callbacks is considered evil. http://www.slideshare.net/TrevorBurnham/sane-async-patterns
So, should I use a library such as promise or continue and enhance my approach?
var Rocket = function () {
this.timer = null;
this.velocity = 200;
this.heightMoon = 5000;
this.goingToMoon = true;
this.rocketStatus = {
velocity: null,
height: 0,
status: null
};
this.listener = {
};
}
Rocket.prototype.report = function () {
for (var i in this.rocketStatus) {
console.log(this.rocketStatus[i]);
};
};
Rocket.prototype.on = function (name,cb) {
if (this.listener[name]){
this.listener[name].push(cb);
}else{
this.listener[name] = new Array(cb);
}
};
Rocket.prototype.initListener = function (name) {
if (this.listener[name]) {
for (var i = 0; i < this.listener[name].length; i++) {
this.listener[name][i]();
}
return true;
}else{
return false;
};
}
Rocket.prototype.launch = function () {
this.initListener("beforeLaunch");
this.rocketStatus.status = "Launching";
this.move();
this.initListener("afterLaunch");
}
Rocket.prototype.move = function () {
var that = this;
that.initListener("beforeMove");
if (that.goingToMoon) {
that.rocketStatus.height += that.velocity;
}else{
that.rocketStatus.height -= that.velocity;
};
that.rocketStatus.velocity = that.velocity;
if (that.velocity != 0) {
that.rocketStatus.status = "moving";
}else{
that.rocketStatus.status = "not moving";
};
if (that.velocity >= 600){
that.crash();
return;
}
if (that.rocketStatus.height == 2000 && that.goingToMoon)
that.leaveModules();
if (that.rocketStatus.height == that.heightMoon)
that.landToMoon();
if (that.rocketStatus.height == 0 && !that.goingToMoon){
that.landToEarth();
return;
}
that.report();
that.initListener("afterMove");
that.timer = setTimeout(function () {
that.move();
},1000)
}
Rocket.prototype.stop = function () {
clearTimeout(this.timer);
this.initListener("beforeStop");
this.velocity = 0;
this.rocketStatus.status = "Stopped";
console.log(this.rocketStatus.status)
this.initListener("afterStop");
return true;
}
Rocket.prototype.crash = function () {
this.initListener("beforeCrash");
this.rocketStatus.status = "Crashed!";
this.report();
this.stop();
this.initListener("afterCrash");
}
Rocket.prototype.leaveModules = function () {
this.initListener("beforeModules");
this.rocketStatus.status = "Leaving Modules";
this.initListener("afterModules");
}
Rocket.prototype.landToMoon = function () {
this.initListener("beforeLandToMoon");
this.rocketStatus.status = "Landing to Moon";
this.goingToMoon = false;
this.initListener("afterLandToMoon");
}
Rocket.prototype.landToEarth = function () {
this.initListener("beforeLandToEarth");
this.stop();
this.rocketStatus.status = "Landing to Earth";
this.initListener("afterLandToEarth");
}
Rocket.prototype.relaunch = function () {
this.initListener("beforeRelaunch");
this.timer = null;
this.velocity = 200;
this.heightMoon = 5000;
this.goingToMoon = true;
this.rocketStatus = {
velocity: 200,
height: 0,
status: "relaunch"
};
this.launch();
this.initListener("afterRelaunch");
}
init;
var rocket = new Rocket();
rocket.on("afterLaunch", function () {console.log("launch1")})
rocket.on("afterLandToMoon", function () {console.log("land1")})
rocket.on("beforeLandToEarth", function () {console.log("land2")})
rocket.on("afterMove", function () {console.log("move1")})
rocket.on("beforeLaunch", function () {console.log("launch2")})
rocket.launch();
You can add any function before or after any event.
This is my solution for this kinda problem. I am not using any special methods anything. I was just wonder is there any good practise for this like problems. I dig some promise,deferred but i just can't able to to this. Any ideas ?

How do I know when I've stopped scrolling?

How do I know when I've stopped scrolling using Javascript?
You can add an event handler for the scroll event and start a timeout. Something like:
var timer = null;
window.addEventListener('scroll', function() {
if(timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(function() {
// do something
}, 150);
}, false);
This will start a timeout and wait 150ms. If a new scroll event occurred in the meantime, the timer is aborted and a new one is created. If not, the function will be executed. You probably have to adjust the timing.
Also note that IE uses a different way to attach event listeners, this should give a good introduction: quirksmode - Advanced event registration models
There isn't a "Stopped Scrolling" event. If you want to do something after the user has finished scrolling, you can set a timer in the "OnScroll" event. If you get another "OnScroll" event fired then reset the timer. When the timer finally does fire, then you can assume the scrolling has stopped. I would think 500 milliseconds would be a good duration to start with.
Here's some sample code that works in IE and Chrome:
<html>
<body onscroll="bodyScroll();">
<script language="javascript">
var scrollTimer = -1;
function bodyScroll() {
document.body.style.backgroundColor = "white";
if (scrollTimer != -1)
clearTimeout(scrollTimer);
scrollTimer = window.setTimeout("scrollFinished()", 500);
}
function scrollFinished() {
document.body.style.backgroundColor = "red";
}
</script>
<div style="height:2000px;">
Scroll the page down. The page will turn red when the scrolling has finished.
</div>
</body>
</html>
Here's a more modern, Promise-based solution I found on a repo called scroll-into-view-if-needed
Instead of using addEventListener on the scroll event it uses requestAnimationFrame to watch for frames with no movement and resolves when there have been 20 frames without movement.
function waitForScrollEnd () {
let last_changed_frame = 0
let last_x = window.scrollX
let last_y = window.scrollY
return new Promise( resolve => {
function tick(frames) {
// We requestAnimationFrame either for 500 frames or until 20 frames with
// no change have been observed.
if (frames >= 500 || frames - last_changed_frame > 20) {
resolve()
} else {
if (window.scrollX != last_x || window.scrollY != last_y) {
last_changed_frame = frames
last_x = window.scrollX
last_y = window.scrollY
}
requestAnimationFrame(tick.bind(null, frames + 1))
}
}
tick(0)
})
}
With async/await and then
await waitForScrollEnd()
waitForScrollEnd().then(() => { /* Do things */ })
(function( $ ) {
$(function() {
var $output = $( "#output" ),
scrolling = "<span id='scrolling'>Scrolling</span>",
stopped = "<span id='stopped'>Stopped</span>";
$( window ).scroll(function() {
$output.html( scrolling );
clearTimeout( $.data( this, "scrollCheck" ) );
$.data( this, "scrollCheck", setTimeout(function() {
$output.html( stopped );
}, 250) );
});
});
})( jQuery );
=======>>>>
Working Example here
I did something like this:
var scrollEvents = (function(document, $){
var d = {
scrolling: false,
scrollDirection : 'none',
scrollTop: 0,
eventRegister: {
scroll: [],
scrollToTop: [],
scrollToBottom: [],
scrollStarted: [],
scrollStopped: [],
scrollToTopStarted: [],
scrollToBottomStarted: []
},
getScrollTop: function(){
return d.scrollTop;
},
setScrollTop: function(y){
d.scrollTop = y;
},
isScrolling: function(){
return d.scrolling;
},
setScrolling: function(bool){
var oldVal = d.isScrolling();
d.scrolling = bool;
if(bool){
d.executeCallbacks('scroll');
if(oldVal !== bool){
d.executeCallbacks('scrollStarted');
}
}else{
d.executeCallbacks('scrollStopped');
}
},
getScrollDirection : function(){
return d.scrollDirection;
},
setScrollDirection : function(direction){
var oldDirection = d.getScrollDirection();
d.scrollDirection = direction;
if(direction === 'UP'){
d.executeCallbacks('scrollToTop');
if(direction !== oldDirection){
d.executeCallbacks('scrollToTopStarted');
}
}else if(direction === 'DOWN'){
d.executeCallbacks('scrollToBottom');
if(direction !== oldDirection){
d.executeCallbacks('scrollToBottomStarted');
}
}
},
init : function(){
d.setScrollTop($(document).scrollTop());
var timer = null;
$(window).scroll(function(){
d.setScrolling(true);
var x = d.getScrollTop();
setTimeout(function(){
var y = $(document).scrollTop();
d.setScrollTop(y);
if(x > y){
d.setScrollDirection('UP');
}else{
d.setScrollDirection('DOWN');
}
}, 100);
if(timer !== 'undefined' && timer !== null){
clearTimeout(timer);
}
timer = setTimeout(function(){
d.setScrolling(false);
d.setScrollDirection('NONE');
}, 200);
});
},
registerEvents : function(eventName, callback){
if(typeof eventName !== 'undefined' && typeof callback === 'function' && typeof d.eventRegister[eventName] !== 'undefined'){
d.eventRegister[eventName].push(callback);
}
},
executeCallbacks: function(eventName){
var callabacks = d.eventRegister[eventName];
for(var k in callabacks){
if(callabacks.hasOwnProperty(k)){
callabacks[k](d.getScrollTop());
}
}
}
};
return d;
})(document, $);
the code is available here: documentScrollEvents
Minor update in your answer. Use mouseover and out function.
$(document).ready(function() {
function ticker() {
$('#ticker li:first').slideUp(function() {
$(this).appendTo($('#ticker')).slideDown();
});
}
var ticke= setInterval(function(){
ticker();
}, 3000);
$('#ticker li').mouseover(function() {
clearInterval(ticke);
}).mouseout(function() {
ticke= setInterval(function(){ ticker(); }, 3000);
});
});
DEMO
I was trying too add a display:block property for social icons that was previously hidden on scroll event and then again hide after 2seconds. But
I too had a same problem as my code for timeout after first scroll would start automatically and did not had reset timeout idea. As it didn't had proper reset function.But after I saw David's idea on this question I was able to reset timeout even if someone again scrolled before actually completing previous timeout.
problem code shown below before solving
$(window).scroll(function(){
setTimeout(function(){
$('.fixed-class').slideUp('slow');
},2000);
});
edited and working code with reset timer if next scroll occurs before 2s
var timer=null;
$(window).scroll(function(){
$('.fixed-class').css("display", "block");
if(timer !== null) {
clearTimeout(timer);
}
timer=setTimeout(function(){
$('.fixed-class').slideUp('slow');
},2000);
});
My working code will trigger a hidden division of class named 'fixed-class' to show in block on every scroll. From start of latest scroll the timer will count 2 sec and then again change the display from block to hidden.
For more precision you can also check the scroll position:
function onScrollEndOnce(callback, target = null) {
let timeout
let targetTop
const startPosition = Math.ceil(document.documentElement.scrollTop)
if (target) {
targetTop = Math.ceil(target.getBoundingClientRect().top + document.documentElement.scrollTop)
}
function finish(removeEventListener = true) {
if (removeEventListener) {
window.removeEventListener('scroll', onScroll)
}
callback()
}
function isScrollReached() {
const currentPosition = Math.ceil(document.documentElement.scrollTop)
if (targetTop == null) {
return false
} else if (targetTop >= startPosition) {
return currentPosition >= targetTop
} else {
return currentPosition <= targetTop
}
}
function onScroll() {
if (timeout) {
clearTimeout(timeout)
}
if (isScrollReached()) {
finish()
} else {
timeout = setTimeout(finish, 500)
}
}
if (isScrollReached()) {
finish(false)
} else {
window.addEventListener('scroll', onScroll)
}
}
Usage example:
const target = document.querySelector('#some-element')
onScrollEndOnce(() => console.log('scroll end'), target)
window.scrollTo({
top: Math.ceil(target.getBoundingClientRect().top + document.documentElement.scrollTop),
behavior: 'smooth',
})
Here's an answer that doesn't use any sort of timer, thus in my case predicted when the scrolling actually ended, and is not just paused for a bit.
function detectScrollEnd(element, onEndHandler) {
let scrolling = false;
element.addEventListener('mouseup', detect);
element.addEventListener('scroll', detect);
function detect(e) {
if (e.type === 'scroll') {
scrolling = true;
} else {
if (scrolling) {
scrolling = false;
onEndHandler?.();
}
}
}
}

Categories

Resources