I've seen a few versions of this question on here, but none of them seem to be the same as my issue, so figured I'd create a new question. I must be doing something very silly here, but for the life of me I cannot spot it.
I'll start with the context, although it is not particularly relevant: I am deferring loading of jQuery with js code, which means none of my code files can contain a $(document).ready(...) call, because "$" will be undefined at first pass. Instead I am adding my ready functions to a "global" array, and after jQuery loads asynchronously, I call all these functions with the following:
function FireLoadEvents() {
while ( OnLoadEvents.length > 0 ) {
var item = OnLoadEvents.splice(0,1);
item[0].ready();
}
}
Now in any of my page components I can use the following to add an event I wish to fire when jQuery is loaded and the document is actually ready:
OnLoadEvents.push( { ready: function() { ... } } );
I've defined the OnLoadEvents variable at the very top of my template in the head:
<script language="text/javascript">
var OnLoadEvents = new Array();
</script>
As you can see it is outside of any function scope, and it is also declared before it is referenced. When tested on its own, this setup is working great, but for some reason when it is added to my actual page heads, script tags later in the page are not able to add items to the OnLoadEvents variable. For instance the following is pulled into my template by a php-include server side, and adds these lines to the body of my page:
<script type='text/javascript'>
OnLoadEvents.push( { ready: function() {
if ( $('.sponsor-link').length != 0 ) {
...
}
} } );
</script>
When I hit this page in Chrome, it reports the following exception on the "push" line above:
Uncaught ReferenceError: OnLoadEvents is not defined
Any idea what is going on? Why would OnLoadEvents be undefined just a few lines down from where I initialize it? I'll note that I've also tried defining and referencing OnLoadEvents as window.OnLoadEvents.
Thanks in advance!
Change this:
<script language="text/javascript">
to this:
<script>
The web-browser doesn't recognize the value of the language attribute and, as a result, doesn't execute the code.
Live demo: http://jsfiddle.net/gyhcM/2/
You don't need the language attribute, nor the type attribute, as the web-browsers assume JavaScript by default.
Related
I'm having some issues with running some functions from an external js file.
The html includes:
<script src="js/file.js"></script>
<script>
$("#center-button").click(function() {
explodePage("center");
});
</script>
The js file includes:
var explodePage = function(button) {
//code here
aboutPage();
}
var aboutPage = function() {
//code here
}
The explodePage function runs fine, but as soon as it reaches the call to the nested aboutPage function, it starts throwing these uncaught typeerrors at me. It works fine if I don't use an external js file and just put everything into the html. Pretty new to this so probably missing something obvious in scope or something. Any solutions?
Declare the function's definition as below:
function explodePage(button) {
//code here
aboutPage();
}
function aboutPage() {
//code here
}
Explanation:
When you use the var keyword for declaring functions, the execution of JS happens as when the variable is initialized, you cannot reference or use variable's before declaration. In contrast with the name function defintion JS interpreter first picks the enclosed functions before execution and initializes it before the code execution. This is called AST- Abstract syntax tree that is followed by JS interpreters.
Also Remember:
Also bind your Jquery code inside a Jquery document ready function, just to make sure the Jquery and the DOM elements are available for the bindings.
It's not a good a idea to pollute the global window object with variables, since there can be collisions. And immediately-invoked function expression is a good solution for this.
(function(){
//You can declare your functions in here, and invoke them below
$( document ).ready(function() {
//Check that the DOM is ready, in order to manipulate it an add events
$("#center-button").click(function() {
explodePage("center");
});
});
})($); //Notice that we are injecting a dependency, in this case jQuery
I am creating a wrapper for some arbitrary code (let's call it managed code). The managed code may include some functions that are defined in the window scope and are expected by other scripts on the page (horrible, 1997, practices, I know, but such is what I have to deal with), as global functions.
The purpose of the wrapper is to delay executing the wrapped code until jQuery is loaded. It looks like this:
(function () {
var once = true,
check = setInterval(function () {
if (window.$ && once) {
once = false; // setInterval can stack up if the UI freezes. Ensure this only gets called once.
executeBundle();
clearInterval(check);
console.log('Jquery loaded');
}
}, 100);
})()
// Wrapper proper
function executeBundle() {
// oodles of code of any origin
}
Now that the managed code is wrapped inside the executeBundle function, all functions/variables declared within it will be scoped to that function. This isn't a problem for the managed code itself, but for other scripts that load separately that may rely on global functions it provides.
I'd like to know if anyone knows a strategy like eval, but without the security issues, that may allow me to preserve the window scope for the running of the managed code. The constraint is that I can't modify the managed code at all--just the wrapper.
Based on T.J. Crowder's phenomenal answer, I realized that I could add the managed code to a <script> element and add that to the <head> like this:
var codeBundle = // Code in one long string
function evaluateBundle() {
var script = $('<script type="text/javascript"/>')
script.html(codeBundle);
$('head').append(script);
}
And let the parser evaluate the code.
I'd like to know if anyone knows a strategy like eval, but without the security issues
If you're evaling code of your own that you would run by having it in a script tag anyway, there are no security issues. You're running code either way.
You can't do this if the code you're wrapping will appear directly within evaluateBundle and it has declarations (vars and function declarations) that were supposed to be at global scope. Handling those would require modifying the wrapped code.
You can do this if you load that code separately, though, and then do a global eval on it. For instance, put it in a script block with a non-JavaScript type so the browser doesn't execute it:
<script type="x-code-to-wrap"></script>
...and then:
function evaluateBundle() {
var code = document.querySelector('script[type="x-code-to-wrap"]').textContent;
(0, eval)(code);
}
(The (0, eval)(code) bit is the global eval, more on MDN).
You may have to adjust the textContent part of that for cross-browser compatibility. This question's answers suggest using jQuery's html function:
function evaluateBundle() {
(0, eval)($('script[type="x-code-to-wrap"]').html());
}
Live example on JSBin
Following http://processingjs.org/articles/PomaxGuide.html for using Processing sketches on webpages, one of my functions utilizes this perfectly:
function drawSomething() {
// some calculations
var pjs = Processing.getInstanceById('canvasID');
var number = 5 // placeholder result of calculations
pjs.drawText(number);
}
Yet with another function, drawSomethingElse, the same pjs variable definition logs:
TypeError: pjs is undefined
All the code is wrapped in docReady, and drawSomething(); is called when the page loads:
$(document).ready(function(){
// do lots of stuff
drawSomethingElse();
}
Scope in javascript works like this. If you declare a var or function inside another function it's only visible inside this function
function outerScope(){
var outerVariable = "is defined in outer scope";
function innerScope(){
var innerVariable = "is defined in inner scope";
console.log(outervariable); //innerScope can see outerVariable (through a closure)
}
console.log(innerVariable) //=undefined outerScope can't see innerVariable
console.log(outerVariable) //outerScope can still see outerVariable
}
console.log(outerScope) //= function, global scope can see outerScope
console.log(outerVariable) //=undefined but global scope can't see inside outerScope
console.log(innerScope) //= undefined, therefore it can't see innerScope
console.log(innerVariable) //=undefined and of course not into inner scope
This is true for all functions, including jQuery functions, they are no exception to this rule. So that's why you have to define a var in the scope you want the scope "layer" you want to use it. And to not pollute the global scope you wrap things into these anonymous functions, just to add a scope "layer"
This model always applies, no matter how many layers you add. You will always be able to understand the behavior. (btw always check all the things with console.log you are unsure about, it helps to track down bugs. the more precise you can answer what is wrong with your solution the better you know how to fix it)
Adapting what you know about scopes and since you didn't define Processing in the current scope you know it therefore must be in global scope, means you can open your browser console and just console.log(Processing) and maybe call the method Processing.getInstanceById() yourself in the console a few times. Maybe it's not the canvas id, maybe it's the name of your sketch that defined the name of the instance. Try it out.
Since you now know that your .pde sketch isn't loaded by the time you want to get the instance via javascript, you have a few options. The easiest would be to make the sketch part of the document, so the $(document).ready() only fires and execute your javascript when both, processing and the sketch are loaded.
Usually processing checks the custom data-processing-sources attribute on the canvas and sends a asynchronous request for the files (your sketch). But since it's asynchronous it's not part of your document loading, so the document is ready but your sketch isn't.
If you instead put the sketch code in a script tag inside the document the document won't be ready until it's loaded. You also need to set the mime type or the browser will think this is javascript and throw an error. It doesn't change anything else, it's just another way of setting up your Processing Sketch.
<script type="text/processing" data-processing-target="canvasID">
//your sketch code
</script>
<canvas id="canvasID"></canvas>
And for you to still be able to load your sketch externally here comes the slightly more confusing 3rd way to set up your sketch. Remove the whole script tag and your sketch.
Skip the data-processing-target and data-processing-sources attributes, and instead of pjs = Processing.getInstanceById write
$(document).ready(function(){
var xhr = new XMLHttpRequest();
xhr.open("GET", "yourSketch.pde");
xhr.onload = function(){
var code = xhr.response;
var canvas = document.getElementById("canvasID")
pjs = new Processing(canvas,code);
//rest of your code
}
xhr.send();
});
Note: This technique won't work if you view your website locally from the file:// protocol
pjs scope is drawSomething function for using it in different function change your code like this
(function() {
var pjs = Processing.getInstanceById('canvasID');
function drawSomething() {
var number = 5 // placeholder result of calculations
pjs.drawText(number);
}
function someotherfunction() {
drawSomething();
}
}());
now you can use pjs anywhere in this anon function
I am trying to create namespaces in JavaScript as in the following script:
var hlAdmin = hlAdmin || {};
hlAdmin.editCompany = function (src) {
// function script
}
Then I call the function in HTML:
onclick="hlAdmin.editCompany(123)"
I get a reference error: Cannot find "editCompany".
Anyone know why?
Based on your comments I assume the following:
The equivalent script (and scoping is like):
<html><head>
</script>
var hlAdmin = hlAdmin || {};
hlAdmin.editCompany = function (src) {
// error in this script
}
</script>
</head></body>
<button onclick="hlAdmin.editCompany(123)">Caption</button>
</body></html>
In this example hlAdmin is indeed in the global scope (the root-scope of the host, called window in browsers).
If (in this example) you get reference error: Cannot find "editCompany", then one should look at other error-messages in your (browser's) error-log, because when there is a fatal error in the function for hlAdmin.editCompany, then that function will not be created (hence .editCompany becomes a property that points to undefined instead of a method that points to the function OR .editCompany doesn't even exist (depending on engine/error)).
To investigate if you indeed have a scoping-problem you could test this by: window['hlAdmin'] || (window['hlAdmin']={}); (or some equivalent variant). If that made the code work, then it seems you have some scoping-problem.
Hope these steps help someone in the future.
It's generally considered bad form to mix inline javascript and non-inline. The preferred way to do this would be to keep all the javascript in one place using an event handler:
window.hlAdmin = window.hlAdmin || {};
window.hlAdmin.editCompany = function (src) {
// function script
}
document.getElementById('yourElementId').onclick = function() {
hlAdmin.editCompany(123);
};
To more specifically address the issue: One thing that could cause this issue is if the hlAdmin object is not ending up in the global scope. You stated that this declaration is "at the top of the JavaScript file", but if it's in any kind of function (such as a function set to window.onload, or the jQuery $(function() { ... });) it would not end up in the global scope when declared as a var. A variable declared with var will only end up globally scoped if it's in the root scope, outside of any kind of function. If rather than using var hlAdmin you instead use window.hlAdmin, this will make sure that even if you're inside a document ready function or something similar, you're creating your hlAdmin in the global context, which will fix the problem if it is in fact an issue of scope.
I found the problem.
The browsers (at least Aurora and Chrome) are dropping the namespace in the onclick attribute. When you look at the browser html the namespace has just disappeared from the markup.
I have a website with three files:
index.html
<html>
<head>
<script src="First.js"></script>
<script src="Second.js"></script>
</head>
<body></body>
</html>
First.js
window.onload = Main;
Second.js
function Main() { var foo = 1; }
When I open the page, I expect the variable 'foo' to be set to '1'. Instead, when I open the page, it breaks indicating 'Main is not defined' and 'Main()' is never called.
If the '.onload' event for the window isn't supposed to fire until the page is fully loaded and thus assumingly both scripts have been loaded, why doesn't the window object have a reference to the 'Main()' method in 'Second.js'? Should 'Main()' be a globally accessible function?
NOTE: I realize I can change the order in which I load the scripts and then my code would work, but that's not the purpose of my question. What I really want to do is validate my assumption.
It doesn't work because the code in <script> elements are run sequentially and independently (this assumes synchronous scripts, the default). That is, two different <script> sections cannot forward-reference each other. Now, JavaScript "hoists" function name () {} constructs within the same context, so this would have worked "out of order" in a single <script>.
Preamble / warning:
Consider not using onload. If something else tries to use it (onload), then something will likely break. (I recommend jQuery because it "gets this right" and "is so darn easy"... then again, so do many other frameworks. Pick one :-)
For instance, this would work in jQuery:
jQuery(function () { Main() })
Explanation of why it works (and how to fix it without reordering scripts or using jQuery):
Note that in this case the jQuery internal event handler is called, which calls the anonymous callback, which then calls Main (which is now resolves to a function-object). The "similar" code here would be:
window.onload = function () { Main() }
These work because Main is not evaluated until the onload has occurred, in which case all the [synchronous] <script> elements have been executed. (Please see my comment as to what it means to evaluate Main.)
On the other hand, window.onload = Main (or jQuery(Main)) evaluate Main right-then and use the resulting value; as others have noted, Main is not set ("is undefined") at this point due to the ordering of the <script> elements (they run sequentially).
Please refer to the note at top as to why to not to use onload directly ;-)
Happy coding.
The second script file should be included first! That's where the function is defined. You can't reference it in the first script until it's not loaded.
The Main() function doesn't exist until Second.js has been loaded, so if you're loading First.js first there's no function-object to use in window.onload = Main; (Main is undefined). Load Second.js first if you aren't already.