From what I have read JQuery's getScript function loads the script file in a global context using a function called 'global eval'. Is there a particular setting or method to change this so it will instead load within the function I am calling it from?
If I do the following code name returns undefined as its not loading the script in the local context.
function callscript(){
var name='fred';
getScript(abc.js);
}
//abc.js:
alert(name);
I believe I have found the solution using a regular JQuery ajax call. The trick is you set the datatype to 'text' as otherwise if its script or if use getScript or the alternative .get() it will auto run the script inside and place it in the global context.
function abc(){
var msg="ciao";
$.ajax({
url: 'themes/_default/system/message.js',
success: function(data){
eval(data);
},
dataType: "text"
});
}
//message.js
(function() {
alert(msg);
})();
This alerts 'ciao' as expected :)
Before anyone says anything yes I'm using eval but its perfectly fine in this situation.
As you already noticed, there's nothing in the docs regarding this. I double checked the source code and found that the underlying call has no options for you to pass to override this behavior.
// http://code.jquery.com/jquery-1.9.1.js
...
getScript: function( url, callback ) {
return jQuery.get( url, undefined, callback, "script" );
},
...
As far as I can tell, loading a script asynchronously into a local scope is not possible with jQuery. jQuery's API doesn't give you any other means to configure its usage like this.
I am still investigating how it might be possible using some other technique.
Ok i know this is 2017, 4 years later, but it seems jQuery team never bothered to address this issue, well sort of. I had the same problem and i think this is the solution, the actual intended way of using getScript in a local context. What I noticed was that there is no way that code could be easily eval'd in a local context against your code, which jQuery has no idea how it is going. I haven't gone deeper, but if you look at the jQuery source, how it is injecting the script into the document, it's genius, it avoids eval altogether. The script it therefore ran as if it's a file that was imported through script tag. Without further ado...
I have decided to do the vice-versa of the situation, it better explains what's going on. You can then reverse it to that example in question.
If you noticed getScript actually sends a unique ID to the server in the query string. I don't know why they didn't mention this in documentation. Use that to identify returned scripts. But you have to do something in the backend...
let imports;
$.getScript("scripts.php?file=abc.js", (data, textStatus, jqXHR) => {
window[jqXHR.getResponseHeader('X-scriptID')](imports);
alert (imports.name);
});
abc.js:
imports.name = 'fred';
backend wraps whatever script we are getting scripts.php:
// code that gets the file from file system into var $output
$output = file_get_contents($_REQUEST['file']);
// generate a unique script function name, sort of a namespace
$scriptID = "__script" . $_REQUEST['_'];
// wrap the script in a function a pass the imports variable
// (remember it was defined in js before this request) we will attach
// things we want to become local on to this object in script file
$output = "window.".$scriptID."=function(imports) { ".$output." };";
// set the script id so we can find this script in js
header ("X-scriptID: " . $scriptID);
// return the output
echo $output;
What going is that the js requests a script through getScript, but it doesn't request directly to the file it uses a php script to fetch the contents of the file. I am doing this so that i can modify the returned data and attach headers that are used to id the returned script (this is large app in mind here where a lot of scripts are requested this way).
When getScript runs the returned script in the browser as usual, the actual content of the script are not ran, just a declaration of the wrapper function with a unique name __script1237863498 or something like (the number was given by getScript upon requisition of that script earlier), attached to the global window object.
Then js uses that response to run the wrapper function and inject properties into the imports object... which become local to the requesting whatever's scope.
I don't know jQuery implementation, but the reason name is returning undefined is because name is a private property of the callscript object. To negate this, you could declare the variable outside of the function call:
var name = ''; //Declare name outside of the function
function callscript(){
name='fred';
getScript('abc.js'); //Shouldn't it be $.getScript? and argument should be passed as a string
}
//abc.js:
console.log(name) //Returns undefined
callscript(); //Call the script
console.log(name); //Returns "fred"
// global.js
var global1 = "I'm a global!";
// other js-file
function testGlobal () {
alert(global1);
}
Related
Mid development I decided to switch to server-side rendering for a better control amongst other benefits. My web application is completely AJAX based, no url redirecting, so the idea here is a website that builds itself up
I just couldn't figure out the proper way to send javascript events/functions along with the html string, or should all the necessary javascript always be preloaded in the static files?
Let's say client clicks a pre-rendered button 'open table'
The server will make a query, build the html table and send it back, but this table also needs javascript triggers and functions to work properly, how are these sent, received and executed?
There are a couple of articles that mention to not use eval() in Javascript, is there any way around this? I don't want to have to preload unnecessary events for elements that don't yet exist
The server is Python and the Client is Javascript/JQuery
Theoretical example :
Client Base Javascript :
$("body").on("click", "#open_table", function() {
$.getJSON('/get_table', function(response){
$("#table_div").append(response.html);
eval(response.javascript()); //??
}
});
Python Server(views.py) :
def get_table(request):
data = {}
#String containing rendered html
data['html'] = get_render_table()
#String containing Javascript code?
data['javascript'] = TABLE_EVENTS_JAVASCRIPT
return HttpResponse(json.dumps(data),content_type='json')
Worth noting my question comes from an experimental/learning perspective
Update:
You can use jQuery.getScript() to lazy load JS. I think this solution is as close as you can get to run JS without using eval().
See this example:
jQuery.getScript("/path/to/script.js", function(data, textStatus, jqxhr) {
/* Code has been loaded and executed. */
console.log( data ); // Data returned
console.log( textStatus ); // Success
console.log( jqxhr.status ); // 200
console.log( "Load was performed." );
});
and "/path/to/script.js" could be a string returned from $.getJOSN response.
Also, the documentation for getScrippt() has examples on how to handle errors and cache files.
Old Answer:
Using .on() attaches events to current and future DOM elements.
You can either attache events prior to DOM insertion or attache event after DOM insertion.
So in your example you can do something like:
$("body").on("click", "#open_table", function() {
$.getJSON('/get_table', function(response){
var code = $(response.html);
code.find(".elementToFind").on("click", function (){
// Code to be executed on click event
});
$("#table_div").append(code);
}
});
I did not test the code but I think it should work.
Assuming you can't just set up an event-binding function and then call it from the main script (the JavaScript you need can't be guessed ahead of time, for example) then one really easy way is just to append the JavaScript to the bottom of the returned HTML content within script tags. When it's appended along with the HTML, the script should simply execute, with no eval() required.
I can't swear that this would work in old browsers, but it's a trick I've used a couple of times, and I've had no problems with it in Firefox, Chrome, or any of the later IE versions.
I think I see what you're asking here, from my understanding you want to send the new "page" asynchorously, and render the new javascript and html. It looks like you already got your request/response down, so i'm not gonna go and talk about sending JSON objects, and the whole "how-to" of sending html and javascript because it looks like you got that part. To do what you want and to dynamically add your javascript in, this stackoverflow question looks like it has what you need
Is there a way to create a function from a string with javascript?
So pertaining to your example, here is how it would look when you recieve the JSON string from your python script:
$("body").on("click", "#open_table", function() {
$.getJSON('/get_table', function(response){
$("#table_div").append(response.html);
/* Create function from string */
var newFunction = Function(response.javascript['param_1'], response.javascript['param_2'], response.javascript['function']);
/* Execute our new function to test it */
newFunction();
}
});
*Your actual function contents would be the string: response.javascript['function']
*Your parameter names if any would be in separate strings ex: response.javascript['param_1']
That is almost a direct copy of the "String to function" code that you can see in the linked question, just replaced it with your relevant code. This code is also assuming that your object is sent with the response.javascript object containing an array with your actual function content and parameter names. I'm sure you could change the actual name of the var too, or maybe put it in an associative array or something that you can keep track of and rename. All just suggestions, but hopefully this works for you, and helps you with your problem.
I am also doing similar work in my project where I had to load partial html using ajax calls and then this partial HTML has elements which requires events to be attached. So my solution is to create a common method to make ajax calls and keep a js method name to be executed post ajax call in html response itself. For example my server returns below html
<input type="hidden" data-act="onPartialLoad" value="createTableEvents" />
<div>.........rest of html response.....<div>
Now in common method, look for input[type='hidden'][data-act='onPartialLoad'] and for each run the method name provided in value attribute (value="createTableEvents")
Dont Use Eval() method as it is not recommended due to security
issues. Check here.
you can run js method using window["method name"]...so here is a part of code that I use.
$.ajax(options).done(function (data) {
var $target = $("#table_div");
$target.fadeOut(function () {
$target.html(data);
$target.fadeIn(function () {
try {
$('input[data-act="onPartialLoad"]', $target).each(function () {
try {
//you can pass parameters in json format from server to be passed into your js method
var params = $(this).attr('params');
if (params == undefined) {
window[$(this).val()]();
}
else {
window[$(this).val()]($.parseJSON(htmlutil.htmlDecode(params)));
}
} catch (e) {
if (console && console.log) {
console.log(e.stack);
console.log($(this).val());
}
}
});
}
catch (e) {
console.log(e.stack);
}
});
});
});
use jQuery.getScript() (as suggested by Kalimah Apps) to load the required js files first.
I'm attempting to extract some information from Medium URLs and I notice that each page has the entire post contents stored in JSON format. The content looks like this on the page:
<script>// <![CDATA[
window["obvInit"]({"value":{"id":"e389ba1d8f57","versionId":"1b74...
How do I easily extract this JSON from the page? What does the preface of window["obvInit"] before the JSON mean? Can I call the function obvInit in my Chrome console and get the JSON output somehow?
What this does is call a function. It's probably (but not necesarrily) been declared like function obvInit(...){...} on the global window namespace. Now for your problem: You can easily extract the passed object by overwriting the function like this:
var _oldObvInit = window.obvInit;
window.obvInit = function(){
console.log(arguments[0]); //use this to extract the object
console.log(JSON.stringify(arguments[0])); //use this to extract JSON
return _oldObvInit.apply(window, arguments);
}
Put this before the script tag you've posted here and after the declaration of the function obvInit.
A bit context: inside every javascript function there's an implicit variable arguments which stores the arguments to the function as an array. And apply calls a function, sets the context (this) and takes the arguments as an array. Exactly what you need to wrap it.
This is a technique known as JSONP. Basically, since some older browsers don't have great support for cross-origin AJAX using XMLHttpRequest, you can insert a <script> tag into the page that gets the resource you want, except wrapped like this:
functionName({ /* ...data... */ });
So it calls a function known as functionName with the data as an argument. You would provide this function in your own code before inserting that script, like so:
function functionName(data) {
// use the data
}
window["obvInit"]() is equivalent to window.obvInit() which is equivalent to calling a function defined as obvInit at the global level.
As scripts are not subject to the same-origin policy, you can now get JSON-like data from any domain that will return it in this format.
In order to load some geojson data, I need to source scripts on an external domain, say http://www.stat.ucla.edu/~jeroen/files/la_county_simplified.min.json. I have no control over the contents of this script; all I know is the url, and the name of an object defined in the script that I am interested in. A dummy version of the script looks like:
var my_data = {"foo" : 123, "bar" : 456}
Now in my application, I would like to load the my_data object dynamically from its URL. Because it is cross domain, I can't use ajax. It isn't quite jsonp either, because my script defines an object, not a function. One way would be to insert it simply as a <script> in the head of the current document. However, I would like to avoid possible side effects.
What would be a cleaner solution? I was thinking of creating an <iframe> and then inserting the <script> tag in the iframe, and extracting the object once the iframe has loaded. However I am not sure this is a reliable method that will work cross browsers (especially binding a callback to extract the object after the script has been loaded in the iframe).
Is there some library or standard solution to load a script in a clean page, and extract copy over a particular object to the main page? I already have a dependency on jQuery so that would be fine.
If you plan to do this pure client-side and can't format your data, you could use JSONP with a twist. Instead of modifying the data to fit the callback, we refit the loader to adopt to the data.
We listen for the onload of the script. When the script loads, the variable should now be in the global scope and we execute our callback, which passes that global variable into our callback.
//your script loader
function loadData(url,varName,callback){
var script = document.createElement('script');
document.getElementsByTagName('head')[0].appendChild(script);
//when the script loads, we pass in `window.my_data`
script.onload = function(){
callback.call(this,window[varName]);
};
script.src = url;
}
//using it
loadData('http://example.com/somefile.js','my_data',function(data){
//executes when script is loaded, where data is `my_data`
});
The drawback of this approach is that every time you are loading the script, you are loading it into the global scope, and collisions could happen.
There is no other way around it since you have to beat the same origin policy you have to load the script in a new script tag, JSONP works this way too but jquery hides it for you.
Either that or the site has cors headers, if the site has no cors headers here is how you can load the data (not using jsonp because it isn't in jsonp format):
function loadJS(url){
var s=document.createElement("script");
s.src=url;
$(s).on("load",function(){
console.log("var abvailable");//do something with the variable here
$(s).remove();
});
document.head.appendChild(s);
}
loadJS("http://code.jquery.com/jquery-1.9.1.min.js");
The iframe method should work fine:
create an iframe
inject a script tag that points to the file
on script load, retrieve the object
The only cross-browser issue I can think of is that you'll need to use addEventListener in modern browsers and attachEvent in old IE.
This is a standard use of an iframe as sandbox - if I understand correctly you are worried about possible conflicts with global variable names.
[Update] To address some of your comments, here is some cross-browser code:
To add an event listener:
function addEvent(element,event,fn,capture){
// capture defaults to false if omitted
if (element.addEventListener) {element.addEventListener(event,fn,(capture||false));}
// else for old IE
else {element.attachEvent('on'+event,fn);}
};
To access the iframe document:
function iframeDocument(ifr){
var doc=ifr.contentWindow||ifr.contentDocument;
if (doc.document) doc=doc.document;
return doc;
};
If you use jQuery, .on("load") and $(ifr).contents() will take care of these cross-browser compatibility issues.
JSON-P is a way of loading JavaScript from a remote domain.
The return format of the JavaScript is to invoke a function with the response data as an parameter.
someGlobalFunctionName({/* your response data */});
function someGlobalFunctionName(data) { /* do something with data */ }
Since the data is contained in an object and passed to a function, there is no global leakage other than the global function itself, which is unavoidable.
More info: http://json-p.org/
When I load a particular webpage, I'd like to call a Javascript function that exists within their page. I could use a bookmarklet with javascript:TheFunction();, but I'd like to know if there's an easier way to do it, either with a Chrome extension or otherwise.
With chrome, you can either install a grease monkey script directly or get the Blank Canvas script handler plugin (the latter of which I use).
Chrome extensions run in a sandbox so you cannot call a function directly from the webpage code how you want. you either have to use javascript:fuction(); and set document.location, or you can create script elements on the page with a callback to your own extension. check out how this guy did it:
https://convore.com/kynetx/kbx-writing-durable-code/
i am refering to this post, and the one above and below it specifically
var anewscript = document.createElement("script");
anewscript.type = 'text/javascript';
anewscript.innerHTML=' callback_tmp= function (mydata){ ' +
' document.getElementById("someElement").onclick(mydata);' +
'}';
document.getElementsByTagName("head")[0].appendChild(anewscript);
An alternative option is to modify the javascript function to make it globally accessible from the [Chrome] debug console.
Change the function from for example
function foo(data)
to
foo = function(data)
then using the debug console, call the method with the attributes required
data = {my: "data"}
foo(data)
I need some scripts inside an existing site's scripts.js.
This site has been online for ages, and I can not touch the scripts file.
I am including it standardly in another page. There are numerous jQuery calls in the scripts file. The place I include it does not have jQuery.
I want to void all $() type things. I tried this...
$ = function() { };
before I included scripts.js and it didn't seem to work. I am still getting errors like
$(document) is undefined
Is there a way to void all these jQuery calls?
Thanks
Even if you do get that working, you'll still have problems because the code was written with the assumption that jQuery was present. Yes, you can avoid $ is null or not defined errors on lines like this:
$('div.foo');
But there's no point in just writing that line: there will always be actions on the returned object:
$('div.foo').html('blah');
After the NOP jQuery function, you'll get a "html" is not a function error, and so on. The only way you could do it would be to fill out a skeleton of every possible jQuery method, making sure each one returns itself when appropriate.
...or just rewrite it properly...
try
window.$ = function(selector, context) {alert('eating the calls to $');}
in your file that you're including before the scripts.js file. This is how it's defined in jquery so should take care of the selector syntax.
You may need to define other overrides to cater for the $.method() type calls tho
Well, it's no surprise that $(document) is undefined, since you're not returning a value from your placeholder function. Thus, things like $(document).ready(function(){}); will naturally be errors.
Basically, if I understand right, you need $ to be a function that does nothing and returns another object where calling any member function does nothing. Further, calling member functions of $ itself (e.g. $.ajax()) should have the same behavior.
You can do this with __noSuchMethod__, which is unfortunately non-standard:
window.$ = function()
{
var doNothingObj = new (function()
{
this.__noSuchMethod__ = function()
{
return doNothingObj;
}
})();
return doNothingObj;
};
window.$.__noSuchMethod__ = window.$;
This will allow arbitrary chains:
$(document).ready(function(){});
$("#foo").animate().animate();
$.ajax({ url: "file.html"});
etc.
Of course, a much saner solution is to refactor the code that uses jQuery.