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.
Related
Suppose I have this event handler:
var mousewheel = function (e) { /* blah */ };
But, I want to debounce it. So I do this, which works as expected:
var mousewheelDebounced = _.debounce(mousewheel, 500);
$(document).on("mousewheel", function (e) {
e.preventDefault();
mousewheelDebounced(e);
}
But this doesn't work as expected:
$(document).on("mousewheel", function (e) {
e.preventDefault();
_.debounce(mousewheel, 500)(e);
}
I prefer the compactness of the second form. But no debouncing occurs.
I assume no reference to the mousewheelDebounced remains, so the underlying handler is called every time. Is that the reason? How can I clean up this code, if possible?
You can see a fiddle here.
On each mousewheel event (which occurs very often) you create a new version of debounce function. This function has no history, and doesn't know that on previous event there was another one created. So it just runs. That's why you should go with the first version.
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.
I have a js file that contains my closure, this file is loaded before jQuery, let's say it can't be moved. How can I pass in or check for jQuery with a view to use it in the closure?
This is what I've got so far:
(function MyClosure() {
var interval = setInterval(function() {
if (typeof jQuery !== 'undefined') {
doJqueryStuff();
clearInterval(interval);
}
}, 500);
function doJqueryStuff() {
// Some stuff with jQuery.
}
})();
It actually works, but is there a "better" way? I always think I'm doing something wrong whenever I use setInterval() for things like this, also the fact I am losing time in that 500ms.
You could wait and attach your execution to the window.onload event, assuming jQuery is loaded once the window is loaded...
window.onload = function() {
// do stuff with jQuery
};
Don't worry - while it does look hackish (at least to me and you) it isn't bad. Often times you need to wait until a complex object is initialized and you need to do the same thing. The best thing is to just ensure the order that your scripts load to solve any dependency issues - but as you requested let's assume the order can't be adjusted.
The only improvement I would suggest: adding an escape hatch to anonymous setInterval function. That way if jQuery never becomes available for some reason, the script can notify the user and stop checking.
var checkCount = 0;
var interval = setInterval(function() {
if (checkCount++ > 20) {
alert("jQuery could not be loaded - degrading user experience");
clearInterval(interval);
}
if (typeof jQuery !== 'undefined') {
doJqueryStuff();
clearInterval(interval);
}
}, 500);
Wait for the onload event on the script tag. In this case, the doJqueryStuff should be a global function.
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" onload='doJqueryStuff()'></script>
I want to extend the $.fn object in order to have a delay between my jquery commands :
like this working (and quite long) code :
$('.d').delay(1000).queue(
function ()
{
$(this).css('background-color', 'green');
$(this).dequeue();
}).delay(1000).queue(function ()
{
$(this).css('background-color', 'red');
$(this).dequeue();
});
a working sample is here : JSBIN
So I tried this :
My code at jsbin
$.fn.myWait = function (ms)
{
return this.queue(
function ()
{
var _self = this;
setTimeout(function ()
{
$(_self).dequeue();
}, ms);
})
};
invoking :
$('.d').myWait(1000).css('background-color', 'red').myWait(1000).css('background-color', 'green');
But it doesnt work.
What am I doing wrong ?
p.s.
I did read this similar solution, but if I remove the animation part and use only css , it also doesnt work.
The .css() does not get queued on the animation queue by itself, that's why you needed to put it in a callback in your first snippet. In the second snippet, it is called immediately (even though there's timeout waiting in the queue - just as like you called .delay()). Instead, you would need to use an .animate() call with a zero-duration.
For allowing the syntax which you wanted, you will need to take a step further. Have a look at the jQuery timing plugin and how their magic works.
I doubt you can do it in this way. You need to defer execution of those attribute changes, which means you shall store said code in individual functions.
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