Edit js file after it's loaded - javascript

I have a fix that I need to apply on some js files that I'm not owner of. I thought of adding some javascript content to the loaded javascript (loaded dynamically) file before it starts executing the content. I know it's a long shot, but, is it even possible?

Just let the external JavaScript load and then operate on whichever elements you need to, like this:
window.onload=function(){ModifyElements();};
And then further down...
function ModifyElements(){
// How are we going to recognise those elements so as to operate on them?
// Do you know the ID ?
Abc.style.width='20px';
// Do you know their class?
var N=document.querySelectorAll('.Comets');
for(var i=0; i<N.length; i++){Comets[i].style.color='red';}
// Some other way???
}

So I found the solution, and, instead of loading the script using: <script src="/my/script/path"></script> I loaded it using the XMLHttpRequest like this
var client = new XMLHttpRequest();
client.open('GET', widget.link);
client.onreadystatechange = this.applyStencilFix;
client.send();
and then I applied my fix editing the the script as a text
applyFix(e: Event): void {
let request: any = e.target;
if(request.responseText) {
let script = document.createElement('script');
script.text = "myFix" + request.responseText;
script.title = request.responseURL;
document.body.appendChild(script);
}
}
and, voilĂ  :)

Related

Is using XMLHttpRequest() an outdated way to make an Ajax call?

