Programmatically referencing a javascript file at runtime - javascript

I am having a problem programatically referencing a javascript file from
a javascript file. Normally I would just reference it in an html page but can't in this case as the directory location of the file is only available at runtime. I have checked out several posts on this topic but as of yet have been unable to find anything that works. Below are code snippets from 4 files:
index.html assigns an ID to the header tag and references the first of two javascript files called scriptFile.js.
scriptFile.js, is a global data file for all new users.
A second file, also called scriptFile.js, is a user specific data file created for each user at runtime. The second javascript file contains data specific to a single user, is located in a different directory than index.html scriptFile.js, but otherwise is structurally identical to the first scriptFile.js. In both cases the files consist of a function returning an array object.
The last file is index.js. Its purpose is to programmatically reference the second scriptFile.js file identified above.
When index.js runs, the value for scripts.length = 2 which is what it is supposed to be. scripts[0].getAttribute('id') and
scripts[0].getAttribute('src') both output correctly. However, while scripts[1].getAttribute('id') outputs correctly as
id = "scriptOneID", the scripts[1].getAttribute('src') outputs as null which is obviously incorrect. A second problem is
that the data for the scriptOneID.enabled property outputs as true, when it should be outputing as false. This tells me
that the object = loadData() statement is referencing data from the index.html scriptFile.js and not the
index.js userNumber/scriptFile.js. This has been verified in testing.
Here are the code snippets. Suggestions or ideas on how to correct this are appreciated. Thanks...
// 1. index.html page
<head id="headID">
<script id="scriptZeroID" type="text/javascript" src="scriptFile.js">
</script>
// 2. scriptFile.js
function loadData()
{
var object = [{ property: 0, enabled: true }];
return object;
};
// 3. userNumber/scriptFile.js
function loadData()
{
var object = [{ property: 0, enabled: false }];
return object;
};
// 4. index.js page
//global declarations and assignments
var object = new Array;
object = loadData(); // assign the data in loadData function to an object array
// local declarations and assignments
var head = document.getElementById("headID");
var script = document.createElement("script");
var userNumber = sessionStorage.getItem("userNumber");
var scriptPath = userNumber + "/scriptFile.js";
script.id = "scriptOneID";
script.type = "text/javascript";
script.scr = scriptPath;
head.appendChild( script );
var scripts = document.getElementsByTagName("script");
console.log("script: " + scripts.length); // output = 2
for (var i = 0; i < scripts.length; i++)
{
console.log("output: " + scripts[i].getAttribute('id') + " / " + scripts[i].getAttribute('src'));
// output for scripts[0]: id = "scriptZeroID", scr = "scriptFile.js"
// output for scripts[1]: id = "scriptOneID", scr = null
};
object = loadData(); // assign the data in loadData function to an object array
console.log("print test value: " + object[1].enabled ); // output = true // this is incorrect, it should = false

Your issue is a script load time problem. In other words it's an asynchronous problem.
Javascript these days is fast. Really fast. Your script gets to your last call to "loadData" long before the script you injected into the head gets loaded. Therefore you are accessing the original version of "loadData" twice.
Just to clarify my comment on the "notification". Add a function like this (as an example) to you index.js file:
function populateObj() {
object = loadData();
}
Then at the bottom of your injected script add:
populateObj();
That's all there is too it. When the injected script finishes loading and is executed your "object" global variable will have the correct data in it.

JQuery has a function to load scripts at any time after page load.
Looks like this:
$.getScript("yourscripttoload.js", function(){
console.log("Script loaded and executed.");
});
Maybe look into that to see if it will work in your situation.

