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
Related
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
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.
It keeps me from easily defining global variables and its often a nuisance. Why doesn't the code outside functions that are called execute? For example, if I call the function myFunction from HTML, this works...
function myFunction() {
var myObject = document.getElementById("myHTMLObject");
myObject.style.backgroundColor = "blue";
}
but not this...
var myObject = document.getElementById("myHTMLObject");
function myFunction() {
myObject.style.backgroundColor = "blue";
}
If I call the function from the HTML, only the code inside that function will run (unless that function calls other functions). Am I making a mistake or is there a way around this? I don't want to encompass all my code in a window.onload function.
P.S. I run my html on Chrome if it makes a difference.
Thanks for any help.
It does execute, and does when when the script runs, which is when the <script> element is parsed.
If you try to get an element that is added to the DOM by HTML that appears after the <script>, then it won't exist when you look for it so you will get nothing back.
If the <script> appears after the element, then you won't have a problem.
If this example:
var myObject = document.getElementById("myHTMLObject");
function myFunction() {
myObject.style.backgroundColor = "blue";
}
doesn't work, then here are a couple possible reasons:
The script is running too early and thus when you do document.getElementById("myHTMLObject");, the page has not yet been loaded and thus myHTMLObject does not exist yet.
You have more than one global definition of myObject and one is overwriting the other.
Your second coding example is recommended for a number of reasons:
Doesn't use a global variables which is advantageous (variables are private to within the function and can't create conflicts with any other code or be interfered with by any other code).
The functionality is entirely contained within the function
There are no timing related issues with when the initialization code is run because the DOM is searched only when the operation is about to be carried out.
Getting DOM objects when needed works better with dynamically added HTML.
A simple user, triggered operation is plenty fast getting a DOM object when needed.
At my current place of work we have a set way of going about JavaScript and was wondering if someone can help shed any light on the below approach to storing variables and cached jQuery objects.
(function($) {
var APP = {};
$(function() {
APP.header = $("#header");
APP.footer = $("#footer");
});
})(jQuery);
Firstly, what is the point of setting what appears to be a global variable and then appending jQuery objects to the variable? Does it keep things cleaner, does it make your app perform any faster or is it an easier way to set variables without having to go:
var header = $("#header");
var footer = $("#footer");
I understand everything else going on, but always wondered why this was common place in all our project JS files? I presume there is a name for what is going on here and I'd like to understand it a little better, especially if there are caveats that I should bring up with my manager about doing this.
The name of these constructs are closure and namespace. A closure is a function that maintains references to variables outside of its scope.
Note that you can now access the APP variable later and it will be correctly updated:
(function($) {
var APP = {};
$(function() {
APP.header = $("#header");
APP.footer = $("#footer");
});
// "APP.header" and "APP.footer" are defined later in the code
})(jQuery);
If you used local variables, you could not get his effect:
(function($) {
$(function() {
var header = $("#header");
var footer = $("#footer");
});
// "header" and "footer" are long gone local variables
})(jQuery);
Note that you could use global variables (technically properties of window) here:
(function($) {
$(function() {
header = $("#header");
footer = $("#footer");
});
// "header" and "footer" are accessible global variables
})(jQuery);
But that's bad practice for a number of reasons (notably, possible collisions and unexpectedly defined variables). So you set up what's called a namespace -- a variable like APP to hold your data that needs to be "global-like".
As to the specific utility of this closure in your case, note that it's surrounded by $(). This is a jQuery way of saying "run this functoin when the DOM has finished loading". The DOM has to be finished loading before trying to access $('#header') and $('#footer') to make sure these items actually exist in the DOM. But since this is a callback, it will be execute later, that's why it's need a closure to maintain a reference to APP when it does eventually get called.
It's called namespacing.
This is so that you can keep the DOM clean, and other plugins/scripts won't interfere. If you were to create a global var called header or footer, another plugin may want to use that global var, and thus breaking your code.
APP appears to be the namespace your company uses to have an applicationwide consistent notation without cluttering the global namespace.
Also using variables to store the access to Dom-Elements is faster then accessing them each time needed via the jQuery Construct $("...")
Other than my comment, some "caveats" to "Global Vars"
Local variables are freed from memory whereas global vars sit with the lifetime of the app open
If code is inside a function or in another scope and references that variable, the engine has to slow down to step back until it reaches your global scope, thus causing "lag"
Little known factoid, global scope is shared with the window object!!! This means it takes even LONGER for the engine to find them, thus another negative slow down point
[1] Ok, I don't even know how to call this, to be honest. So let me get some semi-pseudo code, to show what I'm trying to do. I'm using jQuery to get an already existing script declared inside the page, inside a createDocument() element, from an AJAX call.
GM_xmlhttprequest({
...
load:function(r){
var doc = document_from_string(r.responseText);
script_content = $('body script:regex(html, local_xw_sig)', doc).html();
var scriptEl = document.createElement('script');
scriptEl.type = 'text/javascript';
scriptEl.innerHTML = script_content; // good till here
(function(sc){
eval(sc.innerHTML); // not exactly like this, but you get the idea, errors
alert('wont get here ' + local_xw_sig); // local_xw_sig is a global "var" inside the source
})(scriptEl);
}
});
So far so good, the script indeed contains the source from the entire script block. Now, inside this "script_content", there are auto executing functions, like $(document).ready(function(){...}) that, everything I "eval" the innerHTML, it executes this code, halting my encapsulated script. Like variables that doesn't exist, etc.
Removing certain parts of the script using regex isn't really an option... what I really wanted is to "walk" inside the function. like do a (completely fictional):
script = eval("function(){" + script_content + "};");
alert(script['local_xw_sig']); // a03ucc34095cw3495
Is there any way to 'disassemble' the function, and be able to reach the "var"s inside of it?
like this function:
function hello(){
var message = "hello";
}
alert(hello.message); // message = var inside the function
Is it possible at all? Or I will have to hack my way using regex? ;P
[2] also, is there any way I can access javascript inside a document created with "createDocument"?
Simply trying to access a local variable inside a function from outside of it is impossible due to scope. However, using closures you can absolutely accomplish this:
function hello(msg){
return function message(){
return msg;
}
}
alert(hello("yourMessage")()); // will alert "yourMessage"
Note exactly what's happening here. You are calling a function which returns a function, in which "yourMessage" is now defined inside its scope. Calling that inner closure the second time will yield that variable you set earlier.
If you are not familiar with closures in JS, I suggest you read this wonderful FAQ.
It's not possible that way. You can introspect object's properties (any function is an object), but not before you have created an instance with new operator.
Looking at your code sample, it seems that your approach is a bit messy – eval()'ing script blocks is something one should not do unless absolutely necessary (a situation I can't imagine).
In your example at
function hello(){
var message = "hello";
}
alert(hello.message); // message = var inside the function
you can in fact use hello.toString() to get the function source, like this:
alert(hello.toString().match(/var message = \"(.*)\";/));
You want to eval the script in global scope. Briefly it is,
// Evalulates a script in a global context
globalEval: function( data ) {
data = jQuery.trim( data );
if ( data ) {
if ( window.execScript )
window.execScript( data );
else if ( jQuery.browser.safari )
// safari doesn't provide a synchronous global eval
window.setTimeout( data, 0 );
else
eval.call( window, data );
}
}
Also check out Google's caja for secure external script evaluation.