jquery prevent animation queue build up - javascript

I have problem, if user clicks too fast on button, animation queue is keep piling up so it stops works properly. I would need behaviors, that when he clicks on button animation will always start current animation from beginning and forget about past animations in queue.
I tried stop(true, true) and clearQueue() adding to code below, but I'm doing this based on luck because don't understand jQuery well and didn't succeed with hours of trying.
var o, s, l;
e.animate = function(n, a, r, l, u) {
o = u, s = [], i(n), t(".unddd").each(function(e, n) {
Z.enqueue(function() {
t(n).removeClass("unddd", 1, null, Z.dequeue)
})
}), Z.enqueue(function() {
t(".unindicated").addClass("used_for_hand", o, null, Z.dequeue)
}, t(".unindicated").size()), Z.enqueue(function() {
t("#paybe").addClass("highlight"), Z.dequeue()
}), Z.enqueue(function() {
}), Z.startDequeue()
}, e.animateOut = function(e) {
e ? Z.enqueue(function() {
t(".status").hide(), t(".ble").addClass("discard", o, null, Z.dequeue)
}, t(".ble").size()) : Z.enqueue(function() {
t(".ble").not(".held").addClass("discard", o, null, Z.dequeue)
}, t(".ble").not(".held").size()), Z.startDequeue()
}

You could add a boolean check "isRunning" and set it to true during your animate function, then set it to false when the animation is complete.
After that, just check isRunning == false before calling any animation methods. The animation will only run if it isnt already running.

i managed to work-around this, by disabling onclick after click and, enabling it after animation end

Related

Can I protect custom events from "preventDefault"?

We have a Stencil web component that renders a user dialog. It consists of an "outerComponent" and an "innerComponent".
The outer one cares about dealing with the browser (props, load stuff from cookies etc.) and the inner one renders the actual HTML and lets the user operate it.
(Actually there are more components used inside, from a different project for the UI components such as checkbox, button etc. But I don't think that's relevant here.)
When a checkbox, button etc. is clicked in <inner-component> an onclick-handler within the component is called that executes some UI logic (e.g. set the "checked" property) and then emits a custom event, e.g.:
#Event() checkboxToggleModalEvent: EventEmitter<OptionType>;
...
<checkbox-comp fid="...">
<input type="checkbox" checked={optionCheckbox.userSelection} onClick={this.handleCheckbox} />
...
</checkbox-comp>
...
private handleCheckbox(event: Event) {
const checkboxElement: HTMLInputElement = event.target as HTMLInputElement;
...
const selection: OptionType = { name: indexId, userSelection };
this.checkboxToggleModalEvent.emit(selection);
}
Now, in <outer-component> this event is listened for and the handler cares for the "technical" logic:
#Listen("checkboxToggleModalEvent")
checkboxToggleModalEventHandler(event) {
LogService.log.debug(event);
... some technical logic
}
This works fine in most cases. Now we have an integration on one site, where the events apparently do not get emitted correctly or somehow lost in the middle.
The UI logic is executed normally but the handler in outerComponent never gets called.
I was able to find the piece of code from an integrated library that causes the problem (sorry for pasting the whole function!):
// From the imported library on customer website:
function(t, exports) {
try {
var e = new window.CustomEvent("test");
if (e.preventDefault(),
!0 !== e.defaultPrevented)
throw new Error("Could not prevent default")
} catch (t) {
var n = function(t, e) {
var n, r;
return e = e || {
bubbles: !1,
cancelable: !1,
detail: void 0
},
n = document.createEvent("CustomEvent"),
n.initCustomEvent(t, e.bubbles, e.cancelable, e.detail),
r = n.preventDefault,
n.preventDefault = function() {
r.call(this);
try {
Object.defineProperty(this, "defaultPrevented", {
get: function() {
return !0
}
})
} catch (t) {
this.defaultPrevented = !0
}
}
,
n
};
n.prototype = window.Event.prototype,
window.CustomEvent = n
}
}
If I remove this, everything works as expected.
Now, I'm wondering if we can somehow "protect" our events from being intercepted like this as the component should really work in any case (that's why we chose this technology).
But I also would be very grateful for any hints to what might actually cause the problem.
Thanks a lot!!
n.prototype = window.Event.prototype,
window.CustomEvent = n
Looks like they overloaded CustomEvent and injected their own code.
This is the drawback of using 3rd party software.
In this case, only way to get around this is to get in early, and overload CustomEvent yourself.
But you then have the challenge of making their code work; because they did this overloading for a reason.
What is the 3rd party software? Publically shame them.
For those who want to try overloading, execute this early:
window.customeElements.define = () => {}

