change text every time button clicked - javascript

I've searched a lot but can't find an answer...
I have a list of quotes and every time I click the button I want it to go to a new quote.
Can someone please explain what's wrong here and how I should fix it?
<script language="Javascript">
function buttonClickHandler() {
var textField = document.getElementById("textField");
var quotes = new Array();
var nextQuote = 0;
quotes[0] = "Don't be so humble - you are not that great.";
quotes[1] = "Moral indignation is jealousy with a halo.";
quotes[2] = "Glory is fleeting, but obscurity is forever.";
quotes[3] = "The fundamental cause of trouble in the world is that the stupid are cocksure while the intelligent are full of doubt.";
quotes[4] = "Victory goes to the player who makes the next-to-last mistake.";
quotes[5] = "His ignorance is encyclopedic";
quotes[6] = "If a man does his best, what else is there?";
quotes[7] = "Political correctness is tyranny with manners.";
quotes[8] = "You can avoid reality, but you cannot avoid the consequences of avoiding reality.";
quotes[9] = "When one person suffers from a delusion it is called insanity; when many people suffer from a delusion it is called religion.";
nextQuote++;
textField.value = quotes[nextQuote];
}
</script>
I found this code on the internet and when I use this code, it changes the text on every click.
var currentValue = parseInt(textField.value);
// Add one
currentValue++;
// Put it back with the new +1'd value
textField.value = currentValue;
var quotes = new Array();
The code I used for my array is nearly the same but it doesn't change the text per click. Is there something special I need to do for arrays? Help!!

It wont change it because you declare the array and the index inside your handler, so every time you click you get the quote at index 1. Define the index outside the handler (as well as the array) and increment inside the handler:
var quotes = new Array();
var nextQuote = 0;
quotes[0] = "Don't be so humble - you are not that great.";
quotes[1] = "Moral indignation is jealousy with a halo.";
quotes[2] = "Glory is fleeting, but obscurity is forever.";
quotes[3] = "The fundamental cause of trouble in the world is that the stupid are cocksure while the intelligent are full of doubt.";
quotes[4] = "Victory goes to the player who makes the next-to-last mistake.";
quotes[5] = "His ignorance is encyclopedic";
quotes[6] = "If a man does his best, what else is there?";
quotes[7] = "Political correctness is tyranny with manners.";
quotes[8] = "You can avoid reality, but you cannot avoid the consequences of avoiding reality.";
quotes[9] = "When one person suffers from a delusion it is called insanity; when many people suffer from a delusion it is called religion.";
function buttonClickHandler() {
var textField = document.getElementById("textField");
textField.value = [++nextQuote];
}

Because every time the function is called nextQuote is re-set to 0

You're assinging nextQuote to 0 every single time you call the handler.
var nextQuote = 0;
Try doing this instead:
var quotes = new Array();
quotes[0] = "Don't be so humble - you are not that great.";
quotes[1] = "Moral indignation is jealousy with a halo.";
quotes[2] = "Glory is fleeting, but obscurity is forever.";
quotes[3] = "The fundamental cause of trouble in the world is that the stupid are cocksure while the intelligent are full of doubt.";
quotes[4] = "Victory goes to the player who makes the next-to-last mistake.";
quotes[5] = "His ignorance is encyclopedic";
quotes[6] = "If a man does his best, what else is there?";
quotes[7] = "Political correctness is tyranny with manners.";
quotes[8] = "You can avoid reality, but you cannot avoid the consequences of avoiding reality.";
quotes[9] = "When one person suffers from a delusion it is called insanity; when many people suffer from a delusion it is called religion.";
var nextQuote = 0;
var textField = document.getElementById("textField");
function buttonClickHandler() {
if(nextQuote < 9) {
nextQuote++;
} else {
nextQuote = 0;
}
textField.value = quotes[nextQuote];
}

try something like
var nextQuote = Math.floor((Math.random()*9)+1);
instead of your:
var nextQuote =0;
later change the 9 to your array size, and add it after you've declared all the values into your array.

The difference is the code that works gets the value to increment from outside of buttonClickHandler
var currentValue = parseInt(textField.value);
where as you reinitialise it every time buttonClickHandler is called
var nextQuote = 0;
I think it will work if you replace this declaration with
if (window.nextQuote == null) {
window.nextQuote = 0
} else {
window.nextQuote++
}

