Implementation of a client-side 'require' function compatible with node.js - javascript

Having decided to implement all of my Javascript libraries as modules compatible with node.js, I've now put together a simple 'require' function to be used on the client side of things. I do realize that there are already plenty of very good implementations out there of this, but most seem fairly heavy-handed so I'm just opting to go with the "roll-your-own" approach.
Here's my 'require.js' file:
/*
A client-side script loading class designed to be compatible with node.js
*/
function require(filename)
{
try
{
var
script = null,
ajax = new XMLHttpRequest(),
index = filename.toLowerCase().indexOf('.js');
if(index <= 0 || index != filename.length - 3)
filename += '.js';
ajax.onload = function()
{
script = this.responseText;
};
ajax.open('GET', filename, false);
ajax.send();
return _isolate_script_(script);
}
catch(error)
{
return null;
}
}
function _isolate_script_(_script_)
{
return (new Function
(
'var exports = {}, module = {exports : null}; '
+ _script_ +
'; return module.exports || exports;'
)).call();
}
Example module ('example.js'):
/*
Example of a multiple-class export file
*/
exports.metallic = function(value)
{
return (Math.sqrt(4 + value * value) + value) / 2;
}
exports.inverse_metallic = function(value)
{
return exports.metallic(-value);
}
Example of a module consumer ('main.js'):
function main()
{
var
example = require('example');
if(example != null)
{
var
value = Math.floor(Math.random() * 100) + 1;
alert
(
'example.metallic(' + value + ') = ' + example.metallic(value)
+ '\n' +
'example.inverse_metallic(' + value + ') = ' + example.inverse_metallic(value)
);
}
else
alert('Error: cannot load "example"');
}
Finally, a basic HTML file that invokes our page logic:
<!DOCTYPE html>
<html>
<head>
<script src = 'require.js'></script>
<script src = 'main.js'></script>
</head>
<body onload = "main()"></body>
</html>
So my specific question is simply whether or not I've implemented everything correctly, and moreover if the framework and use-cases seem kosher enough?

It seems that you are trying to reimplement Browserify:
Browsers don't have the require method defined, but Node.js does. With Browserify you can write code that uses require in the same way that you would use it in Node.
If you want to implement a similar thing yourself then take a look at the Browserify source code.
See: https://github.com/substack/node-browserify

I'm just going to answer this myself.
The main issue is that older browsers sometimes freeze-up with synchronous ajax requests. Simply launching the entire 'main' function in it's own thread seems to be a good solution for that. That is, if some unforeseen issue arises with loading scripts then the page itself may not be responsive, but the browser should at least be unaffected. In other words, something along the lines of this:
<!DOCTYPE html>
<html>
<head>
<script src = 'require.js'></script>
<script src = 'main.js'></script>
</head>
<body onload = "setInterval(main)"></body>
</html>
As to concerns about throughput, I've now tested this with huge amounts of data (~100 MB) and it appears to make little difference on performance as to whether the code was loaded with a script tag or via the ajax call. All major browsers on two different operating systems and the results were pretty much the same. So unless I see convincing evidence to the contrary I'm just going to assume this to be the rule rather than the exception.
All that said, I'm certainly still open to any comments or criticisms that may be.

If you want module caching as well try this:
let parent = "";
let cache = {};
/**
* Load a JavaScript text synchronously.
*
* #param url The url.
*/
function cludge(url) {
/* resolve and check */
if (parent !== "")
url = new URL(url, parent).href;
let map = cache[url];
if (map !== undefined)
return map;
map = {};
cache[url] = map;
/* load and execute */
let back = parent;
try {
parent = url;
let request = new XMLHttpRequest();
request.open('GET', url, false);
request.send();
let fun = new Function("exports", "require", request.responseText);
fun(map, cludge);
} finally {
parent = back;
}
return map;
}
But the name resolution is not as elaborate as in nodejs.

Related

Need Functions That Wait Until Cache Can Be Verified

I'm aware that the immediate response is going to be to use promises for the following problem. However, I've looked at every example I can and nothing seems to be readable to me. Most is done through HTML examples, which I don't understand. Similar posts here have a plethora of arguments on which syntax is appropriate which furthers the confusion. So, please show me how I might use a promise to do the following or if another solution is better.
I'd like for a function to check if the cache is available, and if not, it will reinitialize the cache:
function valData(){
if (arrOfNames || arrOfRanges == null){
initializeGame();
Logger.log('Game Initialized')
}
else {
Logger.log('Data Available')
}
};
I'm aware this is likely sloppy, but stay with me
This function works as intended, as does the following:
function reqVal(valname,x){
if (x == 0){
var nameArr = convToString(arrOfNames);
var valIndex = nameArr.indexOf("" + valname)
return arrOfRanges[valIndex];
}
else {
var nameArr = convToString(arrOfNames);
var valIndex = nameArr.indexOf("" + valname)
return valIndex;
}
};
Again, very very sloppy but Ill clean things up once I can make them function
Both of these scripts work fine on their own and return what I need. But if I have a third script:
function test(){
valData();
var valNum = reqVal('curHP', 0);
var valIndex = reqVal('curHP',1)
var newNum = valNum - 5;
Logger.log(valNum)
Logger.log(newNum)
Logger.log(valIndex)
};
The reqVal() function fails due to the asynchronous nature of GAS and the Cache having expired, but the valData() function succeeds and produces the needed array. So, I know I need to add a promise in valData() that the reqVal() function can read from but have no idea how to format that as examples I've seen have been very unclear.

