Same function repeatedly called resets the setTimeout inner function - javascript

All,
I have a 'credit module' (similar to credit system in games), which when a user performs an action, creates an inner div with the cost to be added or substracted so user can see what the cost of the last action was.
Problem: Everything works fine as long as the function is called once, if the user performs multiple actions quickly, the setTimeout functions (which are suppose to animate & then delete the cost div) donot get executed. It seems the second instance of the function resets the setTimeout function of the first.
(function()
{
$("#press").on("click", function(){creditCost(50)});
function creditCost(x)
{
var eParent = document.getElementById("creditModule");
// following code creates the div with the cost
eParent.innerHTML += '<div class="cCCost"><p class="cCostNo"></p></div>';
var aCostNo = document.getElementsByClassName("cCostNo");
var eLatestCost = aCostNo[aCostNo.length - 1];
// following line assigns variable to above created div '.cCCost'
var eCCost = eLatestCost.parentNode;
// cost being assigned
eLatestCost.innerHTML = x;
$(eCCost).animate ({"left":"-=50px", "opacity":"1"}, 250, "swing");
// following code needs review... not executing if action is performed multiple times quickly
setTimeout(function()
{
$(eCCost).animate ({"left":"+=50px", "opacity":"0"}, 250, "swing", function ()
{
$(eCCost).remove();
})
}, 1000);
}
})();
jsfiddle, excuse the CSS

eParent.innerHTML += '<div class="cCCost"><p class="cCostNo"></p></div>';
is the bad line. This resets the innerHTML of your element, recreating the whole DOM and destroying the elements which were referenced in the previous invocations - letting their timeouts fail. See "innerHTML += ..." vs "appendChild(txtNode)" for details. Why don't you use jQuery when you have it available?
function creditCost(x) {
var eParent = $("#creditModule");
// Create a DOM node on the fly - without any innerHTML
var eCCost = $('<div class="cCCost"><p class="cCostNo"></p></div>');
eCCost.find("p").text(x); // don't set the HTML if you only want text
eParent.append(eCCost); // don't throw over all the other children
eCCost.animate ({"left":"-=50px", "opacity":"1"}, 250, "swing")
.delay(1000) // of course the setTimeout would have worked as well
.animate ({"left":"+=50px", "opacity":"0"}, 250, "swing", function() {
eCCost.remove();
});
}

You are starting an animation and scheduling a timeout to work on DOM elements that will get modified in the middle of that operation if the user clicks quickly. You have two options for fixing this:
Make the adding of new items upon a second click to be safe so that it doesn't mess up the previous animations.
Stop the previous animations and clean them up before starting a new one.
You can implement either behavior with the following rewrite and simplification of your code. You control whether you get behavior #1 or #2 by whether you include the first line of code or not.
function creditCost(x) {
// This first line of code is optional depending upon what you want to happen when the
// user clicks rapid fire. With this line in place, any previous animations will
// be stopped and their objects will be removed immediately
// Without this line of code, previous objects will continue to animate and will then
// clean remove themselves when the animation is done
$("#creditModule .cCCost").stop(true, false).remove();
// create HTML objects for cCCost
var cCCost = $('<div class="cCCost"><p class="cCostNo">' + x + '</p></div>');
// add these objects onto end of creditModule
$("#creditModule").append(cCCost);
cCCost
.animate ({"left":"-=50px", "opacity":"1"}, 250, "swing")
.delay(750)
.animate({"left":"+=50px", "opacity":"0"}, 250, "swing", function () {
cCCost.remove();
});
}
})();
Note, I changed from setTimeout() to .delay() to make it easier to stop all future actions. If you stayed with setTimeout(), then you would need to save the timerID returned from that so that you could call clearTimeout(). Using .delay(), jQuery does this for us.

Updated code for anyone who might want to do with mostly javascript. Jsfiddle, excuse the CSS.
function creditCost(x)
{
var eParent = document.getElementById("creditModule");
var eCCost = document.createElement("div");
var eCostNo = document.createElement("p");
var sCostNoTxt = document.createTextNode(x);
eCCost.setAttribute("class","cCCost");
eCostNo.setAttribute("class","cCostNo");
eCostNo.appendChild(sCostNoTxt);
eCCost.appendChild(eCostNo);
eParent.insertBefore(eCCost, document.getElementById("creditSystem").nextSibling);
$(eCCost).animate ({"left":"-=50px", "opacity":"1"}, 250, "swing");
setTimeout(function()
{
$(eCCost).animate ({"left":"+=50px", "opacity":"0"}, 250, "swing", function ()
{
$(eCCost).remove();
})
}, 1000);
}