This is a complete poc solution on how to the programatically reference a javascript file from within a javascript file using a callback function that does not require jQuery. It is based on the premise that the same script file will be run during the page load and then again from a different location, with different data, during runtime . The following solution replaces index.js file in the above example.
// global declarations & assignments
var head = document.getElementById("headID");
var script = document.createElement("script");
var userNumber = sessionStorage.getItem("userNumber");
var scriptPath = userNumber + "/scriptFile.js";
// call back function
function loadScript(scriptPath, callback)
{
script.id = "scriptOneID";
script.type = "text/javascript";
script.src = scriptPath;
script.async = false;
script.defer = false;
// bind the load event to the callback function
script.onreadystatechange = callback;
script.onload = callback;
// append the script to the page header
head.appendChild(script);
};
// place the code that you want to run inside an anonymous function
var anonymousFunction = function()
{
var scripts = document.getElementsByTagName("script");
console.log("script: " + scripts.length); // output = 2
for (var i = 0; i < scripts.length; i++)
{
console.log("output: " + scripts[i].getAttribute('id') + " / " +
scripts[ i].getAttribute('src'));
// output for scripts[0]: id = "scriptZeroID", scr = "scriptFile.js"
// output for scripts[1]: id = "scriptOneID", scr = "1234567890/scriptFile.js" where userNumber = "1234567890"
};
object = loadData(); // assign the data in loadData function to an object array
console.log("print test value: " + object[1].enabled ); // output = false this is correct
};
// execute the loadScript function
loadScript( scriptPath, anonymousFunction );

Related

Global variable not updating as expected

I am building a small tool using Apps Script. I can't seem to get the global variable (URL) to update to include the docId.
What I expect
Run the App function, which first creates a Google Doc and sets the global variable for the documentId (docId). Second it calls myOnClickhandler, which should take the global url variable that includes the updated docId.
What is happening:
The variable 'url' in myClickHandler is not updated to the value of 'docId' set in the createProject function.
var docId = "";
var url = "https://docs.google.com/document/d/" + docId + "/";
function doGet() {
var output = HtmlService.createHtmlOutputFromFile('MyUI');
return output;
}
function createProject(test="test"){
const doc = DocumentApp.create(test);
docId = doc.getId();// <-- Update global docId variable. Doesn't seem to be working.
Logger.log("Doc ID is: " + docId);
}
function myOnClickHandler(){
const myList = ["Hello from the server", url]
Logger.log("URL is: " + url);
}
function app(){
createProject();
myOnClickHandler();
}
Probably a simple mistake on my part, but I am banging my head against the wall on this one. Screenshot attached.
In JavaScript, strings are immutable. Use an arrow function and a template literal instead of a static text string, like this:
let docId;
const getUrl_ = (docId) => `https://docs.google.com/document/d/${docId}/`;
function test() {
docId = 'testString';
const url = getUrl_(docId);
console.log(url);
}

In Karate DSL, calling a javascript file returns java.lang.RuntimeException

I have a javascript file I want to call. contents are below. When I tried calling the file, I keep getting a "no variable found with name: response" even though there is clearly a variable defined. The file executes fine within command-line using node so the javascript function is valid. Any thoughts? I attached the error message in a screenshot.
Javascript content in snippet below.
Karate script:
Scenario: Call JavaScript:
* def sample = read('classpath:reusable/gen-data.js')
* print someValue
function createTestData(sampleJson, fieldsToChange, numRecords) {
var testData = [];
for (var i = 0; i < numRecords; i++) {
var copy = JSON.parse(JSON.stringify(sampleJson));
fieldsToChange.forEach(function(fieldToChange) {
copy[fieldToChange] = copy[fieldToChange] + i;
});
testData.push(copy);
}
return {content: testData};
}
var testData = {
"country": "US",
"taskStatusCode" : "Closed",
"facilityCode" : "US_203532",
};
function getTestData() {
String testData = JSON.stringify(createTestData(testData, ["taskStatusCode", "facilityCode"], 1), null, 1);
console.log("all done getTestData()");
console.log("test data: \n" + testData);
return testData;
};
console.log("calling getTestData()");
getTestData();
I think this error is thrown when the JavaScript is not correct. For example in my case this JS file:
/* Set the custom authentication header */
function fn() {
var authToken = karate.get('authToken');
var out = {};
out['Auth-Token'] = authToken
return out;
}
This file will produce the "no variable found with name: response".
The reason is because "the right-hand-side (or contents of the *.js file if applicable) should begin with the function keyword." according to the karate docs (link).
Now by moving the comment and making the function keyword the first bit of text it works as expected:
function fn() {
/* Set the custom authentication header */
var authToken = karate.get('authToken');
var out = {};
out['Auth-Token'] = authToken
return out;
}
In the OP, the function keyword is the first thing in the file, but there is javascript outside the original function -- which I don't think is legal for karate syntax. In other words, everything has to be in the outer function.
My workaround was to use java instead of JavaScript.

