Is it possible to copy a function (or any object for that matter) from one window context to another in Javascript?
Let's say I have a parent window with a frame in it. The frame defines a function foo(), which I'd like to copy to the outer window. (One reason to do it would be so that foo() is available when the frame document navigates to a different URL.)
I know I can easily create a reference to foo() in the parent by doing
function foo() { ... }
parent.foo = foo;
But with this method foo() still lives in the frame document, and will not be available to the parent should frame get unloaded.
I know I can also create a new function in the parent by using the Function constructor:
parent.foo = new parent.Function(" ... ");
However, this requires me to have my function as a JS string.
DOM supports moving nodes from one document to another via importNode(). Does Javascript have a similar feature?
nitko's solution seems to be correct, though a little more work is required. This function seems to work on FF2, FF3, IE7 and Chrome:
function importFunction(fn, win) {
var code = fn.toString();
var params = code.match(/\(([^)]*)\)/);
if (typeof(params[1]) !== 'undefined') {
params = params[1].split(/\s*,\s*/);
} else {
params = null;
}
code = code.replace(/^[^{]*{/, '');
code = code.replace(/}$/, '');
if (params) {
return new win.Function(params, code);
}
return new win.Function(code);
}
The returned function can be used in the needed window. Something like:
parent.foo = importFunction(foo, parent);
You can always get the code for a given function calling it's toString() method:
parent.foo = new parent.Function( originalFunction.toString() );
Though I don't certainly know it's available in every browser, I think I've seen it working in major browsers, modern versions.
Related
Thanks in advance for the help.
I am trying to monkeypatch an existing javascript function so that one of its lines point to a new location. It would be easy to just redefine the function, except that it is rendered from server side code that has dynamic contents in it.
function GoPrint() {
$.cookie('edit_child','on',{expires:28,path:'/'}); //Dynamically created (edit child could be off)
window.open('../../Common/Output/HTMLtoPDF.aspx','print'); //Always static, need to change this call.
}
In my example, the first line creating the cookie, is created dynamically server side, so the property could be set to off.
I need to the change the window.open to call a different page instead of the htmltopdf page.
Although nasty, I would like to just redefine the function with a replace on the HTMLtoPDF text to point to the new page.
I have started this below, but do not know how to get the existing contents of the function to change it.
function($){
var _oldPrint = $.fn.GoPrint;
$.fn.GoPrint = function(arg1,arg2){
return _oldPrint.call(this,'',);
};
})(jQuery);
Any suggestions?
One way to do it would be to call toString on the old function, sub the old URL out with the new one, and eval the result, but only after considering the security implications.
Purely security-wise, a safer way would be to monkey patch the window.open function inside the monkey patch of GoPrint.
function($) {
var _oldPrint = $.fn.GoPrint;
$.fn.GoPrint = function(arg1, arg2) {
var _oldopen = window.open;
window.open = function() {
_oldopen.call('YOUR_URL_HERE', 'print');
};
return _oldPrint.call(this);
window.open = _oldopen;
};
})(jQuery);
After coding in JS for a while I decided to make my own framework. Something similar to jQuery. But a very very stripped down version. After some googling I put together this code:
function $elect(id) {
if (window === this) {
return new $elect(id);
}
this.elm = document.getElementById(id);
}
$elect.prototype = {
hide: function () { this.elm.style.display = 'none'; },
show: function () { this.elm.style.display = ''; },
toggle: function ()
{
if (this.elm.style.display !== 'none') {
this.elm.style.display = 'none';
} else {
this.elm.style.display = '';
}
}
};
So far this seems to work. But I'm not interested in the functionality. I want to understand the logic. Adding methods part is understandable. Though I didn't understand the function of
if (window === this) {
return new $elect(id);
}
If I remove it, function breaks. Since this is an if statement there are 2 results. True or false. So I tried to remove the if statement and just use return new $elect(id); assuming window === this returns true, but that didn't work. Then I thought it might return false, so removed the whole if statement. That also didn't work. Can someone enlighten me? Also is this code valid? I'm probably missing some stuff.
Just tested on jsfiddle and it doesn't work. Though it works on jsbin o.O
jsfiddle jsbin
EDIT: using $elect(id).toggle(); to call it. Though you can check the demos.
I want to understand the logic.
$elect is a constructor function that is supposed to be called with the new keyword (new $select). If it is not ($elect()), what will happen then? There is no instance constructed, and the this keyword will point to the global object (window) - which we do not want. So this snippet is a guard against that occasion, when it detects that it invokes itself correctly with new and returns that.
If I remove it, function breaks
When you are calling it like $elect(id) without the guard, it will add a elm property to the global object (a global variable in essence) and return nothing. Trying to call the .toggle() method on that will yield the exception undefined has no method 'toggle'.
I tried to remove the if statement, assuming window === this returns true
Well, then you just created an infinite recursive function that will lead to a stack overflow exception when called.
Btw, the proper way to guard against new-less invocation is not to compare against window (the global object might have a different name in non-browser environments) and assume everything else to be OK, but to check for the correct inheritance. You want the constructor only to be applied on instances of your "class", i.e. objects that inherit from $elect.prototype. This is guaranteed when called with new for example. To do that check, you will use the instanceof operator:
function $elect(id) {
if (! (this instanceof $elect)) {
return new $elect(id);
}
this.elm = document.getElementById(id);
}
This also makes the intention of the guard explicit.
To understand how that condition works, you must first know that in the global scope this refers to the window object. In local scope, such as within an object, this refers to the object itself.
Your $elect function is a constructor for your framework. If you call it directly, like so:
$elect('some-id-blah')
It will first realize that you are trying to create an instance (because the condition window === this will evaluate to true), and it will recursively create a new instance of itself. Once it does that, this will no longer refer to the window object, it will refer to the new instance of your library, thus the condition will not be met.
I hope that's somewhat understandable.
Your function is a constructor. By default the this in any function not called explicitly ($elect(), not foo.$elect()) on any other object as a method, will be passed the global object (the window) in this. However the if checks that if the this indeed refers to the window object, the return new $elect(id); is executed. The new $elect(id) does the following:
make a new instance of the $elect.prototype
put the newly created instance in the this and call the method again.
On the second pass, the this === window evaluates to false.
First thing to understand is that this function is calling itself:
function $elect(id) {
if (window === this) {
return new $elect(id);
}
this.elm = document.getElementById(id);
}
First time you call the function window is going to be === this. Because there you're invoking the function as a method. When invoked as a method, functions will be bound to the object the function/method is a part of. In this example, this will be bound to the window, the global scope.
But then with the new keyword, you're invoking the function as a constructor. When invoked as a constructor, a new Object will be created, and this will be bound to that object, so this no longer refers to window the second time around.
With the new instance of $elect, you're then also setting a local variable elm correctly to the instance, allowing your other functions to call on that elm later.
This logic:
if (window === this) {
return new $elect(id);
}
Ensures that, if your constructor function is called as a function:
var foo = $elect(id);
rather than as a constructor:
var fo = new $elect(id);
it will return the correct result - a new $elect object. When called as a function, the default context will be the global context or window when running in the browser, triggering the if clause and returning the result of calling it as a constructor.
Why it isn't working in your fiddle
In the linked fiddle, it is setup to wrap your code in a onload handler. The result of which looks like this:
window.onload=function(){
function $elect(id) {
if (window === this) {
return new $elect(id);
}
this.elm = document.getElementById(id);
}
$elect.prototype = {
hide: function () { this.elm.style.display = 'none'; },
show: function () { this.elm.style.display = ''; },
toggle: function ()
{
if (this.elm.style.display !== 'none') {
this.elm.style.display = 'none';
} else {
this.elm.style.display = '';
}
}
};
}
Because of that, your $elect variable isn't visible outside of the onload handlers scope.
Generally, you would want to add one variable to the global scope to enable access to your framework. Something like this added at the end of your code above will make it visible:
window.$elect = $elect;
Demo Fiddle
If you call it like this:
$elect("theId").hide();
The first time in it is called as a function and will find window === this and do this:
return new $elect("theId")
which creates a new one and call the function again. (Whatever the 2nd invocation of the function returns will get returned.)
The second time in, it is called as a constructor, so window !== this and it will pass down to find the element and store it internally.
Constructor calls that don't return anything from the function itself will return the instance created.
That way the .hide() part will get executed with the proper value of this (the instance created the first time in). And the element will disappear from the screen.
Note that if you call it like this:
var bob = {};
bob.$select("theId").hide();
It will not work because the this is set to bob which gets and 'elm' property set and there is no 'hide' property to be called because its not a $elect kind of object.
NOTE
If all the other methods in the other functions return this you will be able to chain them:
$elect('theId').show().red();
assuming you added a function to color the element red.
I'm still kinda new to Javascript and noticed in lot of places in my code I am using JQuery to get a reference to my canvas element like this:
$('#myCanvas')[0].getContext('2d');
I have my suspicions that it is slowing my site down because I have it in a lot of places that may be called quite often. Ideally, I'd just do this once and access the context from any javascript page.
I tried to make a global variable but it didn't see to work (probably because it runs before the page can load) so instead I put this function in global scope in my first referenced javascript file:
var drawingCanvasContext;
function getDrawingCanvas() {
if (drawingCanvasContext == null) {
drawingCanvasContext = $('#myCanvas')[0].getContext('2d');
}
return drawingCanvasContext;
}
So then whenever I need the canvas in my code I just call that method.. But it just seems rather.. messy. I doubt this is an uncommon desire so I'm curious of the proper solution. I'd prefer it just to be a variable instead of a function and to be accessed globally without all these null checks. Does anyone know how to accomplish this? Thanks.
You can simply do this
var drawingCanvasContext = $('#myCanvas')[0].getContext('2d');
instead of defining a function for that.
If you're careful about global scope pollution, you should wrap it up in a function or use namespacing. E.g.
var myNamespace = myNamespace || {};
myNamespace.drawingCanvasContext = $('#myCanvas')[0].getContext('2d');
I've got only two little improvements on your code:
(function(){
// move the var out of the global context by wrapping everything in a closure
var /* static */ drawingCanvasContext;
function getDrawingCanvas() {
if (!drawingCanvasContext) // no need to check for null, defaultvalue is undefined
drawingCanvasContext = $('#myCanvas')[0].getContext('2d');
return drawingCanvasContext;
}
window.getDrawingCanvas = getDrawingCanvas;
})();
Of course you could/should put that method in a namespace object, I guess you have more than one of such.
Ultimately all you can do is store the context around to be retrieved afterwards. The problem I can see here is probably an order of operations, the context is being requested before the page is ready, and possibly you're losing scope with your VAR declaration (i think).
;(function($, window, document) {
/* private */ var canvas = null;
/* private */ var context = null;
$(document).ready(function() {
canvas = $("#myCanvas")[0];
context = canvas.getContext('2d');
$('body').trigger('CanvasReady', [canvas, context]); // You can fire an event to bind to with jquery for when the canvas is ready.
});
/* public */ window.getCanvas = function() { return canvas; };
/* public */ window.getCanvasContext = function() { return context; };
// You can also use a namespace here too if you want
// window.Canvas = {};
// window.Canvas.GetContext = ... etc ...
})(jQuery, window, document);
This will aquire the canvas when it's ready, fire an event you can subscribe to for when the canvas has been found and is ready for manipulation.
It will expose 2 functions globally:
getCanvas which will get you the Canvas Instance
getCanvasContext which will get you the Canvas Context
You can adapt this to use a registry pattern if you want so it can aquire & store multiple canvas elements/contexts but this is just a simple pattern to show you.
Note: It returns null if the methods are called before the canvas/document is ready, you should be doing all of your setup code after domReady anyway.
You can define a getter on your global variable that will execute each time you read from that variable (see here for tips on making this backward compatible):
var _drawingCanvasContext;//behind the scenes variable that keeps the value
Object.defineProperty(this, "drawingCanvasContext", {
get: function() {
return _drawingCanvasContext || (_drawingCanvasContext = $('#myCanvas')[0].getContext('2d'));
}
});
Here's a fiddle where I set it to 'foo' to show you how it works.
I trying to create a sandbox module that can take a object and prevent that object's code reference to window.
here is how it work in concept.
var sand = function(window) {
var module = {
say: function() {
console.log(window.location);
}
};
return module;
}
sand({}).say(); // window.location is undefine
This doesn't work if the object is pass-in
var $sand = (function(){
return function(obj, context) {
return (function(obj, window) {
window.module = {};
// doesn't work even copy object
for (p in obj) {
window.module[p] = obj[p];
}
console.log(window.location); // undefine
return window.module;
}(obj, context));
};
}());
var module = {
say: function() {
console.log(window.location);
}
};
$sand(module, {}).say(); // still reference to window.location
How can i make this pattern work?
As long as you don't have a variable shadowing window in the scope of your function, the function will be able to access window. Even if you had a variable called window, the code will still be able to access the properties by simply omitting window..
(function(window) {
console.log(window.location); //undefined
console.log(location); //this will still work
})({ });
In other words, sandboxing JavaScript in a browser environment is not possible like this.
In your first example, the only reason window is undefined is because you are passing in an empty object and calling the argument window, so it is hiding the real window.
Also, you can always get access to the window object by hoisting the this variable inside a closure, like so:
console.log ( ( function () { return this; } )() );
So even if you somehow manage to block window, it's trivial to get it back again.
If you define the function outside your sandbox, the context will be the current one, and you can't really do otherwise.
If you really want to do some sandboxing, then you should use iframes to achieve that. Take a look at https://github.com/substack/vm-browserify it is a browser version of the vm module of node, you should be able to extract some good pieces of work, and avoiding eval which is not really clean for what you want to do.
Where SomeMethod could have:
function SomeMethod(item)
{
item.setAttribute('name', item.id);
}
Instead of:
function SomeMethod(itemId)
{
var someItem;
someItem = document.getElementById(itemId);
someItem .setAttribute('name', someItem .id);
}
Silly example, but the idea is not to send in the id itself, but the actual control calling the method. I swear this can be done but have had no luck searching... partially because I'm not even sure what to search on.
I thought it was self, but self doesn't seem to be what I want when the script I have runs.
Use the this Keyword.
You actually don't need to pass this as an argument to your function, because you've got a click event object that you can access. So:
<script>
function clickEventHandler(event) {
if (!event) {
event = window.event; // Older versions of IE use
// a global reference
// and not an argument.
};
var el = (event.target || event.srcElement); // DOM uses 'target';
// older versions of
// IE use 'srcElement'
el.setAttribute('name', el.id);
}
</script>
I tend to use this approach in all function calls from HTML attributes:-
onclick="SomeMethod.call(this)"
Then in the javascript do:-
function SomeMethod()
{
this.setAttribute('name', this.id);
}
This has a distinct advantage when you may also assign directly to event handler properties in Javascript code:-
document.getElementById("someID").onclick = SomeMethod
If SomeMethod took the context element as a parameter it would very awkward to set up:-
function(id) {
var elem = document.getElementById(id)
elem.onclick = function() { SomeMethod(elem); }
}("someID");
Worse yet this would be memory leaking closure.
At this point: SomeMethod(this) - this returns window object so do not use it. The right way to use this keyword is making it context relevant, so use SomeMethod.call(this).