3

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.

bjb568
  • 11,089
  • 11
  • 50
  • 71
mareoraft
  • 3,474
  • 4
  • 26
  • 62
  • 3
    Please don't do this, using eval on remote scripts is not a good design pattern. Anyway to have the code already part of the original JavaScript and control the program based on data you fetch from the remote? Otherwise your forced to use hacks like injecting script tags. Not to mention the content-origin-policy nightmare involved. Here are some thoughts: http://stackoverflow.com/a/87260/227176 – Sukima Dec 11 '14 at 20:01
  • @Sukima Thank you for your advice, it is appreciated. I am still interested in knowing what the solution would be if I had no access to the company codebase. (that is why i posted the question) – mareoraft Dec 11 '14 at 20:48
  • Can you use jQuery's extensibility aspect and turn your .js code into a jQuery plugin (or whichever library that you use). This way, you might be able to "inject" your code into your editor and treat it like a jQuery method. – Michael Dec 11 '14 at 21:00
  • the getScript thing should work as long as graphie.js is already loaded. – dandavis Dec 11 '14 at 21:23
  • @Michael That sounds interesting but I am not sure how it is related to this particular issue. Perhaps you can give more information if you think it will work. – mareoraft Dec 14 '14 at 22:32
  • @dandavis How would I verify whether or not Graphie.js is loaded at the time getScript is executed? – mareoraft Dec 14 '14 at 22:33
  • if `(typeof someGraphieFunction == 'function') {$.getScript}` – Jared Smith Dec 14 '14 at 22:35
  • Why not inject a script element to your body? Something like this:http://stackoverflow.com/questions/5235321/how-do-i-load-a-javascript-file-dynamically – Amir Popovich Dec 14 '14 at 22:36
  • 1
    For clarification, you want the code in the editor to have access to the functions vs. the page hosting the editor? – Jared Smith Dec 14 '14 at 22:38
  • @JaredSmith I want the code that $.get() fetches to have access to the Graphie.js functions. I don't care about the page hosting the editor. I wish I knew enough to give you a clearer answer. Let me know if you need further clarification. – mareoraft Dec 14 '14 at 22:43
  • 1
    I guess what I'm really asking is: you are entering code into an editor, hosted on a webpage, to what end? To execute the code in the editor on the page as a text-editor-cum-sandox? If the code running in the editor has the same global namespace as the hosting page then what amir popovich suggested should work fine. Of course, it also means that what you've already tried shouldn't have failed. If the code your putting into the editor runs separately from the js environment on the hosting page then injecting a script tag into that page will not work. – Jared Smith Dec 14 '14 at 22:49

4 Answers4

3

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.

Witiko
  • 3,167
  • 3
  • 25
  • 43
  • 1
    So what advantages would it have to `eval` the code only inside of that tiny little callback scope? – Bergi Dec 15 '14 at 02:10
  • @Bergi: The fresh-created anonymous function has access to the entire scope in which the op's code is being executed. By only passing an `eval` reference to the `$.get` method, you execute it in the scope of the jQuery library, which may be isolated from the Graphie library. It's just a hunch, but this could well be the problem. – Witiko Dec 15 '14 at 05:41
  • You mean that there are two different global scopes? That could make sense. But if the code declares local variables, actually non of the two would work. – Bergi Dec 15 '14 at 16:08
  • Graphie and the op's code may be executed as a part of an anonymous function and therefore invisible from the global scope. – Witiko Dec 15 '14 at 16:45
  • But in that case, an `eval` in a callback won't help (unless all the variables that it will introduce are pre-declared) – Bergi Dec 15 '14 at 16:46
  • Yes, it is going to be necessary to wait until the script has been loaded over AJAX and executed. I'm sure the op is well aware of that, though, because that's not a part of the problem he's having. – Witiko Dec 15 '14 at 17:01
0

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.

Amit Joki
  • 58,320
  • 7
  • 77
  • 95
  • 2
    What is the rationale behind your advice against the use of `eval()`? – Witiko Dec 15 '14 at 16:57
  • @Witiko general consensus says so and eval is evil. Moreover we can do it with such method where we are being fool-proof. – Amit Joki Dec 16 '14 at 02:17
  • 1
    There is no difference between fetching a script and then injecting it into the page / evaling it. You just lose the local scope in the former case and you have no guarantee that Graphie is even reachable in the global scope. – Witiko Dec 16 '14 at 07:48
  • @Witiko my code executes in the global context. If graphie variable wasn't present, even your code won't work. – Amit Joki Dec 16 '14 at 07:53
  • Unless the Graphie variable is local, in which case `eval` is going to work and script injection isn't. – Witiko Dec 16 '14 at 07:56
  • @Witiko that is exactly the reason why I've included `(_, jQuery, Graphie);` Being confirmed about something is always better than uncertainty. – Amit Joki Dec 16 '14 at 07:58
  • But these are only a string. You are not passing any local references to the injected code. You are only instructing it to look for global variables jQuery and Graphie once it has been executed. – Witiko Dec 16 '14 at 08:01
0

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?

Community
  • 1
  • 1
Travis J
  • 81,153
  • 41
  • 202
  • 273
0

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.

mareoraft
  • 3,474
  • 4
  • 26
  • 62
  • You are depending on undefined behaviour. You have no guarantee that `everything` will have been defined by the time `$.get` returns and the fact that your browser stores `/path/to/myfunctions` in cache and performs the eval instantly after the AJAX call has been issued is in no way a part of the spec. If this is an issue for you, you should consider using a callback rather than expecting your functions to load synchronously. – Witiko Dec 17 '14 at 11:03