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.
Related
Let me preface this by stating that this needs to be done in pure, vanilla Javascript, with no 3rd-party libraries or frameworks (emphatically not JQuery).
Say I have a JS file, named included_script.js, with the following content:
function sayIt() {
alert("Hello!");
}
Now say I have the following simplified JS function that loads the external JS file and attempts to execute the sayIt function defined therein:
function loadIt() {
var externalScript = document.createElement("script");
externalScript.type = "text/javascript";
externalScript.src = "js/included_script.js";
document.getElementsByTagName("head")[0].appendChild(externalScript);
/* BLOCK HERE, and do not continue until externalScript
(included_script.js) has been completely loaded from the server
and included into the document, so that the following execution of 'sayIt'
actually works as expected. */
sayIt(); /*I expect the "Hello!" alert here, but 'sayIt' is undefined (which
I think - but am not 100% sure - is because this line is reached before
externalScript (included_script.js) is fully downloaded from the server). */
}
Note that before appending externalScript to the head I have tried things like externalScript.setAttribute("defer", "defer"), externalScript.setAttribute("async", "async") (even though I know this is redundant), and et cetera. Note also that callbacks are not feasible for use.
How can I make function loadIt block at the "BLOCK HERE" part shown above until externalScript (included_script.js) is completely downloaded to the client, so that the sayIt function defined in externalScript (included_script.js) actually works when called from function loadIt?
UPDATE BASED ON BOBRODES' BRILLIANT, SIMPLE ANSWER:
included_script.js still has the following content:
function sayIt() {
alert("Hello!");
}
loadIt is now turned into a class (it's a lot more complex than this, but this shows the bare-bones mechanics required for it to work):
function loadIt() {
this.loadExternal = async function() {
return new Promise(
function(resolve, reject) {
try {
var externalScript = document.createElement("script");
externalScript.type = "text/javascript";
externalScript.src = "js/included_script.js";
if (externalScript.readyState) {
externalScript.onreadystatechange = function() {
if (externalScript.readyState == "loaded" ||
externalScript.readyState == "complete") {
externalScript.onreadystatechange = null;
resolve(true);
}
};
} else {
externalScript.onload = function() {
resolve(true);
};
}
document.getElementsByTagName("head")[0].appendChild(externalScript);
}
catch(err) {
reject(err);
}
}
);
}
}
Now, in my main code, I can do the following, with it being guaranteed that function sayIt is loaded and ready for use before it's invoked.
From inside an async function:
var loader = new loadIt();
await loader.loadExternal();
sayIt();
From outside an async function:
var loader = new loadIt();
(async function() {
await loader.loadExternal();
})().catch(err => {
console.error(err);
});
sayIt();
This works beautifully -- exactly what I was after. Thanks, Bob!
As a side note, I know there is a rampant and short-sighted "blocking is always evil in every case imaginable, and can never, ever, under any circumstances, result in anything good" mentality, but I disagree that blocking is bad when a heavily-data-driven GUI is being generated, which depends on multiple custom classes that, in-turn, depend on each other and/or other classes/resources/scripts -- especially when the rendered GUI elements have multiple event handlers (onclick, oninput, onfocus, etc.) that expect the existence/usability of instances of these classes and their methods.
If you can't use callbacks, then use promises, which are designed to create a "blocking" mechanism in an asynchronous environment without having to add a separate callback function.
function getArray() {
var j = document.createElement('script');
j.src = "http://code.jquery.com/jquery-2.1.4.min.js";
var head = document.getElementsByTagName('head')[0];
head.appendChild(j);
var my_array = [];
j.addEventListener('load',function(){
// Some jQuery codes to fill my_array
});
return my_array;
}
I use above code to dynamically load jQuery in console, and then use jQuery to get some data from the DOM and store them in the array. However, it returns an empty array. I think it is because loading jQuery takes some time and the function gets returned before the jQuery is loaded and the jQuery codes are executed.
So before getArray() returns, I must make sure the jQuery codes have been executed. I've tried to put return my_array inside the addEventListener, of course it won't work because that way it will return the anonymous function. I can think of some ways to deal with this issue, like making the my_array a global so I don't have to return the function, or putting the jQuery loading codes to another loadjQuery function and call it before I execute the jQuery codes, but is there a better way to do it?
The problem is due to asynchronous call of loading jquery script.
The best way to do it will be, write a function to load a script and pass the callback function, then on successful load of script call your callback function, eg:
function loadScript(callback) {
var j = document.createElement('script');
j.src = "http://code.jquery.com/jquery-2.1.4.min.js";
var head = document.getElementsByTagName('head')[0];
head.appendChild(j);
j.addEventListener('load',function(){
if(typeof(callback) == "function")
});
}
function getArray(){
var my_array = [];
// Some jQuery codes to fill my_array
return my_array;
}
loadScript(getArray)
Unfortunately, that can't be done. JavaScript is an event based single-thread asynchronous language. What you're trying to do can't work in that type of environment.
However, it's likely you simply need to load jQuery before processing this function (even using a simple <script> tag) to solve your issue. Otherwise, you're likely to encounter a very noticeable delay when calling the function due to the downloading & evaluating of the jQuery library. Another issue would be that if you call the function more then 1 time, you'll load jQuery again, and that might create a big big mess.
Alternatively, if you "insist" on using jQuery & have it only loaded once your function is called, you could return a Promise that will resolve to your array like this (This will require a supporting browser or some polyfills / promise library):
function getArray() {
var j = document.createElement('script');
j.src = "http://code.jquery.com/jquery-2.1.4.min.js";
var head = document.getElementsByTagName('head')[0];
head.appendChild(j);
return new Promise(function(resolve, reject) {
j.addEventListener('load',function(){
var my_array = [];
// Some jQuery codes to fill my_array
resolve(my_array);
});
});
}
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);
}
}]);
}();
So I've got these functions:
function UrlExists(url){
$.ajax({
url: url,
success: function(data){
alert('exists');
},
error: function(data){
alert('fail');
}
});
}
function addScript(filepath, callback){
if (filepath) {
var fileref = document.createElement('script');
fileref.setAttribute("type","text/javascript");
fileref.setAttribute("src", filepath);
if (typeof fileref!="undefined")
document.getElementsByTagName("head")[0].appendChild(fileref);
}
if (callback) {
callback();
}
}
And then in my $(document).ready() I've got a bunch of these:
addScript(roofPathMrtu.js);
addScript(roofPathTrtu.js);
addScript(lowerPathMrtu.js);
etc...
Which I then need to check if they were successfully loaded or not, so I call:
UrlExists('roofPathMrtu.js');
The problem is that this UrlExists function is not working, and I think it's because it is running before all the addScript functions are done.
How can I have my UrlExists function run only after all the addScript functions are done? I was going to use the callback parameter of the addScript function on the last one, but I don't think that is gonna work.
A way that I have been doing this is not to use the javascript method of setimeout(), but using the jquery feature when. IF not, then I would use a Que. The syntax is
$.when(function()).then(fucntion2());
or
$.when(function1()).done(function2());
You could overlap these if you wanted to, but it is not best when considering both elegant and efficiency in code. Using the que would probably be the next step, using $.when will not accomplish what you want.
http://api.jquery.com/jQuery.when/
Your addScript() function is inserting the tag into the dom and returns immediately. At that point, the browser still needs to fetch the javascript file specified in the src attribute. I suppose that UrlExists() is being called after the execution of the addScript() functions but before the browser has a chance to fetch the javascript files.
use a $.Deferred object to "listen" to the various done events. You might want to combine a few $.Deferred object and use the $.when function to listen for multiple resolved promises
http://thiswildorchid.com/jquery-progress-and-promises check out this link it might help. But it sounds like you might need this. It helps a lot with async functions and you should see if it is a good fit for you.
What you want to do is define an onload function on the script element. It's not hard, but the implementation starts to look ugly. For the particular problem you're dealing with, I would recommend you look at Require JS.
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?