Load javascript files dynamically

I am trying to load a list of js files dynamically. Everything for the moment works fine. The only problem is that I wrote my func inside the loop so my code will be repeated many times. What I want to do is to make a loop to download the files and then confirm that they are already loaded and executed my func().
For my code if I write func() outside the loop I get an error message. Mean outside onreadystate etc I get error message
function( libs, func ){
if( typeof func === 'function' ) {
libs.forEach( function( fileName ) {
var script = document.createElement('script');
var head = (document.getElementsByTagName("head")[0] || document.documentElement);
script.setAttribute("type", "text/javascript");
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null;
head.remove( script );
func();
}
};
} else { //Others browsers
script.onload = function(){
head.remove( script );
loaded = true;
func();
};
}
script.setAttribute("src", fileName);
head.appendChild( script );
});
}
}
do you realy need to load it by your own function? Why don't you use a script loader like requirejs?
Here you can find other: https://stackoverflow.com/questions/12779565/comparing-popular-script-loaders-yepnope-requirejs-labjs-and-headjs
I'm not exactly sure that I understand correctly, but you want to execute your function when all files are downloaded? The most common way would be to have a counter. I used this function when doing something similar, which takes a function that can be executed after each file load, and one that is executed when all files are loaded.
I used the following function to keep track of the number of files loaded.
function loadJs(files, perFileCallback, doneCallback){
var numFiles = files.length,
numFilesLoaded = 0,
loadedFiles = [],
fileOrder = {};
if(numFiles === 0){
doneCallback([]);
return;
}
function _loadFileCallback (file, fileName) {
numFilesLoaded++;
loadedFiles[fileOrder[fileName]] = file; // Must be placed in correct order.
perFileCallback(numFilesLoaded);
if (numFiles === numFilesLoaded && perFileCallback) {
doneCallback(loadedFiles);
}
}
for (var i = 0; i < files.length; i++) {
fileOrder[files[i]] = i;
_loadFile(files[i], _loadFileCallback); // Load the file with ajax and perform _loadFileCallback when done.
}
}
You can also see all three functions I chained to use it on pastebin.
It uses jQuery, but you can swap it with a normal ajax call if you desire.
This also takes into account that when adding the files to your head, you do it in the correct order. This was because I would have a file which defined var app, and one which defined app.mySite, and it was vital that app was defined before app.mySite could be created.

Including objects from external .js files