Related

How to detect a double-click-drag in Javascript/jQuery

I'd like to detect in a web page when the user selects some text by dragging. However, there's one scenario in Windows which I'm calling a "double-click-drag" (sorry if there's already a better name I don't know) and I can't figure out how to detect it. It goes like this:
press mouse button
quickly release mouse button
quickly press mouse button again
drag with the button held down
This causes the dragging to select whole Words. It's quite a useful technique from the user perspective.
What I'm trying to do is tell the difference between a double-click-drag and a click followed by a separate drag. So when I get to step 2 I will get a click event but I don't want to treat it as a click yet; I want to see if they're about to immediately do step 3.
Presumably Windows detects this on the basis of the timing and how much the mouse has moved between step 2 and 3, but I don't know the parameters it uses so I can't replicate the windows logic. note that even if the mouse doesn't move at all between step 2 and 3, I still get a mousemove event.
I realise that I should be designing interfaces that are touch-friendly and device-neutral, and I have every intention of supporting other devices, but this is an enterprise application aimed at users on windows PCs so I want to optimize this case if I can.
We've done something similar. Our final solution was to create a click handler that suppressed the default response, and then set a global variable to the current date/time. We then set another function to fire in some 200ms or so that would handle the "click" event. That was our base function.
We then modified it to look at the global variable to determine when the last click occured. If it's been less than 200ms (modify based on your needs) we set a flag that would cause the click handler to fizzle and called a double click handler.
You could extend that approach by having your click and double click handlers manually fire the drag functionality.
I don't have access to the aforementioned code right now, but here is an example of that framework being used to track keyboard clicks to determine if a scanner or user has finished typing in a field:
var lastKeyPress = loadTime.getTime();
// This function fires on each keypress while the cursor is in the field. It checks the field value for preceding and trailing asterisks, which
// denote use of a scanner. If these are found it cleans the input and clicks the add button. This function also watches for rapid entry of keyup events, which
// also would denote a scanner, possibly one that does not use asterisks as control characters.
function checkForScanKeypress() {
var iVal = document.getElementById('field_id').value;
var currentTime = new Date()
var temp = currentTime.getTime();
if (temp - lastKeyPress < 80) {
scanCountCheck = scanCountCheck + 1;
} else {
scanCountCheck = 0;
}
lastKeyPress = currentTime.getTime();
}
// The script above tracks how many successive times two keyup events have occurred within 80 milliseconds of one another. The count is reset
// if any keypress occurs more than 80 milliseconds after the last (preventing false positives from manual entry). The script below runs
// every 200 milliseconds and looks to see if more than 3 keystrokes have occurred in such rapid succession. If so, it is assumed that a scanner
// was used for this entry. It then waits until at least 200 milliseconds after the last event and then triggers the next function.
// The 200ms buffer after the last keyup event insures the function is not called before the scanner completes part number entry.
function checkForScan() {
var currentTime = new Date();
var temp = currentTime.getTime();
if (temp - lastKeyPress > 200 && scanCountCheck > 3) {
FiredWhenUserStopsTyping();
scanCountCheck = 0;
}
setTimeout(checkForScan, 200);
}
Here is some code that I just wrote up based upon the above ideas. It's not tested and doesn't contain the actual drag events, but should give you a good starting point:
var lastClick = loadTime.getTime();
function fireOnClickEvent(event) {
event.preventDefault;
var currentTime = new Date()
var temp = currentTime.getTime();
if (temp - lastClick < 80) {
clearTimeout(tf);
doubleClickHandler();
} else {
tf = setTimeout(singleClickHandler, 100);
}
lastClick = currentTime.getTime();
}
function singleClickHandler() {
// Begin normal drag function
}
function doubleClickHandler() {
// Begin alternate drag function
}
A single double-click-drag action involves the following events in sequence:
mousedown -> mouseup -> click -> mousedown -> mousemove
With that in mind, I came up with this simple solution:
let maybeDoubleClickDragging = false;
let maybeDoubleClickDraggingTimeout;
const element = document.querySelector('#container');
element.addEventListener("click", function (e) {
maybeDoubleClickDragging = true;
element.removeEventListener("mousemove", handleMousemove);
});
element.addEventListener("mousedown", (e) => {
element.addEventListener("mousemove", handleMousemove);
if (maybeDoubleClickDragging) {
clearTimeout(maybeDoubleClickDraggingTimeout);
return;
}
});
element.addEventListener("mouseup", (event) => {
maybeDoubleClickDraggingTimeout = setTimeout(() => {
maybeDoubleClickDragging = false;
}, 200);
});
function handleMousemove(e) {
if(maybeDoubleClickDragging) {
element.textContent = 'you are double-click-dragging'
}
}
#container {
width: 300px;
height: 300px;
background: yellow;
}
<div id="container"></div>