As previously posted answers have already stated, the problem is caused because nextQuote is defined inside of buttonClickHandler and thus destroyed every time the function finishes executing and is recreated and initialized to 0 every time the function begins.
You seem to be learning JavaScript using some very old tutorials, the following code will show how this could be refactored to a more modern style.
<script language="Javascript">The language attribute of the <script> tag was deprecated a very long time ago. Don't use it. It was replaced by the type attribute, however don't use the type attribute either. Just a plain <script> tag works in all browsers, they all default to JavaScript since it was the only language to ever gain any traction as a client-side scripting language.
<script>
(function (document) { // Use a self-invoking function to keep our variables
// out of the global scope
"use strict"; // Force the browser into strict mode
var nextQuote = 0, // instead of using separate var statements you can use
// a comma to include all of your variable declarations
// in one statement.
/* Since we will be using the textField DOM element a lot, lets cache a
copy in a variable outside of the handler instead of enduring the
overhead of getElementById every time the handler runs,
querying the DOM is slow.
*/
textField = document.getElementById("textField"),
/* Instead of using new Array(), use an array literal. Literals are
shorter and behave in a more predictable way than the Array
constructor. Another benefit to using a literal is that you can
create the array and initialize it's values in one step avoiding
the tedious quotes[0] = "..."; quotes[1] = "..."; pattern of the
original code. Also, if you want to reorder the items in the list
you don't have to renumber them too.
*/
quotes = [
"Don't be so humble - you are not that great.",
"Moral indignation is jealousy with a halo.",
"Glory is fleeting, but obscurity is forever.",
"The fundamental cause of trouble in the world is that the stupid are cocksure while the intelligent are full of doubt.",
"Victory goes to the player who makes the next-to-last mistake.",
"His ignorance is encyclopedic",
"If a man does his best, what else is there?",
"Political correctness is tyranny with manners.",
"You can avoid reality, but you cannot avoid the consequences of avoiding reality.",
// The last item in the list should not have a comma after it, some
// browsers will ignore it but others will throw an error.
"When one person suffers from a delusion it is called insanity; when many people suffer from a delusion it is called religion."
];
function buttonClickHandler() {
nextQuote++;
// roll back to 0 if we reach the end
if (nextQuote >= quotes.length) {
nextQuote = 0;
}
textField.value = quotes[nextQuote];
}
document.getElementById('button').addEventListener("click", buttonClickHandler, false);
}(document)); /* This is the end of the self-invoking function. The document
object is being passed in as an argument. It will be imported
into the self-invoking function as a local variable also named
document. There are a couple of reasons to do this. Having it
aliased as a local variable will make any references to it
quicker since the browser will not have to look any further
up the scope-chain. Also, if this code is minified, the local
variable will be renamed to a shorter (often 1 character long)
name, saving download time, where references to the built-in
global document object would not.
*/
</script>
The self-invoking function that wraps the code is a very common pattern in modern JavaScript, it would be good to become familiar with it.
Using strict mode will help you avoid a number of easy to create bugs.
If you are deploying JavaScript code into the wild you should be minifying it. Having a build process can make this easy by automating it for you. I would recommend Grunt, it has lots of pre-built tasks to make minifying and other common build tasks easy. It can be a bit tricky to set up at first but there are a lot of great articles out there that can make it much easier to understand.

Related

Why does this dynamic programming optimization actually make code slower?