I have been searching for many hours over several days for this answer and though there are many topics on how to include files in a project (also here at Stack Overflow), I have not yet found THE solution to my problem.
I'm working on a project where I want to include one single object at a time, from many different files (I do not want to include the files themselves, only their content). All the object in all the files have the same name, only the content is different.
It is important that I do not get a SCRIPT tag in the head section of the page as all the content from the files will have the same names. None of the files will have functions anyways, only one single object, that will need to be loaded one at the time and then discarded when the next element is loaded.
The objects will hold the data that will be shown on the page and they will be called from the menu by an 'onclick' event.
function setMenu() // The menu is being build.
{
var html = '';
html += '<table border="0">';
for (var i = 0; i<menu.pages.length; i++)
{
html += '<tr class="menuPunkt"><td width="5"></td><td onclick="pageName(this)">'+ menu.pages[i] +'</td><td width="5"></td></tr>';
}
// menu is a global object containing elements such as an array with
// all the pages that needs to be shown and styling for the menu.
html += '</table>';
document.getElementById("menu").innerHTML = html;
style.setMenu(); // The menu is being positioned and styled.
}
Now, when I click on a menu item the pageName function is triggered and I'm sending the HTML element to the function as well, it is here that I want the content from my external file to be loaded into a local variable and used to display content on the page.
The answer I want is "How to load the external obj into the function where I need it?" (It may be an external file, but only in the term of not being included in the head section of the project). I'm still loading the the file from my own local library.
function pageName(elm) // The element that I clicked is elm.
{
var page = info.innerHTML; // I need only the innerHTML from the element.
var file = 'sites/' + page + '.js'; // The file to be loaded is created.
var obj = ?? // Here I somehow want the object from the external file to be loaded.
// Before doing stuff the the obj.
style.content();
}
The content from the external file could look like this:
// The src for the external page: 'sites/page.js'
var obj = new Object()
{
obj.innerHTML = 'Text to be shown';
obj.style = 'Not important for problem at hand';
obj.otherStuff = ' --||-- ';
}
Any help will be appreciated,
Molle
Using the following function, you can download the external js file in an ajax way and execute the contents of the file. Please note, however, that the external file will be evaluated in the global scope, and the use of the eval is NOT recommended. The function was adopted from this question.
function strapJS(jsUrl) {
var jsReq = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
if (jsReq === null) {
console.log("Error: XMLHttpRequest could not be initiated.");
}
jsReq.onload = function () {
try {
eval(jsReq.responseText);
} catch (e) {
console.log("Error: The script file contains errors." + e);
}
};
try {
jsReq.open("GET", jsUrl, true);
jsReq.send(null);
} catch (e) {
console.log("Error: Cannot retrieving data." + e);
}
}
JSFiddle here
Edit: 1
After some refactoring, I came up with this:
function StrapJs(scriptStr, jsObjName) {
var self = this;
self.ScriptStr = scriptStr;
self.ReturnedVal = null;
function _init() {
eval(self.ScriptStr);
self.ReturnedVal = eval(jsObjName);
}
_init();
}
You can then get the script string any way you want and just instantiate a new StrapJs object with the script string and name of the object to return inside the script string. The ReturnedVal property of the StrapJs object will then contain the object you are after.
Example usage:
var extJS = "var obj = " +
"{ " +
" innerHTML : 'Text to be shown', " +
" style : 'Not important for problem at hand', " +
" otherStuff : ' --||-- ' " +
"}; ";
var extJS2 = "var obj = " +
"{ " +
" innerHTML : 'Text to be shown 2', " +
" style : 'Not important for problem at hand 2', " +
" otherStuff : ' --||-- 2' " +
"}; ";
var strapJS = new StrapJs(extJS, 'obj');
var strapJS2 = new StrapJs(extJS2, 'obj');
console.log(strapJS.ReturnedVal.innerHTML);
console.log(strapJS2.ReturnedVal.innerHTML);
See it in action on this fiddle

Dynamically loading JavaScript synchronously

