Due to certain reason, we are going to remove jquery from our legacy app (Please don't ask why!)
However, there 1000+ of template files by designers, are making used of jquery ready function. We plan to make the following mock strategy.
<head>
<script type="text/javascript">
// // http://stackoverflow.com/a/9899701
(function(funcName, baseObj) {
// The public function name defaults to window.docReady
// but you can pass in your own object and own function name and those will be used
// if you want to put them in a different namespace
funcName = funcName || "docReady";
baseObj = baseObj || window;
var readyList = [];
var readyFired = false;
var readyEventHandlersInstalled = false;
// call this when the document is ready
// this function protects itself against being called more than once
function ready() {
if (!readyFired) {
// this must be set to true before we start calling callbacks
readyFired = true;
for (var i = 0; i < readyList.length; i++) {
// if a callback here happens to add new ready handlers,
// the docReady() function will see that it already fired
// and will schedule the callback to run right after
// this event loop finishes so all handlers will still execute
// in order and no new ones will be added to the readyList
// while we are processing the list
readyList[i].fn.call(window, readyList[i].ctx);
}
// allow any closures held by these functions to free
readyList = [];
}
}
function readyStateChange() {
if (document.readyState === "complete") {
ready();
}
}
// This is the one public interface
// docReady(fn, context);
// the context argument is optional - if present, it will be passed
// as an argument to the callback
baseObj[funcName] = function(callback, context) {
// if ready has already fired, then just schedule the callback
// to fire asynchronously, but right away
if (readyFired) {
setTimeout(function() {
callback(context);
}, 1);
return;
} else {
// add the function and context to the list
readyList.push({
fn: callback,
ctx: context
});
}
// if document already ready to go, schedule the ready function to run
if (document.readyState === "complete") {
setTimeout(ready, 1);
} else if (!readyEventHandlersInstalled) {
// otherwise if we don't have event handlers installed, install them
if (document.addEventListener) {
// first choice is DOMContentLoaded event
document.addEventListener("DOMContentLoaded", ready, false);
// backup is window load event
window.addEventListener("load", ready, false);
} else {
// must be IE
document.attachEvent("onreadystatechange", readyStateChange);
window.attachEvent("onload", ready);
}
readyEventHandlersInstalled = true;
}
}
})("docReady", window);
// Mock jquery.
var jQuery = function(baseObj) {
return {
ready: function(baseObj) {
docReady(baseObj);
}
}
};
var $ = jQuery;
</script>
</head>
Take note, we tend to mock jquery ready with the following code snippet.
// Mock jquery.
var jQuery = function (baseObj) {
return {
ready: function (baseObj) {
docReady(baseObj);
}
}
};
var $ = jQuery;
It works for cases
jQuery(document).ready(function() {...});
$(document).ready(function() {...});
However, how can we make the following syntax works as well?
$(function() {...});
Check if the passed parameter is function
var jQuery = function(baseObj) {
if (typeof baseObj === 'function')
return docReady(baseObj);
Code:
// Mock jquery.
var jQuery = function (baseObj) {
if (typeof baseObj === 'function')
return docReady(baseObj);
return {
ready: function (baseObj) {
docReady(baseObj);
}
}
};
var $ = jQuery;
In the third case you are actually not calling the ready function. As it is just bind a function in it .
$(function() {...});
So the below function is not called , you are just return an object function which is not called.
var jQuery = function (baseObj) {
return {
ready: function (baseObj) {
docReady(baseObj);
}
}
};
You need to called the function directly for the third case, as below.
// Mock jquery.
var jQuery = function (baseObj) {
if (typeof(baseObj) === 'function') docReady(baseObj);
return {
ready: function (baseObj) {
docReady(baseObj);
}
}
};
Demo : http://jsfiddle.net/kishoresahas/vnd92c1u/
I also had alike situation, a some what similar code is live for long now.
Related
I don't know how to ask this on google that's why I asked here instead,
The console.log from the getListByElement() function won't execute here,
I am modifying a very large existing project and uses functionality hooks for validation purposes and executes that hook on certain .on events, what I want to know is why the console.log won't get executed,
which gets executed first,
Order of execution on my understanding
1. trigger event function for the field `fieldName`
2. fieldName.functionalityHook = [Apple.functionalityHook()];
3. Apple.functionalityHook = function(func) {
4. return function(e) {
5. getListByElement(ele); and display console.log();
6. return func;
Here is the sample code that I have,
var Apple= window.Apple; // global
fieldName.functionalityHook = [Apple.functionalityHook()];
Apple.functionalityHook = function(func) {
return function(e) {
var ele = $(e.target);
getListByElement(ele);
return func;
}
}
function getListByElement(ele){
console.log('ele here');
}
Thank You for answering,
as par my understanding your getListByElement() is not invoking because of the function initialization. You are calling the functionalityHook() before its initialization.
fieldName.functionalityHook = [Apple.functionalityHook()];
Apple.functionalityHook = function(func) {..........
and this invocation returning a function
return function(e) {
var ele = $(e.target);
getListByElement(ele);
return func;
}
and inside this function getListByElement() is calling.
So, the correct code arrangement should be like this.
var Apple= window.Apple;
function getListByElement(ele){
console.log('ele here');
}
Apple.functionalityHook = function(func) {
return function(e) {
var ele = $(e.target);
getListByElement(ele);
return func;
}
}
fieldName.functionalityHook = [Apple.functionalityHook()];
I have a problem with a singleton pattern that I have implemented in 'program.js':
var Program = (function() {
var _program; // Instance of program
// Constructor
function Program() {
if (typeof _program != "undefined") {
throw new Error("Program can only be instantiated once.");
}
this.run = false; // flag to allow for program exec
_program = this;
};
// Public methods
Program.prototype.init = function() {
// the run call is nested in 4 callbacks
callbackHell (
this.run = true;
);
};
Program.prototype.execute = function() {
if(this.run == true) {
// do stuff
}
};
Program.getProgram = function () {
if(typeof _program == "undefined")
{
return new this();
}
return _program;
};
// Return the constructor
return Program;
})();
In my 'main.js' I keep a reference to the loaded program and check for the run flag to allow execution. Looks like this:
var program = null;
function initDemo() {
program = Program.getProgram();
program.init();
runDemo();
};
function runDemo() {
if(program != null && program.run) {
program.execute();
}
requestAnimationFrame(runDemo);
};
If I execute this code on a Chrome browser, it will never reach the program.execute() call in main.js. The reference of program will keep the run flag to false, even it is changed in the init() function. I checked for all of this in the debugger. I should point out that the 'this.run = true' call is nested in 4 callbacks. After a while I figured I could just change the run flag of the global reference of program in main.js with the init() function. So instead of 'this.run = true', 'program.run = true'. This works and the loop will run execute(). However this is not the style I am used from OOP. What is actually happening here? It definitely has to do with the callbacks: when I put 'this.run = true' out of the callbacks at the end of init() the flag is changed correctly, however at the wrong time of the program execution.
Probably your callbacks in the callbackHell are doing something asynchronously and there is a delay before program.run will actually be set to true, the sequence is approximately this:
You call program.init()
Your callbacks start working
You call runDemo(), here program.run is false and it exists
Callbacks finish their work and program.run becomes true
The solution to that is to make your runDemo to be another callback, so your main code will look like this:
var program = null;
function initDemo() {
program = Program.getProgram();
program.init(runDemo);
};
function runDemo() {
if(program != null) {
program.execute();
}
requestAnimationFrame(runDemo);
};
Here you don't need the program.run flag at all, instead you just start your demo from inside the "callbackHell":
Program.prototype.init = function(functionToRun) {
// the run call is nested in 4 callbacks
callbackHell (
functionToRun(); // instead of setting program.run
// we call the specified function
);
};
I've been running into some performance issues when a scroll event gets fired on a project i'm working on and found that debouncing the taxing stuff would be a viable solution:
jQuery(document).ready(function() {
jQuery(window).on('scroll', debounce(function(e) {
console.log('debounced');
console.log(e); // will print the corresponding jQuery object
// some heavy work
}, 250, true)
);
});
function debounce(func, wait, immediate) {
var timeout;
return function() {
var obj = this, args = arguments;
if (timeout) clearTimeout(timeout);
else if (immediate) func.apply(obj, args);
timeout = setTimeout(function() {
if (!immediate) func.apply(obj, args);
timeout = null;
}, wait || 100);
};
};
My question is, how come the jQuery event object is properly handed over within debounce() ?
Isn't it supposed to be passed as first argument of function set as handler, here being debounce() ?
This solution seems to get the job done just fine, but is there some conceptual thing i'm missing here ?
NB: credits to John Hann for the debouncing function
Answer is that jquery event gets return of debounce function, in return it has anonymous function so it exacly what event want to get in parameters.
var exampleFunc=function(){
return 1;
};
var a = exampleFunc; //a is exampleFunc reference
a= exampleFunc(); // a is return value of exampleFunc = 1
Some examples the same behavior:
$('el').on("click",function(e){ /* do something */ })
is the same as
someFunc=function(){ /* do something */ };
$('el').on("click",someFunc);
and is the same:
someFunc=function(){ /* do something */ };
someFunc2=function(){ /* do something 2 */ return someFunc; };
$('el').on("click",someFunc2());
and ... the same as:
someFunc2=function(){
/* do something 2 */
return function(){ /* do something */ }
};
$('el').on("click",someFunc2());
Conclusion - using function in next function is using its return value.
var a=5;
var getA(){ return 5; };
var twentyfive=a*getA(); //25
Some example of using function return as another function:
//EXAMPLE NESTED FUNCTION
var funcNested=function(){
console.log("It is funcNested ");
};
var func=function(){ return funcNested; };
$("#test").on("click",func());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="test">Click me</button>
I have to load two apis. The YouTube api which calls onYouTubePlayerReady when it has loaded and another api for SoundManager which calls soundManager.onready(...) when it has loaded. I do a lot of stuff in each of these ready functions to prepare the site. However, I also need to know when both have completed so I can do more initialization that require both to be fully loaded. Any idea how to have a function that is called when both of these ready functions are called?
Use a callback for both of them.
var callback = (function(){
var count = 0;
return function(){
count++;
if(count === 2){
//both ran and do something
}
}
})();
Then in the end of both onloads just do:
callback();
The things in that if statement will only run on the second time this function i s called.
Small fiddle demo: http://jsfiddle.net/maniator/2X8rF/
Just set a couple flags:
var aDone = false;
var bDone = false;
function whenADone(){
// do your other stuff
aDone = true;
if(bDone) whenBothDone();
}
function whenBDone(){
// do your other stuff
bDone = true;
if(aDone) whenBothDone();
}
There is probably a better way with Defered's, but this is simple and should work. Just keep track of what loaded and what didn't.
var status = {
youtube: false,
sound: false
};
var loaded = function() {
if (!status.youtube) return;
if (!status.sound) return;
// load stuff!
};
var onYoutubePlayerReady = function() {
status.youtube = true;
loaded();
};
soundManager.onready = function() {
status.sound = true;
loaded();
}
Using jQuery deferred, you could build promises for each ready function and combine them with jQuery.when to trigger a final action. For example
function promiseYoutube() {
var dfd = $.Deferred();
window.onYoutubePlayerReady = function() {
console.log("Youtube");
dfd.resolve();
};
return dfd.promise();
}
function promiseSoundManager() {
var dfd = $.Deferred();
window.soundManager.onready = function() {
console.log("SoundManager");
dfd.resolve();
};
return dfd.promise();
}
$.when( promiseYoutube(), promiseSoundManager() ).then(function(){
console.log('Youtube+SoundManager');
});
And a Fiddle simulating these callbacks http://jsfiddle.net/nikoshr/hCznB/
You could have both of your readys set a boolean to true then test against those a $(document).ready(function () { if (youTube === true && soundManager === true) { // Do stuff }});
If using jQuery, take a look at deferred:
http://www.erichynds.com/jquery/using-deferreds-in-jquery/
http://api.jquery.com/category/deferred-object/
Thanks
This is mostly a language-agnostic question.
If I'm waiting for two events to complete (say, two IO events or http requests), what is the best pattern to deal with this. One thing I can think of is the following (pseudo js example).
request1.onComplete = function() {
req1Completed = true;
eventsCompleted();
}
request2.onComplete = function() {
req2Completed = true;
eventsCompleted();
}
eventsCompleted = function() {
if (!req1Completed || !req2Completed) return;
// do stuff
}
Is this the most effective pattern, or are there more elegant ways to solve this issue?
Before even going into the details, here's something neat that takes advantage of lambda functions off the top of my head:
function makeCountdownCallback(count, callback) {
return function() {
if (--count == 0)
callback();
};
}
request1.onComplete = request2.onComplete = makeCountdownCallback(2, function() {
// do stuff
});
This obviously assumes that each event fires at most once, and doesn't take advantage of order.
jQuery 1.5 has Deferreds: http://api.jquery.com/category/deferred-object/
You can easily set them up to call back only when some events have been triggered.
Try #1: Here's a solution that doesn't require additional global variables:
request1.onComplete = function() {
// register new handler for event2 here, overwriting the old one
request2.onComplete = function() {
// now they're both done
}
}
request2.onComplete = function() {
// register new handler for event1 here, overwriting the old one
request1.onComplete = function() {
// now they're both done
}
}
The handler for whichever event fires first will clear the other's old handler and assign a new one that includes the stuff you need to do after the completion of both events. Because we re-assign the second handler inside the handler of the first event (whichever that is), we always know we're done when that second handler finishes.
Try #2: Here's something that will work if each event type is different:
function onBoth(fn) {
var last, done = false;
return function(e) {
if (last && last !== e.type && !done) {
fn(); // done
done = true;
}
last = e.type;
}
}
For example, this won't alert "done" until the user has both scrolled and clicked:
var both = onBoth(function() {
alert("done")
});
document.addEventListener("scroll", both, false);
document.addEventListener("click", both, false);
Try #3: The previous try can be modified to work for similar events:
function onBoth(fn) {
var last, done = false;
return function(curr) {
if (last && last !== curr && !done) {
fn(); // done
done = true;
}
last = curr;
}
}
...which should be used like this:
var check = onBoth(function() {
alert("done")
});
request1.onComplete = function() {
check(arguments.callee);
}
request2.onComplete = function() {
check(arguments.callee);
}
Basically, this checks that two different callbacks have executed by storing a reference to the most recently executed call back. Its usage is a little clunky, but it gets the job done (i.e. it will still work if each of the events executes more than once).
One way to do it: http://tobireif.com/posts/waiting_for_two_events/
Q.spread([
getA(), getB()
], function(a, b) {
// Use the results a and b.
// Catch errors:
}).catch(function(error) {
// Minimal for this example:
console.log(error);
});
The lib is at https://github.com/kriskowal/q .