Is there a YUI equivalent to jQuery's $.holdReady()? - javascript

I have a situation where some third party code is executing a callback with
YUI({
delayUntil: 'domready'
}).use(....)
My issue is that I'm using another asynchronous script loader for my code, and I need to delay that callback until after my scripts have loaded. I'm using Yeti for unit testing which injects YUI into test pages, otherwise my code has nothing to do with YUI.
jQuery has a holdReady method that delays executing domready handlers registered through jQuery until some later time. I'm wondering if YUI has some equivalent method. This is for a test page and the code under test doesn't use YUI, so if the solution involves some ugly hack that'll be fine.
EDIT:
It turns out that Yeti uses its own scoped YUI object, and there isn't a way to access it anyway, so even if I found an answer to this question, it wouldn't help me here. In case anyone is wondering how I fixed the Yeti specific problem without finding a way in YUI to defer document ready handlers, here is my code:
!function(){
var mochaListeners = [];
var fakeRunner;
// Stub for Yeti in case mocha isn't loaded on domready
window.mocha = {
run : function(){
return fakeRunner = {
on : function(type, fn){
mochaListeners.push([type, fn]);
}
};
}
};
yepnope([
{
load : [
'assets/lib/lodash.js',
'assets/lib/jquery.js',
'assets/lib/css/mocha.css',
'assets/lib/mocha.js',
'assets/lib/chai.js',
'assets/lib/sinon.js'
],
complete : function(){
mocha.setup('bdd')
}
},{
load : 'assets/js/my_tests.js',
complete : function(){
executeTests = function(){
var runner = mocha.run();
runner.ignoreLeaks = true;
_.forEach(mochaListeners, function(listener){
runner.on(listener[0], function(){
_.extend(fakeRunner, runner);
listener[1].apply(this, arguments);
});
});
};
if(document.readyState === 'complete')
executeTests();
else
$(executeTests);
}
}]);
}();

Related

node.js and mocha-qunit-ui: test with asynchronous requirejs dependencies