Waiting until animation is over until replacing data inside the div [duplicate]

This question already has answers here:
How to wait for one jquery animation to finish before the next one begins?
(6 answers)
Closed 1 year ago.
I am having trouble changing the contents of a div. What I want to achieve is wait for the animation to end and then change the content to launch the animation to reveal the new info. This code works from time to time, meaning sometimes it hangs (isWorking never becomes false, because animation freezes or animation never has the time to finish from the constant looping inside the while.) Is there a way to wait for the animation to end and then change the content? (The way you see it below allows me to browse for the new content while the animation is ongoing which saves time for the end user.) Is there a way to catch when the animation ends?
function DesignResults(InnerHTML) {
while(isWorking){
}
$("#holder").html(InnerHTML);
ShowSearch(true);
}
var isWorking = false;
function ShowSearch(show) {
isWorking = true;
var outer = $("#outer");
var inner = $("#inner");
var height = 0;
if (show == true)
height = inner.outerHeight();
var loaderHeight
if (height > 0) {
loaderHeight = 0;
} else {
loaderHeight = 31;
}
outer.animate({
'height': height + "px"
}, 1600, function () {
$("#loading").animate({ 'height': loaderHeight + "px" }, 900, function () { });
isWorking = false;
});
}
I understand that $(elem).is(':animated') will give me if the animation is still in progress, but that still freezes everything due to the constant looping of the while. Can anyone point me to the right direction?
EDIT:
I guess I am misunderstood. Here is the plan I want to achieve:
Start hiding animation.
While the animation is hiding I am launching another function to get the content
If I get the content faster I wish to wait for the animation to end then change the content and show the layer again.
There isn't a issue here if the data takes more than a second to return
I agree that i can do it after the animation completes as you suggested, but I have put the animation to almost 1 second execute time and that time can be used for the data to be pulled from the database. I am looking for the most effective code.
The last parameter in this function:
$("#loading").animate({ 'height': loaderHeight + "px" }, 900, function () { });
Is the 'complete' function. This function is called when the animation is complete.
So, you can do something in this function when it's complete, like so:
$("#loading").animate({ 'height': loaderHeight + "px" }, 900, function () {
alert('animations complete!');
});
----Edit----
Based on your edits, you probably want to do something like this.
Have a variable that will let you know when both are finished:
var signal = 0;
Have a function to change your content with this in it:
function changeContent() {
signal++;
if (signal === 2) {
// change the content code here
signal = 0;
}
}
When the animation is finished, call:
changeContent();
When you've grabbed the data, call:
changeContent();
When changeContent() is called the first time by either function, it will increment signal to 1 and exit the function. Then on the second time, it will increment signal to 2. Since signal === 2, you know that both the animation and grabbing the data is complete, so you can now change your content.
The good part about using an integer to do this, is if you want to have 3, 4, 5+ functions finish working before changing your content. In this case you just have to change the condition by increasing the number in if (signal === 2).
Just add the additional task here
$("#loading").animate({ 'height': loaderHeight + "px" }, 900, function () { /* Change Content */ });
Than it would be execute after the animation

Write on html on load

