Js switch statement construction - javascript

I'm using apps script/ES5. I have:
function returnText(message) {
switch(message.indexOf(mykey)!== -1) {
case true:
var mykey = 'ziptest'
break;
case true:
var mykey = 'setnum'
break;
default:
console.log('default');
}
}
function returnText1() {
returnText('ziptest 19886991201')
}
returnText1();
It defaults to the default option. How can I fix this so that it selects the option where:
var mykey = 'ziptest'

You're misunderstanding how a switch statement works. The code in a JavaScript function is largely step-by-step, in order from beginning to end. The condition in switch () is evaluated once, when that point of the code is reached.
If you want to check for two different strings in message, although you could use a switch, it would be inappropriate and confusing. Instead, use if/else if/else:
function returnText(message) {
if (message.indexOf("ziptest")!== -1) {
return "ziptest";
} else if (message.indexOf("setnum") !== -1) {
return "setnum";
} else {
return "default";
}
}
(Since each branch returns, you don't actually need else there, but...)
In a comment you've said:
right now I have only 3 options but I might want to add 5-6 more later
In that case, use a loop. If you could use ES2015+ features, I'd use find:
const strings = ["ziptest", "setnum", /*...*/];
function returnText(message) {
return strings.find(str => message.indexOf(str) !== -1) || "default";
}
find returns the first entry for which the callback returns a truthy value (and stops looping), or undefined if it runs out of entries without the callback returning a truthy value. So with the above, we check each str returning true if the string exists in message. If find returns undefined, the || "default" kicks in and supplies the default string instead.
But since you can't, you can use some as TheMaster shows, or just a simple loop:
var strings = ["ziptest", "setnum", /*...*/];
function returnText(message) {
for (var i = 0; i < strings.length; ++i) {
var str = strings[i];
if (message.indexOf(str) !== -1) {
return str;
}
}
return "default";
}
Side note: I see you've tagged this google-apps-script, so you're probably stuck with .indexOf(str) !== -1. But I hear they're updating it, so you might have .includes(str) now.

You should probably use arrays, if you have many keys.
var myKeys = ['ziptest', 'setnum'];
function returnText(message) {
var selectKey;
myKeys.some(function(key){
if(message.indexOf(key) !== -1) {
selectKey = key;
return true;
}
})
return selectKey;
}
function returnText1() {
return returnText('ziptest 19886991201')
}
console.log(returnText1());

If you really want to use switch then you can do something like this.
function returnText(message) {
switch (true) {
case message.indexOf('ziptest') > -1:
console.log('ziptest');
break;
case message.indexOf('setnum') > -1:
console.log('setnum');
break;
default:
console.log('default');
}
}
function returnText1() {
returnText('ziptest 19886991201')
}
returnText1();

Related

Is it possible to use switch case for more than one array.indexOf

I have some set of if conditions for a particular array using javascript
if (activity.indexOf("strategy session") != -1) {
$("#FoPStrategySession").show();
}
if (activity.indexOf("sessions") != -1) {
$("#acprojectname").show();
if (supportmodel == "Level") {
$(".accombohide").hide();
$("[title='Test']").val("NA");
$("[title='Test2']").val("NA");
}
}
if (activity.indexOf("virtual") != -1) {
if (supportmodel == "Level") {
$(".lvl3_consult").hide();
$("[title='Test']").val("NA");
$("[title='Test2']").val("NA");
}
}
if (activity.indexOf("Other") != -1) {
$("#acactivityother").show();
}
Is there any other way to efficiently write this code using switch case or any other method?
No need for multiple if() or switch() statements.
You can reduce cyclomatic complexity (now is 7) and end up with a better code. Note that have been refactored some jQuery selectors $('[title="Test"], [title="Test2"]').val('NA'); and using comparison operators === and !== instead of == and != respectively.
"The cyclomatic complexity of a section of source code is the count of the number of linearly independent paths through the source code." -- http://en.wikipedia.org/wiki/Cyclomatic_complexity
Also have been created variables to avoid jQuery searching the DOM multiple times for the same selectors.
Code:
var $foPStrategySession = $('#FoPStrategySession'),
$acprojectname = $('#acprojectname'),
$titleTests = $('[title="Test"], [title="Test2"]'),
$acactivityother = $('#acactivityother'),
$accombohide = $('.accombohide'),
$lvl3_consult = $('.lvl3_consult'),
obj = {
'strategy session': function () {
$foPStrategySession.show();
},
'sessions': function () {
$acprojectname.show();
if (supportmodel === 'Level') {
$accombohide.hide();
$titleTests.val('NA');
}
},
'virtual': function () {
if (supportmodel === 'Level') {
$lvl3_consult.hide();
$titleTests.val('NA');
}
},
'Other': function () {
$acactivityother.show();
}
};
Object.keys(obj).forEach(function (o) {
if (activity.indexOf(o) !== -1) {
obj[o]();
}
});
First of all you shouldn't be querying the DOM all over again:
var strategySession = $("#FoPStrategySession");
var acprojectname = $('#acprojectname');
// and so on..
Later you'll access these DOM elements with their references created above.
Now you can use Array.prototype.forEach to simplify your code with a switch:
activity.forEach(function(act) {
// I suggest you that you lower case each activity
// to avoid further issues...
switch(act.toLowerCase())
case "strategy session":
strategySession.show();
break;
// other cases...
default:
throw Error("Not supported activity");
}
});
Side note: I understand that activity is an array. Otherwise you would be checking if activity is some particular string using equality operators and my answer should need some refactor. Comment out my answer if I was mistaken about activity being an array....

Get element from this array $scope.pricingList= [[Object { amount="1", type="HOURLY"}], [Object { amount="1", type="HOURLY"}]]

