I'm not even sure if this is possible, but is there anyway to set an execution context beyond setting the value for "this"?
The case I am primarily referring to is executing code from one frame in the context of another frame, so that when I access global objects (ex: window, document...) from a function that was defined in frame1, It will be executed in the frame2 environment.
If this is not possible, what are some workarounds? Please don't say "just define the function in the child frame", I'm dealing with a larger application framework, and it would be both pointless and memory inefficient if I had to load up two instances of the entire framework.
EDIT: Here is some code which should demonstrate what I am trying to do. When run, it should show an alert that, if a solution is found, displays "iframe.html" at the end of the location string.
<script>
function run() {
go.call(window.iframe);
}
function go() {
alert(window.location);
}
</script>
<iframe src="iframe.html" name="iframe" onload="run()">
Thanks.
This makes use of the deprecated with statement as well as eval, but it may be the only way to accomplish what you want.
function run() {
with (window.iframe) {
eval("(" + go.toString() + ")()");
}
}
function go() {
alert(window.location);
}
If you can "frame" your "integration" code in a closure that aliases window to window.iframe, you could achieve what you want:
(function(window) {
// your integration code...
// the whole code you want to frame...
alert(window.location);
})(window.iframe);
But, you have to "frame" all the code you want to interact with.
Also, you can expose "integration" functions for you to call from "outside" by passing other "context" objects in:
var context = {};
(function(window, context) {
// your integration code...
context.f = function() { ... };
// the whole code you want to frame...
alert(window.location);
})(window.iframe, context);
context.f();
Use Function.apply and Function.call.
function foo(x, y, z) {
console.log(this === someFrame); // true
}
foo.call(someFrame, 1, 2, 3);
EDIT
Based on your code sample and comments below, the answer is no. Scripts cannot change the global scope.
Include the code in both frames. Then, call the function in the other frame. Let's say there's a top level global function called doWork().
You field a button press in frame2 and you want to execute a function in frame1 (where frame1 is the window object representing frame1). Assuming they are in the same domain and pass the same-origin tests, you can simply reach into frame1 and call doWork() in that frame with:
frame1.doWork();
This works because all top level javascript global variables and functions are properties of the window object for that browser window/frame. Each window/frame has it's own set of top level properties. If you are within the same domain and can get the appropriate window object, you can just call javascript in that window. The javascript must be present in that window. You can't execute javascript from your window in the content of another window, but if you put the code in both windows, you can execute it in either window by starting with the right window object.
How you get the right window object for a particular frame depends upon how your frames are structured.
Again assuming these frames are in the same domain and pass the usual same-origin tests, it would also be possible write a javascript function in frame2 that operates on frame1 contents. For example, you could have this code in frame2:
frame1.document.getElementById("foo").value = "";
So, you couldwrite a function in frame2 that would operate on frame1. You cannot, however, just execute a function from frame2 in the content of frame1 without writing it to know about operating on a different frame.
For example, you could write a function like this that takes the desired window to operate on:
function clearValue(id, win) {
win.document.getElementById(id).value = "";
}
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
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
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.
I have defined a new window in javascript like this:
var r = window.open("", "rgroup", "status=1,width=800,height=600");
and I have a function that prints certain output, and I am trying to run it like this:
r.onload = runproduct();
but it stills run on the main parent window.
I must add, I am not trying to give the order of running from the parent window, but from a second new window, so to make it short, I have:
1) Main window (function is appearing here)
2) Second window (I am giving the order from here)
3) Third window (this is where I want the output to happen)
Is it easy to fix? I am scratching my head as for why giving the order to run a function on the second window from the main, works, but giving the order to a third window from a second window doesn't...
Thank you for any clarification! This is a bit confusing...
Functions enclose the variable context at the time they're created, not when they're run. I.e. your runproduct() is using the variable scope chain of it's creating context (the main window). Regardless of when it's run, any references to globals or other closure variables will be to the state in the main window.
To fix this, you can write your runproduct() function to explicitely refer to the window reference. E.g.
var r;
function runproduct() {
console.log(window); // refers to main window
console.log(r); // refers to second window
}
r = window.open(...etc...);
I want to use window.open to open a window to one of my JSP file. But the browser keeps showing connecting... And even firebug stops working every time I click the text. Neither the p nor the input tags work, but when I use a href to link the JSP it can link to the file:
<!DOCTYPE html>
<html>
<head><title>Sample JSP Page</title>
<script>
function open(){
//window.open("hello.jsp","hello","height=700, width=800");
var x=window.open("hello.jsp","window","status=1,height=700, width=800");
x.focus();
}
</script>
</head>
<body>
<h1>Sample JSP Page</h1>
<p onclick="open()">do not work</p>
<form>
<input type="button" value="new window" onclick="window.open('test-with-utils')"></form>
</body>
</html>
That's because you have redefined window.open when you defined the function open. Use a different function name instead.
Change the name of the function.
The window object is the top level object in JavaScript, and contains in itself several other objects, such as "document", "history" etc.
When you define a variable or a function of your own you really add a new property to the window object. And this will work ( and a little live example ):
var foo = "bar";
alert ( window.foo ); // says "bar"
In addition if you add this little snippet to your code:
window.onerror = function ( msg, url, num ) {
alert ( "Error: " + msg + "\nURL: " + url + "\nLine: " + num );
return true;
};
you will get this error, when press the button:
Error: Uncaught RangeError: Maximum call stack size exceeded
This means an endless recursion. It is a side effect - you define a new open function, and when you call window.open(), you recursively call your function.
Just to expand on the reason that you are having problems here, you may want to read a little about javascript Scope (Very Helpful Blog). Essentially, consider the following code:
<script>
var thisOne=true;
function thatOne() {
alert("whizbang");
}
var theOther={foo:"bar"};
//More code here...
</script>
Once you reach the comment, you know you can access those variables and the function directly, like if (thisOne) {...}, element.onclick=thatOne; or console.log(theOther.foo). However, you can also access them as children of the root object which, in a web browser, is called window. So you can do:
console.log(window["thisOne"]);
window.thatOne.call(obj, params);
console.log(window.foo.bar);
so by defining open() as a function which is not inside another element (which is to say, is inside the root element), you overwrite the window.open() function. When you attempt to call the function later on, you get problems because the open function calls window.open, which calls window.open, which calls window.open...
There's a few ways to get around this -
Define the onclick handler inline
To do this, get rid of the whole <script>..</script> element then, using whichever element you choose (that supports it) add the onclick attribute:
onclick="window.open('hello.jsp','window','status=1,height=700, width=800');"
This is a nice and quick method, and it keeps all the logic right there with it's triggering element, but it is not easily extensible and you may find yourself sneered at by some. ("Oh, you use inline javascript? how quaint")
change the method name
This will take the least effort from you in terms of getting your page working now from what you have (it's also essentially what everyone else has suggested). Just change the name of the open method to something like openANewWindow() or gotoJSP() or anything that doesn't already exist in the root object, making sure to get both where you define it (in the script element) and where you use it (in the onclick attributes).
Use a closure
This is almost definitely not what you want in this case, its more complexity than you need for a single function. Just including this as an example of how to get out of the root object, seeing as being in it seems to be the heart of your problem.
You have probably already seen in javascript how to define an object, but you may not know that by defining an object, all you are really doing is adding an object property to the root object. You can use this behavior to your advantage, to give a hierarchical structure to your functions.
For example:
<script>
var MyFunctions = (function() {
function open(){
var x=window.open("hello.jsp","window","status=1,height=700, width=800");
x.focus();
}
return {open:open};
})();
</script>
This creates an anonymous function that is immediately run. Inside the scope of this function, another function, open() is defined, however it is defined within the scope of that anonymous function, not the root object (window). After open() is defined, a reference to it is returned as the value of the object property: open.
The result of all this is that the open property of the MyFunctions object is the function you need. You can then call it with MyFunctions.open() or even window.MyFunctions.open().