I'm simply using an example from a book I'm reading. The example is labeled, "Loading HTML with Ajax." This is the JS portion of the code:
var xhr = new XMLHttpRequest();
xhr.onload = function() {
if(xhr.status === 200) {
document.getElementById('content').innerHTML = xhr.responseText;
}
};
xhr.open('GET', 'data/data.html', true);
xhr.send(null);
I'm getting the CSS portion of the code (headers, etc.) when I load the page onto the browser but none of the JS (there should be maps which would load onto the page). The example says I should comment out this portion of the code above:
xhr.onload = function() {
if(xhr.status === 200) {
document.getElementById('content').innerHTML = xhr.responseText;
...if I'm running the code locally without a server but that's not working, either.
Is using XMLHttpRequest() an outdated way to make an Ajax call?
Yes, but it still works and that's not the problem. The more modern way is fetch.
I'm getting the CSS portion of the code (headers, etc.) when I load the page onto the browser but none of the JS (there should be maps which would load onto the page).
That's because assigning HTML that contains script tags to innerHTML doesn't run the script defined by those tags. The script tags are effectively ignored.
To run those scripts, you'll need to find them in the result and then recreate them, something along these lines:
var content = document.getElementById('content');
content.innerHTML = xhr.responseText;
content.querySelectorAll("script").forEach(function(script) {
var newScript = document.createElement("script");
newScript.type = script.type;
if (script.src) {
newScript.src = script.src;
} else {
newScript.textContent = script.textContent;
}
document.body.appendChild(newScript);
});
Note that this is not the same as loading the page with script elements in it directly. The code within script tags without async or defer or type="module" is executed immediately when the closing script tag is encountered when loading a page directly (so that the loaded script can use document.write to output to the HTML stream; this is very mid 1990s). Whereas in the above, they're run afterward.
Note that on older browsers, querySelectorAll's NodeList may not have forEach, that was added just a few years ago. See my answer here if you need to polyfill it.
Because I didn't completely understand T.J.'s answer (no offense, T.J.), I wanted to provide a simple answer for anyone who might be reading this. I only recently found this answer on Mozilla.org: How do you set up a local testing server? (https://developer.mozilla.org/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server). I won't go into details, I'll just leave the answer up to Mozilla. (Scroll down the page to the section titled, "Running a simple local HTTP server.")

Can I use JavaScript to change the JavaScript files a HTML document accesses?

I am trying to write a HTML page that asks users a series of questions. The answers to these questions are evaluated by my JavaScript code and used to determine which additional JavaScript file the user needs to access. My code then adds the additional JavaScript file to the head tag of my HTML page. I don't want to merge the code into a single JavaScript file because these additional files are large enough to be a nightmare if they're together, and I don't want to add them all to the head when the page first loads because I will have too many variable conflicts. I'm reluctant to redirect to a new webpage for each dictionary because this will make a lot of redundant coding. I'm not using any libraries.
I begin with the following HTML code:
<head>
<link rel="stylesheet" type="text/css" href="main.css">
<script src="firstSheet.js" type="text/JavaScript"></script>
</head>
//Lots of HTML.
<div id="mainUserMenu">
</div>
And I have the following JavaScript function:
function thirdLevelQuestions(secondLevelAnswer) {
//Code here to calculate the variables. This part works.
activeDictionary = firstKey + secondKey + '.js';
//Changing the HTML header to load the correct dictionary.
document.head.innerHTML = '<link rel="stylesheet" type="text/css" href="main.css"><script src="' + activeDictionary + '" type="text/JavaScript"></script><script src="firstSheet.js" type="text/JavaScript"></script>';
//for loop to generate the next level of buttons.
for (var i = 0; i < availableOptions.length; i++) {
document.getElementById('mainUserMenu').innerHTML += '<button onclick="fourthLevelQuestions(' + i + ')">' + availableOptions[i] + '</button>';
}
}
This creates the buttons that I want, and when I inspect the head element I can see both JavaScript files there. When I click on any of the buttons at this level they should call a function in the second file. Instead Chrome tells me "Uncaught ReferenceError: fourthLevelQuestions is not defined" (html:1). If I paste the code back into firstSheet.js the function works, so I assume the problem is that my HTML document is not actually accessing the activeDictionary file. Is there a way to do this?
What Can be done
You are trying to load Javascript on Demand. This has been a well thought out problem lately and most of the native solutions didn't work well across bowser implementations. Check a study here with different solutions and background of the problem explained well.
For the case of large web applications the solution was to use some javascript library that helped with modularising code and loading them on demand using some script loaders. The focus is on modularizing code and not in just script loading. Check some libraries here. There are heavier ones which includes architectures like MVC with them.
If you use AJAX implementation of jQuery with the correct dataType jQuery will help you evaluate the scripts, they are famous for handling browser differences. You can as well take a look at the exclusive getScript() which is indeed a shorthand for AJAX with dataType script. Keep in mind that loading script with native AJAX does not guarantee evaluation of the javascript included, jQuery is doing the evaluation internally during the processing stage.
What is wrong
What you have done above might work in most modern browsers (not sure), but there is an essential flaw in your code. You are adding the script tags to your head using innerHTML which inserts those lines to your HTML. Even if your browser loads the script it takes a network delay time and we call it asynchronous loading, you cannot use the script right away. Then what do you do? Use the script when its ready or loaded. Surprisingly you have an event for that, just use it. Can be something like:
var head= document.getElementsByTagName('head')[0];
var script= document.createElement('script');
script.type= 'text/javascript';
script.onreadystatechange= function () {
if (this.readyState == 'complete') helper();
}
script.onload= helper;
script.src= 'helper.js';
head.appendChild(script);
Check this article for help with implementation without using external libraries
From the variable name activeDictionary If I guess that you are loading some data sets as opposed to javascript programs, you should try looking into JSON and loading and using them dynamically.
If this Question/Answer satisfies your needs, you should delete your question to avoid duplicate entries in SO.
The best way to achieve this would be with jQuery:
$(document).ready(function() {
$('#button').click(function() {
var html = "<script src='newfile.js' type='text/javascript'></script>";
var oldhtml = "<script src='firstSheet.js' type='text/javascript'></script>";
if ($(this).attr('src') == 'firstSheet.js') {
$('script[src="firstSheet.js"]').replace(html);
return;
}
$('script[src="newfile.js"]').replace(oldhtml);
});
});
I would suggest you create the elements how they should be and then append them. Also, if you are dynamically adding the firstSheet.js you shouldn't include it in your .html file.
function thirdLevelQuestions(secondLevelAnswer) {
var mainUserMenu = document.getElementById('mainUserMenu');
activeDictionary = firstKey + secondKey + '.js';
var css = document.createElement('link');
css.rel = 'stylesheet';
css.type = 'text/css';
css.href = 'main.css';
var script1 = document.createElement('script');
script1.type = 'text/javascript';
script1.src = 'firstSheet.js';
var script2 = document.createElement('script');
script2.type = 'text/javascript';
script2.src = activeDictionary;
document.head.appendChild(css);
document.head.appendChild(script1);
document.head.appendChild(script2);
for (var i = 0; i < availableOptions.length; i++) {
var btn = document.createElement('button');
btn.onclick = 'fourthLevelQuestions(' + i + ')';
var val = document.createTextNode(availableOptions[i]);
btn.appendChild(val);
mainUserMenu.appendChild(btn);
}
}

How do I use jQuery in Windows Script Host?

I'm working on some code that needs to parse numerous files that contain fragments of HTML. It seems that jQuery would be very useful for this, but when I try to load jQuery into something like WScript or CScript, it throws an error because of jQuery's many references to the window object.
What practical way is there to use jQuery in code that runs without a browser?
Update: In response to the comments, I have successfully written JavaScript code to read the contents of files using new ActiveXObject('Scripting.FileSystemObject');. I know that ActiveX is evil, but this is just an internal project to get some data out of some files that contain HTML fragments and into a proper database.
Another Update: My code so far looks about like this:
var fileIo, here;
fileIo = new ActiveXObject('Scripting.FileSystemObject');
here = unescape(fileIo.GetParentFolderName(WScript.ScriptFullName) + "\\");
(function() {
var files, thisFile, thisFileName, thisFileText;
for (files = new Enumerator(fileIo.GetFolder(here).files); !files.atEnd(); files.moveNext()) {
thisFileName = files.item().Name;
thisFile = fileIo.OpenTextFile(here + thisFileName);
thisFileText = thisFile.ReadAll();
// I want to do something like this:
s = $(thisFileText).find('input#txtFoo').val();
}
})();
Update: I posted this question on the jQuery forums as well: http://forum.jquery.com/topic/how-to-use-jquery-without-a-browser#14737000003719577
Following along with your code, you could create an instance of IE using Windows Script Host, load your html file in to the instance, append jQuery dynamically to the loaded page, then script from that.
This works in IE8 with XP, but I'm aware of some security issues in Windows 7/IE9. IF you run into problems you could try lowering your security settings.
var fileIo, here, ie;
fileIo = new ActiveXObject('Scripting.FileSystemObject');
here = unescape(fileIo.GetParentFolderName(WScript.ScriptFullName) + "\\");
ie = new ActiveXObject("InternetExplorer.Application");
ie.visible = true
function loadDoc(src) {
var head, script;
ie.Navigate(src);
while(ie.busy){
WScript.sleep(100);
}
head = ie.document.getElementsByTagName("head")[0];
script = ie.document.createElement('script');
script.src = "http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js";
head.appendChild(script);
return ie.document.parentWindow;
}
(function() {
var files, thisFile, win;
for (files = new Enumerator(fileIo.GetFolder(here).files); !files.atEnd(); files.moveNext()) {
thisFile = files.item();
if(fileIo.GetExtensionName(thisFile)=="htm") {
win = loadDoc(thisFile);
// your jQuery reference = win.$
WScript.echo(thisFile + ": " + win.$('input#txtFoo').val());
}
}
})();
This is pretty easy to do in Node.js with the cheerio package. You can read in arbitrary HTML from whatever source you want, parse it with cheerio and then access the parsed elements using jQuery style selectors.

Preload script without execute

Problem
In order to improve the page performance I need to preload scripts that I will need to run on the bottom page.
I would like to take control of when the script is parsed, compiled and executed.
I must avoid the script tag, because it is blocker for common render engines (geeko, etc).
I can't load it using defer property, because I need to control when the script is executed.
Also, async property is not a possibility.
sample:
<html><head>
//preload scripts ie: a.js without use the script
</head><body> ..... all my nice html here
//execute here a.js
</body></html>
This allows me to maximize the render performance of my page, because the browser will start to donwload the scripts content, and it will render the page at the same time in parallel. Finally, I can add the script tag, so the browser will parse, compile and execute the code.
The only way that I could do that is using a hidden image tag. (This is a simplified version of Stoyan)
i.e.
<html><head>
<img src="a.js" style=display:none;>
</head><body> ..... all my nice html here
<script src="a.js">
</body></html>
Question
I didn't find any problem using this technique, but does anyone know a better way to do this?
Is there any meta prefetch?
Additional information
I'm using requirejs, so I'm trying to preload the modules code, without executing it, because this code depends of DOM elements.
With similar technique you may preload scripts and stylesheets using img for Internet Explorer and object tag for every other browser.
var isMSIE = /*#cc_on!#*/false;
var resources = ['a.js', 'b.js', 'c.css'];
for (var i=0; i<resources.length; i++){
if (isMSIE){
new Image().src = resources[i];
} else {
var o = document.createElement('object');
o.data = resources[i];
document.body.appendChild(o);
}
}
There is a blog post describing such a technique and outlining caveats: Preload CSS/JavaScript without execution.
But why don't you want to just use dynamically added scripts just like suggested in other answer, this will probably lead to a cleaner solution with more control.
You can use the prefetch attribute of a link tag to preload any resource, javascript included. As of this writing (Aug 10, 2016) it isn't supported in Safari, but is pretty much everywhere else:
<link rel="prefetch" href="(url)">
More info on support here:
http://caniuse.com/#search=prefetch
Note that IE 9,10 aren't listed in the caniuse matrix because Microsoft has discontinued support for them.
More info here and more options for preloading, like prerender and more
For each script you'd like to download without executing, make an object containing a name and a url, and put those objects into an array.
Looping through the array, use jQuery.ajax with dataType: "text" to download your scripts as text. In the done handler of the ajax call, store the text content of the file (which is passed in first argument) in the appropriate object, increment a counter, and call an "alldone" function when that counter is equal to the number of files you are downloading in this manner.
In the "alldone" function (or later) do the following: Loop through your array again, and for each entry, use document.createElement("script"), document.createTextNode(...), and (...scriptNode...).appendChild(...) to dynamically generate scripts having the intended source inline, rather than via "src" attribute. Finally, do document.head.appendChild(...scriptNode...), which is the point when that script is executed.
I have used this technique in a project where I needed to use frames, where several frames and/or the frameset need identical JavaScript files, in order to make sure each of those files is requested only once from the server.
Code (tested and working) follows
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
<html>
<head>
<script id="scriptData">
var scriptData = [
{ name: "foo" , url: "path/to/foo" },
{ name: "bar" , url: "path/to/bar" }
];
</script>
<script id="scriptLoader">
var LOADER = {
loadedCount: 0,
toBeLoadedCount: 0,
load_jQuery: function (){
var jqNode = document.createElement("script");
jqNode.setAttribute("src", "/path/to/jquery");
jqNode.setAttribute("onload", "LOADER.loadScripts();");
jqNode.setAttribute("id", "jquery");
document.head.appendChild(jqNode);
},
loadScripts: function (){
var scriptDataLookup = this.scriptDataLookup = {};
var scriptNodes = this.scriptNodes = {};
var scriptNodesArr = this.scriptNodesArr = [];
for (var j=0; j<scriptData.length; j++){
var theEntry = scriptData[j];
scriptDataLookup[theEntry.name] = theEntry;
}
//console.log(JSON.stringify(scriptDataLookup, null, 4));
for (var i=0; i<scriptData.length; i++){
var entry = scriptData[i];
var name = entry.name;
var theURL = entry.url;
this.toBeLoadedCount++;
var node = document.createElement("script");
node.setAttribute("id", name);
scriptNodes[name] = node;
scriptNodesArr.push(node);
jQuery.ajax({
method : "GET",
url : theURL,
dataType : "text"
}).done(this.makeHandler(name, node)).fail(this.makeFailHandler(name, node));
}
},
makeFailHandler: function(name, node){
var THIS = this;
return function(xhr, errorName, errorMessage){
console.log(name, "FAIL");
console.log(xhr);
console.log(errorName);
console.log(errorMessage);
debugger;
}
},
makeHandler: function(name, node){
var THIS = this;
return function (fileContents, status, xhr){
THIS.loadedCount++;
//console.log("loaded", name, "content length", fileContents.length, "status", status);
//console.log("loaded:", THIS.loadedCount, "/", THIS.toBeLoadedCount);
THIS.scriptDataLookup[name].fileContents = fileContents;
if (THIS.loadedCount >= THIS.toBeLoadedCount){
THIS.allScriptsLoaded();
}
}
},
allScriptsLoaded: function(){
for (var i=0; i<this.scriptNodesArr.length; i++){
var scriptNode = this.scriptNodesArr[i];
var name = scriptNode.id;
var data = this.scriptDataLookup[name];
var fileContents = data.fileContents;
var textNode = document.createTextNode(fileContents);
scriptNode.appendChild(textNode);
document.head.appendChild(scriptNode); // execution is here
//console.log(scriptNode);
}
// call code to make the frames here
}
};
</script>
</head>
<frameset rows="200pixels,*" onload="LOADER.load_jQuery();">
<frame src="about:blank"></frame>
<frame src="about:blank"></frame>
</frameset>
</html>
other question closely related to above approach
other related question
You should have a look at the following links:
http://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/
http://tomdale.net/2012/01/amd-is-not-the-answer/
And at how ember.js is using a tool called minispade and preprocessing with ruby to make the process of loading, parsing and running javascript modules fast.
Why not to try this?
var script = document.createElement('script');
script.src = 'http://path/to/your/script.js';
script.onload = function() {
// do something here
}
document.head.appendChild(script);
you can use .onload event to control when it is loaded. One caveat is that .onload() doesn't work in IE and you can use this:
script.onreadystatechange = function() {
if (/^loaded|complete$/i.test(this.readyState)) {
// loaded
};
}
Additionally adding scripts via DOM is non-blocking and i believe you can perfectly achieve your goals with this approach.
I've answered the same question there:
https://stackoverflow.com/a/46121439/1951947
just use the <link> tag to preload your script and then you can use it with the <script> tag
eg: <link href="/js/script-to-preload.js" rel="preload" as="script">

Using JavaScript to perform a GET request without AJAX

Out of curiosity, I'm wondering about the best (easiest, fastest, shortest, etc; make your pick) way to perform a GET request in JavaScript without using AJAX or any external libraries.
It must work cross-browser and it's not allowed to distort the hosting web page visually or affect it's functionality in any way.
I don't care about headers in the request, just the url-part. I also don't care about the result of the request. I just want the server to do something as a side effect when it receives this request, so firing it is all that matters. If your solution requires the servers to return something in particular, that's ok as well.
I'll post my own suggestion as a possible answer, but I would love it if someone could find a better way!
Have you tried using an Image object? Something like:
var req = new Image();
req.onload = function() {
// Probably not required if you're only interested in
// making the request and don't need a callback function
}
req.src = 'http://example.com/foo/bar';
function GET(url) {
var head = document.getElementsByTagName('head')[0];
var n = document.createElement('script');
n.src = url;
n.type = 'text/javascript';
n.onload = function() { // this is not really mandatory, but removes the tag when finished.
head.removeChild(n);
};
head.appendChild(n);
}
I would go with Pekka idea and use hidden iframe, the advantage is that no further parsing will be done: for image, the browser will try to parse the result as image, for dynamically creating script tag the browser will try to parse the results as JavaScript code.. iframe is "hit and run", the browser doesn't care what's in there.
Changing your own solution a bit:
function GET(url) {
var oFrame = document.getElementById("MyAjaxFrame");
if (!oFrame) {
oFrame = document.createElement("iframe");
oFrame.style.display = "none";
oFrame.id = "MyAjaxFrame";
document.body.appendChild(oFrame);
}
oFrame.src = url;
}

Categories