Reputation: 121
I have multiple if
statements that are being called upon. I want to set it so that if the user cancels all 4 prompts it will prompt them that it's invalid and return them to the beginning of the function? I tried to set it as an if
statement, but could not quite get it to work.
I am kind of new to JavaScript so please bear with me or keep it simple.
// Get references to the #generate element
var generateBtn = document.querySelector("#generate");
const myArrayUpper = Array.from(Array(26)).map((e, i) => i + 65);
const alphabetUpper = myArrayUpper.map((x) => String.fromCharCode(x));
const myArrayLower = Array.from(Array(26)).map((e, i) => i + 97);
const alphabetLower = myArrayLower.map((x) => String.fromCharCode(x));
const arrayNumeric = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
const arraySpecialCharacters = ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')'];
function generatePassword() {
var results = "";
var numberOfCharacters = window.prompt("How many characters would you like your password to contain");
var characterQuantity = parseInt(numberOfCharacters);
if (characterQuantity >= 8 && characterQuantity <= 128) {
var lowerCase = window.confirm("click OK to confirm lowercase letter.");
var upperCase = window.confirm("Click OK to confirm uppercase letter.");
var numeric = window.confirm("Click OK to confirm numeric values");
var specialCharacters = window.confirm("Click OK to confirm special characters");
var okayButton = [];
if (upperCase == true) okayButton.push(alphabetUpper);
if (lowerCase == true) okayButton.push(alphabetLower);
if (numeric == true) okayButton.push(arrayNumeric);
if (specialCharacters == true) okayButton.push(arraySpecialCharacters);
for (var i = 0; i < characterQuantity; i++) {
var storeButton = Math.floor(Math.random() * okayButton.length);
var selectedArray = okayButton[storeButton];
results += selectedArray[Math.floor(Math.random() * selectedArray.length)];
// results += alphabetLower[Math.floor(Math.random() *26)];
// results += arrayNumeric[Math.floor(Math.random() *10)];
// results += arraySpecialCharacters[Math.floor(Math.random() *10)];
}
} else {
window.alert('This is an invalid entry. Select an entry between 8 and 128');
return generatePassword();
}
return results;
}
// challenge make it so that if they hit cancel to many times instead of error have it prompt them to do it again
// Write password to the #password input
function writePassword() {
var password = generatePassword();
var passwordText = document.querySelector("#password");
passwordText.value = password;
}
// Add event listener to generate button
generateBtn.addEventListener("click", writePassword);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Password Generator</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="wrapper">
<header>
<h1>Password Generator</h1>
</header>
<div class="card">
<div class="card-header">
<h2>Generate a Password</h2>
</div>
<div class="card-body">
<textarea readonly id="password" placeholder="Your Secure Password" aria-label="Generated Password"></textarea>
</div>
<div class="card-footer">
<button id="generate" class="btn">Generate Password</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
Upvotes: 0
Views: 145
Reputation: 163262
For instructional purposes, here's a refactoring:
const possibleCharacters = [
...confirm('Numeric?') && '0123456789' || [],
...confirm('Lower case?') && 'abcdefghijklmnopqrstuvwxyz' || [],
...confirm('Upper case?') && 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' || [],
...confirm('Special?') && '!@#$%^&*()' || []
];
alert('Your password:\n' +
[...crypto.getRandomValues(
new Uint32Array(
Number.parseInt(prompt('Character count?'))
)
)].map(i => possibleCharacters[i % possibleCharacters.length]).join('')
);
That's all there is to it!
An explanation:
...confirm('Numeric?') && '0123456789' || []
Let's look at just the boolean portion of this line and see how it looks to the JavaScript interpreter.
<true or false> AND truthy-value-a OR truthy-value-b
That is, confirm()
will return true
or false
. The string '0123456789'
is truthy, just all non-empty strings are. The empty Array is truthy, just as all Objects are.
When evaluating the false
AND true
, the interpreter knows right away that the statement is false
, because the first item is false
. The next item is never evaluated for truthiness. What matters more though is that false
is now effectively the return value of that statement. If the first item were true
and the next item is truthy, that next item is the return value.
So, true && 'some string'
evaluates to 'some string'
. That means that if the user confirms the dialog, the dialog item is true
, and the string is returned.
If the user does not confirm the dialog, it's false
, the string is never evaluated (due to the AND &&
), and we go on to the OR ||
. false
OR true
evaluates to true
. false
OR some-truthy-value evaluates to some-truthy-value. Since the empty array is truthy, it is returned. The reason why we always must return something iterable here will be apparent at this next section.
The ellipsis ...
is the Spread Operator in JavaScript. If we wanted to make an array of arrays...
[
...[1, 2, 3],
...[4, 5, 6]
] // Basically the same as [1, 2, 3, 4, 5, 6]
The cool thing though is that strings are iterable too:
[...'asdf'] // Synonymous with ['a', 's', 'd', 'f']
So above, for each character set, we either iterate over a string of possible characters, or an empty array. That all gets merged into the one big array of possibleCharacters
.
[...crypto.getRandomValues(
new Uint32Array(
Number.parseInt(prompt('Character count?'))
)
)]
Here, we prompt the user for a character count. Using the response from that, we create a new TypedArray to store some large 32-bit unsigned integers. We populate that array using crypto.getRandomValues()
, and the return value of that function is our TypedArray.
Why crypto? It's probably overkill for this use, but it is convenient for returning securely random numbers. Other random number generators are more predictable, in theory and in specific conditions.
So, if we have an array of numbers, why do we need the outer spread [... ]
operator and array? This is just a quick way to get a non-typed regular Array from the TypedArray. That matters because we're about to .map()
the array to our password in the next step.
This one, you've probably seen before. It's pretty simple. For every item in the array, run the callback function and return a new array with the return values of all those function calls.
Here, we're mapping the big random numbers into characters from our possibleCharacters
array. How?...
%
)If you divide a number by another number but only want integer results, what's the remainder? You can find that via modulous.
0 % 2 // 0
1 % 2 // 1
2 % 2 // 0
3 % 2 // 1
4 % 2 // 0
// etc.
So, how does that help us? Well, some of these random numbers are really big, like 2596003863
or 3979143875
. We can't get the 985,031,522nd item of an array with less than 100 items in it. However, with the modulo operator, we can keep the randomness while also keeping it within our array. Think of it like a gameshow wheel where the contestant gives it a really hard pull. The wheel may spin around many times, but it still lands on a random spot.
2616031673 % 72 // 17
3016112995 % 72 // 19
874672118 % 72 // 62
// etc.
Finally, while we'll have an array of characters, we need to shove them all back together as a string. This is where .join()
comes in. Whatever is the first parameter is the string inserted between each item of the array. If you use an empty string like we are here, all the characters just go together.
I hope you (or somebody) finds the explanation of these items helpful!
Upvotes: 2
Reputation: 121
So one way i made it work is just set an if statement for if none of the values are true using this below:
if (upperCase || lowerCase || numeric || specialCharacters) {
for (var i = 0; i < characterQuantity; i++) {
var storeButton = Math.floor(Math.random() * okayButton.length);
var selectedArray = okayButton[storeButton];
results += selectedArray[Math.floor(Math.random() * selectedArray.length)];
}
} else {
window.alert('This is an invalid entry. Select at least ONE to proceed');
return generatePassword();
Upvotes: 0
Reputation: 36
If I understand your question correctly, it should be sufficient to just nest the four ifs in a while loop?
Initialize them as false, and if they all remain false at the end of the user being asked to select one of the options to make them true, then you restart the loop.
if(characterQuantity >= 8 && characterQuantity <= 128) {
var lowerCase = false
var upperCase = false
var numeric = false
var specialCharacters = false
while (!lowerCase && !upperCase && !numeric && !specialCharacters) {
lowerCase = window.confirm("click OK to confirm lowercase letter.");
upperCase = window.confirm("Click OK to confirm uppercase letter.");
numeric = window.confirm("Click OK to confirm numeric values");
specialCharacters = window.confirm("Click OK to confirm special characters");
if (!lowerCase && !upperCase && !numeric && !specialCharacters) window.alert("It is not valid to not select any of these. Try again")
}
Edit: I think you could use an object literal to make the code a bit cleaner, but that's an augmentation really, not strictly necessary to solve your problem.
Upvotes: 1
Reputation: 3075
Your question is a bit hard to follow, but I'm going to try to respond to 1) the title, and then 2) what I think you meant by it based on the code.
return generatePassword();
, you returned the result of calling generatePassword
. However, you just as easily could have returned generatePassword
itself, something that is common practice in a language like javascript, by writing return generatePassword;
. Then, the caller could run the returned function as appropriate. Javascript has first class functions, meaning functions can be treated just like any other variable. The term "method" is generally reserved for functions which are members of a class, so I've stuck with just "function" in this description.if (conditions not met) { return generatePassword(); }
, just as you did in the else
branch of your length check. Recursion can be expensive compared to looping, so again, loops are (very broadly speaking) preferred, but your inclination to simply call generatePassword
again is not incorrect.Upvotes: 1
Reputation: 781068
Put a loop around the code that asks these questions. If they answer confirm at least one of them, break out of the loop.
There's also little need for variables like upperCase
and lowerCase
. Just test the confirm()
call directly.
// Get references to the #generate element
var generateBtn = document.querySelector("#generate");
const myArrayUpper = Array.from(Array(26)).map((e, i) => i + 65);
const alphabetUpper = myArrayUpper.map((x) => String.fromCharCode(x));
const myArrayLower = Array.from(Array(26)).map((e, i) => i + 97);
const alphabetLower = myArrayLower.map((x) => String.fromCharCode(x));
const arrayNumeric = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
const arraySpecialCharacters = ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')'];
function generatePassword() {
var results = "";
var numberOfCharacters = window.prompt("How many characters would you like your password to contain");
var characterQuantity = parseInt(numberOfCharacters);
if (characterQuantity >= 8 && characterQuantity <= 128) {
var okayButton = [];
while (true) {
if (window.confirm("click OK to confirm lowercase letter.")) {
okayButton.push(alphabetLower);
}
if (window.confirm("Click OK to confirm uppercase letter.")) {
okayButton.push(alphabetUpper);
}
if (window.confirm("Click OK to confirm numeric values")) {
okayButton.push(arrayNumeric);
}
if (window.confirm("Click OK to confirm special characters")) {
okayButton.push(arraySpecialCharacters);
}
if (okayButton.length > 0) {
break;
}
alert("You need to confirm at least one kind of character, try again.");
}
for (var i = 0; i < characterQuantity; i++) {
var storeButton = Math.floor(Math.random() * okayButton.length);
var selectedArray = okayButton[storeButton];
results += selectedArray[Math.floor(Math.random() * selectedArray.length)];
// results += alphabetLower[Math.floor(Math.random() *26)];
// results += arrayNumeric[Math.floor(Math.random() *10)];
// results += arraySpecialCharacters[Math.floor(Math.random() *10)];
}
} else {
window.alert('This is an invalid entry. Select an entry between 8 and 128');
return generatePassword();
}
return results;
}
// challenge make it so that if they hit cancel to many times instead of error have it prompt them to do it again
// Write password to the #password input
function writePassword() {
var password = generatePassword();
var passwordText = document.querySelector("#password");
passwordText.value = password;
}
// Add event listener to generate button
generateBtn.addEventListener("click", writePassword);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Password Generator</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="wrapper">
<header>
<h1>Password Generator</h1>
</header>
<div class="card">
<div class="card-header">
<h2>Generate a Password</h2>
</div>
<div class="card-body">
<textarea readonly id="password" placeholder="Your Secure Password" aria-label="Generated Password"></textarea>
</div>
<div class="card-footer">
<button id="generate" class="btn">Generate Password</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
Upvotes: 2