I have a jQuery function that looks like this:
$.get(urlCall, function (data) {
$('#divId').children().fadeOut("slow", function() {
$('#divId').append(data);
$('#divId').fadeIn("slow");
});
});
The problem is that it is calling the append and fadeIn lines once for EACH child under '#divId'. I was expecting the line
$('#divId').children().fadeOut("slow", function() {
to fade out all children and then execute the function() a single time. But since it's executing the function() once for each child, I'm appending a lot of lines that I don't want appended.
I'm sure that there must be a way to say, "fade out all children, and then do X once", but I can't seem to figure out how to do it.
Help?
you can use .promise()
$('#divId').children().fadeOut("slow").promise().done(function() {
$('#divId').append(data);
$('#divId').fadeIn("slow");
});
The .promise() method returns a dynamically generated Promise that is
resolved once all actions of a certain type bound to the collection,
queued or not, have ended.
By default, type is "fx", which means the returned Promise is resolved
when all animations of the selected elements have completed.
For the the downvoter
Demo: Fiddle
The only way to reliably do this is to make your own function that keeps track of how many animations have completed, and compares against the number of original animations. Something like:
var target = $("#divId"),
children = target.children();
$("#trigger").on("click", function () {
toggleFadeAll(children, function () {
$("#message").fadeToggle();
});
});
function toggleFadeAll(els, callback) {
var counter = 0,
len = els.length;
els.fadeToggle(function () {
if (++counter === len) {
callback.call(null);
}
});
}
DEMO: http://jsfiddle.net/jbE3C/
(this will obviously need to be adjusted based on what animating you actually want to happen)
The toggleFadeAll will call the callback provided when all fadeToggle animations have completed.
Using .promise() fails (doesn't execute the desired callback at the right point in time) when there are other animations possibly happening to the elements.
Why not just separate the fading and the appending? So it would be
$.get(urlCall, function (data) {
$('.someElement').each(function(){
$('.fadeElement').fadeIn();
});
$('.otherElement').append(data)
});
Related
What I basically want to achieve is create some kind of delayedChange plugin to be able to call some action (such as ajax call to the server) only after some delay the last input change event was fired. At the moment I've came up with this (jsfiddle). I should see alert only in 5 seconds (5000 msec) the last text change had place but it fires immediately.
(function ($) {
var timer;
$.fn.delayedChange = function (onchange, delay) {
return this.each(function () {
$(this).bind('change', function () {
if (typeof onchange == 'function') {
window.clearTimeout(timer);
timer = window.setTimeout(onchange.call(this), delay);
}
});
});
}
})(jQuery);
$(document).ready(function(){
$('input').delayedChange(function(){
alert($(this).attr('id'));
}, 5000);
});
The weirdest is that this code actually worked for some time, and then it's functionality just vanished for no reason. Obviously there is some explanation but I can't see it for now. Are there some more certain ways to implement/improve such plugin?
The functionality you've described is called "debouncing". Libraries such as underscore, lodash, and ampersand have a debounce method to make this effect convenient.'
With underscore, the code is:
$('input').each(function () {
$(this).on('change', _.debounce(...your function..., 5000));
});
No new function is needed, although you will need to include a new dependency.
I'd made a mistake with the first version. You need to generate a separate debounce function for each element, otherwise changing different elements will cause the timer to reset for all of the elements.
See JSfiddle!
I am wanting to animate a set of elements and execute a callback when finished like so:
s.selectAll('.active').animate( {
transform: matrix
},
300,
mina.linear,
function() {
//callback doesnt fire
alert('callback')
}
)
The elements are animated correctly but the callback isnt executed.
However, when I apply the animation to a group of elements, the callback is fired:
group.animate( {
transform: matrix
},
300,
mina.linear,
function() {
alert('callback')
}
)
.. But I don't want to put my selected elements in a group as this would cause more complications in other places.
Is it possible to animate a set of elements that I got via a .select() or .selectAll() while being able to fire the callback?
Thanks a lot in advance!
Edit: For future readers, you can animate a set of elements by using forEach and counting if all elements are done animating:
function hideToPoint(elements, x, y, callback) {
var finished = 0;
elements.forEach(function(e) {
e.animate( {
//do stuff
},
300,
mina.linear,
function () {
finished++;
if (finished == elements.length) {
callback();
}
}
)
})
}
I'm going to have a stab at answering a couple of problems, even though I'm not sure if related to the callback. Its hard to tell if its just the example code or not without a proper test like a jsfiddle.
However, there are at least 2 problems in the code above.
Creating a matrix is with
new Snap.Matrix(); // as opposed to Snap.matrix()
Also
elements.animate()
The problem here is that animate acts on one element (edit: looks like it can work on elements within a set, but not the callback as example here, edit2: callbacks on sets may now be supported), not multiple elements as such (you can sometimes apply somethings to a set which deals with them individually, but as far as I'm aware, thats not the case with animate).
So you either want to do a
elements.forEach( function(el) { el.animate({blah: value}, 2000, mina.linear.callback )
});
or if its an svg group (as opposed to a set), the original code would possibly work (but I would call it 'myGroup' or something instead of 'elements' for code readability and guessing what it contains)
fiddle (have included a different animation using snap animation string)
I don't know if it is suppoused to do this, but I guess it is...
I thought at first that it may be something wrong with my whole script and I managed to make new file on localhost and test just fadeOut(); function.
Apparently it returned function twice again so I went to jsfiddle to check what will happen there. And same thing happened. In this case console.log(); returned twice.
What I did and what I am trying to do ?
Well I want to return specified function, or in fiddle sample, specified console.log(); only once. However I am fadingOut multiple elements (two, to be exact).
Is there any way to do that, instead of duplicating each element to fadeOut at the same time ?
Sample that will return console.log(); twice.
setTimeout(function () {
$( ".one, .two" ).fadeOut(300, function () {
console.log("Return Function!");
});
}, 2000);
Sample that will return console.log(); once.
setTimeout(function () {
$( ".one" ).fadeOut(300);
$( ".two" ).fadeOut(300, function () {
console.log("Return Function!");
});
}, 2000);
Fiddle Preview:
Fiddle Redirect
There are two elements in the collection, so fadeOut is called twice, so yes, it's supposed to do that.
You can have the callback fire only once regardless of the number of elements in the collection by using the returned promise from the animation and $.when and $.then
setTimeout(function () {
$.when( $( ".one, .two" ).fadeOut(300) ).then(function () {
console.log("Return Function!");
});
}, 2000);
FIDDLE
Perhaps a simple flag would suffice? Especially if you wanted to log to the console as soon as the first one was done (for whatever reason):
var already_returned = false;
setTimeout(function () {
$( ".one, .two" ).fadeOut(300, function () {
if (!already_returned) {
already_returned = true;
console.log("Return Function!");
}
});
}, 2000);
Need to see your HTML, but appears to me that you may have more than one element assigned with the class "two" - that would explain why in the second example you would outcome 2 log lines...
In the first example, you may (and probably is) selecting 2 elements (one with the class "one" and another with the class "two").
I checked this out of curiosity and my console shows two different javascript declarations:
Return Function! fiddle.jshell.net/dvLden/ApUYq/show/:38
Return Function! fiddle.jshell.net:38
Apparently fiddle runs this twice.
if you browse to http://fiddle.jshell.net/dvLden/ApUYq/show/:38 you'll get only one "Return Function" in your console.
This always gets me. After initializing all lovely UI elements on a web page, I load some content in (either into a modal or tabs for example) and the newly loaded content does not have the UI elements initialized. eg:
$('a.button').button(); // jquery ui button as an example
$('select').chosen(); // chosen ui as another example
$('#content').load('/uri'); // content is not styled :(
My current approach is to create a registry of elements that need binding:
var uiRegistry = {
registry: [],
push: function (func) { this.registry.push(func) },
apply: function (scope) {
$.each(uiRegistry.registry, function (i, func) {
func(scope);
});
}
};
uiRegistry.push(function (scope) {
$('a.button', scope).button();
$('select', scope).chosen();
});
uiRegistry.apply('body'); // content gets styled as per usual
$('#content').load('/uri', function () {
uiRegistry.apply($(this)); // content gets styled :)
});
I can't be the only person with this problem, so are there any better patterns for doing this?
My answer is basically the same as the one you outline, but I use jquery events to trigger the setup code. I call it the "moddom" event.
When I load the new content, I trigger my event on the parent:
parent.append(newcode).trigger('moddom');
In the widget, I look for that event:
$.on('moddom', function(ev) {
$(ev.target).find('.myselector')
})
This is oversimplified to illustrate the event method.
In reality, I wrap it in a function domInit, which takes a selector and a callback argument. It calls the callback whenever a new element that matches the selector is found - with a jquery element as the first argument.
So in my widget code, I can do this:
domInit('.myselector', function(myelement) {
myelement.css('color', 'blue');
})
domInit sets data on the element in question "domInit" which is a registry of the functions that have already been applied.
My full domInit function:
window.domInit = function(select, once, callback) {
var apply, done;
done = false;
apply = function() {
var applied, el;
el = $(this);
if (once && !done) {
done = true;
}
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
if (done) {
return;
}
$(ev.target).find(select).each(apply);
});
};
Now we just have to remember to trigger the 'moddom' event whenever we make dom changes.
You could simplify this if you don't need the "once" functionality, which is a pretty rare edge case. It calls the callback only once. For example if you are going to do something global when any element that matches is found - but it only needs to happen once. Simplified without done parameter:
window.domInit = function(select, callback) {
var apply;
apply = function() {
var applied, el;
el = $(this);
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
$(ev.target).find(select).each(apply);
});
};
It seems to me browsers should have a way to receive a callback when the dom changes, but I have never heard of such a thing.
best approach will be to wrap all the ui code in a function -even better a separate file -
and on ajax load just specify that function as a call back ..
here is a small example
let's say you have code that bind the text fields with class someclass-for-date to a date picker then your code would look like this ..
$('.someclass-for-date').datepicker();
here is what i think is best
function datepickerUi(){
$('.someclass-for-date').datepicker();
}
and here is what the load should look like
$('#content').load('/uri', function(){
datepickerUi();
})
or you can load it at the end of your html in script tag .. (but i dont like that , cuz it's harder to debug)
here is some tips
keep your code and css styles as clean as possible .. meaning that for text fields that should be date pickers give them one class all over your website ..
at this rate all of your code will be clean and easy to maintain ..
read more on OOCss this will clear what i mean.
mostly with jquery it's all about organization ... give it some thought and you will get what you want done with one line of code ..
edit
here is a js fiddle with something similar to your but i guess it's a bit cleaner click here
I've got a short function that should show messages on a website.
function showHint() {
$('#notify').html('message text').show('slide', {direction: 'right'}, 500);
}
And there is another function that hides the messages.
function hideHint() {
$('#notify').hide('slide', {direction: 'right'}, 500);
}
The Problem is that if I call this function more than one times it tries to show all messages at the same time and everything breaks. I want to call the function twice and then it should queue the animations and show one message after another. The function should be called more than one times at the same time but shown one after another. The next message should be shown when the firs hides.
How could I solve the Problem? Would be nice!
Here's a mini custom plugin that I've used in the past that chains a bunch of animations one after another.
// Good for serializing animations
$.fn.chain = function(fn) {
var elements = this;
var i = 0;
function nextAction() {
if (elements.eq(i)) fn.apply(elements.eq(i), [nextAction]);
i++;
}
nextAction();
};
You might call it like so (Here's an example of it in use):
$(document).ready(function() {
$('li').chain(function(nextChain) { this.slideToggle("fast", nextChain); });
});
The function you pass to chain passes another function that you must call when you're down with one cycle. In the example above, we just pass the nextChain function as the callback to the slideToggle.
Your showhint function could just start by hiding the notification and when that is complete the callback would be what is the existing showhint function, that would change the text and show it. Code shouldn't be difficult given what you've already done.
can you not just use a notification plugin? here are two (one, two) that are pretty spiffy.