How to write this JavaScript code without eval?
var typeOfString = eval("typeof " + that.modules[modName].varName);
if (typeOfString !== "undefined") {
doSomething();
}
The point is that the name of the var that I want to check for is in a string.
Maybe it is simple but I don't know how.
Edit: Thank you for the very interesting answers so far. I will follow your suggestions and integrate this into my code and do some testing and report. Could take a while.
Edit2: I had another look at the could and maybe itis better I show you a bigger picture. I am greatful for the experts to explain so beautiful, it is better with more code:
MYNAMESPACE.Loader = ( function() {
function C() {
this.modules = {};
this.required = {};
this.waitCount = 0;
this.appendUrl = '';
this.docHead = document.getElementsByTagName('head')[0];
}
function insert() {
var that = this;
//insert all script tags to the head now!
//loop over all modules:
for (var modName in this.required) {
if(this.required.hasOwnProperty(modName)){
if (this.required[modName] === 'required') {
this.required[modName] = 'loading';
this.waitCount = this.waitCount + 1;
this.insertModule(modName);
}
}
}
//now poll until everything is loaded or
//until timout
this.intervalId = 0;
var checkFunction = function() {
if (that.waitCount === 0) {
clearInterval(that.intervalId);
that.onSuccess();
return;
}
for (var modName in that.required) {
if(that.required.hasOwnProperty(modName)){
if (that.required[modName] === 'loading') {
var typeOfString = eval("typeof " + that.modules[modName].varName);
if (typeOfString !== "undefined") {
//module is loaded!
that.required[modName] = 'ok';
that.waitCount = that.waitCount - 1;
if (that.waitCount === 0) {
clearInterval(that.intervalId);
that.onSuccess();
return;
}
}
}
}
}
};
//execute the function twice a second to check if all is loaded:
this.intervalId = setInterval(checkFunction, 500);
//further execution will be in checkFunction,
//so nothing left to do here
}
C.prototype.insert = insert;
//there are more functions here...
return C;
}());
var myLoader = new MYNAMESPACE.Loader();
//some more lines here...
myLoader.insert();
Edit3:
I am planning to put this in the global namespace in variable MYNAMESPACE.loadCheck, for simplicity, so the result would be, combining from the different answers and comments:
if (MYNAMESPACE.loadCheck.modules[modName].varName in window) {
doSomething();
}
Of course I will have to update the Loader class where ever "varName" is mentioned.
in JS every variable is a property, if you have no idea whose property it is, it's a window property, so I suppose, in your case, this could work:
var typeOFString = typeof window[that.modules[modName].varName]
if (typeOFString !== "undefined") {
doSomething();
}
Since you are only testing for the existence of the item, you can use in rather than typeof.
So for global variables as per ZJR's answer, you can look for them on the window object:
if (that.modules[modName].varName in window) {
...
}
If you need to look for local variables there's no way to do that without eval. But this would be a sign of a serious misdesign further up the line.
Related
Explanation:
As a personal project, I'm trying to create my own lightweight version of Dependency Injection for JavaScript - Some would probably disagree with calling this DI because it has no interfaces, but I arrived at the conclusion that interfaces were overkill in JS since we can so easily type check. I have looked at the source of Angular, but I just feel like the complexity there may be overkill for my projects, and I'm interested in attempting my own for a learning experience anyway.
Question:
My question is, fundamentally, is the syntax I'm trying to implement impossible or not?
I'll explain my goal for the syntax, then provide the error and code snippet, and below that I'll post the full code.
Goal for Syntax:
I'd like the creation of a component, and injection of dependencies to work like this, where everything is a component, and anything can be a dependency. I created scope with a string path, using "/scopeName/subScopeName:componentName" to select a scope, so that code users can select the scope while defining the component in a simple way, using a ":" to select a component from the scope.
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
/* ...snip - see full code for the process component - snip ... */
JHTML.addComponent('/generate:init', function (jsonInput, process) {
var html = process(jsonInput);
return html;
}).inject([null, '/generate:process']);
The inject function just takes an array of component paths in the order the component's arguments are expected. null can be used to skip, allowing direct argument input instead, as shown above.
I also have something I call hooks, which are components stored in a certain place, and then there's a function returnUserHandle which will return an object consisting of just the hooks, so all of the functions are hidden in closures, and you can feed the code user just the usable methods, clean and easy, and can produce the final product as a library without the wiring, no need for my DI framework as a dependency. Hopefully that makes sense.
Error:
Right now, running the code (which is a very simple library to generate HTML by parsing a JSON structure) I get the error that process is undefined in the line var html = process(jsonInput);. I was having trouble understanding whether this is a fundamental design problem, or just a bug. Maybe this syntax is not possible, I'm hoping you can tell me.
Code:
Here's the code, and a link to the JS Bin.
/* Dependency Injection Framework - viziion.js */
function Viziion(appName) {
if (typeof appName == 'string') {
var that = this;
this.name = appName;
this.focus = null;
this.scope = {
'/': {
'subScopes': {},
'components': {}
}
};
this.hooks = {};
this.addScope = function(scopeName) {
if (typeof scopeName == 'string') {
var scopeArray = scopeName.split('/');
var scope = that.scope['/'];
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
scope.subScopes[scopeArray[i]] = {
'subScopes': {},
'components': {}
};
}
}
}
} else {
throw 'Scope path must be a string.';
}
return that;
};
this.addComponent = function(componentName, func) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if ((i + 1) === scopeArray.length) {
scope.components[scopeName] = func;
that.focus = scope.components[scopeName];
} else if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string1.';
}
return that;
};
this.returnComponent = function(componentName, callback) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if ((i + 1) === scopeArray.length) {
//console.log('yep1');
//console.log(scope.components[scopeName]);
callback(scope.components[scopeName]);
} else if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string2.';
}
};
this.addHook = function(hookName, func) {
if (typeof hookName == 'string') {
that.hooks[hookName] = func;
that.focus = that.hooks[hookName];
} else {
throw 'Hook name must be a string.';
}
return that;
};
this.inject = function(dependencyArray) {
if (dependencyArray) {
var args = [];
for (var i = 0; i < dependencyArray.length; i++) {
if (dependencyArray[i] !== null) {
that.returnComponent(dependencyArray[i], function(dependency) {
args.push(dependency);
});
}
}
console.log(that.focus);
that.focus.apply(null, args);
return that;
}
};
this.returnUserHandle = function() {
return that.hooks;
};
} else {
throw 'Viziion name must be a string.';
}
}
/* JSON HTML Generator - A Simple Library Using Viziion */
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
JHTML.addComponent('/generate:process', function(children) {
var html = [];
var loop = function() {
for (var i = 0; i < children.length; i++) {
if (children[i].tag) {
html.push('<' + tag + '>');
if (children[i].children) {
loop();
}
html.push('</' + tag + '>');
return html;
} else {
throw '[JHTML] Bad syntax: Tag type is not defined on node.';
}
}
};
}).inject();
JHTML.addComponent('/generate:init', function(jsonInput, process) {
console.log(process);
var html = process(jsonInput);
return html;
}).inject([null, '/generate:process']);
JHTML.addHook('generate', function(jsonInput, init) {
var html = init(jsonInput);
return html;
}).inject([null, '/generate:init']);
handle = JHTML.returnUserHandle();
/* HTML Generator Syntax - Client */
var htmlChunk = [{
tag: '!DOCTYPEHTML'
}, {
tag: 'html',
children: [{
tag: 'head',
children: []
}, {
tag: 'body',
children: []
}]
}];
console.log(handle.generate(htmlChunk));
is the syntax I'm trying to implement impossible or not?
It's absolutely possible, and I'm sure with a bit of bugfixing it'd work just fine.
What you're describing is essentially the same as Asynchronous Module Definition (AMD) which is used extensively for handling code dependencies.
Rather than continuing to pursue your own version of the same concept, I recommend that you give requirejs a try and follow the existing standards with your projects.
I have a hash called options. The problem that I'm facing is that options['beforeOpen'] might already be a function, in which case I don't want to overwrite it. I'd like to instead call it then call another function that needs to be called every time
In this example the method that needs to be called every time is methodThatINeedToDo. I thought the code below would accomplish this but it's not working as I expected.
function methodThatINeedToDo(){alert('maintenance');}
var options = {beforeOpen: function(){alert('first');}}
if(typeof options['beforeOpen'] == "function"){
options['beforeOpen'] = function(){options['beforeOpen'].call(); methodThatINeedToAddToDo();}
} else {
options['beforeOpen'] = methodThatINeedToDo;
}
The problem is that within the function you're defining to override options['beforeOpen'], you're using options['beforeOpen'], which by that time has been overwritten!
You need to cache it and use the cached value within your new function:
var cachedBeforeOpen = options.beforeOpen;
if (typeof cachedBeforeOpen == "function") {
options.beforeOpen = function() {
cachedBeforeOpen.call();
methodThatINeedToDo();
};
} else {
options.beforeOpen = methodThatINeedToDo;
}
Simply always call methodThatINeedToDo, since you want to and in there check to see if you should call your options method:
function methodThatINeedToDo(){
options.beforeOpen && options.beforeOpen();
alert('maintenance');
}
That really smells like the wrong solution. Why not Publish/Subscribe pattern?
Here's a little example: http://jsfiddle.net/ajyQH/
$(function() {
var yourObj = { yourFct : [] };
$('#btn').click(function() {
yourObj.yourFct.push(function() {
$('#testibert').append($('<p>').text('hallo'));
});
});
$('#btn_exec').click(function() {
var len = yourObj.yourFct.length;
for(var i = 0; i < len; i++) {
yourObj.yourFct[i]();
}
});
});
var oldCall = options['beforeOpen'];
var newCall = function(){
oldCall();
methodThatINeedToAddToDo();
};
options['beforeOpen'] = newCall;
This is my code (it's a bookmarklet)
javascript:(function(){
a=document.createElement('script');
a.setAttribute('src','//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js');
document.body.appendChild(a);
data='[["#txtapplicantlname","agrawal","text"],["#txtapplicantfname","aayush","text"],["#txtfather","Ranjan","text"],["#txtmother","Neelam","text"],["#txtPincode","452010","text"],["#txtPhone","2147483647","text"],["#txtEmail","aayush#mail.com","text"]]';
for(a=$.parseJSON(data),b=a.length-1;0<=b;b--){
c=a[b];
if (c[2] == 'text') {
console.log(c);
$(c[0]).val(c[1]);
}
}
})();
It used to work fine until I inserted the if statement, then it broke. The console doesn't give me any errors and I have googled a lot for javascript string comparison errors and found nothing useful.
I tried to use equals and compareTo and ended up with console errors and nothing working.
Uncaught TypeError: Cannot call method 'equals' of undefined fillform.php:1
Uncaught TypeError: Cannot call method 'compareTo' of undefined
Help is highly appreciated.
Note: The variables are named like that for a reason, it being that it was initially compiled with Google closure compiler and the if statement is being edited in.
There are several things wrong with this code; string comparison is not one of them.
1) You aren't waiting for the asynchronously loaded script to complete. This code should pretty much always fail because $.parseJSON() isn't available. In fact, once I fixed that problem, this code works fine for me:
(function(){
a=document.createElement('script');
a.setAttribute('src','//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js');
var afterJqueryLoad = function() {
data='[["#txtapplicantlname","agrawal","text"],["#txtapplicantfname","aayush","text"],["#txtfather","Ranjan","text"],["#txtmother","Neelam","text"],["#txtPincode","452010","text"],["#txtPhone","2147483647","text"],["#txtEmail","aayush#mail.com","text"]]';
for(a=$.parseJSON(data),b=a.length-1;0<=b;b--){
c=a[b];
if (c[2] == 'text') {
console.log(c);
$(c[0]).val(c[1]);
}
}
};
var jqueryReady = false;
a.onreadystatechange= function () {
if((this.readyState == 'complete' || this.readyState == 'loaded') && !jqueryReady) {
jqueryReady = true;
afterJqueryLoad();
}
};
a.onload = function() {
if(!jqueryReady) {
jqueryReady = true;
afterJqueryLoad();
}
};
document.body.appendChild(a);
})();
2) Use better var names (a, b, and c are not good var names).
3) Use var to scope vars correctly. Right now your code is shadowing globals and stomping on vars even within the same scope; the a var, for example, would stomp on your script elem var. (You should still change the var names per (2) but using var is not optional; you must always do this to scope vars correctly.)
4) Use spaces to help readability; your for line is unnecessarily difficult to read having no spaces.
All together now:
(function(){
var jqueryScriptElem = document.createElement('script');
jqueryScriptElem.setAttribute('src', '//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js');
var afterJqueryLoad = function() {
var data = '[["#txtapplicantlname","agrawal","text"],["#txtapplicantfname","aayush","text"],["#txtfather","Ranjan","text"],["#txtmother","Neelam","text"],["#txtPincode","452010","text"],["#txtPhone","2147483647","text"],["#txtEmail","aayush#mail.com","text"]]',
dataParsed = $.parseJSON(data);
for(var dataItemIndex = dataParsed.length - 1; 0 <= dataItemIndex; dataItemIndex--) {
var dataItem = dataParsed[dataItemIndex];
if (dataItem[2] == 'text') {
console.log(dataItem);
$(dataItem[0]).val(dataItem[1]);
}
}
};
var jqueryReady = false;
jqueryScriptElem.onreadystatechange = function () {
if((this.readyState == 'complete' || this.readyState == 'loaded') && !jqueryReady) {
jqueryReady = true;
afterJqueryLoad();
}
};
jqueryScriptElem.onload = function() {
if(!jqueryReady) {
jqueryReady = true;
afterJqueryLoad();
}
};
document.body.appendChild(jqueryScriptElem);
})();
I want watch for the creation of new global variables in Javascript so that, anytime a global variable is created, an event is fired.
I've heard of the watch() function but that is only for watching for specific variable names. I want a catchall.
If you already know which names pollute your global namespace (see Intercepting global variable definition in javascript), you can use this trick to figure out when does it actually happen:
window.__defineSetter__('someGlobalVar', function() {
debugger;
});
Be sure to have your developer tools open when you run this.
Obviously works only if your browser supports __defineSetter__ but that's true for modern browsers. Also, don't forget to remove your debug code after you've finished.
Found here.
I don't know how to make this work "on demand" as soon as a var is created, but I can suggest a polling approach. In a browser window, all global become a member of the global "window" object. (Because technically, "window" is the "global object"). So you could do something like the following:
1) enumerate all the properties on a window
window.proplist = window.proplist || {};
for (propname in window) {
if (propname !== "proplist") {
window.proplist[propname] = true;
}
}
2) Set a timer to periodically "poll" window for new properties
setInterval(onTimer, 1000);
3) Wake up on the timer callback and look for new props
function onTimer() {
if (!window.proplist) {
return;
}
for (propname in window) {
if (!(window.proplist[propname])) {
window.proplist[propname] = true;
onGlobalVarCreated(propname);
}
}
}
Afaik, .watch() is only SpiderMonkey (Firefox).
I played around with a polling function, I finally came up with this:
var mywatch = (function() {
var last = {
count: 0,
elems: {}
};
return function _REP(cb) {
var curr = {
count: 0,
elems: {}
},
diff = {};
for(var prop in window) {
if( window.hasOwnProperty(prop) ) {
curr.elems[prop] = window[prop]; curr.count++;
}
}
if( curr.count > last.count ) {
for(var comp in curr.elems) {
if( !(comp in last.elems) ) {
diff[comp] = curr.elems[comp];
}
}
last.count = curr.count;
last.elems = curr.elems;
if(typeof cb === 'function')
cb.apply(null, [diff]);
}
setTimeout(function() {
_REP(cb);
}, 400);
};
}());
And then use it like:
mywatch(function(diff) {
console.log('NEW GLOBAL(s): ', diff);
});
Be aware that this only handles new globals. But you can easily extend this for the case last.count > curr.count. That would indicate that global variables were deleted.
You cant have an event fired when some script does var v = 10, but as selbie said, you can poll the window object... I meant to suggest the same, but he beat me to it. Here's my other example... you count how many window objects are there, and execute GlobalVarCreated() function:
var number_of_globals = 0; //last known globals count
var interval = window.setInterval(function(){
var new_globals_count = 0; //we count again
for(var i in window) new_globals_count++; //actual counting
if(number_of_globals == 0) number_of_globals = new_globals_count; //first time we initialize old value
else{
var number_of_new_globals = new_globals_count - number_of_globals; //new - old
if(number_of_new_globals > 0){ //if the number is higher then 0 then we have some vars
number_of_globals = new_globals_count;
for(var i = 0; i<number_of_new_globals; i++) GlobalVarCreated(); //if we have 2 new vars we call handler 2 times...
}
}
},300); //each 300ms check is run
//Other functions
function GlobalVarCreated(){}
function StopInterval(){window.clearInterval(interval);}
You can load up that code in Chrome or FF console only change: function GlobalVarCreated(){console.log("NEW VAR CREATED");} and test it:
var a = 10
b = 10
String NEW VAR CREATED is displayed 2 times.
I include myscript.js in the file http://site1.com/index.html like this:
<script src=http://site2.com/myscript.js></script>
Inside "myscript.js", I want to get access to the URL "http://site2.com/myscript.js". I'd like to have something like this:
function getScriptURL() {
// something here
return s
}
alert(getScriptURL());
Which would alert "http://site2.com/myscript.js" if called from the index.html mentioned above.
From http://feather.elektrum.org/book/src.html:
var scripts = document.getElementsByTagName('script');
var index = scripts.length - 1;
var myScript = scripts[index];
The variable myScript now has the script dom element. You can get the src url by using myScript.src.
Note that this needs to execute as part of the initial evaluation of the script. If you want to not pollute the Javascript namespace you can do something like:
var getScriptURL = (function() {
var scripts = document.getElementsByTagName('script');
var index = scripts.length - 1;
var myScript = scripts[index];
return function() { return myScript.src; };
})();
You can add id attribute to your script tag (even if it is inside a head tag):
<script id="myscripttag" src="http://site2.com/myscript.js"></script>
and then access to its src as follows:
document.getElementById("myscripttag").src
of course id value should be the same for every document that includes your script, but I don't think it is a big inconvenience for you.
Everything except IE supports
document.currentScript
Simple and straightforward solution that work very well :
If it not IE you can use document.currentScript
For IE you can do document.querySelector('script[src*="myscript.js"]')
so :
function getScriptURL(){
var script = document.currentScript || document.querySelector('script[src*="myscript.js"]')
return script.src
}
update
In a module script, you can use:
import.meta.url
as describe in mdn
I wrote a class to find get the path of scripts that works with delayed loading and async script tags.
I had some template files that were relative to my scripts so instead of hard coding them I made created the class to do create the paths automatically. The full source is here on github.
A while ago I had use arguments.callee to try and do something similar but I recently read on the MDN that it is not allowed in strict mode.
function ScriptPath() {
var scriptPath = '';
try {
//Throw an error to generate a stack trace
throw new Error();
}
catch(e) {
//Split the stack trace into each line
var stackLines = e.stack.split('\n');
var callerIndex = 0;
//Now walk though each line until we find a path reference
for(var i in stackLines){
if(!stackLines[i].match(/http[s]?:\/\//)) continue;
//We skipped all the lines with out an http so we now have a script reference
//This one is the class constructor, the next is the getScriptPath() call
//The one after that is the user code requesting the path info (so offset by 2)
callerIndex = Number(i) + 2;
break;
}
//Now parse the string for each section we want to return
pathParts = stackLines[callerIndex].match(/((http[s]?:\/\/.+\/)([^\/]+\.js)):/);
}
this.fullPath = function() {
return pathParts[1];
};
this.path = function() {
return pathParts[2];
};
this.file = function() {
return pathParts[3];
};
this.fileNoExt = function() {
var parts = this.file().split('.');
parts.length = parts.length != 1 ? parts.length - 1 : 1;
return parts.join('.');
};
}
if you have a chance to use jQuery, the code would look like this:
$('script[src$="/myscript.js"]').attr('src');
Following code lets you find the script element with given name
var scripts = document.getElementsByTagName( 'script' );
var len = scripts.length
for(var i =0; i < len; i++) {
if(scripts[i].src.search("<your JS file name") > 0 && scripts[i].src.lastIndexOf("/") >= 0) {
absoluteAddr = scripts[i].src.substring(0, scripts[i].src.lastIndexOf("/") + 1);
break;
}
}
document.currentScript.src
will return the URL of the current Script URL.
Note: If you have loaded the script with type Module then use
import.meta.url
for more import.meta & currentScript.src
Some necromancy, but here's a function that tries a few methods
function getScriptPath (hint) {
if ( typeof document === "object" &&
typeof document.currentScript === 'object' &&
document.currentScript && // null detect
typeof document.currentScript.src==='string' &&
document.currentScript.src.length > 0) {
return document.currentScript.src;
}
let here = new Error();
if (!here.stack) {
try { throw here;} catch (e) {here=e;}
}
if (here.stack) {
const stacklines = here.stack.split('\n');
console.log("parsing:",stacklines);
let result,ok=false;
stacklines.some(function(line){
if (ok) {
const httpSplit=line.split(':/');
const linetext = httpSplit.length===1?line.split(':')[0]:httpSplit[0]+':/'+( httpSplit.slice(1).join(':/').split(':')[0]);
const chop = linetext.split('at ');
if (chop.length>1) {
result = chop[1];
if ( result[0]!=='<') {
console.log("selected script from stack line:",line);
return true;
}
result=undefined;
}
return false;
}
ok = line.indexOf("getScriptPath")>0;
return false;
});
return result;
}
if ( hint && typeof document === "object") {
const script = document.querySelector('script[src="'+hint+'"]');
return script && script.src && script.src.length && script.src;
}
}
console.log("this script is at:",getScriptPath ())
Can't you use location.href or location.host and then append the script name?