D365 Error when calling function from another JS

I am having an issue calling JS function from another JS file.
Main JS where the function is defined.
var Common = Common || {};
Common.BaseAction = Common.BaseAction || {};
Common.BaseAction.SetNotification = function (message, level, uniqueId)
{
Xrm.Page.ui.setFormNotification(message, level, uniqueId);
}
Common.BaseAction.clearNotification = function (uniqueId) {
Xrm.Page.ui.clearFormNotification(uniqueId);
}
JS from where I am calling the function
var apItem = apItem || {};
apItem.BaseForm = apItem.BaseForm || {};
apItem.BaseForm.SetName = function ()
{
var bookName = Xrm.Page.getAttribute("ap_bookid").getValue()[0].name;
var condition = Xrm.Page.getAttribute("ap_condition").getText();
if (bookName !== null && condition !== null) {
Xrm.Page.getAttribute("ap_name").setValue(bookName + " - " + condition);
}
}
apItem.BaseForm.CountOverDueBy = function() {
var rentedTill = Xrm.Page.getAttribute("ap_rented_till").getValue();
var nowD = Date.now();
if (rentedTill !== null) {
var overdueBy = parseInt((Date.now() - rentedTill) / 86400000);
if (overdueBy > 0) {
Xrm.Page.getAttribute("ap_overdue_by").setValue(overdueBy);
Common.BaseAction.SetNotification("Book is Overdue by " + overdueBy
+ " Days.", "WARNING", "OverDueWarning");
}
else {
Xrm.Page.getAttribute("ap_overdue_by").setValue(null);
Common.BaseAction.clearNotification("OverDueWarning");
}
}
}
In the entity's form, I have added both above files with common.js being at the top and from the event handler I am calling function apItem.BaseForm.CountOverDueBy
Save + Published and Ctrl + F5 gives following error
ReferenceError: Common is not defined
at Object.apItem.BaseForm.CountOverDueBy (https://<domain>/%7B636651014350000438%7D/WebResources/ap_ItemFormBase.js?ver=2091450722:24:13)
at eval (eval at RunHandlerInternal (https://<domain>/form/ClientApiWrapper.aspx?ver=2091450722:153:1), <anonymous>:1:17)
at RunHandlerInternal (https://<domain>/form/ClientApiWrapper.aspx?ver=2091450722:159:1)
at RunHandlers (https://<domain>/form/ClientApiWrapper.aspx?ver=2091450722:118:1)
at OnScriptTagLoaded (https://<domain>/form/ClientApiWrapper.aspx?ver=2091450722:233:1)
at https://<domain>/form/ClientApiWrapper.aspx?ver=2091450722:202:1
I have tried everything but nothing seems to be working.
The way you register the JS files in form, starting from common.js on top & then ap_ItemFormBase.js should work. But product team made few performance improvements around script files like lazy script loading/parallel script loading. This is little tricky & modern scripting is messy between different clients like UUI & web.
Like explained in blog post, if you set the pre-requisite js as dependency, it will load before you consume it in dependent files.
Open the ap_ItemFormBase.js web resource from solution (not from Form Properties), go to Dependencies tab & add the common.js. This will make sure file is ready before reference is used.

Appending to External Browser Window

I have a Windows app that contains a browser control that loads pages from my website. However, due to the Windows app, I cannot debug Javascript in the usual ways (Firebug, console, alerts, etc).
I was hoping to write a jQuery plug-in to log to an external browser window such that I can simply do something like:
$.log('test');
So far, with the following, I am able to create the window and display the templateContent, but cannot write messages to it:
var consoleWindow;
function getConsoleWindow() {
if (typeof (consoleWindow) === 'undefined') {
consoleWindow = createConsoleWindow();
}
return consoleWindow;
}
function createConsoleWindow() {
var newConsoleWindow = window.open('consoleLog', '', 'status,height=200,width=300');
var templateContent = '<html><head><title>Console</title></head>' +
'<body><h1>Console</h1><div id="console">' +
'<span id="consoleText"></span></div></body></html>';
newConsoleWindow.document.write(templateContent);
newConsoleWindow.document.close();
return newConsoleWindow;
}
function writeToConsole(message) {
var console = getConsoleWindow();
var consoleDoc = console.document.open();
var consoleMessage = document.createElement('span');
consoleMessage.innerHTML = message;
consoleDoc.getElementById('consoleText').appendChild(consoleMessage);
consoleDoc.close();
}
jQuery.log = function (message) {
if (window.console) {
console.log(message);
} else {
writeToConsole(message);
}
};
Currently, getElementById('consoleText') is failing. Is what I'm after possible, and if so, what am I missing?
Try adding
consoleDoc.getElementById('consoleText');
right before
consoleDoc.getElementById('consoleText').appendChild(consoleMessage);
If the line you added is the one that fails, then that means consoleDoc is not right, if the next line is the only one that fails then ..ById('consoleText') is not matching up
If I don't close() the document, it appears to work as I hoped.

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

Get the url of currently executing js file when dynamically loaded

So I'm trying to load a script dynamically and figure out the URL path at which that script was loaded. So some guy gave me a pretty awesome solution to this problem if the scripts are statically loaded ( How to get the file-path of the currently executing javascript code ). But I need a dynamically loaded solution. For example:
$(function()
{ $.getScript("brilliant.js", function(data, textStatus)
{ // do nothing
});
});
where "brilliant.js" has:
var scripts = document.getElementsByTagName("script");
var src = scripts[scripts.length-1].src;
alert("THIS IS: "+src);
Ideally this should either print out "brilliant.js" or "〈hostname+basepath〉/brilliant.js"
Currently brilliant.js works for statically included scripts, but not for scripts included dynamically (like with $.getScript). Anyone have any ideas? Is there somewhere in the dom that stores all the scripts that have been loaded?
EDIT: Andras gave a pretty good solution, though it probably only works for jQuery. Since that's probably the most popular library, and definitely what I'm going to be using. It can probably be extended for other libraries as well. Here's my simplified version:
var scriptUri;
curScriptUrl(function(x)
{ scriptUri = x;
alert(scriptUri);
});
function curScriptUrl(callback)
{ var scripts = document.getElementsByTagName("script");
var scriptURI = scripts[scripts.length-1].src;
if(scriptURI != "") // static include
{ callback(scriptURI);
}else if($ != undefined) // jQuery ajax
{ $(document).ajaxSuccess(function(e, xhr, s)
{ callback(s.url);
});
}
}
When your script gets loaded with jQuery (and I guess other frameworks as well), your script will become indistinguishable from a script that was originally in the HTML document. jQuery makes a request reaching out for your script and puts back the reply as the text child of a <script> node. Your browser has no way of knowing where it originated from, whether it was modified before inserted, etc. It is just a script node as far as she is concerned.
There can be workarounds, however. In the case of jQuery, you can hook up to the ajax events and exploit the fact that they are called right after your script executes. Basically, this would yield "brilliant.js" in your example:
var handler = function (e, xhr, s) {
alert(s.url);
}
$(document).ajaxSuccess(handler);
A more elaborate one:
(function ($, undefined) {
/* Let's try to figure out if we are inlined.*/
var scripts = document.getElementsByTagName("script");
if (scripts[scripts.length - 1].src.length === 0) {
// Yes, we are inlined.
// See if we have jQuery loading us with AJAX here.
if ($ !== undefined) {
var initialized = false;
var ajaxHandler = function (e, xhr, s) {
if (!initialized) {
initialized = true;
alert("Inlined:" + s.url);
initmywholejsframework();
}
}
//If it is, our handler will be called right after this file gets loaded.
$(document).ajaxSuccess(ajaxHandler);
//Make sure to remove our handler if we ever yield back.
window.setTimeout(function () {
jQuery(document).unbind("ajaxSuccess", ajaxHandler);
if (!initialized) {
handleInlinedNonjQuery();
}
}, 0);
}
} else {
//We are included.
alert("Included:" + scripts[scripts.length - 1].src);
initmywholejsframework();
}
//Handle other JS frameworks etc. here, if you will.
function handleInlinedNonjQuery() {
alert("nonJQuery");
initmywholejsframework();
}
//Initialize your lib here
function initmywholejsframework() {
alert("loaded");
}
})(jQuery);
B T, sorry if this doesn't help, but I'm curious why you would need to do this? The reason I'm asking is I don't see why you can't just use the relative file paths to load these files? Finding out where you're located could be done with window.location, but why would you? And as for loading them, can't you make an ajax call to the file and then eval them?
This will work in every browser except IE and doesn't depend on assuming what the name of a file is:
var getErrorLocation = function (error) {
var loc, replacer = function (stack, matchedLoc) {
loc = matchedLoc;
};
if ("fileName" in error) {
loc = error.fileName;
} else if ("stacktrace" in error) { // Opera
error.stacktrace.replace(/Line \d+ of .+ script (.*)/gm, replacer);
} else if ("stack" in error) { // WebKit
error.stack.replace(/at (.*)/gm, replacer);
loc = loc.replace(/:\d+:\d+$/, ""); // remove line number
}
return loc;
};
try {
0();
} catch (e) {
var scriptURI = getErrorLocation(e);
}
alert("THIS IS: " + scriptURI);

Categories