I continue the work on: https://codereview.stackexchange.com/questions/7315/fade-in-and-fade-out-in-pure-javascript
What is the best way to detect if the fade in or fade out is completed before setting a new function. This is my way, but I guess there is a much better way?
I added the alert's to make it easier for you to see.
Why I want to do this is because: If one press the buttons before the for loop has finnished, the animation will look bad.
I want the buttons to work only when the fadeing is completed.
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
</head>
<body>
<div>
<span id="fade_in">Fade In</span> |
<span id="fade_out">Fade Out</span></div>
<div id="fading_div" style="display:none;height:100px;background:#f00">Fading Box</div>
</div>
</div>
<script type="text/javascript">
var done_or_not = 'done';
// fade in function
function function_opacity(opacity_value, fade_in_or_fade_out) { // fade_in_or_out - 0 = fade in, 1 = fade out
document.getElementById('fading_div').style.opacity = opacity_value / 100;
document.getElementById('fading_div').style.filter = 'alpha(opacity='+opacity_value+')';
if(fade_in_or_fade_out == 1 && opacity_value == 1)
{
document.getElementById('fading_div').style.display = 'none';
done_or_not = 'done';
alert(done_or_not);
}
if(fade_in_or_fade_out == 0 && opacity_value == 100)
{
done_or_not = 'done';
alert(done_or_not);
}
}
window.onload =function(){
// fade in click
document.getElementById('fade_in').onclick = function fadeIn() {
document.getElementById('fading_div').style.display='block';
var timer = 0;
if (done_or_not == 'done')
{
for (var i=1; i<=100; i++) {
set_timeout_in = setTimeout("function_opacity("+i+",0)", timer * 10);
timer++;
done_or_not = 'not_done'
}
}
};
// fade out click
document.getElementById('fade_out').onclick = function fadeOut() {
clearTimeout(set_timeout_in);
var timer = 0;
if (done_or_not == 'done')
{
for (var i=100; i>=1; i--) {
set_timeout_out = setTimeout("function_opacity("+i+",1)", timer * 10);
timer++;
done_or_not = 'not_done'
}
}
};
}// END window.onload
</script>
</body>
</html>
I agree with some of the comments. There are many nice JavaScript libraries that not only make it easier to write code but also take some of the browser compatibility issues out of your hands.
Having said that, you could modify your fade functions to accept a callback:
function fadeIn(callback) {
return function() {
function step() {
// Perform one step of the animation
if (/* test whether animation is done*/) {
callback(); // <-- call the callback
} else {
setTimeout(step, 10);
}
}
setTimeout(step, 0); // <-- begin the animation
};
}
document.getElementById('fade_in').onclick = fadeIn(function() {
alert("Done.");
});
This way, fadeIn will return a function. That function will be used as the onclick handler. You can pass fadeIn a function, which will be called after you've performed the last step of your animation. The inner function (the one returned by fadeIn) will still have access to callback, because JavaScript creates a closure around it.
Your animation code could still use a lot of improvement, but this is in a nutshell what most JavaScript libraries do:
Perform the animation in steps;
Test to see if you're done;
Call the user's callback after the last step.
One last thing to keep in mind: animation can get pretty complex. If, for instance, you want a reliable way to determine the duration of the animation, you'll want to use tween functions (also something most libraries do). If mathematics isn't your strong point, this might not be so pleasant...
In response to your comment: you say you want it to keep working the same way; "If the function is bussy, nothing shall happen."
So, if I understand correctly, you want the animations to be blocking. In fact, I can tell you two things:
Your animations aren't blocking (at least, not the animations themselves -- read below).
You can still make it work the way you want with any kind of asynchronous animations.
This is what you are using done_or_not for. This is, in fact, a common pattern. Usually a boolean is used (true or false) instead of a string, but the principle is always the same:
// Before anything else:
var done = false;
// Define a callback:
function myCallback() {
done = true;
// Do something else
}
// Perform the asynchronous action (e.g. animations):
done = false;
doSomething(myCallback);
I've created a simple example of the kind of animation you want to perform, only using jQuery. You can have a look at it here: http://jsfiddle.net/PPvG/k73hU/
var buttonFadeIn = $('#fade_in');
var buttonFadeOut = $('#fade_out');
var fadingDiv = $('#fading_div');
var done = true;
buttonFadeIn.click(function() {
if (done) {
// Set done to false:
done = false;
// Start the animation:
fadingDiv.fadeIn(
1500, // <- 1500 ms = 1.5 second
function() {
// When the animation is finished, set done to true again:
done = true;
}
);
}
});
buttonFadeOut.click(function() {
// Same as above, but using 'fadeOut'.
});
Because of the done variable, the buttons will only respond if the animation is done. And as you can see, the code is really short and readable. That's the benefit of using a library like jQuery. But of course you're free to build your own solution.
Regarding performance: most JS libraries use tweens to perform animations, which is usually more performant than fixed steps. It definitely looks smoother to the user, because the position depends on the time that has passed, instead of on the number of steps that have passed.
I hope this helps. :-)
you could move your fade code to its own function
var alpha = 100
function fade() {
if (alpha > 0) {
alpha -= 1;
*your opacity changes*
(e.g.: .opacity = (alpha/100);
.style.filter = 'alpha(opacity='+alpha+')';)
}
else {
*complete - modify the click event*
}
}
you may want to add .MozOpacity to go with .opacity and .filter. as mentioned in the comments above, though, cancelling or forcing a completed fade may be better than preventing new clicks while the loop's running.
ps: i respect you for coding that yourself. javascript libraries are overused and often unnecessary.
It could be resolved in few ways. Please take a look at code at the stub of my home page
http://marek.zielonkowski.com/thescript.js
I did it by clearing the time out (I think, I don't remember well because it was few years ago)
clearInterval(isMoving);
or
this.fadeIn = function (){
iCurrentAlpha = (iCurrentAlpha===null) ? alpha : iCurrentAlpha;
clearInterval(isFading);
isFading = setInterval(function(){changeFading(1);},timer);
}
Or You can code your custom events and dispatch something like 'onFadeOutStop' to the listener.
And please do not use setTimeout('function_name()') with quotes - because it is bad habbit (set Timeout behaves then like eval which supposed to be evil :)
You should use jquery. you can run a function after an animation
$('#clickthis').click(function(){
//jquery has really easy way of animating css properties
//the 5000 is the duration in milliseconds
$('#animatethis').animate({opacity:0},5000,function(){
//this will only run once the animation is complete
alert('finished animating');
});
//or use the function..
$('#animatethis').fadeOut(5000,function(){
//this will only run once the animation is complete
alert('finished fading out');
});
});
that should do it. I didn't test it so forgive me if it has any errors.
-L
Related
We want to show a loading progress bar when jQuery code is being executed, however the progress bar is shown after the jQuery code has executed.
<div id="loading">
<img id="loading-image" src="images/ajax-loader.gif" alt="Loading..." />
</div>
$(window).load(function() {
$('#loading').hide();
});
$("[id*=btnAddCa_Cl]").click(function() {
var ttlCationCaco3 = $("input[id$='txtTdsAdjustmentForCation']").val();
if (parseFloat(ttlCationCaco3) > 0) {
$("input[id$='txtCalciumPpmAsCaCO3']").val(parseFloat($("input[id$='txtTdsAdjustmentForCation']").val()) + parseFloat($("input[id$='txtCalciumPpmAsCaCO3']").val()));
$("input[id$='txtCalciumPpmAsCaCO3']").trigger('keyup');
} else {
$("input[id$='txtChloridePpmAsCaCO3']").val(parseFloat($("input[id$='txtTdsAdjustmentForAnion']").val()) + parseFloat($("input[id$='txtChloridePpmAsCaCO3']").val()));
$("input[id$='txtChloridePpmAsCaCO3']").trigger('keyup');
}
});
From the comments under the question it appears that you want the loading spinner graphic (note: not a progress bar, the two are very different in behaviour) to appear while your long-running chemistry-related calculations run.
The issue you have here is that the calculations are a synchronous process and prevent the DOM from being updated while they are in process. To fix this you could place the calculation logic inside a timeout with a very short delay in order to give the DOM some time to update and not block the UI from being amended. As such, try this:
let $loading = $('#loading');
let $calcPpmAsCao3 = $("input[id$='txtCalciumPpmAsCaCO3']");
let $chloridePpmAsCao3 = $("input[id$='txtChloridePpmAsCaCO3']");
let ttlCationCaco3 = parseFloat($("input[id$='txtTdsAdjustmentForCation']").val());
let ttlAnionCaco3 = parseFloat($("input[id$='txtTdsAdjustmentForAnion']").val());
$("[id*=btnAddCa_Cl]").click(function() {
$loading.show();
setTimeout(function() {
// calculation logic here \/ \/ \/
if (ttlCationCaco3 > 0) {
$calcPpmAsCao3.val(function(i, v) {
return parseFloat(this.value) + ttlCationCaco3;
}).trigger('keyup');
} else {
$chloridePpmAsCao3.val(function(i, v) {
return parseFloat(this.value) + ttlAnionCaco3;
}).trigger('keyup');
}
// calculation logic here /\ /\ /\
$loading.hide();
}, 10);
});
#loading {
display: none;
}
There's a couple of things to note in this example. Firstly accessing the DOM is one of (relatively) slowest things you can do in JS. As such it's worth taking steps to reduce the number of times you do it. If you need to refer to an element multiple times, 'cache' it in a variable.
Also, call parseFloat() on a value once and store it in a variable. Then you don't need to keep calling parseFloat() on something which you already had access to.
Lastly, I used CSS to hide #loading by default. Don't do this in JS as the element will be visible for a split second before the JS runs and hides it. CSS executes first and is quicker, so it avoids this issue.
I'm having a bit of a trouble trying to figure this out today, i want to make 5 items inside my DOM (which is listed under the same attribute element, $('.elements')) fade in and out, and after reading up a bit on the API i thought .each() would be a fabulous idea to implement a fade in and fade out showcase gallery.
However, i'm currently using:
$('.elements').each(function() {
$(this).fadeIn(2000).delay(200).fadeOut(2000).show();
})
but everything gets faded in and out at once.
How do i do a sequential effect where everything is chained together and it starts from the first item in the list (a.k.a - $('elements').eq(0)?) down to the last one, and then restarts again?
Do i really need a while loop to do this in javascript/jquery? I was hoping there would be a similar function that i could chain for jQuery to perform to reduce load and filesize.
Also, is there a way to restrict the images from overflowing out from my div?
(function loop() {
$('.elements').each(function() {
var $self = $(this);
$self.parent().queue(function (n) {
$self.fadeIn(2000).delay(200).fadeOut(2000, n);
});
}).parent().promise().done(loop);
}());
demo: http://jsfiddle.net/uWGVN/2/
updated to have it looping without end.
2nd update: a different, probably more readable, approach:
(function fade(idx) {
var $elements = $('.elements');
$elements.eq(idx).fadeIn(2000).delay(200).fadeOut(2000, function () {
fade(idx + 1 < $elements.length ? idx + 1 : 0);
});
}(0));
​demo: http://jsfiddle.net/uWGVN/3/
You can add a callback
offical doc :
('#clickme').click(function() {
$('#book').fadeOut('slow', function() {
// Animation complete.
});
});
and call the same function with i++ et $('.elements').eq(i)
http://jsfiddle.net/dFnNL/
For your overflowing , style it with CSS:
div.(class) { position:relative; overflow:hidden; }
Beautiful way :
(function hideNext(jq){
jq.eq(0).hide("slow", function(){
(jq=jq.slice(1)).length && hideNext(jq);
});
})($('a'))
last first :
(function hideNext(jq){
jq.eq(jq.length-1).hide("slow", function(){
(jq=jq.slice(0,length-1)).length && hideNext(jq);
});
})($('a'))
I've been writing JS (mainly jQuery) for quite a few months now, but today I decided to make my first abstraction as a jQuery method. I already have working code but I feel/know that I'm not doing it the right way, so I come here for some enlightenment.
Note: Please do not reply that there's already something out there that does the trick as I already know that. My interest in this matter is rather educational.
What my code is intended to do (and does):
Limit the characters of a textfield and change the color of the counter when the user is approaching the end.
And here's what I have:
$(function(){
$('#bio textarea').keyup(function(){
$(this).char_length_validation({
maxlength: 500,
warning: 50,
validationSelector: '#bio .note'
})
})
$('#bio textarea').trigger('keyup');
})
jQuery.fn.char_length_validation = function(opts){
chars_left = opts.maxlength - this.val().length;
if(chars_left >= 0){
$(opts.validationSelector + ' .value').text(chars_left);
if(chars_left < opts.warning){
$(opts.validationSelector).addClass('invalid');
}
else{
$(opts.validationSelector).removeClass('invalid');
}
}
else{
this.value = this.value.substring(0, opts.maxlength);
}
}
In the HTML:
<div id="bio">
<textarea>Some text</textarea>
<p class="note>
<span class="value">XX</span>
<span> characters left</span>
</p>
</div>
Particularly I feel really uncomfortable binding the event each on each keyup instead of binding once and calling a method later.
Also, (and hence the title) I need to call the method initially (when the page renders) and then every time the user inputs a character.
Thanks in advance for your time :)
chars_left is a global variable which is not good at all. Here is a better (slightly changed) version:
jQuery.fn.char_length_validation = function(opts) {
this.each(function() {
var chars_left = opts.maxlength - $(this).val().length;
$(this).keyup(function() {
chars_left = opts.maxlength - $(this).val().length;
if (chars_left >= 0) {
$(opts.validationSelector).text(chars_left);
if (chars_left < opts.warning) {
$(opts.validationSelector).addClass('invalid');
}
else {
$(opts.validationSelector).removeClass('invalid');
}
}
else {
$(this).val($(this).val().substring(0, opts.maxlength));
}
});
});
this.keyup(); // makes the "initial" execution
return this;
};
See a DEMO.
Some explanation:
In a jQuery plugin in function, this refers to the elements selected by the selector. You should use this.each() to loop over all of these and set up every element accordingly.
In this example, every element gets its on chars_left variable. The event handler passed to keyup() has access to it as it is a closure. Update: It is already very late here ;) It is not necessary to declare it here as you recompute the value every time anyway. Still, it should give you an idea how to have private variables that persist over time.
You should always return this to support chaining.
Further thoughts:
You might want to think about how you could make it work for several textareas (i.e. you have to think about the validation selector). Don't tie it to a specific structure.
You should have default options.
Update: Of course you can make your plugin work with only one textarea (like some jQuery functions work).
You can do the binding and initial triggering in the method:
jQuery.fn.charLengthValidation = function(opts) {
return this.keyup(function() {
var charsLeft = opts.maxLength - $(this).val().length;
if (charsLeft >= 0) {
$(opts.validationSelector + ' .value').text(charsLeft);
$(opts.validationSelector).toggleClass('invalid', charsLeft < opts.warning);
} else {
$(this).val($(this).val().substring(0, opts.maxLength));
}
}).trigger('keyup');
}
$(function() {
$('#bio textarea').charLengthValidation({
maxLength: 25,
warning: 10,
validationSelector: '#bio .note'
});
});
Recently I am giving another shot to Dashcode ;)
It's great. Is just I think is not well documented.
I have a stackLayout object with only two views in it, and a couple of buttons that interchange the views with a transition.(views show data of a large array, list)
Animations and transitions work perfect. The problem is, when I press a button while animating the animation starts again and it looks ugly (If I had n views for a datasource array of length n this should't be a problem, but this is not my case).
I want to disable buttons while animation is taking place.
Is there any callback, delegate, or any way I can get a notification when the animation is finished?
This is what I have done:
function _changeView(transitionDirection, newIndex){
//Create transition
var newTransition = new Transition(Transition.SWAP_TYPE, 0.9, Transition.EASE_TIMING);
newTransition.direction = transitionDirection;
//I only have two views. I use currentView's id to calculate not current view id and change text inside of it.
var stackLayout = document.getElementById('stackLayout').object;//stackLayout object
var nextViewId = (stackLayout.getCurrentView().id == 'view1')? '2':'1'; //
//change the text in the view that is going to appear
document.getElementById('text'+nextViewId).innerHTML = list[curIndex];
stackLayout.setCurrentViewWithTransition('view'+ nextViewId, newTransition, false);
}
function goPrevious(event)
{
curIndex--;
if(curIndex < 0){
curIndex = list.length-1;
}
_changeView(Transition.LEFT_TO_RIGHT_DIRECTION, curIndex);
}
function goNext(event)
{
curIndex++;
if(curIndex >list.length - 1){
curIndex = 0;
}
_changeView(Transition.RIGHT_TO_LEFT_DIRECTION, curIndex);
}
Eventually found the answer to this. Here's how I did it:
document.getElementById('stackLayout').object.endTransitionCallback=function(stackLayout, oldView, newView) {
//PUT CODE HERE USING stackLayout, oldView, newView to show params
}
In fact you can find all the stackLayout methods and properties in the StackLayout.js file in your project!!
Hope this helps
I've got a bunch of 'project' divs that I want to expand when they're clicked on. If there's already a project open, I want to hide it before I slide out the new one. I also want to stop clicks on an already open project from closing and then opening it again.
Here's an example of what I mean (warning - wrote the code in the browser):
$('.projects').click(function() {
var clicked_project = $(this);
if (clicked_project.is(':visible')) {
clicked_project.height(10).slideUp();
return;
}
var visible_projects = $('.projects:visible');
if (visible_projects.size() > 0) {
visible_projects.height(10).slideUp(function() {
clicked_project.slideDown();
});
} else {
clicked_project.slideDown();
}
});
Really, my big issue is with the second part - it sucks that I have to use that if/else - I should just be able to make the callback run instantly if there aren't any visible_projects.
I would think this would be a pretty common task, and I'm sure there's a simplification I'm missing. Any suggestions appreciated!
slideToggle?
$('.projects').click(function() {
var siblings = $(this).siblings('.projects:visible');
siblings.slideUp(400);
$(this).delay(siblings.length ? 400 : 0).slideToggle();
});
Used a delay rather than a callback because the callback is called once per matched item. This would lead to multiple toggles if multiple items were visible.
Like this?
$(".projects")
.click(function () {
var a = $(this);
if (a.is(":visible")) return a.height(10)
.slideUp(), void 0;
var b = $(".projects:visible");
b.size() > 0 ? b.height(10)
.slideUp(function () {
a.slideDown()
}) : a.slideDown()
})