So far I have tried this:
if ($scope.flag) {
$scope.partialPricing.push([$scope.p]);
$scope.flag = false;
} else {
for (var i = 0; i < $scope.partialPricing.length; i++) {
console.log("finding object = " + $scope.partialPricing);
if ($scope.partialPricing[i].type != $scope.p.type) {
$scope.partialPricing.push([$scope.p]);
break;
} else {
console.log("Already Given . Please Clear .");
}
}
}
Problem is when it enters into the else condition, it gets
$scope.partialPricing = [Object Object] and also, $scope.partialPricing[i].type = undefined.
My goal is to prevent user from giving same type twice. Here type is Hourly , Monthly , Weekly.
He can set the value only once. Tell me the solution or any other way i can do it?
Your if condition in the loop won't work properly: it will add the element to the list if its type is different from the first element of the list, independently of all the rest of the list.
The easiest is to make a lookup function as follows:
function lookupByType(type) {
for (var i = 0; i < $scope.partialPricing.length; i++) {
if ($scope.partialPricing[i].type == type) {
return true;
}
}
return false;
}
And then use it as follows (no for loop here):
if (lookupByType($scope.p.type)) {
console.log("Already Given . Please Clear .");
} else {
$scope.partialPricing.push($scope.p);
}
About $scope.flag, I assume you're aware that you use it to bypass the verification, you probably have a good reason for this. However, if the goal is only to insert the first element, there's no need for it: the lookup function will always return false if the list so far is empty.
Edit: You also had a type problem: your pricing list was an array of array of objects, and you used it as an array of objects. You probably want to use the latter, so you need to push($scope.p) rather than push([$scope.p]).
You may switch your else part a bit and check for equality, because that is what you need for exiting the loop. Then break and make your next action according of the check.
if ($scope.flag) {
$scope.partialPricing.push([$scope.p]);
$scope.flag = false;
} else {
var found = false;
for (var i = 0; i < $scope.partialPricing.length; i++) {
console.log("finding object = " + $scope.partialPricing);
if ($scope.partialPricing[i].type === $scope.p.type) {
console.log("Already Given . Please Clear .");
found = true;
break;
}
}
if (!found) {
$scope.partialPricing.push([$scope.p]);
}
}
Otherwise, you could use Array#some and perform a check
if ($scope.flag) {
$scope.partialPricing.push([$scope.p]);
$scope.flag = false;
} else {
var found = false;
if ($scope.partialPricing.some(function (price) { return price.type === $scope.p.type; })) {
console.log("Already Given . Please Clear .");
} else {
$scope.partialPricing.push([$scope.p]);
}
}
Why you need to push "$scope.P" into the partialPricing. Any logic related you handled this line?
$scope.partialPricing.push([$scope.p]);

Javascript array indexOf returns undefined

When calling my function checkIss(), issFullArray.indexOf(issToCheck) always returns undefined. I've run a .length, output the contents of issFullArray, I can't figure out why it's not working- the array looks fine to me. As you can see below, I've tried explicitly setting issArray as an array and copying the array returned by my getIssList()
function updateIss() {
var issArray = [];
var currService = current.u_business_service;
var currIss = current.u_is_service;
issArray = getIssList(currService).slice(); //getIssList() returns an arry
if (checkIss(issArray, currIss) === false) {
//do stuff
}
}
function checkIss(issFullArray, issToCheck) {
if (issFullArray.indexOf(issToCheck) < 0) {
return false;
} else {
return true;
}
}
Easiest to just loop through the array and compare each value and return true if there is a match otherwise return false. Not much more code and works for all browsers.
function checkIss(issFullArray, issToCheck) {
for(i=0; i<issFullArray.length; i++) {
if(issFullArray[i]==issToCheck) {
return true;
}
}
return false;
}

Any ways to short-up such chain call?

Is there any ways to short-up such chain call?
if (obj && obj.prop && obj.prop.subProp1 && obj.prop.subProp1.subPropFunc) {
obj.prop.subProp1.subPropFunc();
}
The only alternative I can imagine is try-catch. Any other ideas?
*I really tired of writing these. It's much easier in coffeescript using ?..
This should work given your sample code (haven't tested "all cases", just a copy of your sample):
function propsExist(obj) {
if (!obj) return false;
for (var i = 1; i < arguments.length; i++) {
if (!obj[arguments[i]]) return false;
obj = obj[arguments[i]];
}
return true;
}
if (propsExist(obj, "prop", "subProp1", "subPropFunc")) {
obj.prop.subProp1.subPropFunc();
}
The method propsExist() takes a variable number of arguments, the first of which being the original object you want to check properties/functions on. It will iterate through the list of properties you send to it and check them in-order. If one doesn't exist, it will return false. If it makes it through the whole loop, it validated successfully!
If you always want to call the sub-property's function if it validates, you could also just change the propsExist function to call it instead of returning true (then rename the function to something like callIfValid(obj, ...)
Same idea as the previous post, just a different solution.
function checkChain(variablePath,startingPoint){
var check = startingPoint || window,
parts = variablePath.split("."),
i;
for (i=0;i<parts.length;i++) {
check = check[parts[i]];
if (!check) {
return null;
}
}
return check;
}
var foo = { bar : { cat : { says : function(x){ alert(x); } } } };
var test1 = checkChain("foo.bar.cat.says");
if (test1) {
test1("meow");
}
var test2 = checkChain("foo.bar.cat.bark");
if (test2) {
test2("burp");
}
var test3 = checkChain("cat.says",foo.bar);
if (test3) {
test3("huh?");
}

Extracting nested function names from a JavaScript function

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.

Categories