I'm using the module pattern, one of the things I want to do is dynamically include an external JavaScript file, execute the file, and then use the functions/variables in the file in the return { } of my module.
I can't figure out how to do this easily. Are there any standard ways of performing a pseudo synchronous external script load?
function myModule() {
var tag = document.createElement("script");
tag.type = "text/javascript";
tag.src = "http://some/script.js";
document.getElementsByTagName('head')[0].appendChild(tag);
//something should go here to ensure file is loaded before return is executed
return {
external: externalVariable
}
}
There is only one way to synchronously load and execute a script resource, and that is using a synchronous XHR
This is an example of how to do this
// get some kind of XMLHttpRequest
var xhrObj = createXMLHTTPObject();
// open and send a synchronous request
xhrObj.open('GET', "script.js", false);
xhrObj.send('');
// add the returned content to a newly created script tag
var se = document.createElement('script');
se.type = "text/javascript";
se.text = xhrObj.responseText;
document.getElementsByTagName('head')[0].appendChild(se);
But you shouldn't in general use synchronous requests as this will block everything else.
But that being said, there are of course scenarios where this is appropriate.
I would probably refactor the containing function into an asynchronous pattern though using an onload handler.
The accepted answer is NOT correct.
Loading a file synchronously is not the same as executing the file synchronously - which is what the OP requested.
The accepted answer loads the file sync, but does nothing more than append a script tag to the DOM. Just because appendChild() has returned does not in anyway guarantee that the script has finished executing and it's members are initialised for use.
The only (see caveat) way to achieve the OPs question is to sync load the script over XHR as stated, then read as text and pass into either eval() or a new Function() call and wait for that function to return. This is the only way to guarantee the script is loaded AND executed synchronously.
I make no comment as to whether this is a wise thing to do either from a UI or security perspective, but there are certainly use cases that justify a sync load & execute.
Caveat:
Unless you're using web workers in which case just call loadScripts();
This is the code that I'm using for multiple file load in my app.
Utilities.require = function (file, callback) {
callback = callback ||
function () {};
var filenode;
var jsfile_extension = /(.js)$/i;
var cssfile_extension = /(.css)$/i;
if (jsfile_extension.test(file)) {
filenode = document.createElement('script');
filenode.src = file;
// IE
filenode.onreadystatechange = function () {
if (filenode.readyState === 'loaded' || filenode.readyState === 'complete') {
filenode.onreadystatechange = null;
callback();
}
};
// others
filenode.onload = function () {
callback();
};
document.head.appendChild(filenode);
} else if (cssfile_extension.test(file)) {
filenode = document.createElement('link');
filenode.rel = 'stylesheet';
filenode.type = 'text/css';
filenode.href = file;
document.head.appendChild(filenode);
callback();
} else {
console.log("Unknown file type to load.")
}
};
Utilities.requireFiles = function () {
var index = 0;
return function (files, callback) {
index += 1;
Utilities.require(files[index - 1], callBackCounter);
function callBackCounter() {
if (index === files.length) {
index = 0;
callback();
} else {
Utilities.requireFiles(files, callback);
}
};
};
}();
And this utilities can be used by
Utilities.requireFiles(["url1", "url2",....], function(){
//Call the init function in the loaded file.
})
The most Node.js-like implementation I could come up with was able to load JS files synchonously, and use them as objects/modules
var scriptCache = [];
var paths = [];
function Import(path)
{
var index = 0;
if((index = paths.indexOf(path)) != -1) //If we already imported this module
{
return scriptCache [index];
}
var request, script, source;
var fullPath = window.location.protocol + '//' + window.location.host + '/' + path;
request = new XMLHttpRequest();
request.open('GET', fullPath, false);
request.send();
source = request.responseText;
var module = (function concealedEval() {
eval(source);
return exports;
})();
scriptCache.push(module);
paths.push(path);
return module;
}
An example source (addobjects.js):
function AddTwoObjects(a, b)
{
return a + b;
}
this.exports = AddTwoObjects;
And use it like this:
var AddTwoObjects = Import('addobjects.js');
alert(AddTwoObjects(3, 4)); //7
//or even like this:
alert(Import('addobjects.js')(3, 4)); //7
the accepted answer is not correct:
the script.async = false; directive only means that html parsing will be paused during script execution. this does not guarantee in which order javascript code will run. see https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/
the easiest and most elegant solution which was yet to be mentioned here is using promises, like so:
function loadScript(url) {
return new Promise((resolve, reject) => {
var script = document.createElement('script')
script.src = url
script.onload = () => {
resolve()
}
script.onerror = () => {
reject('cannot load script '+ url)
}
document.body.appendChild(script)
})
}
and then when you want to execute scripts in order:
loadScript('myfirstscript.js').then(() => {
console.log('first script ran');
loadScript('index.js').then(() => {
console.log('second script ran');
})
})
I had the following problem(s) with the existing answers to this question (and variations of this question on other stackoverflow threads):
None of the loaded code was debuggable
Many of the solutions required callbacks to know when loading was finished instead of truly blocking, meaning I would get execution errors from immediately calling loaded (ie loading) code.
Or, slightly more accurately:
None of the loaded code was debuggable (except from the HTML script tag block, if and only if the solution added a script elements to the dom, and never ever as individual viewable scripts.) => Given how many scripts I have to load (and debug), this was unacceptable.
Solutions using 'onreadystatechange' or 'onload' events failed to block, which was a big problem since the code originally loaded dynamic scripts synchronously using 'require([filename, 'dojo/domReady']);' and I was stripping out dojo.
My final solution, which loads the script before returning, AND has all scripts properly accessible in the debugger (for Chrome at least) is as follows:
WARNING: The following code should PROBABLY be used only in 'development' mode. (For 'release' mode I recommend prepackaging and minification WITHOUT dynamic script loading, or at least without eval).
//Code User TODO: you must create and set your own 'noEval' variable
require = function require(inFileName)
{
var aRequest
,aScript
,aScriptSource
;
//setup the full relative filename
inFileName =
window.location.protocol + '//'
+ window.location.host + '/'
+ inFileName;
//synchronously get the code
aRequest = new XMLHttpRequest();
aRequest.open('GET', inFileName, false);
aRequest.send();
//set the returned script text while adding special comment to auto include in debugger source listing:
aScriptSource = aRequest.responseText + '\n////# sourceURL=' + inFileName + '\n';
if(noEval)//<== **TODO: Provide + set condition variable yourself!!!!**
{
//create a dom element to hold the code
aScript = document.createElement('script');
aScript.type = 'text/javascript';
//set the script tag text, including the debugger id at the end!!
aScript.text = aScriptSource;
//append the code to the dom
document.getElementsByTagName('body')[0].appendChild(aScript);
}
else
{
eval(aScriptSource);
}
};
var xhrObj = new XMLHttpRequest();
xhrObj.open('GET', '/filename.js', false);
xhrObj.send(null);
eval(xhrObj.responseText);
If this is a cross-domain request, it will not work. In that case you have to upload the requested file to your server, or make a mirror php that outputs it, and require that php.
With jquery (works with cross-domain request too):
$.getScript('/filename.js',callbackFunction);
callbackFunction will be called synchronously.
For loading more scripts see this thread.
There actually is a way to load a list of scripts and execute them synchronously. You need to insert each script tag into the DOM, explicitly setting its async attribute to false:
script.async = false;
Scripts that have been injected into the DOM are executed asynchronously by default, so you have to set the async attribute to false manually to work around this.
Example
<script>
(function() {
var scriptNames = [
"https://code.jquery.com/jquery.min.js",
"example.js"
];
for (var i = 0; i < scriptNames.length; i++) {
var script = document.createElement('script');
script.src = scriptNames[i];
script.async = false; // This is required for synchronous execution
document.head.appendChild(script);
}
// jquery.min.js and example.js will be run in order and synchronously
})();
</script>
<!-- Gotcha: these two script tags may still be run before `jquery.min.js`
and `example.js` -->
<script src="example2.js"></script>
<script>/* ... */<script>
References
There is a great article by Jake Archibald of Google about this called Deep dive into the murky waters of script loading.
The WHATWG spec on the tag is a good and thorough description of how tags are loaded.
If you need to load an arbitrary number of scripts and only proceed when the last one is done, and you cannot use XHR (e.g. due to CORS limitations) you can do the following. It is not synchronous, but does allow a callback to occur exactly when the last file is done loading:
// Load <script> elements for all uris
// Invoke the whenDone callback function after the last URI has loaded
function loadScripts(uris,whenDone){
if (!uris.length) whenDone && whenDone();
else{
for (var wait=[],i=uris.length;i--;){
var tag = document.createElement('script');
tag.type = 'text/javascript';
tag.src = uris[i];
if (whenDone){
wait.push(tag)
tag.onload = maybeDone;
tag.onreadystatechange = maybeDone; // For IE8-
}
document.body.appendChild(tag);
}
}
function maybeDone(){
if (this.readyState===undefined || this.readyState==='complete'){
// Pull the tags out based on the actual element in case IE ever
// intermingles the onload and onreadystatechange handlers for the same
// script block before notifying for another one.
for (var i=wait.length;i--;) if (wait[i]==this) wait.splice(i,1);
if (!wait.length) whenDone();
}
}
}
Edit: Updated to work with IE7, IE8, and IE9 (in quirks mode). These IE versions do not fire an onload event, but do for onreadystatechange. IE9 in standards mode fires both (with onreadystatechange for all scripts firing before onload for any).
Based on this page there may be a small chance that old versions of IE will never send an onreadystatechange event with readyState=='complete'; if this is the case (I could not reproduce this problem) then the above script will fail and your callback will never be invoked.
You can't and shouldn't perform server operations synchronously for obvious reasons. What you can do, though, is to have an event handler telling you when the script is loaded:
tag.onreadystatechange = function() { if (this.readyState == 'complete' || this.readyState == 'loaded') this.onload({ target: this }); };
tag.onload = function(load) {/*init code here*/}
onreadystatechange delegation is, from memory, a workaround for IE, which has patchy support for onload.
same as Sean's answer, but instead of creating a script tag, just evaluate it. this ensures that the code is actually ready to use.
My strategy, classic example when load jQuery UI, i hope this can help you
( function( tools, libs ){
// Iterator
var require = function( scripts, onEnd ){
onEnd = onEnd || function(){};
if( !scripts || scripts.length < 1 )return onEnd();
var src = scripts.splice( 0, 1),
script = document.createElement( "script" );
script.setAttribute( "src", src );
tools.addEvent( "load", script, function(){
require( scripts, onEnd );
} );
document.getElementsByTagName( "head" )[ 0 ].appendChild( script );
};
// Install all scripts with a copy of scripts
require( libs.slice(), function(){
alert( "Enjoy :)" );
} );
// Timeout information
var ti = setTimeout( function(){
if( !window.jQuery || !window.jQuery.ui )alert( "Timeout !" );
clearTimeout( ti );
}, 5000 );
} )(
{ // Tools
addEvent : function( evnt, elem, func ){
try{
if( elem.addEventListener ){
elem.addEventListener( evnt, func, false );
}else if( elem.attachEvent ){
var r = elem.attachEvent( "on" + evnt, func );
}
return true;
}catch( e ){
return false;
}
}
},
[ // Scripts
"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"
]
);
When using Angular you can take advantage of the fact that every Provider is instantiated before other services are instantiated. You can combine this fact with using xhr and the eval() as mentioned by #Neil. The code would be following:
app.provider('SomeScriptSyncLoader', function() {
var resourceUrl = 'http://some/script.js';
var dummy = {};
this.$get = function() {
var q = jQuery.ajax({
type: 'GET', url: resourceUrl, cache: false, async: false
});
if (q.status === 200) {
eval(q.responseText); // execute some script synchronously as inline script - eval forces sync processing
}
return dummy;
};
});
To force the Provider to be inialized you need to inject it in at least one other directive/service. Preferably this would be the service which takes advantage of the code loaded by script.
app.directive('myDirective', ['SomeScriptSyncLoader', function(someScriptSyncLoader) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
// some ode
},
template: "this is my template"
};
}]);
I know this is an old question, but maybe someone else read this and find it useful !
Just created a new components uses ES6 to load scripts dynamically in synchronous way.
The Project details and source code are on GitHub https://github.com/amgadfahmi/scripty
I may be late to answering this question.
My current solution is to recursively add <script> tags such that the addition of the subsequent script is in the callback of its predecessor. It assumes that each function contains one function and that function is the same as the file name (minus the extension). This probably isn't the best way to do things, but it works ok.
Code to consider
Code directory structure:
- directory
---- index.html
---- bundle.js
---- test_module/
-------- a.js
-------- b.js
-------- log_num.js
-------- many_parameters.js
index.html
<head>
<script src="bundle.js"></script>
</head>
bundle.js
// Give JS arrays the .empty() function prototype
if (!Array.prototype.empty){
Array.prototype.empty = function(){
return this.length == 0;
};
};
function bundle(module_object, list_of_files, directory="") {
if (!list_of_files.empty()) {
var current_file = list_of_files.pop()
var [function_name, extension] = current_file.split(".")
var new_script = document.createElement("script")
document.head.appendChild(new_script)
new_script.src = directory + current_file
new_script.onload = function() {
module_object[function_name] = eval(function_name)
bundle(module_object, list_of_files, directory)
/*
nullify the function in the global namespace as - assumed - last
reference to this function garbage collection will remove it. Thus modules
assembled by this function - bundle(obj, files, dir) - must be called
FIRST, else one risks overwritting a funciton in the global namespace and
then deleting it
*/
eval(function_name + "= undefined")
}
}
}
var test_module = {}
bundle(test_module, ["a.js", "b.js", "log_num.js", "many_parameters.js"], "test_module/")
a.js
function a() {
console.log("a")
}
b.js
function b() {
console.log("b")
}
log_num.js
// it works with parameters too
function log_num(num) {
console.log(num)
}
many_parameters.js
function many_parameters(a, b, c) {
var calc = a - b * c
console.log(calc)
}
here is my code
var loaded_script = [];
function loadScript(urls, callback, sync) {
var len = urls.length, count = 0;
// check are all js loaded, then execute callback (if any)
var check = function() {
if (count == len) {
callback && typeof callback=="function" && callback();
}
};
for (var i = 0; i < len; i++) {
var url = urls[i];
// check if script not loaded (prevent load again)
if (loaded_script.indexOf(url) == -1) {
var script = document.createElement("script");
script.type = "text/javascript";
// set sync loading here (default is async)
if (sync) {
script.async = false;
}
// script onload event
if (script.readyState) { // IE
script.onreadystatechange = function() {
if (script.readyState=="loaded" || script.readyState=="complete") {
script.onreadystatechange = null;
count++, check();
}
};
} else { // Others
script.onload = function() {
count++, check();
};
}
// add script to head tag
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
// mark this script has loaded
loaded_script.push(url);
} else {
count++, check();
}
}
}
I use this on pjax site.
loadScript(
[
"js/first.js",
"js/second.js",
],
function() {
alert("Scripts loaded.");
},
true
);
I've had a similar task a few days earlier, and here's how I did it.
This loader works both in file:// prefixes as well as in http:// and https://, and is cross-browser compatible.
It however, cannot load specific classes or functions as modules from scripts; it will load the whole script altogether and make it available to the DOM.
// Loads a script or an array of scripts (including stylesheets)
// in their respective index order, synchronously.
// By Sayanjyoti Das #https://stackoverflow.com/users/7189950/sayanjyoti-das
var Loader={
queue: [], // Scripts queued to be loaded synchronously
loadJsCss: function(src, onl) {
var ext=src.toLowerCase().substring(src.length-3, src.length);
if(ext=='.js') {
var scrNode=el('script', null, null, null);
scrNode.type='text/javascript';
scrNode.onload=function() {onl();};
scrNode.src=src;
document.body.appendChild(scrNode);
}else if(ext=='css') {
var cssNode=el('link', null, null, null);
cssNode.rel='stylesheet';
cssNode.type='text/css';
cssNode.href=src;
document.head.appendChild(cssNode);
onl();
}
},
add: function(data) {
var ltype=(typeof data.src).toLowerCase();
// Load a single script
if(ltype=='string') {
data.src=data.src;
Loader.queue.splice(0, 1, data, Loader.queue[0]);
Loader.next();
}
// Load an array of scripts
else if(ltype=='object') {
for(var i=data.src.length-1; i>=0; i--) {
Loader.queue.splice(0, 1, {
src: data.src[i],
onload: function() {
if(Loader.next()==false) {
data.onload();
return;
}
Loader.next();
}
}, Loader.queue[0]);
}
Loader.next();
}
},
next: function() {
if(Loader.queue.length!=0 && Loader.queue[0]) {
var scr=Loader.queue[0];
// Remove the script from the queue
if(Loader.queue.length>1)
Loader.queue.splice(0, 2, Loader.queue[1]);
else
Loader.queue=[];
// Load the script
Loader.loadJsCss(scr.src, scr.onload);
}else return false;
}
};
The above function is very powerful and elegant; it allows you to load a single script or an array of script synchronously (i.e, next script not loaded until previous script loading finished). Moreover, a loaded script may load more scripts, which defers the queue in the parent script.
BTW, a script here means a JavaScript file or a CSS stylesheet.
Here's how to use it:-
// Load a single script
Loader.add({
src: 'test.js',
onload: function() {
alert('yay!');
}
});
// Load multiple scripts
Loader.add({
src: ['test1.js', 'test2.js', 'mystyles.css', 'test3.js'],
onload: function() {
alert('all loaded!');
}
});
Note that, the onload function in the Loader arguments is called when all of the scripts have loaded, not when one or a single script is loaded.
You can also load more scripts in the scripts you loaded, such as in test.js, test1.js, etc. By doing this, you will defer the load of the next parent script and the queue in the child script will be prioritized.
Hope it helps :-)
I use jquery load method applied to div element. something like
<div id="js">
<!-- script will be inserted here -->
</div>
...
$("#js").load("path", function() { alert("callback!" });
You can load scripts several times and each time one script will completely replace the one loaded earlier

Categories