Short description first: The test in the following snipped is never executed:
setTimeout(function() {
test("test", function() {
ok(true, "okay");
})
}, 1000);
Is there any way not to autostart the mocha-qunit-ui but wait for some asynchronous callbacks to create tests?
So, why would I need it this way?
I'm trying to run in-browser-tests for my classes with nodejs and the mocha-qunit-ui. The classes are loaded asynchrously by requirejs.
So I could create a test like this:
test("test", function() {
stop();
requirejs(
['Dependency'],
function(Dependency)
{
expect(1);
var d = new Dependency();
ok(d.test(), "test");
start();
}
);
}
But I want to run multiple tests on the same class with the same dependencies, so I thought of creating a test object
var d = new Dependency();
outside of the test. But then the test would be inside an asynchronous call, and therefore is never called.
Well, I'm not terribly familiar with mocha-qunit-ui plugin specifically, but QUnit has a config object that you can use to tell it not to run until you are ready (so you can load all of your classes, etc first):
<script src="path/to/qunit.js"></script>
<script>
QUnit.config.autostart = false; // be sure this is set BEFORE you add your actual tests
require(
[ "whatever", "your", "dependencies", "are" ],
function() {
QUnit.start(); // tell QUnit you're ready to go
}
);
<script>

How to find if the chained asynchronous scripts has been loaded?

Here's the scenario. I am doing a $.getScript()function call to get a script in my javascript file. The script that I'm downloading from the $.getScript() tries to download some other scripts that it's dependent on. In my script I'm using done() to check if the script loaded completely or not. And if it did, then I try calling the function that's not on the script that I just loaded form $.getScript but the script that was loaded in it.
It's getting confusing so let me demonstrate with some code:-
//my script.js
$.getScript("http://myexternaljs.com/first.js").done(function(){
doSecond(); //<- this resides in second.js which is being called in first.js below
}
//first.js
(function(){
$.getScript("http://firstexternal.com/second.js");
}());
//second.js
function doSecond(){
return console.log("hello world");
}
The problem here is second.js takes a little time to download so I keep getting doSecond() is undefined error on my call to doSecond() on done().
I could use a timeout and check if second.js loaded or not but is there a better way to do this?
I'm open to any AMD loaders or Promises answers as well.
You can also use $.ajaxSuccess:
$(document).ajaxComplete(function(ev, jqXhr, options) {
// You could be as specific as you need to here.
if ( options.url.match('second.js') ) {
doSecond();
}
});
Alternatively, you could do this inside ajaxComplete:
$(document).ajaxComplete(function(ev, jqXhr, options) {
// You could simplify to
// doSecond && doSecond()
// if you can trust that it will always be a function
if ( doSecond && $.isFunction(doSecond) ) {
doSecond();
}
});
The facts:
You have first.js and within this script is an include for second.js
You need to make a call to doSecond() which is defined in second.js
You need to ensure doSecond() is available before you call it
You can't directly change first.js or second.js but you can have someone else change it
Possible solutions, ordered by best to worst
1) Request that second.js be removed from first.js. Call them separately so that you can nest them:
$.getScript("first.js").done(function(){
$.getScript("second.js").done(function(){
doSecond();
});
});
This is the best solution. There are alternatives to this that basically do he same thing in principle (e.g. other people's answers here). If first.js was including second.js synchronously or otherwise forcing load before continuing (e.g. option #3 below), you wouldn't be running up against this problem to begin with. Therefore first.js already must be structured to deal with second.js be *a*sync loaded, so there shouldn't be an issue with them removing it from the file and you calling it yourself.
But you mentioned that the location of second.js is defined in first.js so this somehow isn't feasible to you (why not? can they put the path/to/script in a variable for you to access?)
2) Request that second.js be wrapped in a .done or equivalent loaded callback that pops a callback function that you can define.
// inside first.js
$.getScript("second.js").done(function(){
if (typeof 'secondLoaded'=='function')
secondLoaded();
});
// on-page or elsewhere, you define the callback
function secondLoaded() {
doSecond();
}
This is just a generic and easy "callback" example. There are a million ways to implement this principle, depending on what all is actually in these scripts and how much effort people are willing to make to restructure things.
3) Request that second.js script include be changed to be included via document.write
document.write(unescape("%3Cscript src='second.js' type='text/javascript'%3E%3C/script%3E"));
This will force js to resolve document.write before js can move on, so second.js should be loaded by the time you want to use doSecond(). But this is considered bad practice because until document.write is resolved, nothing else can happen. So if second.js is taking forever to load or eventually times out.. that makes for bad UX. So you should avoid this option unless you have no other choice because of "red tape" reasons.
4) use setTimeout to try and wait for it to load.
function secondLoaded() {
if (!secondLoaded.attempts) secondLoaded.attempts = 0;
if (secondLoaded.attempts < 5) {
if (typeof 'doSecond'=='function') {
doSecond();
} else {
secondLoaded.attempts++;
window.setTimeout('secondLoaded()',100);
}
}
}
secondLoaded();
I list this worse than #3 but really it's kind of a tossup.. In this situation you basically either have to pick between deciding a cutoff time to just not execute doSecond() (in this example, I try 5 times at 100ms intervals), or code it to just keep checking forever and ever (remove the .attempts logic or else swap it up w/ setInterval and removeInterval logic).
You could modify how $.getScript works.
$.fn.getScript = (function() {
var originalLoad = $.fn.getScript;
return function() {
originalLoad.apply($, arguments);
$(document).trigger('load_script')
};
})();
This will fire an event every time a script is loaded.
So you can wait for these events to fire and check if your method exists.
$(document).one('second_loaded', function() {
doSecond();
}).on('load_script', function() {
doSecond && document.trigger('second_loaded');
});
Note that one rather than on. It makes the event fire once.
Have you considered using jQuery.when:
$.when($.getScript("http://myexternaljs.com/first.js"),$.getScript("http://firstexternal.com/second.js"))
.done(function(){
doSecond();
}
If I were you, I'd facade $.getScript and perform some combination of the above tricks. After reading through the comments it seems that there is a possibility of loading the same script twice.
If you use requirejs this problem is solved for you because it only loads each script once. The answer here is to hang on to the requests made.
Loader:
var requiredScripts = {};
function importScript(url) {
if (!requiredScripts[url]) {
requiredScripts[url] = $.getScript(url);
}
return requiredScripts[url];
}
Usage:
// should cause 2 requests
$.when(
importScript('first.js'),
importScript('second.js')
).done(function() {
// should cause no requests
$.when(importScript('first.js')).done(function() {});
$.when(importScript('second.js')).done(function() {});
});
Real world example here using MomentJS and UnderscoreJS: http://jsfiddle.net/n3Mt5/
Of course requirejs would handle this for you and with better syntax.
Requirejs
define(function(require) {
var first = require('first'),
second = require('second');
});
$(window).one("second", function(e, t) {
if ( $(this).get(0).hasOwnProperty(e.type) && (typeof second === "function") ) {
second(); console.log(e.type, e.timeStamp - t);
$(this).off("second")
};
return !second
});
$.getScript("first.js")
.done(function( data, textStatus, jqxhr ) {
if ( textStatus === "success" ) {
first();
$.getScript("second.js")
.done(function( script, textStatus, jqxhr, callbacks ) {
var callbacks = $.Callbacks("once");
callbacks.add($(window).trigger("second", [ $.now() ]));
return ( textStatus === "success" && !!second
? callbacks.fire()
: $(":root").animate({top:"0"}, 1000, function() { callbacks.fire() })
)
});
};
})
// `first.js` : `function first() { console.log("first complete") }`
// `second.js` : `function second() { console.log("second complete") }`

What happens if multiple scripts set window.onload?

There are a number of posts on StackOverflow and other websites regarding the problem of avoiding namespace collisions. In my scenario, I just want a method in my JavaScript to be executed after the DOM is accessible.
If I do the following will it avoid namespace collisions?
<script type="text/javascript">window.onload = function() { //Define my namespace var here, and execute all my code }</script>
What if a script that is injected later also sets an onload function ? Will mine get overwritten? I'm fully aware that I can test this out, but I would also like some feedback as I am new to JavaScript and there could be a number of other scenarios which will do the something that I am not aware of.
EDIT: I need to support only Safari 5.0+
Yes, the last one will overwrite the previous ones.
The solution: use the new event API: addEventListener.
This is a fine Javascript way to do it right
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent(nameOfSomeFunctionToRunOnPageLoad);
addLoadEvent(function() {
/* more code to run on page load */
});
Explained Source
There's lots of information on this, but here's the short version:
if you want to play nicely with onload, you can do
var prev_onLoad = window.onload;
window.onload = function() {
if (typeof(prev_onLoad)=='function')
prev_onLoad();
// rest of your onLoad handler goes here
}
and hope that other's play nicely or make sure that's the last setting of onload in the code.
However, more modern browsers have event registration functions (addEventListener and attachEvent on IE) which take care of this chaining among other things. Quite a few cross-browser onload event functions have been written which take care of this logic for you.
It'll be overriden .
In Javascript, when you define handle event like
window.onload = function(){
console.log("in Load function 1");
};
window.onload = function(){
console.log(" In load function 2");
};
That will make an " assign " window.onload => function() . And window.onload will be assign to last function .
But in jQuery,
You can handle event in many times and the browser will make all
$("body").on("click",function(){
console.log("make a callback function 1");
});
$("body").on("click",function(){
console.log("make a callback function 2");
});
Because jQuery make a callback not "assign".
Hope it helps you.

Conditional javascript loading

For my backend I want to automatically load javascript files when it detects certain elements. Here is an example:
if($('.wysiwyg').length>0) {
include('javascript/ckeditor/ckeditor.js');
$(".wysiwyg").ckeditor();
}
But when I execute the code I get $(".wysiwyg").ckeditor is not a function because it seems the browser is still loading or parsing the javascript file that was included on the line before. If I put an alert popup right before the function it does work because it "pauzes" the script I guess and gives it time to load the file.
Is there a way I can know when the file is actually loaded so that the followed code can be executed?
EDIT:
Seems that I asked this question a bit too soon. I found out the e.onload property for a callback function that solved this problem. This is my function now if others might stumble upon the same problem:
function include(script, callback) {
var e = document.createElement('script');
e.onload = callback;
e.src = script;
e.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(e);
}
if($('.wysiwyg').length>0) {
include('javascript/ckeditor/ckeditor.js', function() {
$(".wysiwyg").ckeditor();
});
}
Why not use the built in ajax-based getScript?
It also has a callback mechanism that allows you to execute some code only after the required script has been succesfully loaded :
function include(script,callback){
$.getScript(script, function() {
if(typeof callback == 'function')
callback.apply({},arguments);
});
}
and then you can use it in such a manner:
if($('.wysiwyg').length>0) {
include('javascript/ckeditor/ckeditor.js',function(){
$(".wysiwyg").ckeditor();
});
}
When you're using jQuery with promises you can use a modified version of the above code like so:
function include(srcURL) {
var deferred = new $.Deferred();
var e = document.createElement('script');
e.onload = function () { deferred.resolve(); };
e.src = srcURL;
document.getElementsByTagName("head")[0].appendChild(e);
return deferred.promise();
}
Then you can use the above code with a '$.when(include('someurl.js'))' call.
This will let you have
The global window context (which you need for CKEditor)
The ability to defer executing other code until the when resolves
A script that doesn't require a callback and a context for that to be passed because jQuery is handling that with the promises functionality it includes.
I hope this helps someone else who is looking for more than a callback, and multiple scripts to be loaded with jQuery's promises/deferred functionality.
You can also try YepNope - a conditional javascript loader
yepnope is an asynchronous conditional resource loader that's
super-fast, and allows you to load only the scripts that your users
need.
You can do it this way
$(document).ready(function()
{
if($('.wysiwyg').length>0) {
$('head').append('<script language="javascript" src="javascript/ckeditor/ckeditor.js"></script>');
$(".wysiwyg").ckeditor();
}
});
Modernizr can do this for you. See this MetaFlood article: Use jQuery and Modernizr to load javascript conditionally, based on existence of DOM element.

JavaScript: When does JavaScript evaluate a function, onload or when the function is called?

When does JavaScript evaluate a function? Is it on page load or when the function is called?
The reason why I ask is because I have the following code:
function scriptLoaded() {
// one of our scripts finished loading, detect which scripts are available:
var jQuery = window.jQuery;
var maps = window.google && google.maps;
if (maps && !requiresGmaps.called) {
requiresGmaps.called = true;
requiresGmaps();
}
if (jQuery && !requiresJQuery.called) {
requiresJQuery.called = true;
requiresJQuery();
}
if (maps && jQuery && !requiresBothJQueryGmaps.called) {
requiresBothJQueryGmaps.called = true;
requiresBothJQueryGmaps();
}
}
// asynch download of script
function addScript(url) {
var script = document.createElement('script');
script.src = url;
// older IE...
script.onreadystatechange=function () {
if (this.readyState == 'complete') scriptLoaded.call(this);
}
script.onload=scriptLoaded;
document.getElementsByTagName('head')[0].appendChild(script);
}
addScript('http://google.com/gmaps.js');
addScript('http://jquery.com/jquery.js');
// define some function dependecies
function requiresJQuery() { // create JQuery objects }
function requiresGmaps() { // create Google Maps object, etc }
function requiresBothJQueryGmaps() { ... }
What I want to do is perform asynchronous download of my JavaScript and start at the earliest possible time to begin executing those scripts but my code has dependencies on when the scripted have been obviously downloaded and loaded.
When I try the code above, it appears that my browser is still attempting to evaluate code within my require* functions even before those functions have been called. Is this correct? Or am I misunderstanding what's wrong with my code?
Functions are evaluated when called.
For example
function test() {
window.foo = 'bar';
}
console.log(window.foo); // => undefined
test();
console.log(window.foo); // => bar
Even though test was created before the first console.log, window.foo is not populated until test is actually called.
If your requires* functions are hanging/blocking, then you need to show the code for those (why would you not provide the source for the problematic ones?)
Edit:
Currently, your site is blanking out on me when you attach the loaded <script> to the <head>.
Anyway, a quick fix would be to place the scripts you wants near the bottom of the page, before </body>, because only scripts in <head> will fully block the page while loading.
There are some elegant ways to late-load resources, but, to keep it simple ..
<script type="text/javascript" src="http://path/to/jquery.js"></script>
<script type="text/javascript">
requiresJQuery(); // jQuery is available at this point
</script>
</body>
The point is that, since the <script> is placed AFTER your main elements, the DOM elements will be available (and potentially loaded) before the browser starts to download your other libraries.
Yes, you are probably misunderstanding. Even if your functions contain a syntax error, it should not matter until you actually call the function.
Could it be that you're calling those functions from somewhere else? Maybe you didn't provide accurate code samples?

Categories