Detecting when finger is removed from touchpad but scroll is still happening due to "predictive touch" - JS

I have a function that handles scrolls every period of time. This function works when users scroll with mouse wheel:
let shouldHandle = true
window.addEventListener('wheel', e => {
if (shouldHandle) {
handleScroll(e) // I will handle scrolls here
shouldHandle = false
setTimeout(() => {
shouldHandle = true
}, 750)
}
})
However, when I am scrolling using my laptop's touchpad, scroll still happens even when I remove my finger (especially when I accelerate my finger enough and scroll, then immediately remove my finger from the touchpad). As a result, scroll still happens after the 750ms even when the users are not technically scrolling. This question has been asked here. The question did not receive an answer to handle this behavior.
I want to handle scroll only after a period of time has elapsed from the last scroll. The scrolls I want to handle must not be because of scrolling caused by this "predictive touch" scroll. Is there a way to achieve this as of now?
The only way to handle that that comes to mind is to watch scrollY for a period of time (polling, perhaps every 50ms or so) after you've seen a scroll start and wait for it to stabilize (X milliseconds in the same position, for whatever value of X you decide on) and only then consider that scroll "completed" and start your 750ms timer. Constantly polling would be bad, but doing it for a brief period while the scrolling is still actively occurring seems acceptable.
Rough sketch (could probably use an overall timeout, for instance):
// VERY ROUGHLY
let shouldHandle = true
let lastScrollY = null
let lastScrollTimer = 0
window.addEventListener('wheel', e => {
if (shouldHandle) {
handleScroll(e) // I will handle scrolls here
shouldHandle = false
waitForScrollEnd(() => {
setTimeout(() => {
shouldHandle = true
}, 750) // Or perhaps 700 on the basis that up to 50 was spent in `waitForScrollToEnd`
})
}
})
function waitForScrollEnd(cb) {
clearTimeout(lastScrollTimer)
lastScrollY = window.scrollY
lastScrollTimer = setTimeout(poll, 50)
function poll() {
if (lastScrollY === window.scrollY) {
lastScrollY = null
lastScrollTimer = 0 // This is entirely optional but makes it parallel with the `else` below
cb()
} else {
lastScrollY = window.scrollY
lastScrollTimer = setTimeout(poll, 50)
}
}
}

Can I modify a Javascript library at runtime?

