Solution: There appears to have been an issue in the way I was calling my queuing mechanism into window.onload.
Preamble - I'm glad to see I'm being flagged as a duplicate, where the solution is to use window.onload, which is something I'm already using here. A couple comments have indicated possible issues with the queue that I got from another stackoverflow solution, but leave me in the dark due to lack of elaboration.
Question: If I make a function call in the head, it fails. If I make a function call in the body, it succeeds. Why?
Extension: Why do the calls to the same function in global.js, which is above the failing call, succeed?
I have some javascript that creates an onload "queue" of sorts (function addLoadEvent in global.js). A call to this function adds to the queue for when onload is called.
Except, I've found that a particular script function call fails if it's located in the head vs in the body. I can't figure out why, because everything that's needed (other js functions) are loaded above the function call itself, and the actual call to the function isn't triggered until onload, ensuring that the necessary html elements exist.
Order of loading:
html file up to head
global.js - including addLoadEvent(func)
addLoadEvent x2 (succeeds)
inline script in head - includes initialFighter()
addLoadEvent (location one - fails)
html file after head
addLoadEvent (location two - succeeds)
onload triggers queued calls
For the purpose of this question, the function call that fails or succeeds based on it's location is the following.
addLoadEvent(initialFighter());
Here's the stripped down HTML, note the 2 locations flagged. If I copy paste the above function call to Location one, it fails. If I copy paste the above function call to Location two, it succeeds.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="global.js"></script>
<script type="text/javascript">
function showFighter(id) {
var pic = document.getElementById("picture");
var picPath = 'url(images/' + id + '.png)';
pic.style.backgroundImage = (picPath);
}
function initialFighter() {
var fighter = getURLParameter('fighter');
if (typeof(fighter) != "undefined" && fighter != null) {
showFighter(fighter);
} else {
showFighter('Foobar');
}
}
***** LOCATION ONE *****
</script>
</head>
<body>
<header></header>
<nav id="nav"></nav>
<section id="fighters">
<div id="picture"></div>
<div id="text"></div>
<script type="text/javascript">
***** LOCATION TWO *****
</script>
</section>
<footer id="footer"></footer>
</body>
</html>
Below is global.js, which is the very first script file loaded:
Note that there are 2 addLoadEvent(func) calls here, and they succeed (don't run until after html elements exist) despite being above practically everything else.
function addLoadEvent(func) {
var prevOnLoad = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (prevOnLoad) {
prevOnLoad();
}
func();
}
}
}
function loadFile(id, filename) {
var xmlhttp;
if (window.XMLHttpRequest) {
xmlhttp=new XMLHttpRequest();
}
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4 && xmlhttp.status==200) {
document.getElementById(id).innerHTML=xmlhttp.responseText;
}
}
xmlhttp.open('GET', filename, true);
xmlhttp.send();
}
addLoadEvent(loadFile('nav', 'nav.txt'));
addLoadEvent(loadFile('footer', 'footer.txt'));
function getURLParameter(name) {
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
}
The argument to addLoadEvent is supposed to be a function, which will be called during the onload callback. You're calling the function immediately, and passing its return value, so the function isn't waiting for the onload event, and you get errors because the elements aren't in the DOM yet. It should be:
addLoadEvent(function() { loadFile('nav', 'nav.txt'); });
addLoadEvent(function() { loadFile('footer', 'footer.txt'); });
addLoadEvent(initialFighter);
You don't need an anonymous wrapper function for initialFighter, since it doesn't take any arguments. You just need to leave out the (), so you pass a reference to the function, instead of calling the function immediately.
Also, instead of chaining onload event handlers by saving the old value and calling it inside the new function, you can simply use addEventListener, as these automatically add to the list of listeners instead of replacing it:
function addLoadEvent(func) {
window.addEventListener("load", func);
}
Related
I am using mimetex.cgi to convert LaTeX text into Maths Text. For which I have put the following in the head tag
<head>
<script src="../../asciimath/js/ASCIIMathMLwFallback.js" type="text/javascript">
</script>
<script type="text/javascript">
var AScgiloc = '../../includes/svgimg.php';
var AMTcgiloc = "http://www.imathas.com/cgi-bin/mimetex.cgi";
</script>
</head>
In the body tag I have the following div which is refreshed by ajax. This contains math text.
<div id="mathtext"> .... </div>
Problem that I am facing:
On the first page load the LaTeX code in mathtext div is getting converted to Math Text image. However, when the div is loaded with new LaTeX code using ajax, it doesn't get converted to Math Text.
If I click on refresh, the LaTeX gets converted to MathText again.
I am not sure what is it that I am doing wrong here.
Edit 1: Including the onload function that is a part of ASCIIMathMLwFallback.js
if(typeof window.addEventListener != 'undefined')
{
//.. gecko, safari, konqueror and standard
window.addEventListener('load', generic, false);
}
else if(typeof document.addEventListener != 'undefined')
{
//.. opera 7
document.addEventListener('load', generic, false);
}
else if(typeof window.attachEvent != 'undefined')
{
//.. win/ie
window.attachEvent('onload', generic);
}
//** remove this condition to degrade older browsers
else
{
//.. mac/ie5 and anything else that gets this far
//if there's an existing onload function
if(typeof window.onload == 'function')
{
//store it
var existing = onload;
//add new onload handler
window.onload = function()
{
//call existing onload function
existing();
//call generic onload function
generic();
};
}
else
{
//setup onload function
window.onload = generic;
}
}
if (checkForMathML) {
checkForMathML = false;
var nd = AMisMathMLavailable();
AMnoMathML = (nd != null);
}
It calls a function generic() using the above... I guess it would do if I call this function at the end of my ajax query ?
Solved it !
ASCIIMathMLwFallback.js has a function called as generic() which is being called on page load here.This function translates Latex into math functions.
In order to convert latex to math function using a ajax query , simply call the generic function by adding the below to the ajax code.
generic.call();
This will ensure that all Latex is converted to math !!
I'm trying to get TestService.Server.WWW_SERVER_URL, but TestService.Server is undefined.
When I call test1(), it works well. But I cannot access the object literal TestServer.
Is there a different way?
test.html
<script type="text/javascript" language="javascript" src="TestService.js"></script>
<script type="text/javascript" language="javascript">
function test() {
alert("TestService.Server.WWW_SERVER_URL[" + TestService.Server.WWW_SERVER_URL + "]");
//test1();
}
</script>
TestService.js
document.write("<scr" + "ipt type='text/javascript' src='TestServer.js'><" + "/scr" + "ipt>");
var TestService = {
Server: TestServer,
Delimiter: ""
};
function test1() {
test2();
}
TestServer.js
var TestServer = {
WWW_SERVER_URL: "http://www.test.com"
};
function test2() {
alert("test2 has been called!");
}
You have this in your TestService.js
document.write("<scr" + "ipt type='text/javascript' src='TestServer.js'><" + "/scr" + "ipt>");
var TestService = {
Server: TestServer,
Delimiter: ""
};
you are trying to set a property in TestService with TestServer which hasnt loaded yet as you do not give time for the newly added script to load
TestService.Server will evaluate to undefined since TestServer does not exist yet
Setup an onload function that will add your script and then set your TestService.Server variable when its loaded
var TestService = {
Server: null,
Delimiter: ""
};
function test1() {
test2();
}
window.onload = function() {
var head = document.querySelector("head");
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", "TestServer.js");
head.addEventListener("load", function(event) {
if (event.target.nodeName === "SCRIPT"){
TestService.Server = TestServer;
}
}, true);
head.appendChild(script);
}
If you attach scripts dynamically, IE, Firefox, and Chrome will all
download the scripts in an asynchronous manner.
Firefox and Chrome will wait till all of the async requests return and
then will execute the scripts in the order that they are attached in
the DOM but IE executes the scripts in the order that they are
returned over the wire.
source
In your case, you can't gurantee TestServer.js get executed before TestService.js. So I will recommend you change the way you access global variable cross-file.
You can add TestServer.js to your html right before TestService.js, so they can execute one by one.
Anyhow, it is NOT recommended to do stuff like this, you can wrap them in your own namespace. Plus you'd better check the variable you want to use cross-file whether it's undefined before you use it.
I have made a namespaces framework for javascript. I am loading some plugins (.js files) which are dynamically added to the HTML.
I am going to try to simplify the code.
This function is used to dinamically load a JS. The callback function is called after .js file has been loaded. Consider that the following code has already been run.
MYNAMESPACE.plugins = ["plugin1", "plugin2"];
MYNAMESPACE.getJS = {
get: function (url, callback) {
var script = document.createElement("script");
var head = document.getElementsByTagName('head')[0];
script.type = "text/javascript";
script.src = url;
head.insertBefore(script, head.firstChild)
script.onload = callback;
script.onreadystate = callback;
return script;
}
};
I have a init function that loads the plugins contained in MYNAMESPACE.plugins as follows:
MYNAMESPACE.init = function (callback) {
for (index in MYNAMESPACE.plugins) {
plugin = MYNAMESPACE.plugins[index];
MYNAMESPACE.getJS.get(plugin + '.js', function ()
{
// This callback is executed when the js file is loaded
});
}
// Here I want to execute callback function, but after all the callbacks in the for loop have been executed. Something like: if (alljsloaded) callback();
}
In my HTML I have the following script tag:
<html>
<head>
<script type="text/javascript">
$(document).ready(function () {
MYNAMESPACE.init();
// The following line is not executed correctlybecause init finished before the scripts are loaded and the functionOnPlugin1 is undefined.
MYNAMESPACE.functionOnPlugin1();
});
</script>
</head>
<body>
</body>
</html>
And I want to change it for something like this:
<html>
<head>
<script type="text/javascript">
$(document).ready(function () {
MYNAMESPACE.init(function() { MYNAMESPACE.functionOnPlugin1(); });
});
</script>
</head>
<body>
</body>
</html>
But I don't know how to modify the function MYNAMESPACE.init() so it executes the callback after ALL the plugin scripts are loaded.
Any ideas? Maybe using closures.
Use for(;;) to loop through an array, not for(.. in ..).
Create a counter in the function, increment it every time that a script has loaded. When it has reached the number of scripts, invoke the callback function.
Code:
MYNAMESPACE.init = function (callback) {
var allPluginsLength = MYNAMESPACE.plugins.length;
var loadedCount = 0;
if (allPluginsLength === 0) callback(); // No plugins, invoke callback.
else for (var index=0; index<allPluginsLength; i++) {
plugin = MYNAMESPACE.plugins[index];
MYNAMESPACE.getJS.get(plugin + '.js', function () {
// This callback is executed when the js file is loaded
if (++loadedCount == allPluginsLength) {
callback();
}
});
}
}
Also, change your getJS.get method. Currently, callback always executes when a state changes, which is not desirable. To prevent the callback from being executed twice, the following can be used:
script.onload = function() {
callback && callback();
callback = false;
};
// onreadystatechange instead of onreadystate, btw.
script.onreadystatechange = function() {
if (this.readyState == 'complete') {
callback && callback();
callback = false;
}
};
callback && callback() is used, to ensure that a callback exists. If it does, it's then invoked.
After invoking the callback, it's immediately removed (callback = null).
This is necessary, because the onload and onreadystatechange events can fire when the script has loaded. Without my suggested code, the callback will be called multiple times for a single script.
Some changes like this should work:
MYNAMESPACE.init = function (callback) {
var remaining = MYNAMESPACE.plugins.length;
for (index in MYNAMESPACE.plugins) {
plugin = MYNAMESPACE.plugins[index];
MYNAMESPACE.getJS.get(plugin + '.js', function ()
{
// This callback is executed when the js file is loaded
remaining = remaining - 1;
if (remaining === 0)
{
callback();
}
});
}
}
I have used google feed API to read a Rss feed url and display the title. When I call the function get_rss1_feeds directly It works fine. But when I call it with setTimeout or setInterval I am able to see only blank screen and the page does not stop loading!!
<script src="http://www.google.com/jsapi?key=AIzaSyA5m1Nc8ws2BbmPRwKu5gFradvD_hgq6G0" type="text/javascript"></script>
<script type="text/javascript" src="jquery-1.5.2.min.js"></script>
<script type="text/javascript" src="query.mobile-1.0a4.1.min.js"></script>
<script type="text/javascript" src="jsRss.js"></script>
<script type="text/javascript" src="notification.js"></script>
My notification.js
/** global variable **/
var Rsstitle;
/** end global variable **/
document.addEventListener("deviceready", onDeviceReady, false);
// PhoneGap is ready
//
function onDeviceReady() {
// Empty
}
function get_rss1_feeds() {
console.log('test'); // this is being outputted
var Rss1_title = getRss("http://yofreesamples.com/category/free-coupons/feed/?type=rss", function(entry_title) {
if(Rsstitle != entry_title)
Rsstitle = entry_title;
console.log('test1',Rsstitle); // not working
});
}
//get_rss1_feeds() works fine
setTimeout(get_rss1_feeds,5000);
My jsRss.js file
function getRss(url, callback){
console.log('test2'); // this is being outputted
if(url == null) return false;
google.load("feeds", "1");
// Our callback function, for when a feed is loaded.
function feedLoaded(result) {
if (!result.error) {
var entry = result.feed.entries[0];
var entry_title = entry.title; // need to get this value
callback && callback(entry_title);
}
}
function Load() {
// Create a feed instance that will grab feed.
var feed = new google.feeds.Feed(url);
// Calling load sends the request off. It requires a callback function.
feed.load(feedLoaded);
}
google.setOnLoadCallback(Load);
}
You need to set a breakpoint in the getRss() function and see what's going on when it's called from setTimeout(). My guess would be that something in that function has a scoping issue and isn't available from the global scope that setTimeout runs in, but is available from the normal scope you tried it in. It could be variables or it could be functions that aren't available.
This can sometimes happen if functions are declared inside another function and thus aren't actually available globally.
FYI, this block of code is very odd:
var Rsstitle;
if(Rsstitle != entry_title)
Rsstitle = entry_title;
You can replace it with this:
var Rsstitle = entry_title;
today I've been working on loading dynamic javascript code (files). The solution I use is :
function loadScript(scriptUrl) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement('script');
script.id = 'uploadScript';
script.type = 'text/javascript';
script.src = scriptUrl;
head.appendChild(script);
}
the problem with this is that I don't know how to make sure WHEN the script contents are executed. Since the script contains classes (well JS classes), I wrote down a function that is called through setTimeout and checks if those objects are defined. This is not flexible as it is not automatical. So? Are there ways to load those scripts and have a reliable notification on when they have been executed?
You can use jquery's getScript and pass a callback function.
$.getScript("test.js", function(){
alert("Script loaded and executed.");
});
See: jquery.
The easiest way short of a JS library is to make an XMLHttpRequest and eval() the return. Your "onload" would be when the data is returned, and "oninit" would be right after you've eval'd.
EDIT: If you want to sequence this, you can create an AssetLoader that takes an array of scripts and won't eval() a script until the one preceding it has been fetched and initialized.
EDIT 2: You can also use the script.onload stuff in the post referenced in the comments. The eval method has a slight advantage in that you can separate and control the load and execution portions of the script import.
EDIT 3: Here's an example. Consider a file called foo.js that contains the following:
function foo () {
alert('bar');
}
Then execute the following from another page in your browser:
function initScript (scriptString) {
window.eval(scriptString);
}
function getScript (url, loadCallback, initCallback, callbackScope) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.onreadystatechange = function (e) {
if (req.readyState == 4) {
if (loadCallback) loadCallback.apply(callbackScope);
initScript.call(null, req.responseText);
if (initCallback) initCallback.apply(callbackScope);
}
}
req.send();
}
function fooScriptLoaded () {
alert('script loaded');
}
function fooScriptInitialized () {
alert('script initialized');
foo();
}
window.onload = function () {
getScript('foo.js', fooScriptLoaded, fooScriptInitialized, null);
}
You will see the alerts "script loaded", "script initialized", and "bar". Obviously the implementation of XMLHttpRequest here isn't complete and there are all sorts of things you can do for whatever scope you want to execute the script in, but this is the core of it.
You could use a counter variable, and a kind of callback:
var scriptsToLoad = 10;
var loadedScripts = 0;
//...
function increaseLoadCount() {
loadedScripts++;
if(loadedScripts == scriptsToLoad) {
alert("start here");
}
}
//script1-10.js
increaseLoadCount();
Maybe a bit nicer than with a timeout..