My company allows us to write code in a javascript editor online. Other libraries are preloaded, so the code we write has access to these libraries.
Specifically, we can use Underscore.js and jQuery.js functions in our code. We can also use our very own library Graphie.js.
In an effort to save myself time, I have slowly built up my own personal set of functions which I copy and paste into every code I write. That set of functions is now so long that I want to fetch it externally (in order to save space, etc).
$.getScript( 'url/to/myfunctions.js' )
I tried the above code, but it was too good to be true. This jQuery function getScript seems to run myfunctions as their own independent unit. This fails because myfunctions use our Graphie.js functions within them.
$.get( 'url/to/myfunctions', eval )
This above code fetches and successfully evals my code (i configured my server to do so). Also too good to be true. Any jQuery and Underscode functions in my code actually work. But any Graphie functions in my code cause an error.
Instead of
$.get( 'url/to/myfunctions', eval );
try
$.get( 'url/to/myfunctions', function(code) { eval(code); } );
This way the eval function is going to be executed within the same scope as the rest of your code, rather than within the scope of jQuery. After the code has been fetched and executed, you can continue with the execution of the rest of your code:
$.get( 'url/to/myfunctions', function(code) {
eval(code);
callback();
});
function callback() {
// Your code goes here
}
Explanation
For the purpose of the explanation, let's use this simplified model of the environment, in which your code is being executed:
// JQuery is defined in the global scope
var $ = {
get: function( url, fn ) {
var responses = {
"url/to/myfunctions": "try {\
if(graphie) log('Graphie is visible.');\
} catch (e) {\
log('Graphie is not visible. (' + e + ')');\
}"
}; fn( responses[url] );
}
};
(function() {
// Graphie is defined in a local scope
var graphie = {};
(function() {
// Your code goes here
$.get( "url/to/myfunctions", eval );
$.get( "url/to/myfunctions", function(code) { eval (code); } );
})();
})();
The output: <ol id="output"></ol>
<script>
function log(msg) {
var el = document.createElement("li");
el.appendChild(document.createTextNode(msg));
output.appendChild(el);
}
</script>
As you can see, the function passed to $.get gets executed inside its body. If you only pass eval to $.get, then you don't capture the local variable graphie, which is then invisible to the evaluated code. By wrapping eval inside an anonymous function, you capture the reference to the local variable graphie, which is then visible to the evaluated code.
I'd advise against the use of eval. However, you can follow the following model.
First in your myFunctions.js, wrap all your code into a single function.
(function(_, $, graphie) {
// declare all your functions here which makes use of the paramters
}) // we will be calling this anonymous function later with parameters
Then after getting the script you could do
$.get( 'url/to/myfunctions', function(fn){
var el = document.createElement('script');
el.type = 'text/javascript';
el.text = fn + '(_, jQuery, Graphie);';
document.head.appendChild(el);
});
Note that, I've put Graphie as the parameter, but I'm not sure of it. So put your correct graphie variable there.
Assuming that you have ajax access to this script (since that is what $.get is doing in your sample code shown), you could attempt to use jQuery's .html() to place the script which should execute it with the page's variable environment.
$.ajax({
url: 'url/to/myfunctions.js',
type: 'GET',
success: function (result) {
var script = '<scr'+'ipt>'+result+'</scr'+'ipt>';
var div = $("<div>");
$("body").append(div);
div.html(script);
}
});
Internally, this script will end up being executed by jQuery's globalEval function. https://github.com/jquery/jquery/blob/1.9.1/src/core.js#L577
// Evaluates a script in a global context
// Workarounds based on findings by Jim Driscoll
// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
globalEval: function( data ) {
if ( data && jQuery.trim( data ) ) {
// We use execScript on Internet Explorer
// We use an anonymous function so that context is window
// rather than jQuery in Firefox
( window.execScript || function( data ) {
window[ "eval" ].call( window, data );
} )( data );
}
}
I also asked a question related to this here: Why is it that script will run from using jquery's html but not from using innerHTML?
Thanks to everyone's help, here is the solution that worked...
The myfunctions.js file has to be wrapped in a function:
function everything(_,$,Graphie){
// every one of myfunctions now must be attached to the Graphie object like this:
Graphie.oneOfMyFunctions = function(input1,input2,etc){
// content of oneOfMyFunctions
}
// the rest of myfunctions, etc.
}
Then in my code I can retrieve it with:
$.get( '//path/to/myfunctions', eval )
everything(_,jQuery,mygraphievar);
Somehow, the code being evaled didn't have access to the global variable mygraphievar, which is why it had to be passed in and NOT part of the evaled code (here Amit made a small error).
Also, the everything function is executed OUTSIDE of the $.get() so that the changes to mygraphievar are made before any other code below gets executed.
One should notice that $.get() is actually an asynchronous function and will not call eval until after other code is executed. This causes the code to fail the very first time I run it, but after the first time the functions get saved in memory and then everything works correctly. The proper solution would be to write ALL of the code I want to execute in the callback function of the $.get(), but I was lazy.
One should also know that a slightly simpler solution is possible with $.getScript() but I don't have time to verify it.
Related
Before anyone marks it as duplicate, this post does not actually answer the question but suggests a different way altogether to solve that particular issue.
Mine is a different issue. Please let me explain.
In my case, there are various .js files (plugins) which are being loaded with jquery getscript and stored in variables. Then whenever required they will be executed (more than once)
The code for loading script (this code will only run once at the init of the system for each plugin js file)
var storedFunc;
$.getScript(pathToPluginJSFile, function( data, textStatus, jqxhr ) {
storedFunc = data;
});
All the plugins are in this format
(function(){
//lots of code here
})()
But when I checked the storedFunc variable in console, I found out that it has been stored as String variable. Like this,
"(function(){
//lots of code here
})()"
Now to execute this, I used eval, like this (this code can be executed multiple times based on the need)
eval(storedFunc)
Everything is working fine and i am happy with it, but here comes the problem, I read in somewhere that the usage of eval is kind of like a bad thing to do. So now I am afraid that thought everything is working fine, all these negativity of using eval spread on the internet might scare my client away. :(
So, please tell me how I can run that stored function (which has become a string) without using eval.
Or should I use anything else than $.getScript which does not convert a function into a string ?
Or if there is any other way altogether rewriting this plugin functionality?
Please show me the way. I am in need of this solution badly.
Any help will be appreciated.
Thanks in advance.
Understanding how $.getScript works
Seems there is some confusion on how $.getScript works. If you notice jQuery's documentation on the method, and as #Pointy made mention of in the comments, this is stated:
Load a JavaScript file from the server using a GET HTTP request, then execute it.
Here's an example: Let's pretend the contents of the file being returned is only this:
// Contents of yourExternalFile.js
console.log('Executed!');
Now, when you use $.getScript:
$.getScript(pathToPluginJSFile, function( data, textStatus, jqxhr ) {
// The script you retrieved has already executed at this point, and you will find "Executed!" in the console.
console.log('All Done');
});
Console output:
> Executed!
> All Done
The $.getScript method is not meant to be used to return a string of the content of the file. However, while that data is available in the callback, the contents of the file have already been executed. So by taking the string version of the file, and re-executing it with either new Function, or even eval, you are executing it twice on the page (jQuery does it once, and so do you).
Original Post:
Use the Function constructor instead of using eval.
// Your function as a string stored to a variable
var stringFunction = "(function(){console.log('Executed');})()";
// Use the Function constructor to create a new function:
var executableFunction = new Function(stringFunction);
// Now you can execute it
executableFunction(); // logs "Executed"
This snippet from this SO question/answer addresses the difference between eval and new Function.
eval() evaluates a string as a JavaScript expression within the current execution scope and can access local variables.
new Function() parses the JavaScript code stored in a string into a function object, which can then be called. It cannot access local variables because the code runs in a separate scope.
Additional Information (Based on comments)
Yes, you can just get the string contents of the file and store them to a variable without the contents of the file executing. You can have that function to execute anytime. You just need to use the regular get method using jquery, and set the dataType to text. This way, the script will not execute, and you can execute it as you see fit:
var storedFunction;
$.get({url: pathToPluginJSFile, dataType: 'text'})
.done(function (data) {
// Turn the script into a new function and store it
// The information in the script file has not done anything yet
storedFunction = new Function(data);
})
.fail(function () {
console.log('Failed :(');
});
The only thing you will have to watch out for, is making sure that the function was assigned to the storedFunction variable as you are making an api call, and you have to wait for that to finish before attempting to make the function execute.
// Later on, call that function anytime, and as often as you want:
storedFunction();
I'm using PhantomJS v2.0 and CasperJS 1.1.0-beta3. I want to query a specific part inside the page DOM.
Here the code that did not work:
function myfunc()
{
return document.querySelector('span[style="color:#50aa50;"]').innerText;
}
var del=this.evaluate(myfunc());
this.echo("value: " + del);
And here the code that did work:
var del=this.evaluate(function()
{
return document.querySelector('span[style="color:#50aa50;"]').innerText;
});
this.echo("value: " + del);
It seems to be the same, but it works different, I don't understand.
And here a code that did also work:
function myfunc()
{
return document.querySelector('span[style="color:#50aa50;"]').innerText;
}
var del=this.evaluate(myfunc);
this.echo("value: " + del);
The difference here, I call the myfunc without the '()'.
Can anyone explain the reason?
The problem is this:
var text = this.evaluate(myfunc());
Functions in JavaScript are first class citizen. You can pass them into other functions. But that's not what you are doing here. You call the function and pass the result into evaluate, but the result is not a function.
Also casper.evaluate() is the page context, and only the page context has access to the document. When you call the function (with ()) essentially before executing casper.evaluate(), you erroneously try to access the document, when it is not possible.
The difference to casper.evaluate(function(){...}); is that the anonymous function is defined and passed into the evaluate() function.
There are cases where a function should be called instead of passed. For example when currying is done, but this is not applicable to casper.evaluate(), because it is sandboxed and the function that is finally run in casper.evaluate() cannot use variables from outside. It must be self contained. So the following code will also not work:
function myFunc2(a){
return function(){
// a is from outer scope so it will be inaccessible in `evaluate`
return a;
};
}
casper.echo(casper.evaluate(myFunc2("asd"))); // null
You should use
var text = this.evaluate(myfunc);
to pass a previously defined function to run in the page context.
It's also not a good idea to use reserved keywords like del as variable names.
Source: http://blog.tomasjansson.com/creating-custom-unobtrusive-file-extension-validation-in-asp-net-mvc-3-and-jquery
$(function () {
jQuery.validator.unobtrusive.adapters.add('fileextensions', ['fileextensions'], function (options) {
var params = {
fileextensions: options.params.fileextensions.split(',')
};
options.rules['fileextensions'] = params;
if (options.message) {
options.messages['fileextensions'] = options.message;
}
});
jQuery.validator.addMethod("fileextensions", function (value, element, param) {
var extension = getFileExtension(value);
var validExtension = $.inArray(extension, param.fileextensions) !== -1;
return validExtension;
});
function getFileExtension(fileName) {
var extension = (/[.]/.exec(fileName)) ? /[^.]+$/.exec(fileName) : undefined;
if (extension != undefined) {
return extension[0];
}
return extension;
};
} (jQuery));
Wouldn't jQuery already be available inside this function, why would it be passed in at the end there? I don't get this and I've seen it a few times before, never had to use it so, was curious what's going on here.
Passing it in isn't doing anything. That syntax isn't right since the function, as it's used there, is a callback and not IIFE.
Only reason I could think to do that would be if no conflict mode is used. Even then the syntax is still not correct.
Read More: jQuery.noConflict
We pass jQuery or other jQuery Control variable ($, jQuery, jq, jQ, jQ1101) to the module or plugin because in the DOM, We can have multiple version of jQuery loaded or we can have other libraries which uses $ as control variable. Such as PrototypeJS or Zepto
By passing jQuery Control variable we ensure that we have right control variable for our module and internally we just use $ as jQuery variable.
Please see this example.
<html>
<head>
<title>StackOverflow 19257741</title>
<script type="text/javascript" src="http://zeptojs.com/zepto.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
</head>
<body>
<div id="content">
<!-- Other HTML Tags -->
</div>
</body>
<script type="text/javascript">
//Change jQuery Control because you have other library loadded or you have multiple jQuery loaded.
var jQ1101 = jQuery.noConflict(true);
//Now, you can not access jQuery by $ or jQuery
//This module have to have jQuery to do DOM Manipulation
var module = (function ($, zepto) {
var i = 0, //private ivar for module use only.
_init = function () {
var me = this;
//TODO: Module can init or do something here...
return me;
},
_fill = function (selector) {
//We can use $ here as jQuery Control
$(selector).css({ "backgroundColor": "#000000", "width": "100%", height: "100%" });
//Wait for 2 seconds
window.setTimeout(function() {
//Not select dom with zepto
zepto(selector).css({ "backgroundColor": "#777777", "width": "100%", height: "100%" });
}, 2000);
};
return {
init: _init,
fill: _fill
};
})(jQ1101, $); //We have to pass the current Control for jQuery so, module can use library for internal function.
//Call module then call fill method by passing variable
module.init().fill("#content");
//Two different Library
console.log(jQ1101.fn.jquery); //jQuery 1.10.1
console.log($); //Zepto
</script>
<html>
The code in that blog post is valid JavaScript syntax and will execute, but it doesn't do what the author probably expected. The $(function...) call looks like an attempt to run that function on DOM ready in the usual manner, but that's not what happens.
Let's deconstruct the code. First, strip out all the code in the function and add some logging:
console.log( 'before' );
$( function () {
console.log( 'DOM ready' );
} (jQuery) );
console.log( 'after' );
This is (perhaps surprisingly) valid JavaScript and will log:
before
DOM ready
after
But here's the problem: If you put that script in a web page, you really want it to log:
before
after
DOM ready
After all, you're running the script before the DOM is ready, expecting the function to be run later, after the DOM becomes ready. That's the whole point of using the $() call.
What went wrong? To make it more clear, let's break out the inner function as a separate named function:
console.log( 'before' );
function ready() {
console.log( 'DOM ready' );
}
$( ready(jQuery) );
console.log( 'after' );
And one more change to make it completely step-by-step:
console.log( 'before' );
function ready() {
console.log( 'DOM ready' );
}
var result = ready( jQuery );
$( result );
console.log( 'after' );
Each of these versions has exactly the same semantics and runs in the same order.
Now it should be clear what happened. We're calling the ready function immediately and passing its return value into the $() call. The ready function doesn't return any value, so that value is undefined.
The last line, then, is the equivalent of:
$( undefined );
And that call simply returns an empty jQuery object ([]).
The culprit, of course, is that (jQuery) at the end. Adding that is what caused the function to be called immediately, instead of passing a reference to the function into the $() call. And the presence of jQuery inside the parentheses is meaningless: this ready function doesn't expect any arguments, so that is ignored. It would be the same thing if () appeared there.
It's very much like a common error you see with setTimeout() and similar calls:
// Oops - calls doStuff immediately, not after one second
setTimeout( doStuff(), 1000 );
This raises the question: why didn't this code run into a problem since it doesn't doesn't work as expected? Well, the function does get called either way - the only difference is when it's run.
So two possible reasons why it didn't cause a problem.
This block of code may have been placed at the end of the <body> as is popular practice these days. In that case the DOM would probably be ready-enough when the code is run. DOM elements are created in order as the <body> is loaded, and if you put a <script> tag after a particular DOM element, that DOM element will indeed be available when that script is run.
The code may not require the DOM to be ready. Looking at the validator code in the blog post, it does look like setup code that doesn't do anything with the DOM when it's first run. If so, then as long as the jQuery.validator plugin is already loaded, then it wouldn't make any difference if this code runs immediately or later when the full DOM is ready.
After my previous question, I come up to the following working code that is intended to refresh the DOM periodically by replacing the <div id="test_css_id">...</div> itself. The behavior of both AJAX requests present in the below code is to reload the same code itself.
<div id="test_css_id">
<a id="link_css_id" href="test_url.html">LINK</a>
<script type="text/javascript">
var refreshTimer;
$('#link_css_id').click(function(event) {
event.preventDefault();
$.ajax({
url: $(this).attr('href'),
type: 'PUT',
success: function(data) {
clearInterval(refreshTimer);
$('#test_css_id').replaceWith(data); // Replaces all code including JavaScript with the response data (note: the response data is exactly the same as the code shown here).
}
});
});
$(document).ready(function() {
function refreshFunction(){
$.ajax({
url: 'test_url.html',
type: 'GET',
success: function(data) {
clearInterval(refreshTimer);
$('#test_css_id').replaceWith(data); // Replaces all code including JavaScript with the response data (note: the response data is exactly the same as the code shown here).
}
});
}
refreshTimer = setInterval(refreshFunction, 1000);
});
</script>
</div>
However, as said by the author of the accepted answer, "there are other ways you can do it [..] one way is to wrap all of that code into a module". I am not expert in JavaScript but I would like to understand and learn it a little more.
How can I wrap all of that code into a module in order to avoid using global variables?
Your current code looks like this:
var refreshTimer; //a global variable
$(...).click(...);
To make refreshTimer not global, you need to put it inside a function:
function main(){
var refresherTimer; //this variable is now local to "main"
$(...).click(...);
}
main();
However, doing it this way won't solve the problem completely. While we did get rid of the global variables, we added a new one - the "main" function itself.
The final trick is to turn the "main" function into an anonymous function and invoke it directly. This is the famous "module pattern":
(function(){
var refreshTimer; //local variable to that anonymous function
$(...).click(...);
}()); //and here we call the function to run the code inside it.
The extra parenthesis around everything are important. If you do just function(){}() instead of (function(){}()) then you will get a syntax error.
Here's a nice description of the module pattern in JavaScript.
I was implementing a on-demand script controller based on jquery's getscript, it looks like this:
function controller = function(){
var script = function(){
var scripts = {};
return {
load: function(jsurl){
$.getScript(jsurl, null);
},
run: function(js){
window[js].apply(this,null);
}
}
};
return {
script: script()
};
}
var ctlr = controller();
then here is a remote script with a function to be loaded - remote.js
function remotefunc(){
alert( 'remotefunc invoked' );
}
and here is how the whole thing supposed to work, in the main script:
ctlr.script.load( 'remote.js' ); // remote script successfully loaded
ctlr.script.run( 'remotefunc' ); // got an error, window['remotefunc'] undefined
but as you can see, 'remotefunc' is defined in the global 'window' scope, so the window object is supposed to be able to 'see' it.
I thought the problem was probably the closure stuff in the 'controller' definition, so I did a direct $.getScirpt without using the 'controller':
$.getScript( 'http://path/to/remote.js', function(){
window['remotefunc'].apply( this, null ); // this worked
} );
strange. So it is about the 'controller' implementation(I kind need it)! Anybody can help me out with this? How to fix the 'controller' implementation so the
window[js].apply(this,null);
can actually work?
Thanx.
The reason it's telling you window['remotefunc'] is undefined is because you are not giving it time to actually download and execute the remote script before attempting to call a function defined in it.
The remote script is loaded asynchronously, which means the script execution isn't paused while waiting for a response.
You will need to either re-implement the getScript method to be synchronous or somehow work your class around the fact that the function will not be available in any determinate amount of time.
EDIT: Just found another possible solution, try calling this before your request
$.ajaxSetup({async: false});
This will make the getScript method synchronous
When using something like getSript, it's important to remember that it is fetching asynchronously. Meaning, the browser fires off the request and while that's happening, code after that line executes without pause.
jQuery provides a callback function parameter to get script that allows you to do something after the asynchronous fetch is finished.
Try this:
var script = function(){
var scripts = {};
return {
load: function(jsurl, callback){
$.getScript(jsurl, callback);
},
run: function(js){
window[js].apply(this,null);
}
}
};
Then, when using it:
ctlr.load( 'remote.js', function(){
// remote script successfully loaded
ctlr.run( 'remotefunc' );
});
Could this be a timing issue?
In your working example you call the function in a callback which jQuery will not invoke until the script is loaded. In your non-working example, you call the function immediately after getScript which is asynchronously loading the script.