This is from Leetcode problem: Concatenated Words.
Below is a working solution. I added what I thought to be an optimization (see code comment), but it actually slows down the code. If I remove the wrapping if statement, it runs faster.
To me, the optimization helps avoid having to:
call an expensive O(n) substring()
check inside wordsSet
making an unnecessary function call to checkConcatenation
Surely if (!badStartIndices.has(end + 1)) isn't more expensive than all the above, right? Maybe it has something to do with Javascript JIT compilation? V8? Thoughts?
Use the following test input:
// Notice how the second string ends with a 'b'!
const words = [
'a',
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab',
];
// Function in question.
var findAllConcatenatedWordsInADict = function (words) {
console.time();
// 1) put all words in a set
const wordsSet = new Set(words);
let badStartIndices;
// 2) iterate words, recursively check if it's a valid word
const concatenatedWords = [];
function checkConcatenation(word, startIdx = 0, matches = 0) {
if (badStartIndices.has(startIdx)) {
return false;
}
if (startIdx === word.length && matches >= 2) {
concatenatedWords.push(word);
return true;
}
for (let end = startIdx; end < word.length; end++) {
// I ADDED THE IF STATEMENT AS AN OPTIMIZATION. BUT CODE RUNS FASTER WITHOUT IT.
// NOTE: Code is correct with & without if statement.
if (!badStartIndices.has(end + 1)) {
const curWord = word.substring(startIdx, end + 1);
if (wordsSet.has(curWord)) {
if (checkConcatenation(word, end + 1, matches + 1)) {
return true;
}
}
}
}
badStartIndices.add(startIdx);
return false;
}
for (const word of words) {
// reset memo at beginning of each word
badStartIndices = new Set();
checkConcatenation(word);
}
console.timeEnd();
return concatenatedWords;
};
Turns out this depends entirely on the input data, not on JavaScript or V8. (And as of writing this, I don't know what data you used for benchmarking.)
With the example input from the Leetcode page you've linked, badStartIndices never does anything useful (both of the .has checks always return false); so it's fairly obvious that doing this fruitless check twice is a little slower than doing it just once. In that case, the "dynamic programming" mechanism of the solution never kicks in, so the effective behavior degenerates to brute force, which is good enough because the input data is well-behaved. (In fact, deleting badStartIndices entirely would be even faster for such a test case.)
If I construct "evil" input data that actually leads to exponential combinatorial blow-up, i.e. where the badStartIndices.has(...) checks actually have something to do, then adding the early check does have a (small) performance benefit. (And without either of the checks, the computation would take "forever" for such inputs.)
So, taking a step back, this is one more example to illustrate that benchmarking is difficult; in particular, in order to get useful results, care must be taken to select relevant/realistic input data.
If the tests are too simple, developers are likely to not build optimizations that would help (a little or a lot) in high-load situations.
If the tests are too demanding, developers are likely to waste time on overly complicated code that ends up being slower than it could be for its target use case.
And if the code must handle any input with maximum performance, then as the developer you have the extra challenge of avoiding overhead for simple inputs while still scaling well to tough inputs...

Why is my DOM manipulation not working in JavaScript?

I am trying to replace the list of paragraphs with only one random paragraph, but for some reason the JavaScript code will not do the job.
I have tried rearranging the variables after the function has ended, but I can't figure out what's wrong.
This is how my HTML elements begin:
<body>
<div id = "quotes">
<p>“Art is the supreme task and the truly metaphysical activity in this life.”</p>
<p>“Underneath this reality in which we live and have our being, another and altogether different reality lies concealed.”</p>
<p>“We obtain the concept, as we do the form, by overlooking what is individual and actual; whereas nature is acquainted with no forms and no concepts, and likewise with no species, but only with an X which remains inaccessible and undefinable for us.”</p>
<p>“Everything which distinguishes man from the animals depends upon this ability to volatilize perceptual metaphors in a schema, and thus to dissolve an image into a concept.”</p>
<p>“Our destiny exercises its influence over us even when, as yet, we have not learned its nature: it is our future that lays down the law of our today.”</p>
And this is my attempt at DOM manipulation:
"use strict";
const quotes = document.querySelectorAll("p");
const randomize = function() {
let num = (Math.floor(Math.random() * Math.floor(quotes.length)) - 1);
let quote = quotes.item(num).innerHTML;
return quote;
}
let randomQuote = randomize();
let passage = document.getElementById('quotes').innerHTML;
passage = randomQuote;
console.log(randomQuote);
The only way to change the HTML of a node (with innerHTML) is to assign to its innerHTML property, which invokes an internal setter operation. Extracting the innerHTML of a node into a variable and then reassigning that variable won't do anything. (reassigning a variable reference to something else won't ever change anything, by itself.)
So, use
document.getElementById('quotes').innerHTML = randomQuote;
You also need to fix your num random number generator - use Math.floor(Math.random() * quotes.length); to generate a number between 0 and quotes.length - 1, otherwise num will sometimes be -1 (whose index doesn't exist, of course):
"use strict";
const quotes = document.querySelectorAll("p");
const randomize = function() {
const num = Math.floor(Math.random() * quotes.length);
return quotes.item(num).innerHTML;
}
const randomQuote = randomize();
document.getElementById('quotes').innerHTML = randomQuote;
<body>
<div id="quotes">
<p>“Art is the supreme task and the truly metaphysical activity in this life.”</p>
<p>“Underneath this reality in which we live and have our being, another and altogether different reality lies concealed.”</p>
<p>“We obtain the concept, as we do the form, by overlooking what is individual and actual; whereas nature is acquainted with no forms and no concepts, and likewise with no species, but only with an X which remains inaccessible and undefinable
for us.”</p>
<p>“Everything which distinguishes man from the animals depends upon this ability to volatilize perceptual metaphors in a schema, and thus to dissolve an image into a concept.”</p>
<p>“Our destiny exercises its influence over us even when, as yet, we have not learned its nature: it is our future that lays down the law of our today.”</p>
The problem lies here:
let passage = document.getElementById('quotes').innerHTML;
passage = randomQuote;
You should do:
let passage = document.getElementById('quotes');
passage.innerHTML = randomQuote;
Why
In this line:
let passage = document.getElementById('quotes').innerHTML;
You are actually getting the reference of a string from .innerHTML to passage, not the element itself.
Therefore, in the next line:
passage = randomQuote;
You are only replacing the string with a another string, instead of replacing the property value of an element. Because passage is not an element, it is a string.
In your example instead of assigning new quote to the innerHtml, you just change the variable with value for it that doesn't keep reference to the innerHtml anymore, just it's value
Just change this:
let passage = document.getElementById('quotes').innerHTML;
to:
document.getElementById('quotes').innerHTML= randomQuote;
The issue is that
let passage = document.getElementById('quotes').innerHTML;
Sets the value of passage to the instantaneous value of the innerHTML of quotes, it is not a reference (which is not possible in javascript btw).
passage = randomQuote;
Just overwrites the value of passage with the random quote. Instead you should write
document.getElementById('quotes').innerHTML = randomQuote;

Call function saved as string on chosen object

I have something like:
var sFunction = 'my_function("param1", "param2")';
var oMyObject = ...;
And I want to combine it so the result would be equal to:
oMyObject.my_function("param1", "param2");
Would much appreciate any tips.
Remark
As many of you suggested to find a root cause and try not to deal with the problematic input here are some pieces of information about the origins of the "problem".
The sFunction comes from database, hardcoded in one of the columns. It is custom one which should be called on object retrieved basing on other parameters of sFunction's database record.
So being backed up by your comments I will try suggesting changing data model in hope that it is not too late for that. Thank you all for your help.
I am given that as an input, it may come from db or anywhere else. I just have to deal with it in described way.
As Luca noted, you're probably best off solving the problem that brought you to the point of having code in a string that you feel you need to evaluate at runtime. The number of use cases for doing that is very low.
For instance, instead of
sFunction = 'my_function("param1", "param2")';
perhaps you could have
call = {
f: "my_function",
params: ["param1", "param2"]
};
Then it's:
oMyObject[call.f].apply(oMyObject, call.params);
call could even start life as JSON text you parse -- live example:
var json =
'{' +
'"f": "my_function",' +
'"params": ["param1", "param2"]' +
'}';
var call = JSON.parse(json);
var oMyObject = {
my_function: function(p1, p2) {
console.log(p1, p2);
}
};
oMyObject[call.f].apply(oMyObject, call.params);
That's markedly safer than an arbitrary code execution.
You can do this with your sFunction (eval("oMyObject." + sFunction)), but consider:
It lets any arbitrary code in sFunction run.
If User A supplies the code and then you run it on User B's system, you're compromising User B's privacy. (I am not a lawyer, but you could be doing so in a way that violates a country's data protection or privacy laws.)
Now, if you're loading code from a DB and you know that the code in the DB can only be put there by trusted people (for instance, developers on your team, not end users of the system), that's fine, it's largely like running a script file. But there's almost certainly a better way to do it than delivering the code as a string and evaling it.
But if the code comes from "anywhere else", it's not fine; see bullet points above. The setup is fundamentally broken and better options are available. Take that information to your boss, and if necessary to his/her boss, and if necessary his/her boss, until you find someone who can change the requirement.
Here's a string hack that doesn't use eval(), but as I (and others) have said, this is not a good solution. The better solution would be to return the function name and any arguments as a comma delimited string, which would at least make this kind of solution more straight-forward.
var sFunction = 'my_function("param1", "param2")';
// The object would have to already have the function:
var oMyObject = {
my_function: function(x,y){
return x + y;
}
};
// Remove the last ")" and split the remainder into an array at the "("
var funcParts = sFunction.replace(")","").split("(");
// Split the second part (the arguments) into its own array
var funcArgs = funcParts[1].split(",");
// Pass the function name as a string key to the object and then pass the arguments to that
console.log(oMyObject[funcParts[0]](funcArgs[0], funcArgs[1]));
The bigger question is, what ultimately are you trying to accomplish as there is almost always a better approach than this.
To do a dynamic function call you can of course eval as I did in the comments, which is of course a terrible idea. Here is a quick-and-dirty alternative:
const dynamicCallMethod = (obj, s) => {
try {
const fname = s.match(/([$\w]+\(/);
const params = s.match(/("[\w$]+")/g);
return obj[fname](...params);
} catch (e) {
return e;
}
};
Note I still think there's any easier way to do this if you describe the scenario in more detail. The above will fail for any non-ascii characters, for instance.

Can't assign querySelectorAll() to a variable - weird behaviour

I was trying to crawl a very old website for a specific tag, I need to get it by it's for= attribute. So I used this piece of code.
var character = document.querySelectorAll("label[for=char_1]");
For some reason it returns an undefined, but I was using this script for a few days now and it worked like a charm. Here's the fun part. Typing that command in browsers console will result in undefined. But typing this alone:
document.querySelectorAll("label[for=char_1]");
Will return a proper NodeList. Why it won't assign to a variable...?
edit: It seems that deleting var and typing character without it will make it work. It's resolved but I would still love to get an answer to "why is this happening"?
edit2:
for (var i = 0; i < 15; i++) {
var character = document.querySelectorAll("label[for=char_" + i +"]");
console.log(character); // this will return [] from the script.
var color = character[0].children[0].style.color;
}
A simple for loop. All I get is Cannot read property 'children' of undefined. But I can type in the very same command document.querySelectorAll... and it will work in the browser and will return NodeList.
I had it working like this in a very hacky script. It worked.
var character1 = document.querySelectorAll("label[for=char_1]");
var characterColor1 = character1[0].children[0].style.color;
edit3:
var character1 = document.querySelectorAll("label[for=char_1]");
var characterColor1 = character1[0].children[0].style.color;
var character2 = document.querySelectorAll("label[for=char_2]");
var characterColor2 = character2[0].children[0].style.color;
// ...
The above code works without a single problem though. I don't think it's DOM not being ready as this code is also run from Greasemonkey script and it works. The only difference is the for loop.
var x = ""; // returns undefined, because it's a var assignment.
var elements = document.querySelectorAll('div');
That's expected behavior when pasted into the console.
edit: It seems that deleting var and typing character without it will make it work. It's resolved
I'm afraid you're creating a global scope variable now. or perhaps characters is an already defined variable in that scope.
Buhah, as I said in edit 3 "the only difference is the for loop". I was so busy trying to find an answer in the DOM-related things that I made the simplest mistake ever done in programming.
See?
char_1
With...
for(var i = 0...)
0! And I was testing char_1 in the browser instead of char_0. Which - truly - returns [] instead of something useful. Time to go on a holiday break I guess, my brain seems to be there already. :)

Password Strength Meter

I'm trying to create my own JS Password Strength Meter.
It was working before but i didn't like how it worked so I tried using
{score +=10;}
Instead of just:
score++
This is my code:
http://jsfiddle.net/RSq4L/
Best Regards,
Shawn,
Hope someone can help
Multiple issues:
Your passwordStrength() function was not defined in the global scope in the jsFiddle so it wasn't getting called. This is probably an artifact of how you set up the jsFiddle, perhaps not an issue in your real code.
The method of getting the appropriate ratingMsg will not work because you don't have array values for every possible score so many scores will generate an "undefined" ratingMsg.
Your CSS classes are also sparse so there are many score values that they will not match for either and no appropriate CSS class/style will be in effect. If you want a specific class for each rating value, then perhaps you should put the classname in the ratings array so it can be fetched from there along with the ratingsMsg.
For the first issue, in your jsFiddle, you also have to make sure the password processing function is defined in the global scope. The way your jsFiddle is set up, it is not (it's in the onload handler). You can fix this in the jsFiddle by just setting the first drop-down in the upper left to "no wrap (head)".
For the second issue, you are using:
ratingMsg[score]
but, your array is a sparse array not guaranteed to have an entry for most possible scores. You simply can't do it that way because many elements you access will have undefined values which won't give you a meaningful message. For example, if score was 15, you would be accessing ratingMsg[15], but there is no value in that space in the array so you won't get a meaningful rating message.
The solution is to find a different way to select the right message. The simplest way would just be an if/else if/else if statement that would check which range the score is in and set the appropriate msg. There are more elegant table driven ways, but all will involve searching through a data structure to find which two values the current score is between and using that msg.
If you look at this jsFiddle http://jsfiddle.net/jfriend00/dA7XC/, you'll see that your code is getting called, but it only hits values in the array sometimes.
And, here's a rewritten algorithm that finds the appropriate msg no matter what the score show in this fiddle: http://jsfiddle.net/jfriend00/jYcBT/.
It uses a data structure like this:
var ratingMsg = [
0, "Unclassified",
10, "Weak",
20, "Fair",
50, "Better",
60, "Medium",
70, "Good",
90, "Strong"
];
and a for loop like this to get the appropraite ratingMsg:
for (var i = ratingMsg.length - 2 ; i >= 0; i-=2) {
if (score >= ratingMsg[i]) {
msg = ratingMsg[i+1];
break;
}
}
Here you go: http://jsfiddle.net/RSq4L/11/
The first problem is that in your fiddle you have the onLoad option set, so your passwordStrength function is not actually being declared in the global scope. It is being declared inside of the onLoad block that jsFiddle wraps your code with. This causes the page to error out when the keypress handler tries to invoke the function.
You can fix this problem in several different ways:
By explicitly declaring the function as global as per my example above.
By choosing one of jsFiddle's "no wrap" options instead of onLoad.
By dynamically binding your event-handler instead of setting it through the element's onkeydown attribute in the markup.
The second problem is how you are keying your score messages. You have:
var ratingMsg = new Array(0);
ratingMsg[0] = "Unclassified";
ratingMsg[10] = "Weak";
ratingMsg[30] = "Fair";
ratingMsg[50] = "Better";
ratingMsg[60] = "Medium";
ratingMsg[70] = "Good";
ratingMsg[90] = "Strong";
...and you lookup the message by doing ratingMsg[score]. This will only work if the score exactly matches one of your indices. And based upon your math this will not always be the case.
I would suggest doing something like:
ratingMsg = {};
ratingMsg[0] = "Unclassified";
ratingMsg[10] = "Weak";
ratingMsg[30] = "Fair";
ratingMsg[50] = "Better";
ratingMsg[60] = "Medium";
ratingMsg[70] = "Good";
ratingMsg[90] = "Strong";
function closestRating(score) {
var bestKey = 0;
var bestMatch = 100;
for (var key in ratingMsg) {
if (key <= score && score - key < bestMatch) {
bestMatch = score - key;
bestKey = key;
}
}
return ratingMsg[bestKey];
}
On an unrelated note, are you sure you want to be using onkeydown? I think onkeyup would work better.
Your fiddler script had several errors. Here's the corrected one: new script.
You were missing a semicolon here: document.getElementById("passwordDescription").innerHTML = "" + ratingMsg[score] + ""
You forgot to escape '^' on your regular expression
I just wrote this for it:
Jquery Plugin for password strength forcing

Categories