I have created a function who tracks on which slide I am currently on and display the result
e.g. If I am on slide 2 of 3 it will display 2/3
my problem is that right now it is set to do that every time I click the forward arrow but it displays nothing on page load.
$('.forward').click(function() {
var current = $('#slider').data('AnythingSlider').currentPage; // returns page #
var count = $("#slider").children().length - 2;
$("#bottom-image").html(current + "/" + count) ;
});
I am trying to find out how to execute this function on page load and where to put it in my code. I am currently learning Javascript through Codecadamedy so I have a basic knowledge of Javascript but I am not enough fluent right now to figure this one out.
Here is a link to the current non working code : http://www.soleilcom.com/metacor_dev/our-plants.php
It looks like you are using jQuery. To execute a function on DOM load in query, do this:
$(document).ready(function() {
/* your code */
});
In your case, that would be:
$(document).ready(function() {
$('.forward').click(function() {
var current = $('#slider').data('AnythingSlider').currentPage; // returns page #
var count = $("#slider").children().length - 2;
$("#bottom-image").html(current + "/" + count) ;
});
});
For things like most event handlers, and most other things, initializing at DOM load is good enough. If your code needs to take account for rendered elements or rendered heights, use $(window).load() instead. (In your case DOM load is fine).
Note that this will just establish the click handler at load time. To also run it once, you can do it automatically by either calling the function yourself or triggering a click. To call it yourself, first define another function. The use the function in both the click handler and in one immediate call:
$(document).ready(function() {
var forward = function() {
var current = $('#slider').data('AnythingSlider').currentPage; // returns page #
var count = $("#slider").children().length - 2;
$("#bottom-image").html(current + "/" + count) ;
}
$('.forward').click(forward);
forward();
});
Or to trigger it yourself, just define the click handler and trigger a click programatically:
$(document).ready(function() {
$('.forward').click(function() {
var current = $('#slider').data('AnythingSlider').currentPage; // returns page #
var count = $("#slider").children().length - 2;
$("#bottom-image").html(current + "/" + count) ;
}).click();
});
It looks like you are using jQuery, so you would use this:
$(document).ready(function(){
// Code here
});
Or, you can use the shortcut:
$(function(){
// Code here
});
Read more about this on the jQuery website
If you give the function a name, then you can use it multiple times:
$(document).ready(function() {
// Bind to click event
$('.forward').click(forwardSlide)
// Execute function on page load
forwardSlide();
});
function forwardSlide() {
var current = $('#slider').data('AnythingSlider').currentPage; // returns page #
var count = $("#slider").children().length - 2;
};
Is this what you're looking for?

How to pass an array to an user defined object or convert non-OO code to OO? javascript