I use a web based development environment for data entry forms. The environment lets me create rules that are triggered by form events. These events run in js in the browser but there is almost no support for debugging, which makes problem solving a nightmare.
The code in the browser has a central event handler, which has a logging feature but the quantity of information produced by it is so large, it makes finding what you need difficult. Think of info level logging gone mad. Plus you have to open a separate window to access the log.
I need to be able to log certain events to the console, or trigger breakpoints at specified rules. Is there a way to modify the environment's code below to allow it to call my debugger instead of (or in addition) to SFLog?
function handleEvent(n,t,q,r,u,f,e,o,s,h,c,l){
if(eventsCancelled!==!0){
SFLog({type:3,source:"handleEvent",category:"Events",
message:"{2} event fired from {1} - {0}",parameters:[n,t,q]});
var b="Events/Event[#SourceID='"+n+"'][#SourceType='"+t+"'][Name/text()="+q.xpathValueEncode()+"]";
//Rest of the event handler...
function SFLog(n){
if(checkExists(_debug)){var s=translateDebugLevel(n.type);
if(s>=_debug){
varu=n.type,e=n.source,r=n.category,q=n.message,h=n.parameters,o=checkExists(n.exception)? WriteExceptionXml(n.exception):null,t=n.data,l=checkExists(n.humanateData)?
n.humanateData:!0,f=(new Date).format("yyyy-MM-ddTHH:mm:ss:fff");
checkExists(t)&&(dataString=t.xml,checkExists(dataString)||(dataString=t),l===!0&&(dataString=Humanate(dataString)));
//more code for SFLog...
Cleaned Up Code
function handleEvent(n, t, q, r, u, f, e, o, s, h, c, l) {
if (eventsCancelled !== !0) {
SFLog({
type: 3,
source: "handleEvent",
category: "Events",
message: "{2} event fired from {1} - {0}",
parameters: [n, t, q]
});
var b = "Events/Event[#SourceID='" + n + "'][#SourceType='" + t + "'][Name/text()=" + q.xpathValueEncode() + "]";
//Rest of the event handler...
}
}
function SFLog(n) {
if (checkExists(_debug)) {
var s = translateDebugLevel(n.type);
if (s >= _debug)
{
varu = n.type;
e = n.source;
r = n.category;
q = n.message;
h = n.parameters;
o = checkExists(n.exception) ?
WriteExceptionXml(n.exception) :
null;
t = n.data;
l = checkExists(n.humanateData) ?
n.humanateData :
!0;
f = (new Date).format("yyyy-MM-ddTHH:mm:ss:fff");
checkExists(t) &&
(dataString = t.xml, checkExists(dataString) ||
(dataString = t), l === !0 && (dataString = Humanate(dataString)));
//more code for SFLog.
I agree with #Eddie but one solution could be to wrap the logger function and and override it, and only log the events you care about. e.g.:
function SFLog(n){
//old code
}
//run on the console, the first line, and then the second.
var oldLoggger = SFLog;
function SFLog(n) {
if(/*some criteria*/) {
oldLogger(n);
}
}
This way you can run the default logger with different conditions, but it probably would be best if you could modify the logger code itself to accept certain criteria, like, event type to log, or targetElement's ID, class etc.
PD: If you need to modify the eventHandler itself, you should:
remove the event handler first.
create your wrapper function.
add the wrapper function as event handler

Javascript forgets about other keydown when a keyup is triggered

I followed this question/answer to create a multiple key press detector. It works as intended: you press D and the square moves right, then you press S while pressing the D and the square moves in angle. So far so good. However, if you are pressing D and S at the same time and you unpress the S, the square will stop moving. The expected behaviour is that it keeps moving to the right, since you are still pressing D.
Here's a jsfiddle and the demo I'm making (my web).
So, why is .keyup() making other .keydown() event invalid? This is the offending jQuery code:
// Check if it's a function
function isFunction(functionToCheck) {
var getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
}
// Store the keys pressed and callback for each key
var keys = {};
var keycalls = {};
// Loop and call all the needed callbacks
function run_keys() {
$.each(keys, function (index, value) {
if (value == true) {
console.log(keys);
var fun = keycalls[index];
if (isFunction(fun))
fun();
}
});
}
// Store a key
$(document).keydown(function (e) {
keys[e.which] = true;
run_keys();
});
// Delete a key
$(document).keyup(function (e) {
delete keys[e.which];
run_keys();
});
// Assign a callback to a key
keyboard = function (key, callback) {
console.log(callback);
keycalls[key.toUpperCase().charCodeAt()] = callback;
}
// Assign keys
// Assign w to up action
keyboard("w", function () {
$(".keyboard").animate({
'top': "-=5px"
}, 0);
});
keyboard("a", function () {
$(".keyboard").animate({
'left': "-=5px"
}, 0);
});
keyboard("s", function () {
$(".keyboard").animate({
'top': "+=5px"
}, 0);
});
keyboard("d", function () {
$(".keyboard").animate({
'left': "+=5px"
}, 0);
});
Disclaimer: this is just for fun and learning, not about having a game engine. I know there are many good ones already available.
UPDATE
One of the solutions I've thought is making an independent loop for calling run_keys(), but I'd like to avoid that if possible. It works, however I'm still interested in the problem.
I believe your problem is coming from the end of keypress events after the keyup, rather than anything "going wrong", use a setTimout loop instead of calling run_keys at the end of each handler.
Here is an example of a setTimeout loop
(function looper() {
run_keys();
window.setTimeout(looper, 100);
}());
It's a bit like setInterval but the delay starts after the invocation, meaning you won't get (the same kind of) cascading errors as is possible with setInterval.

Strange behaviour when calling an object more than once

I have some code here that basically fades in and out some text from an array.
If I call it once, it works as intended. IE, it displays some text for 3 seconds and the fades in the next text from array.
If however I try to restart the animation again, it seems to run twice as fast. Can anyone help me? I think I can cancelling the previous animation correctly
In the code below, I call rotator.start(); just to demo the problem I am having. Just call it once to see how it should behave.
http://jsfiddle.net/zmTAC/3/
<div id="foobar"></div>
<script>
var rotator = {
quoteIndex: -1,
duration: 500,
delay: 3000,
play: false,
quotes: [],
theElement: null,
start: function (quotes, theElement) {
this.quoteIndex = -1;
this.quotes = quotes;
this.theElement = theElement;
this.stop();
this.play = true;
this.showNextQuote();
return this;
},
showNextQuote: function () {
this.quoteIndex = (this.quoteIndex + 1) % this.quotes.length;
if (this.play) {
$(this.theElement).html(this.quotes[this.quoteIndex])
.fadeIn(this.duration)
.delay(this.delay)
.fadeOut(this.duration, this.showNextQuote.bind(this));
}
return this;
},
stop: function () {
this.play = false;
$(this.theElement).stop(true, true).show();
return this;
}
};
rotator.start(["foo1", "bar1"], "#foobar");
rotator.start(["foo2", "bar2"], "#foobar");
rotator.start(["foo3", "bar3"], "#foobar");
rotator.start(["foo4", "bar4"], "#foobar");
rotator.start(["foo5", "bar5"], "#foobar");
rotator.start(["foo6", "bar6"], "#foobar");
rotator.start(["foo7", "bar7"], "#foobar");
rotator.start(["foo8", "bar9"], "#foobar");
rotator.start(["foo9", "bar9"], "#foobar");
rotator.start(["foo0", "bar0"], "#foobar");
</script>
It looks like $.stop() is not cancelling the completed callback to $.fadeOut(). I added a third element to the quotes array in the final call and every other quote was shorter (i.e. the second, then the first(in the second loop), then the third, etc.)
I'm not certain that is the cause (and if it is it is a bug in $.stop()) but a hack (but it works) is to create a container element which you compeltely remove in rotator.stop() and (re)create in rotator.start(). Like this:
var rotator = {
quoteIndex: -1,
duration: 500,
delay: 3000,
play: false,
quotes: [],
theElement: null,
start: function (quotes, theElement) {
this.quoteIndex = -1;
this.quotes = quotes;
this.stop();
this.theElement = theElement;
$(this.theElement).html('<span class="rotator"/>');
this.play = true;
this.showNextQuote();
return this;
},
showNextQuote: function () {
if (this.play) {
this.quoteIndex = (this.quoteIndex + 1) % this.quotes.length;
$('.rotator', this.theElement).html(this.quotes[this.quoteIndex])
.fadeIn(this.duration)
.delay(this.delay)
.fadeOut(this.duration, this.showNextQuote.bind(this));
}
return this;
},
stop: function () {
this.play = false;
$('.rotator', this.theElement).stop(true, true).remove();
$(this.theElement).show();
return this;
}
};
See it working in this fiddle
By calling start method you are overriding the quotes. It is playing between foo0 and bar0. What you want to do is pass a single array of quotes like so:
rotator.start(["foo1", "bar1", "foo2", "bar2", "foo3", "bar3"], "#foobar");
Works fine on Firefox and IE for me. JSFiddle I also changed to jQuery 1.11.0 as 1.10 didn't work at all on IE.
EDIT
I am not sure what went wrong on jQuery side or on browser side. What I know you were doing a known mistake with the this.play variable. You set it to false, then to true, and then checked if it was true in the same function. If you run this function twice at the same time what you might get is:
this.stop(); // second execution is on this line
this.play = true; // first execution is on this line
this.showNextQuote();
What might happen is the second execution sets this.play to false and then the first execution sets it to true. What happens next in showNextQuote method is run by both executions as this.play is true for both (showNextQuote function checks if this.play is true).
I am not sure if it was the problem and I am pretty sure it was not the problem but what I showed you is what happens when you try to run multiple asynchronous operations. I am pretty sure it is buggy jQuery. Maybe newer version fixed this?
Maybe some other community member has more to say on this?
Solution
EDIT2
Thinking of it, JS would not allow it to happen as it would not execute this code in a separate threads. I wont delete it though as it explains you how easy it is to make this mistake with asynchronous jQuery that might be causing your problem.
can you try this instead of the commented line in your stop method
//$(this.theElement).stop(true, true).show();
$(this.theElement).finish().show();
working example here

Categories

Resources