I need to detect and eval the Javascript code contained in a string.
The following code works, but it only evaluates the first <script>...</script> it founds.
function executeJs(html) {
var scriptFragment = "<script(.+?)>(.+?)<\/script>";
match = new RegExp(scriptFragment, "im");
var matches = html.match(match);
if (matches.length >= 2) {
eval(matches[2]);
}
}
I wonder if there is a method that allows me to iterate and execute all Javascript fragments.
The reason it only takes the first one is because you're missing the g flag. Try this:
function executeJs(html) {
var scriptFragment = '<script(.*?)>(.+?)<\/script>';
var re = new RegExp(scriptFragment, 'gim'), match;
while ((match = re.exec(html)) != null) {
eval(match[2]);
}
}
executeJs('<script>alert("hello")</script>abc<script>alert("world")</script>');
Here is some code that does the same thing in a slightly different way. You can pass the string to the function and it will eval all the script tags and return the cleaned source(without script). There is also a slight difference in the way IE handles it, that is handled in the code as well, you may adapt it to your requirements. Also, the evaluated code has the global context. Hope it helps.
function parseScript(_source)
{
var source = _source;
var scripts = new Array();
// Strip out tags
while(source.indexOf("<script") > -1 || source.indexOf("</script") > -1)
{
var s = source.indexOf("<script");
var s_e = source.indexOf(">", s);
var e = source.indexOf("</script", s);
var e_e = source.indexOf(">", e);
// Add to scripts array
scripts.push(source.substring(s_e+1, e));
// Strip from source
source = source.substring(0, s) + source.substring(e_e+1);
}
// Loop through every script collected and eval it
for(var i=0; i<scripts.length; i++)
{
try
{
//eval(scripts[i]);
if(window.execScript)
{
window.execScript(scripts[i]); // IE
}
else
{
window.setTimeout(scripts[i],0); // Changed this from eval() to setTimeout() to get it in Global scope
}
}
catch(ex)
{
// do what you want here when a script fails
alert("Javascript Handler failed interpretation. Even I am wondering why(?)");
}
}
// Return the cleaned source
return source;
}
Blixt should be right...
You may also take a look at prototype's String.evalScripts function.
http://api.prototypejs.org/language/string.html#evalscripts-instance_method
Related
I have a table in a separate HTML file that I am loading with jQuery. I am then defining the variable "aa". I am attempting to use this variable in my JavaScript Function "report(period)". I tried creating a global variable but that didn't help. I am not entire sure I was doing it correctly. I am fairly new to JavaScript and know even less of jQuery. I've gone through other similar posts but it’s very difficult to understand exactly what's happening. Any help would greatly be appreciated.
jQuery
jQuery(function($) {
aa = document.getElementById('part1Table').rows[0].cells[2].innerHTML;
});
Javascript
function report(period) {
x = document.getElementById("tblabiNew").rows[2].cells[1].innerHTML; /*----- for testing use a number instead (example: x = "205-000040-634") ------*/
/*---------------------------------------------------------------------------------------------- Start - Object Removal Control ------------------------------------------------------------------------------------*/
if (x==aa) {
var i = 1; do {
+ i; i++;
var e = document.getElementById (i);
e.style.display = 'none'
} while (i < 15)
/*polebrea21*/
var polebrea = 21;
do {
+ polebrea;
polebrea++;
var e = document.getElementById (polebrea);
e.style.display = 'none'
} while (polebrea < 28)
/*polebrea31*/
var polebrea = 31;
do {
+ polebrea;
polebrea++;
var e = document.getElementById (polebrea);
e.style.display = 'none'
} while (polebrea < 38)
/*regulatory51*/
var regulatory = 51;
do {
+ regulatory;
regulatory++;
var e = document.getElementById (regulatory);
e.style.display = 'none'
} while (regulatory < 64)
/*regulatory51*/
/*regulatory81*/
var regulatory = 81;
do {
+ regulatory;
regulatory++;
var e = document.getElementById (regulatory);
e.style.display = 'none'
} while (regulatory < 94)
};
};
If you want "global" variable you should declare it outside of all functions body. So this should be.
var aa;
jQuery(function($) {
aa = //do something with aa
});
but anything you use without declaring is by default global (mind it work that way only in browsers).
If you want create local variable, add var keyword before it name, like this:
function report(period) {
var x = //...
}
I believe your aa variable is not declared because report function is called before page is ready.
Everything in function given to jQuery() run after DOM is ready, so if I write:
jQuery(function($) { console.log(1); });
console.log(2);
I get "2, 1" and not "1, 2".
You should really learn JavaScript and jQuery if you want to use it. Your report code seems like it can be replaced with one line with jQuery.
If I understand your scenario correctly you are not able to obtain the relevant node because the HTML fetched via ajax has not been injected into the DOM and hence can't be fetched using document.getElementById.
Could you provide the code which fetches the remove HTML and then what is done with it ? That may be helpful to understand the situation.
Anyway, this is something you may want to try:
$.ajax({
method: "GET",
url: "some/remote/url",
success: function(htmlContent) {
aa = $(htmlContent).find('#part1Table')[0].rows[0].cells[2].innerHTML;
// Do some processing
}
})
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?
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.
Given a function, I'm trying to find out the names of the nested functions in it (only one level deep).
A simple regex against toString() worked until I started using functions with comments in them. It turns out that some browsers store parts of the raw source while others reconstruct the source from what's compiled; The output of toString() may contain the original code comments in some browsers. As an aside, here are my findings:
Test subject
function/*post-keyword*/fn/*post-name*/()/*post-parens*/{
/*inside*/
}
document.write(fn.toString());
Results
Browser post-keyword post-name post-parens inside
----------- ------------ --------- ----------- --------
Firefox No No No No
Safari No No No No
Chrome No No Yes Yes
IE Yes Yes Yes Yes
Opera Yes Yes Yes Yes
I'm looking for a cross-browser way of extracting the nested function names from a given function. The solution should be able to extract "fn1" and "fn2" out of the following function:
function someFn() {
/**
* Some comment
*/
function fn1() {
alert("/*This is not a comment, it's a string literal*/");
}
function // keyword
fn2 // name
(x, y) // arguments
{
/*
body
*/
}
var f = function () { // anonymous, ignore
};
}
The solution doesn't have to be pure regex.
Update: You can assume that we're always dealing with valid, properly nested code with all string literals, comments and blocks terminated properly. This is because I'm parsing a function that has already been compiled as a valid function.
Update2: If you're wondering about the motivation behind this: I'm working on a new JavaScript unit testing framework that's called jsUnity. There are several different formats in which you can write tests & test suites. One of them is a function:
function myTests() {
function setUp() {
}
function tearDown() {
}
function testSomething() {
}
function testSomethingElse() {
}
}
Since the functions are hidden inside a closure, there's no way for me invoke them from outside the function. I therefore convert the outer function to a string, extract the function names, append a "now run the given inner function" statement at the bottom and recompile it as a function with new Function(). If the test function have comments in them, it gets tricky to extract the function names and to avoid false positives. Hence I'm soliciting the help of the SO community...
Update3: I've come up with a new solution that doesn't require a lot of semantic fiddling with code. I use the original source itself to probe for first-level functions.
Cosmetic changes and bugfix
The regular expression must read \bfunction\b to avoid false positives!
Functions defined in blocks (e.g. in the bodies of loops) will be ignored if nested does not evaluate to true.
function tokenize(code) {
var code = code.split(/\\./).join(''),
regex = /\bfunction\b|\(|\)|\{|\}|\/\*|\*\/|\/\/|"|'|\n|\s+/mg,
tokens = [],
pos = 0;
for(var matches; matches = regex.exec(code); pos = regex.lastIndex) {
var match = matches[0],
matchStart = regex.lastIndex - match.length;
if(pos < matchStart)
tokens.push(code.substring(pos, matchStart));
tokens.push(match);
}
if(pos < code.length)
tokens.push(code.substring(pos));
return tokens;
}
var separators = {
'/*' : '*/',
'//' : '\n',
'"' : '"',
'\'' : '\''
};
function extractInnerFunctionNames(func, nested) {
var names = [],
tokens = tokenize(func.toString()),
level = 0;
for(var i = 0; i < tokens.length; ++i) {
var token = tokens[i];
switch(token) {
case '{':
++level;
break;
case '}':
--level;
break;
case '/*':
case '//':
case '"':
case '\'':
var sep = separators[token];
while(++i < tokens.length && tokens[i] !== sep);
break;
case 'function':
if(level === 1 || (nested && level)) {
while(++i < tokens.length) {
token = tokens[i];
if(token === '(')
break;
if(/^\s+$/.test(token))
continue;
if(token === '/*' || token === '//') {
var sep = separators[token];
while(++i < tokens.length && tokens[i] !== sep);
continue;
}
names.push(token);
break;
}
}
break;
}
}
return names;
}
The academically correct way to handle this would be creating a lexer and parser for a subset of Javascript (the function definition), generated by a formal grammar (see this link on the subject, for example).
Take a look at JS/CC, for a Javascript parser generator.
Other solutions are just regex hacks, that lead to unmaintainable/unreadable code and probably to hidden parsing errors in particular cases.
As a side note, I'm not sure to understand why you aren't specifying the list of unit test functions in your product in a different way (an array of functions?).
Would it matter if you defined your tests like:
var tests = {
test1: function (){
console.log( "test 1 ran" );
},
test2: function (){
console.log( "test 2 ran" );
},
test3: function (){
console.log( "test 3 ran" );
}
};
Then you could run them as easily as this:
for( var test in tests ){
tests[test]();
}
Which looks much more easier.
You can even carry the tests around in JSON that way.
I like what you're doing with jsUnity. And when I see something I like (and have enough free time ;)), I try to reimplement it in a way which better suits my needs (also known as 'not-invented-here' syndrome).
The result of my efforts is described in this article, the code can be found here.
Feel free to rip-out any parts you like - you can assume the code to be in the public domain.
The trick is to basically generate a probe function that will check if a given name is the name of a nested (first-level) function. The probe function uses the function body of the original function, prefixed with code to check the given name within the scope of the probe function. OK, this can be better explained with the actual code:
function splitFunction(fn) {
var tokens =
/^[\s\r\n]*function[\s\r\n]*([^\(\s\r\n]*?)[\s\r\n]*\([^\)\s\r\n]*\)[\s\r\n]*\{((?:[^}]*\}?)+)\}\s*$/
.exec(fn);
if (!tokens) {
throw "Invalid function.";
}
return {
name: tokens[1],
body: tokens[2]
};
}
var probeOutside = function () {
return eval(
"typeof $fn$ === \"function\""
.split("$fn$")
.join(arguments[0]));
};
function extractFunctions(fn) {
var fnParts = splitFunction(fn);
var probeInside = new Function(
splitFunction(probeOutside).body + fnParts.body);
var tokens;
var fns = [];
var tokenRe = /(\w+)/g;
while ((tokens = tokenRe.exec(fnParts.body))) {
var token = tokens[1];
try {
if (probeInside(token) && !probeOutside(token)) {
fns.push(token);
}
} catch (e) {
// ignore token
}
}
return fns;
}
Runs fine against the following on Firefox, IE, Safari, Opera and Chrome:
function testGlobalFn() {}
function testSuite() {
function testA() {
function testNested() {
}
}
// function testComment() {}
// function testGlobalFn() {}
function // comments
testB /* don't matter */
() // neither does whitespace
{
var s = "function testString() {}";
}
}
document.write(extractFunctions(testSuite));
// writes "testA,testB"
Edit by Christoph, with inline answers by Ates:
Some comments, questions and suggestions:
Is there a reason for checking
typeof $fn$ !== "undefined" && $fn$ instanceof Function
instead of using
typeof $fn$ === "function"
instanceof is less safe than using typeof because it will fail when passing objects between frame boundaries. I know that IE returns wrong typeof information for some built-in functions, but afaik instanceof will fail in these cases as well, so why the more complicated but less safe test?
[AG] There was absolutely no legitimate reason for it. I've changed it to the simpler "typeof === function" as you suggested.
How are you going to prevent the wrongful exclusion of functions for which a function with the same name exists in the outer scope, e.g.
function foo() {}
function TestSuite() {
function foo() {}
}
[AG] I have no idea. Can you think of anything. Which one is better do you think? (a) Wrongful exclusion of a function inside. (b) Wronfgul inclusion of a function outside.
I started to think that the ideal solution will be a combination of your solution and this probing approach; figure out the real function names that are inside the closure and then use probing to collect references to the actual functions (so that they can be directly called from outside).
It might be possible to modify your implementation so that the function's body only has to be eval()'ed once and not once per token, which is rather inefficient. I might try to see what I can come up with when I have some more free time today...
[AG] Note that the entire function body is not eval'd. It's only the bit that's inserted to the top of the body.
[CG] Your right - the function's body only gets parsed once during the creation of probeInside - you did some nice hacking, there ;). I have some free time today, so let's see what I can come up with...
A solution that uses your parsing method to extract the real function names could just use one eval to return an array of references to the actual functions:
return eval("[" + fnList + "]");
[CG] Here is with what I came up. An added bonus is that the outer function stays intact and thus may still act as closure around the inner functions. Just copy the code into a blank page and see if it works - no guarantees on bug-freelessness ;)
<pre><script>
var extractFunctions = (function() {
var level, names;
function tokenize(code) {
var code = code.split(/\\./).join(''),
regex = /\bfunction\b|\(|\)|\{|\}|\/\*|\*\/|\/\/|"|'|\n|\s+|\\/mg,
tokens = [],
pos = 0;
for(var matches; matches = regex.exec(code); pos = regex.lastIndex) {
var match = matches[0],
matchStart = regex.lastIndex - match.length;
if(pos < matchStart)
tokens.push(code.substring(pos, matchStart));
tokens.push(match);
}
if(pos < code.length)
tokens.push(code.substring(pos));
return tokens;
}
function parse(tokens, callback) {
for(var i = 0; i < tokens.length; ++i) {
var j = callback(tokens[i], tokens, i);
if(j === false) break;
else if(typeof j === 'number') i = j;
}
}
function skip(tokens, idx, limiter, escapes) {
while(++idx < tokens.length && tokens[idx] !== limiter)
if(escapes && tokens[idx] === '\\') ++idx;
return idx;
}
function removeDeclaration(token, tokens, idx) {
switch(token) {
case '/*':
return skip(tokens, idx, '*/');
case '//':
return skip(tokens, idx, '\n');
case ')':
tokens.splice(0, idx + 1);
return false;
}
}
function extractTopLevelFunctionNames(token, tokens, idx) {
switch(token) {
case '{':
++level;
return;
case '}':
--level;
return;
case '/*':
return skip(tokens, idx, '*/');
case '//':
return skip(tokens, idx, '\n');
case '"':
case '\'':
return skip(tokens, idx, token, true);
case 'function':
if(level === 1) {
while(++idx < tokens.length) {
token = tokens[idx];
if(token === '(')
return idx;
if(/^\s+$/.test(token))
continue;
if(token === '/*') {
idx = skip(tokens, idx, '*/');
continue;
}
if(token === '//') {
idx = skip(tokens, idx, '\n');
continue;
}
names.push(token);
return idx;
}
}
return;
}
}
function getTopLevelFunctionRefs(func) {
var tokens = tokenize(func.toString());
parse(tokens, removeDeclaration);
names = [], level = 0;
parse(tokens, extractTopLevelFunctionNames);
var code = tokens.join('') + '\nthis._refs = [' +
names.join(',') + '];';
return (new (new Function(code)))._refs;
}
return getTopLevelFunctionRefs;
})();
function testSuite() {
function testA() {
function testNested() {
}
}
// function testComment() {}
// function testGlobalFn() {}
function // comments
testB /* don't matter */
() // neither does whitespace
{
var s = "function testString() {}";
}
}
document.writeln(extractFunctions(testSuite).join('\n---\n'));
</script></pre>
Not as elegant as LISP-macros, but still nice what JAvaScript is capable of ;)
<pre>
<script type="text/javascript">
function someFn() {
/**
* Some comment
*/
function fn1() {
alert("/*This is not a comment, it's a string literal*/");
}
function // keyword
fn2 // name
(x, y) // arguments
{
/*
body
*/
}
function fn3() {
alert("this is the word function in a string literal");
}
var f = function () { // anonymous, ignore
};
}
var s = someFn.toString();
// remove inline comments
s = s.replace(/\/\/.*/g, "");
// compact all whitespace to a single space
s = s.replace(/\s{2,}/g, " ");
// remove all block comments, including those in string literals
s = s.replace(/\/\*.*?\*\//g, "");
document.writeln(s);
// remove string literals to avoid false matches with the keyword 'function'
s = s.replace(/'.*?'/g, "");
s = s.replace(/".*?"/g, "");
document.writeln(s);
// find all the function definitions
var matches = s.match(/function(.*?)\(/g);
for (var ii = 1; ii < matches.length; ++ii) {
// extract the function name
var funcName = matches[ii].replace(/function(.+)\(/, "$1");
// remove any remaining leading or trailing whitespace
funcName = funcName.replace(/\s+$|^\s+/g, "");
if (funcName === '') {
// anonymous function, discard
continue;
}
// output the results
document.writeln('[' + funcName + ']');
}
</script>
</pre>
I'm sure I missed something, but from your requirements in the original question, I think I've met the goal, including getting rid of the possibility of finding the function keyword in string literals.
One last point, I don't see any problem with mangling the string literals in the function blocks. Your requirement was to find the function names, so I didn't bother trying to preserve the function content.
In trying to make my Javascript unobtrusive, I'm using onLoads to add functionality to <input>s and such. With Dojo, this looks something like:
var coolInput = dojo.byId('cool_input');
if(coolInput) {
dojo.addOnLoad(function() {
coolInput.onkeyup = function() { ... };
});
}
Or, approximately equivalently:
dojo.addOnLoad(function() {
dojo.forEach(dojo.query('#cool_input'), function(elt) {
elt.onkeyup = function() { ... };
});
});
Has anyone written an implementation of Ruby's andand so that I could do the following?
dojo.addOnLoad(function() {
// the input's onkeyup is set iff the input exists
dojo.byId('cool_input').andand().onkeyup = function() { ... };
});
or
dojo.byId('cool_input').andand(function(elt) {
// this function gets called with elt = the input iff it exists
dojo.addOnLoad(function() {
elt.onkeyup = function() { ... };
});
});
I don't know Dojo, but shouldn't your first example read
dojo.addOnLoad(function() {
var coolInput = dojo.byId('cool_input');
if(coolInput)
coolInput.onkeyup = function() { ... };
});
Otherwise, you might end up trying to access the element before the DOM has been built.
Back to your question: In JavaScript, I'd implement andand() as
function andand(obj, func, args) {
return obj && func.apply(obj, args || []);
}
Your example could then be written as
dojo.addOnLoad(function() {
andand(dojo.byId('cool_input'), function() {
this.onkeyup = function() { ... };
});
});
which isn't really that much shorter than using the explicit if statement - so why bother?
The exact syntax you want is not possible in JavaScript. The way JavaScript executes would need to change in a pretty fundamental fashion. For example:
var name = getUserById(id).andand().name;
// ^
// |-------------------------------
// if getUserById returns null, execution MUST stop here |
// otherwise, you'll get a "null is not an object" exception
However, JavaScript doesn't work that way. It simply doesn't.
The following line performs almost exactly what you want.
var name = (var user = getUserById(id)) ? user.name : null;
But readability won't scale to larger examples. For example:
// this is what you want to see
var initial = getUserById(id).andand().name.andand()[0];
// this is the best that JavaScript can do
var initial = (var name = (var user = getUserById(id)) ? user.name : null) ? name[0] : null;
And there is the side-effect of those unnecessary variables. I use those variables to avoid the double lookup. The variables are mucking up the context, and if that's a huge deal, you can use anonymous functions:
var name = (function() {return (var user = getUserById(id)) ? user.name : null;})();
Now, the user variable is cleaned-up properly, and everybody's happy. But wow! what a lot of typing! :)
You want dojo.behavior.
dojo.behavior.add({
'#cool_input': {
onKeyUp: function(evt) { ... }
}
});
How about something like this:
function andand(elt, f) {
if (elt)
return f(elt);
return null;
}
Call like this:
andand(dojo.byId('cool_input'), function(elt) {
// this function gets called with elt = the input iff it exists
dojo.addOnLoad(function() {
elt.onkeyup = function() { ... };
});
});
As far as I know there isn't a built-in JavaScript function that has that same functionality. I think the best solution though is to query by class instead of id and use dojo.forEach(...) as you will be guaranteed a non-null element in the forEach closure.
You could always use the JavaScript equivalent:
dojo.byId('cool_input') && dojo.byId('cool_input').whateverYouWantToDo(...);
I've never used dojo, but most javascript frameworks (when dealing with the DOM) return the calling element when a method is called from the element object (poor wording, sorry). So andand() would be implicit.
dojo.addOnLoad(function() {
dojo.byId('cool_input').onkeyup(function(evt) { /*event handler code*/
});
});
For a list:
Array.prototype.andand = function(property, fn) {
if (this.filter(property).length > 0) this.map(fn);
}