I really have trouble with OO coding in js. I have written a piece of code which rotates through 3 divs, and pauses on hover of any div. This code is just regular js using an array/json as the input. the code is a bit long so sorry about that. I just need some guidance on how I can convert this primitive code to a better form, as in OO and encap. When I tried myself I could not pass the slides array/json to my defined object. Is there a trick or guideline i can follow on how to rewrite this to a better form?
Edit - What is a good guideline to follow so I can rewrite this with objects instead of global variables and loose functions
var slideIndex = 0;
var prevIndex = 0;
var t;
function initPromo(){
sortSlides();
nextPromo();
addListeners();
}
function addListeners(){
for(var i=0; i<slides.length; i++)
$(slides[i].el).hover(function(){ stopPromo(); }, function(){ resumePromo(); });
}
function resumePromo(){ startTimer(); }
function stopPromo(){ clearTimeout(t); }
function nextPromo(){
if(slideIndex > 0 || prevIndex > 0) $(slides[prevIndex].el).css("display","none");
$(slides[slideIndex].el).css("display","block");
prevIndex = slideIndex;
slideIndex = (slideIndex<slides.length-1) ? slideIndex+1 : 0;
startTimer();
}
function startTimer(){ t = setTimeout("nextPromo()", 3000); }
function SortByWeight(a,b) { return b.weight - a.weight; }
function SortByWeightFr(a,b) { return b.frWeight - a.frWeight; }
function sortSlides(){
($("body.en").length > 0) ? slides.sort(SortByWeight) : slides.sort(SortByWeightFr);
}
var slides = [
{
el:'#ps1',
weight:1,
frWeight:3
},
{
el:'#ps2',
weight:0.5,
frWeight:6
},
{
el:'#ps3',
weight:4,
frWeight:9
}
];
window.onload = function () {
initPromo();
};
HTML
<body class="en">
<div id="homepageSlides">
<div id="promoSlides">
<div id="ps1">ps1</div><div id="ps2">ps2</div><div id="ps3">ps3</div>
</div>
</div>
</body>
Edit: Early days in OO coding, not asked in the right way
Well your "plain javascript" code is already taking you part way there. The first function you have defined identies the domain object: Promo.
var Promo = function () { };
You have actions on an instance of promo, init, start, stop, resume, etc. These can be defined on the prototype of Promo.
Promo.prototype.init = function() {
// ...
};
It could get a little annoying typing prototype each time, so we could bundle the prototype into a pointer that allows us a lot easier access...
var Promo = function () { };
(function(obj) {
obj.init = function() {
// ...
};
})(Promo.prototype);
So we've got some structure but we need to now separate concerns. Throughout your plain javascript you've got config type data strewn through the code. It's generally a good idea to isolate these bits of data to a single entry point for your object.
obj.init = function(_el) {
// where _el is the base element of this widget
};
I see you're also using jQuery which is good because it gives you a lot of power. One convention I like to use is instead of passing a huge amount of config data into a given widget, I like to give my objects minimal config and let them inspect the HTML to determine additional configuration data. This has the added advantage of if you wanted to add slides in the future or otherwise make changes to the slide content you need'nt worry about changing the JS.
Let's say we were to alter the slide HTML to look like...
<div id="promoSlides">
<div data-type="slide" data-slide-id="1">ps1</div>
<div data-type="slide" data-slide-id="2">ps2</div>
<div data-type="slide" data-slide-id="3">ps3</div>
</div>
Using jQuery we could identify how many slides are present.
obj.init = function(_el) {
this.baseElement = $(_el);
this.slides = this.baseElement.find('*[data-type="slide"]');
};
Now we're passing in minimal config, we've separated out the identification of the slides to the HTML, and we've got a nice pattern for a self-sufficient object. The rest would be to fill in the details (totally untested, but something like this)...
var Promo = function () { };
(function (obj) {
obj.init = function(_el, _delay) {
// Initialize markup
this.baseElement = $(_el);
this.slides = this.baseElement.find('*[data-type="slide"]');
this.slideDelay = _delay;
// Sort slides
// (not sure what's going on here)
// Bind events
this.baseElement
.on('mouseenter', this.stop.bind(this))
.on('mouseleave', this.start.bind(this));
};
obj.start = function() {
this.timer = setInterval(this.advance.bind(this), this.slideDelay);
};
obj.stop = function() {
clearInterval(this.timer);
};
obj.advance = function() {
// Slide the visible slide off screen
// (note: the parent tag will need overflow:hidden)
var visible = this.baseElement.find('*[data-type="slide"]:visible');
visible.animate({ left: '-' + (visible.width()) + 'px' }, 1000);
// Slide the next slide in
var next = visible.next();
next.css('left', this.baseElement.width() + 1).animate({ left: '0' }, 1000);
};
})(Promo.prototype);
Note that I made use of bind which isn't supported yet in older versions of IE.
Its not the converting to object oriented style what is needed for that code there.
Here are issues i see there:
pollution of global scope
mixing fixed CSS rules with Javascript
use of .length attribute within a loop
no event delegation
misplacement of <script> tag, resulting in use of window.onload
creating new jQuery object when it is not needed
use of CSS3 selectors in jQuery calls
no clue how to use setTimeout()
tight coupling to HTML ( id on each slide )

How to improve image cross-fade performance?

I want to be able to do a cross fade transition on large images whose width is set to 100% of the screen. I have a working example of what I want to accomplish. However, when I test it out on various browsers and various computers I don't get a buttery-smooth transition everywhere.
See demo on jsFiddle: http://jsfiddle.net/vrD2C/
See on Amazon S3: http://imagefader.s3.amazonaws.com/index.htm
I want to know how to improve the performance. Here's the function that actually does the image swap:
function swapImage(oldImg, newImg) {
newImg.css({
"display": "block",
"z-index": 2,
"opacity": 0
})
.removeClass("shadow")
.animate({ "opacity": 1 }, 500, function () {
if (oldImg) {
oldImg.hide();
}
newImg.addClass("shadow").css("z-index", 1);
});
}
Is using jQuery animate() to change the opacity a bad way to go?
You might want to look into CSS3 Transitions, as the browser might be able to optimize that better than Javascript directly setting the attributes in a loop. This seems to be a pretty good start for it:
http://robertnyman.com/2010/04/27/using-css3-transitions-to-create-rich-effects/
I'm not sure if this will help optimize your performance as I am currently using IE9 on an amped up machine and even if I put the browser into IE7 or 8 document mode, the JavaScript doesn't falter with your current code. However, you might consider making the following optimizations to the code.
Unclutter the contents of the main photo stage by placing all your photos in a hidden container you could give an id of "queue" or something similar, making the DOM do the work of storing and ordering the images you are not currently displaying for you. This will also leave the browser only working with two visible images at any given time, giving it less to consider as far as stacking context, positioning, and so on.
Rewrite the code to use an event trigger and bind the fade-in handling to the event, calling the first image in the queue's event once the current transition is complete. I find this method is more well-behaved for cycling animation than some timeout-managed scripts. An example of how to do this follows:
// Bind a custom event to each image called "transition"
$("#queue img").bind("transition", function() {
$(this)
// Hide the image
.hide()
// Move it to the visible stage
.appendTo("#photos")
// Delay the upcoming animation by the desired value
.delay(2500)
// Slowly fade the image in
.fadeIn("slow", function() {
// Animation callback
$(this)
// Add a shadow class to this image
.addClass("shadow")
// Select the replaced image
.siblings("img")
// Remove its shadow class
.removeClass("shadow")
// Move it to the back of the image queue container
.appendTo("#queue");
// Trigger the transition event on the next image in the queue
$("#queue img:first").trigger("transition");
});
}).first().addClass("shadow").trigger("transition"); // Fire the initial event
Try this working demo in your problem browsers and let me know if the performance is still poor.
I had the same problem too. I just preloaded my images and the transitions became smooth again.
The point is that IE is not W3C compliant, but +1 with ctcherry as using css is the most efficient way for smooth transitions.
Then there are the javascript coded solutions, either using js straight (but need some efforts are needed to comply with W3C Vs browsers), or using libs like JQuery or Mootools.
Here is a good javascript coded example (See demo online) compliant to your needs :
var Fondu = function(classe_img){
this.classe_img = classe_img;
this.courant = 0;
this.coeff = 100;
this.collection = this.getImages();
this.collection[0].style.zIndex = 100;
this.total = this.collection.length - 1;
this.encours = false;
}
Fondu.prototype.getImages = function(){
var tmp = [];
if(document.getElementsByClassName){
tmp = document.getElementsByClassName(this.classe_img);
}
else{
var i=0;
while(document.getElementsByTagName('*')[i]){
if(document.getElementsByTagName('*')[i].className.indexOf(this.classe_img) > -1){
tmp.push(document.getElementsByTagName('*')[i]);
}
i++;
}
}
var j=tmp.length;
while(j--){
if(tmp[j].filters){
tmp[j].style.width = tmp[j].style.width || tmp[j].offsetWidth+'px';
tmp[j].style.filter = 'alpha(opacity=100)';
tmp[j].opaque = tmp[j].filters[0];
this.coeff = 1;
}
else{
tmp[j].opaque = tmp[j].style;
}
}
return tmp;
}
Fondu.prototype.change = function(sens){
if(this.encours){
return false;
}
var prevObj = this.collection[this.courant];
this.encours = true;
if(sens){
this.courant++;
if(this.courant>this.total){
this.courant = 0;
}
}
else{
this.courant--;
if(this.courant<0){
this.courant = this.total;
}
}
var nextObj = this.collection[this.courant];
nextObj.style.zIndex = 50;
var tmpOp = 100;
var that = this;
var timer = setInterval(function(){
if(tmpOp<0){
clearInterval(timer);
timer = null;
prevObj.opaque.opacity = 0;
nextObj.style.zIndex = 100;
prevObj.style.zIndex = 0;
prevObj.opaque.opacity = 100 / that.coeff;
that.encours = false;
}
else{
prevObj.opaque.opacity = tmpOp / that.coeff;
tmpOp -= 5;
}
}, 25);
}

Categories