chrome doesn't display innerHTML changes - javascript

I am creating a game of Boggle using an external javascript page to create the arrays. The problem I am having is
Chrome doesn't display the letters without creating a button to display them separately.
As the user enters words, the words are not displaying until the user quits the game.
It works fine on Firefox but I want it to work in Chrome too.
Any suggestions?
function words(x) {
switch (x) {
case 1:
var word = new Array("balte", "table", "hat", "tab", "belt", "lab", "eat", "tea", "ate", "tale", "bale", "let", "bet", "teal", "late", "beat");
break;
case 2:
var word = new Array("atwre", "water", "wet", "wear", "tear", "war", "ret", "rate", "eat", "ate", "tea", "awe", "raw", "rat", "wart", "art", "tar");
break;
case 3:
var word = new Array("dclaen", "can", "cane", "and", "clan", "lane", "lean", "lend", "land", "den", "dean", "dance", "lance", "clean", "deal", "ale", "dale", "candle", "clad");
break;
case 4:
var word = new Array("aepinlar", "air", "airplane", "plane", "plan", "lane", "lean", "pane", "ear", "near", "nap", "nape", "lair", "pen", "pan", "ape", "leap", "ale", "peal", "nap", "rap", "par", "pare", "pale", "are", "rail", "ail", "pail", "nail", "air", "pair", "ran", "pin", "pine", "line", "nip", "rip", "ripe", "lip", "earn", "learn", "ire");
break;
case 5:
var word = new Array("redykboa", "keyboard", "key", "board", "bored", "bore", "bark", "dark", "dork", "oar", "boar", "ark", "dare", "bare", "are", "red", "rod", "road", "bode", "rode", "ode", "bread", "read", "bead", "bred", "break", "drey", "day", "boy", "broke", "rake", "bake", "ear", "dear", "bear", "dye", "dyer", "doer", "oak", "boa", "doe", "okay", "dab", "bade", "ade", "drake", "bard", "yard", "year", "beak", "beard", "bad", "bed", "bay");
break;
}
return word;
}
compWords = new Array();
notAword = new Array();
playWords = new Array();
function displayLetters() {
var num = Math.floor(Math.random() * 5) + 1;
compWords = words(num);
yourWord = compWords[0];
document.getElementById("letters").innerHTML = yourWord;
}
function displayEntries() {
document.getElementById("entries").innerHTML = playWords.toString();
}
function boggle() {
var play = "";
var score = 0;
var flag = 0;
//get player entries
while (play != "Q") {
play = prompt("enter a word or enter Q when done");
playWords.push(play);
if (play != "Q")
//document.getElementById("entries").innerHTML = playWords.toString();
displayEntries();
}
// check winning score and list bad words
var complgth = compWords.length;
var playlgth = (playWords.length - 1);
for (var i = 0; i < playlgth; i++) {
flag = 0;
for (var k = 0; k < complgth; k++) {
if (playWords[i] == compWords[k]) {
score++;
flag = 1;
}
}
if (flag == 0)
notAword.push(playWords[i]);
}
document.getElementById("result").innerHTML = ("Your score is " +
score + ". The following entries " +
"are not valid words: <br />" +
notAword.toString());
}
body {
background-color: #000040;
color: #88ffff;
font-family: Verdana, Arial, sans-serif;
}
#container {
margin-left: auto;
margin-right: auto;
width: 80%;
min-width: 700px;
}
#logo {
text-align: center;
margin: 0;
font-family: Geneva, Arial, Helvetica, sans-serif;
padding-top: 30px;
padding-bottom: 20px;
}
#nav {
float: left;
width: 200px;
padding-top: 10px;
text-align: left;
color: #88FFFF;
font-size: 12px;
}
#nav a {
text-decoration: none;
margin: 15px;
display: block;
color: #88FFFF;
font-size: 12px;
}
#content {
margin-left: 150px;
padding: 30px;
overflow: auto;
border: medium groove #88FFFF;
line-height: 135%;
}
.floatright {
padding-left: 20px;
float: right;
}
.floatleft {
float: left;
padding: 30px 0px 20px;
}
#footer {
font-size: .60em;
font-style: italic;
text-align: center;
border-top: 2px double #000040;
padding-top: 20px;
padding-bottom: 20px;
}
h2 {
text-transform: uppercase;
color: #88ffff;
font-size: 1.2em;
border-bottom: 1px none;
margin-right: 20px;
}
h3 {
color: #88ffff;
font-size: 1.2em;
border-bottom: 1px solid #000000;
margin-right: auto;
text-align: left;
padding-top: 10px;
padding-right: 20px;
padding-bottom: 10px;
padding-left: 20px;
line-height: 120%;
}
.details {
padding-left: 20%;
padding-right: 20%;
}
img {
border: 0;
}
.content {
margin: 20px;
padding: 20px;
height: 3700px;
width: 500px;
}
a {
text-decoration: none;
margin: 15px;
display: block;
color: #88FFFF;
font-size: 12px;
}
a:hover {
color: #000040;
background-color: #88ffff;
}
span {
font-size: 20px;
font-weight: bold;
font-family: "Courier New", Courier, mono;
color: #88ffff;
background-position: center center;
text-align: center;
vertical-align: middle;
}
table {
border-collapse: collapse
}
td {
border: 2px solid #88ffff;
width: 5em;
color: #88ffff;
}
.nobdr {
border: none;
cell-padding: 5px;
}
.OK {
border-radius: 50%;
}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Greg's Gambits | Greg's Game of Boggle</title>
<link href="greg.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="gregBoggle.js"></script>
<script>
</script>
</head>
<body>
<div id="container">
<img src="images/superhero.jpg" width="120" height="120" class="floatleft" />
<h1 align="center"><em>Greg's Game of Boggle</em></h1>
<div style="clear:both;"></div>
<div id="nav">
<p>Home
About
Play a Game
Sign In
Contact Us</p>
</div>
<div id="content">
<p>The object of the game is to create as many words as you can, in a given time limit, from the letters show below. When you are ready to begin, click the Diplay letters button, then Begin game button.</p>
<p><input type="button" value="Display letters" onclick="displayLetters();" /></p>
<p><input type="button" value="Begin the game" onclick="boggle();" /></p>
<h2><br /><br />Letters you can use:<br />
<div id="letters"> </div><br /></h2>
<h2>Your words so far: <br />
<div id="entries"> </div><br /></h2>
<h2>Results:<br />
<div id="result"> </div>
</h2>
</div>
<div id="footer">Copyright © 2013 Greg's Gambits<br />
foulksy#gmail.com
</div>
</div>
</body>
</html>

Browsers aren't required to render DOM changes until the script finishes and returns to the main event loop. Firefox also does this when you call prompt(), but I don't think this is required.
Instead of using a while loop, you should use an input element that the user fills in. You can then use an event listener to read the input and update the DOM. This will work in all browsers.

Related

First time deploying Chrome extension - Uncaught TypeError: Cannot read properties of null (reading 'length')

I am trying to deploy my first javascript application, which is a Chrome extension.
This simply generates random passwords and stores it with the url of current active tab.
App runs fine on local but after deploying it to Chrome, I got this error:
Uncaught TypeError: Cannot read properties of null (reading 'length')
index.js:65 (anonymous function)
I am a beginner, so any kind of criticism about my code is highly appreciated.
Thank you so much.
function render() {
*line65* **if(passwords.length === 0)** {
document.getElementById("saved-passwords-container").style.display= "none";
} else {
document.getElementById("saved-passwords-container").style.display= "unset";
}
let list = ""
**for (let i = 0; i < passwords.length; i++)** {
list += `<div class="saved-password-line"><span>${passwords[i]}</span></br></br><span class="link"><a target='_blank'href='${links[i]}'>${links[i]}</a></span></div>`
}
document.getElementById("passwords-el").innerHTML = list
}
Here is the full index.js file:
var characters = [];
for (var i=32; i<127; i++)
characters.push(String.fromCharCode(i));
for( var i = 0; i < characters.length; i++){
if ( characters[i] === '<') {
characters.splice(i, 1);
i--;
}
}
for( var i = 0; i < characters.length; i++){
if ( characters[i] === '>') {
characters.splice(i, 1);
i--;
}
}
let pw1El = document.getElementById("pw1-el")
let pw1 = ""
let passwords = []
passwords = JSON.parse(localStorage.getItem("savedPasswords"))
let links = []
links = JSON.parse(localStorage.getItem("savedLinks"))
render()
document.getElementById("char-count-el").value = 20
document.getElementById("gen-btn").addEventListener("click", function() {
var charCount = document.getElementById("char-count-el").value
pw1 = ""
for(let i = 0; i < charCount; i++) {
let randomIndex = Math.floor(Math.random() * characters.length)
pw1 += (characters[randomIndex])
}
pw1El.textContent = pw1
})
document.getElementById("save-btn").addEventListener("click", function() {
passwords.push(pw1El.innerText)
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
links.push(tabs[0].url)
})
localStorage.setItem("savedPasswords", JSON.stringify(passwords))
localStorage.setItem("savedLinks", JSON.stringify(links))
render()
})
function render() {
**if(passwords.length === 0)** {
document.getElementById("saved-passwords-container").style.display= "none";
} else {
document.getElementById("saved-passwords-container").style.display= "unset";
}
let list = ""
**for (let i = 0; i < passwords.length; i++)** {
list += `<div class="saved-password-line"><span>${passwords[i]}</span></br></br><span class="link"><a target='_blank'href='${links[i]}'>${links[i]}</a></span></div>`
}
document.getElementById("passwords-el").innerHTML = list
}
document.getElementById("clear-btn").addEventListener("click", function() {
passwords = []
links = []
localStorage.setItem("savedPasswords", JSON.stringify(passwords))
localStorage.setItem("savedLinks", JSON.stringify(links))
render()
})
document.getElementById("copy-btn").addEventListener("click", function() {
var input = document.getElementById("pw1-el").textContent;
navigator.clipboard.writeText(input);
alert("Copied Text: " + input);
})
index.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div class="container">
<h1>Generate a</br>random password</h1>
<p>Never use an unsecure password again.</p>
<hr>
<div>
<label for="char-count-el">Character Count:</label>
<input type="number" id="char-count-el">
<button id="gen-btn"><span>Generate password</span></button>
</div>
<div>
<label>Your Password:</label>
<div class="pw-container">
<span class="password-line" id="pw1-el">...</span>
<button class="side-btn" id="save-btn">SAVE</button>
<button class="side-btn" id="copy-btn">COPY</button>
</div>
</div>
<div id="saved-passwords-container">
<hr>
<label>Saved Passwords:</label>
<div class="pw-container">
<div id="passwords-el">...</div>
<button class="side-btn" id="clear-btn">CLEAR</button>
</div>
</div>
</div>
<script src="index.js"></script>
</body>
</html>
index.css
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
background-color: #ffffff;
color: white;
display: flex;
justify-content: center;
align-items: center;
}
h1::first-line {
color: white;
}
h1 {
color: #00ffaa;
margin-bottom: 5px;
line-height: 1;
}
label {
font-size: 11px;
display: block;
color: #D5D4D8;
margin-top: 10px;
}
input {
height: 38px;
border-radius: 5px;
border: none;
width: 70px;
padding: 0px 10px;
text-align: center;
background-color: #D5D4D8;
margin-right: 20px;
font-size: 14px;
}
.container {
background: #1F2937;
margin: 0;
padding: 10px 30px 40px;
width: 100%;
min-width: 500px;
box-shadow: 0px 10px 30px 10px #2640644b;
display: flex;
flex-direction: column;
}
.pw-container {
display: flex;
border-radius: 5px;
background-color: #3e4f66;
padding: 10px;
margin-top: 10px;
}
.password-line {
color: #00ffaa;
font-size: 16px;
padding: 5px 10px;
margin-top: 0px;
flex-grow: 1;
flex: 1 1 1;
min-width: 0;
word-wrap: break-word;
white-space: pre-wrap;
word-break: break-word;
}
#passwords-el {
padding-right: 30px;
flex-grow: 1;
flex: 1 1 0;
min-width: 0;
word-wrap: break-word;
white-space: pre-wrap;
word-break: break-word;
}
.saved-password-line {
color: #D5D4D8;
font-size: 14px;
padding: 10px 15px;
border-bottom: solid 1px #d5d4d814;
border-radius: 5px;
margin-bottom: 10px;
line-height: 0.9;
}
a {
color: #d5d4d872;
text-decoration: underline;
}
.side-btn {
font-size: 12px;
width: 60px;
border: none;
background: none;
color: #D5D4D8;
padding: 5px 10px;
border-radius: 5px;
justify-self: flex-end;
}
.side-btn:hover {
background-color: #ffffff28 ;
}
#gen-btn {
color: #ffffff;
background: #0EBA80;
text-transform: capitalize;
text-align: center;
width: 200px;
height: 40px;
padding: 10px 10px;
border: none;
border-radius: 5px;
margin-bottom: 10px;
margin-top: 10px;
transition: all 0.5s;
box-shadow: 0px 0px 30px 5px #0eba8135
}
#gen-btn:hover {
box-shadow: 0px 0px 30px 10px #0eba8157
}
#gen-btn span {
cursor: pointer;
display: inline-block;
position: relative;
transition: 0.5s;
}
#gen-btn span:after {
content: '\279c';
position: absolute;
opacity: 0;
top: 0;
right: -20px;
transition: 0.5s;
}
#gen-btn:hover span {
padding-right: 25px;
}
#gen-btn:hover span:after {
opacity: 1;
right: 0;
}
p {
color: #D5D4D8;
margin-top: 0px;
}
hr {
border-width: 1px 0px 0px 0px;
border-color: #95959576;
margin: 15px 0;
}
manifest.json
{
"manifest_version": 3,
"version": "1.0",
"name": "Password Generator",
"action": {
"default_popup": "index.html",
"default_icon": "icon.png"
},
"permissions": [
"tabs"
]
}
I solved it.
I understand that (please correct me if I'm wrong)
if the local storage is empty, it does not return an empty array when parsed.
Apparently, when I do:
passwords = JSON.parse(localStorage.getItem("savedPasswords"))
passwords is no longer an array.
I instead use:
passwords.push(JSON.parse(localStorage.getItem("savedPasswords")))
But that just pushes a nested array inside passwords.
So I added a for loop, and used an if statement to address the initial error:
let locSavedPasswords = localStorage.getItem("savedPasswords")
if(locSavedPasswords !== null) {
for( var i = 0; i < (JSON.parse(locSavedPasswords)).length; i++){
passwords.push(JSON.parse(locSavedPasswords)[i])
}}
Initially, savedPasswords won't exist in localStorage, so localStorage.getItem('savedPasswords') will return null.
You then do JSON.parse(null), which doesn't immediately crash because null is first coerced to a string and becomes 'null' which is then JSON-parsed and turns back to null since the string with contents null is valid JSON.
But you then do .length on it and crash.
The solution is to handle the case where the item is not yet set and handle it like it was a JSON-stringified empty array. You can do so for example using the nullish coalescing operator ??:
let passwords = JSON.parse(localStorage.getItem("savedPasswords") ?? '[]')
Or, you can keep initializing it with [] as you did before but wrap the assignment with the actual value in a condition:
let passwords = []
const json = localStorage.getItem('savedPasswords')
if (json !== null) {
passwords = JSON.parse(json)
}
Personally, what I like to do for structured data in localStorage is something like this, which also handles the case that other things like invalid JSON somehow got stored there (without bricking the application):
let passwords = []
try {
const data = JSON.parse(localStorage.getItem('savedPasswords'))
if (Array.isArray(data)) passwords = data
} catch {}

How to update the 'sortNotes' function with the new input data?

I am doing a note app:
I created in javascript a const 'notes' that assigs an array of objects inside. Each object has a title and description with respective values.
Created a function assigned to a const 'sortNotes' which sorts the objects by ordering alphabetically by his title( a to z).
created a function assigned to a const 'notesOutput' that creates for Each object an element (h5) for the title and a (p) for the description.
created a function assigned to a const 'newNote' that creates a new object in the array with the same properties (title and description)
finally, but not least, created an event listener to the form with submit event. It´s responsible to take the value of the title input and description input when clicked on the button submit.
then I call the function 'newNote' with correct arguments to create a new Object inside the array. -- apparently it works.
Called the function 'notesOutput' to show in the output the new note with title and description -- apparently it works
before, I called the function 'sortNotes' that is responsible to order alphabetically from A to Z the notes. What happens is that doesn´t work as I expected. It doesn't take to count the notes that are already there in the output and the notes that are newly created after so it´s not well organized. I suppose that I have to update something in this function 'sortNotes' responsible to sort() but I can´t figure out what.
const notes = [{
title: 'Bank',
description: 'Save 100€ every month to my account'
}, {
title: 'Next trip',
description: 'Go to spain in the summer'
}, {
title: 'Health',
description: 'Dont´forget to do the exams'
}, {
title: 'Office',
description: 'Buy a better chair and a better table to work'
}]
const sortNotes = function(notes) {
const organize = notes.sort(function(a, b) {
if (a.title < b.title) {
return -1
} else if (a.title > b.title) {
return 1
} else {
return 0
}
})
return organize
}
sortNotes(notes)
const notesOutput = function(notes) {
const ps = notes.forEach(function(note) {
const title = document.createElement('h5')
const description = document.createElement('p')
title.textContent = note.title
description.textContent = note.description
document.querySelector('#p-container').appendChild(title)
document.querySelector('#p-container').appendChild(description)
})
return ps
}
notesOutput(notes)
const newNote = function(titleInput, descriptionInput) {
notes.push({
title: titleInput,
description: descriptionInput
})
}
const form = document.querySelector('#form-submit')
const inputTitle = document.querySelector('#form-input-title')
inputTitle.focus()
form.addEventListener('submit', function(e) {
e.preventDefault()
const newTitle = e.target.elements.titleNote.value
const newDescription = e.target.elements.descriptionNote.value
newNote(newTitle, newDescription)
sortNotes(notes)
notesOutput(notes)
console.log(notes)
e.target.elements.titleNote.value = ''
e.target.elements.descriptionNote.value = ''
inputTitle.focus()
})
* {
font-family: 'Roboto', sans-serif;
color: white;
letter-spacing: .1rem;
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 100%;
}
body {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.container {
background-color: seagreen;
padding: 2rem;
}
.container-p {
padding: 2rem 0;
}
form {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
}
label {
text-transform: uppercase;
font-weight: 600;
letter-spacing: .25rem;
}
input,
textarea {
width: 100%;
padding: .5rem;
margin: 1rem 0;
color: #0d4927;
font-weight: bold;
font-size: 1rem;
}
.container-submit__button {
font-size: 1rem;
font-weight: bold;
text-transform: uppercase;
color: #0d4927;
padding: 1rem 2rem;
border: 2px solid #0d4927;
cursor: pointer;
margin: 1rem 0;
align-self: flex-end;
}
h1 {
font-size: 2rem;
letter-spacing: .3rem;
}
h2 {
font-size: 1.5rem;
font-weight: 300;
}
h5 {
font-size: 1.05rem;
margin: 1rem 0 .8rem 0;
padding: .4rem;
letter-spacing: .12rem;
display: inline-block;
border: 2px solid;
}
<div class="container" id="app-container">
<h1>NOTES APP</h1>
<h2>Take notes and never forget</h2>
<div id="p-container" class="container-p">
</div>
<div class="container-submit" id="app-container-submit">
<form action="" id="form-submit">
<label for="">Title</label>
<input type="text" class="input-title" id="form-input-title" name="titleNote">
<label for="">Description</label>
<textarea name="descriptionNote" id="form-input-description" cols="30" rows="10"></textarea>
<button class="container-submit__button" id="app-button" type="submit">Add Notes</button>
</form>
</div>
</div>
sort sorts the array in place so you do not need a function that returns something to nowhere
Your sort was case sensitive. Remove toLowerCase if you want to make it case sensitive again
Do NOT pass notes in the function. It needs to be a global object
Empty the container before outputting
No need to return stuff that is not used
let notes = [{
title: 'Bank',
description: 'Save 100€ every month to my account'
}, {
title: 'Office',
description: 'Buy a better chair and a better table to work'
}, {
title: 'Health',
description: 'Dont´forget to do the exams'
}, {
title: 'Next trip',
description: 'Go to spain in the summer'
}]
const sortNotes = function(a, b) {
if (a.title.toLowerCase() < b.title.toLowerCase()) {
return -1
} else if (a.title.toLowerCase() > b.title.toLowerCase()) {
return 1
} else {
return 0
}
}
const notesOutput = function() {
document.querySelector('#p-container').innerHTML = "";
notes.sort(sortNotes)
notes.forEach(function(note) {
const title = document.createElement('h5')
const description = document.createElement('p')
title.textContent = note.title
description.textContent = note.description
document.querySelector('#p-container').appendChild(title)
document.querySelector('#p-container').appendChild(description)
})
}
const newNote = function(titleInput, descriptionInput) {
notes.push({
title: titleInput,
description: descriptionInput
})
}
const form = document.querySelector('#form-submit')
const inputTitle = document.querySelector('#form-input-title')
form.addEventListener('submit', function(e) {
e.preventDefault()
const newTitle = e.target.elements.titleNote.value
const newDescription = e.target.elements.descriptionNote.value
newNote(newTitle, newDescription)
notesOutput(notes)
e.target.elements.titleNote.value = ''
e.target.elements.descriptionNote.value = ''
inputTitle.focus()
})
notesOutput()
inputTitle.focus()
* {
font-family: 'Roboto', sans-serif;
color: white;
letter-spacing: .1rem;
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 100%;
}
body {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.container {
background-color: seagreen;
padding: 2rem;
}
.container-p {
padding: 2rem 0;
}
form {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
}
label {
text-transform: uppercase;
font-weight: 600;
letter-spacing: .25rem;
}
input,
textarea {
width: 100%;
padding: .5rem;
margin: 1rem 0;
color: #0d4927;
font-weight: bold;
font-size: 1rem;
}
.container-submit__button {
font-size: 1rem;
font-weight: bold;
text-transform: uppercase;
color: #0d4927;
padding: 1rem 2rem;
border: 2px solid #0d4927;
cursor: pointer;
margin: 1rem 0;
align-self: flex-end;
}
h1 {
font-size: 2rem;
letter-spacing: .3rem;
}
h2 {
font-size: 1.5rem;
font-weight: 300;
}
h5 {
font-size: 1.05rem;
margin: 1rem 0 .8rem 0;
padding: .4rem;
letter-spacing: .12rem;
display: inline-block;
border: 2px solid;
}
<div class="container" id="app-container">
<h1>NOTES APP</h1>
<h2>Take notes and never forget</h2>
<div id="p-container" class="container-p">
</div>
<div class="container-submit" id="app-container-submit">
<form action="" id="form-submit">
<label for="">Title</label>
<input type="text" class="input-title" id="form-input-title" name="titleNote">
<label for="">Description</label>
<textarea name="descriptionNote" id="form-input-description" cols="30" rows="10"></textarea>
<button class="container-submit__button" id="app-button" type="submit">Add Notes</button>
</form>
</div>
</div>

How can i turn a card in this game to its original position IF is wrong?

var cards = [
{
rank: "Queen",
suit: "Hearts",
cardImage: "images/queen-of-hearts.png",
id: 0,
},
{
rank: "Queen",
suit: "Diamonds",
cardImage: "images/queen-of-diamonds.png",
id: 1,
},
{
rank: "King",
suit: "Hearts",
cardImage: "images/king-of-hearts.png",
id: 2,
},
{
rank: "King",
suit: "Diamonds",
cardImage: "images/king-of-diamonds.png",
id: 3
}
];
//1
function createBoard() {
for (var i = 0; i < cards.length; i++) {
var cardElement = document.createElement('img');
// console.log(cardElement);
cardElement.setAttribute('src', 'images/back.png');
cardElement.setAttribute('data-id', i);
document.getElementById('game-board').appendChild(cardElement);
cardElement.addEventListener('click', flipCard);
cardElement.style.width = '210px';
}
}
createBoard();
//2
function flipCard () {
var cardId = this.getAttribute('data-id');
cardsInPlay.push(cards[cardId].rank);
cardsInPlay.push(cards[cardId].id);
this.setAttribute('src', cards[cardId].cardImage);
// CHECK FOR MATCH HERE =>
if (cardsInPlay.length === 2) {
if (cardsInPlay[0] === cardsInPlay[1]) {
alert("You found a match!");
}
else {
alert("Sorry, try again.");
console.log(cardsInPlay);
cardsInPlay[0].setAttribute('src', 'images/back.png'); // this doesnt work
cardsInPlay[1].setAttribute('src', 'images/back.png'); // this doesnt work
}
}
}
var cardsInPlay = [];
body{
text-align: center;
margin: 0;
}
h1 {
font-family: "Raleway", sans-serif;
font-weight: 400;
color: #0d2c40;
font-size: 45px;
letter-spacing: 1px;
margin: 0;
color: white;
}
p {
font-family: "Droid Serif", serif;
line-height: 26px;
font-size: 18px;
}
a {
font-family: raleway;
text-decoration: none;
color: #F15B31;
letter-spacing: 1px;
font-weight: 600;
font-size: 18px;
}
h2 {
font-family: raleway;
font-size: 20px;
color: #0d2c40;
letter-spacing: 1px;
font-weight: 600;
}
header {
background-color: #F15B31;
padding: 30px 20px 30px 20px;
}
main {
width: 850px;
margin: 35px auto
}
a {
margin: 0 20px;
color: white;
}
nav a:hover {
border-bottom: 2px solid white;
}
nav {
background-color: #00A6B3;
padding: 20px 0;
}
img {
margin: 40px 8px 0 8px;
}
footer {
text-transform: uppercase;
padding: 0 20px;
background-color: #0D2C40;
color: white;
letter-spacing: .08em;
font-weight: 500;
}
.copyright {
float: left;
}
.message {
float: right;
}
.clearfix:after {
visibility: hidden;
display: block;
content: " ";
clear: both;
height: 0;
font-size: 0;
}
.name {
color: #F15B31;
font-weight: 700;
}
#game-board{
width: 1200px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="css/style.css" rel="stylesheet" type="text/css">
<title>Memory card game</title>
</head>
<body>
<header>
<h1>Memory Game</h1>
</header>
<nav>
<p>INSTRUCTIONS GAME</p>
</nav>
<main>
<h2>INSTRUCTIONS</h2>
<p>Concentration, also known as Match Match, Memory, Pelmanism, Shinkei-suijaku, Pexeso or simply Pairs, is a card game in which all of the cards are laid face down on a surface and two cards are flipped face up over each turn. The object of the game is to turn over pairs of matching cards.</p>
<div id="game-board" class="board clearfix"></div>
</main>
<footer>
<div class="clearfix">
<p class="copyright">Copyright 2017</p>
<p class="message">Created with ♥ by <span class="name">GA</span></p>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
I want to know how I can turn back to its original position BOTH cards that didn't match. If there is a match, there is an alert saying congrats you win, OTHERWISE try again, but i want that two cards to return to its original position if didnt match. BUT ONLY ONE CARD TURN BACK TO ITS ORIGINAL POSITION(the one with the this , but I thought the this refers to both) The card images are not in here. Can someone help with this please?
var cards = [
{
rank: "Queen",
suit: "Hearts",
cardImage: "images/queen-of-hearts.png",
id: 0,
},
{
rank: "Queen",
suit: "Diamonds",
cardImage: "images/queen-of-diamonds.png",
id: 1,
},
{
rank: "King",
suit: "Hearts",
cardImage: "images/king-of-hearts.png",
id: 2,
},
{
rank: "King",
suit: "Diamonds",
cardImage: "images/king-of-diamonds.png",
id: 3
}
];
//1
function createBoard() {
for (var i = 0; i < cards.length; i++) {
var cardElement = document.createElement('img');
// console.log(cardElement);
cardElement.setAttribute('src', 'images/back.png');
cardElement.setAttribute('data-id', i);
document.getElementById('game-board').appendChild(cardElement);
cardElement.addEventListener('click', flipCard);
cardElement.style.width = '210px';
}
}
createBoard();
//2
function flipCard () {
var cardId = this.getAttribute('data-id');
cardsInPlay.push(cards[cardId].rank);
cardsInPlay.push(cards[cardId].id);
this.setAttribute('src', cards[cardId].cardImage);
// CHECK FOR MATCH HERE =>
if (cardsInPlay.length === 2) {
if (cardsInPlay[0] === cardsInPlay[1]) {
alert("You found a match!");
}
else {
alert("Sorry, try again.");
console.log(cardsInPlay);
cardsInPlay[0].setAttribute('src', 'images/back.png'); // this doesnt work
cardsInPlay[1].setAttribute('src', 'images/back.png'); // this doesnt work
}
}
}
var cardsInPlay = [];
body{
text-align: center;
margin: 0;
}
h1 {
font-family: "Raleway", sans-serif;
font-weight: 400;
color: #0d2c40;
font-size: 45px;
letter-spacing: 1px;
margin: 0;
color: white;
}
p {
font-family: "Droid Serif", serif;
line-height: 26px;
font-size: 18px;
}
a {
font-family: raleway;
text-decoration: none;
color: #F15B31;
letter-spacing: 1px;
font-weight: 600;
font-size: 18px;
}
h2 {
font-family: raleway;
font-size: 20px;
color: #0d2c40;
letter-spacing: 1px;
font-weight: 600;
}
header {
background-color: #F15B31;
padding: 30px 20px 30px 20px;
}
main {
width: 850px;
margin: 35px auto
}
a {
margin: 0 20px;
color: white;
}
nav a:hover {
border-bottom: 2px solid white;
}
nav {
background-color: #00A6B3;
padding: 20px 0;
}
img {
margin: 40px 8px 0 8px;
}
footer {
text-transform: uppercase;
padding: 0 20px;
background-color: #0D2C40;
color: white;
letter-spacing: .08em;
font-weight: 500;
}
.copyright {
float: left;
}
.message {
float: right;
}
.clearfix:after {
visibility: hidden;
display: block;
content: " ";
clear: both;
height: 0;
font-size: 0;
}
.name {
color: #F15B31;
font-weight: 700;
}
#game-board{
width: 1200px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="css/style.css" rel="stylesheet" type="text/css">
<title>Memory card game</title>
</head>
<body>
<header>
<h1>Memory Game</h1>
</header>
<nav>
<p>INSTRUCTIONS GAME</p>
</nav>
<main>
<h2>INSTRUCTIONS</h2>
<p>Concentration, also known as Match Match, Memory, Pelmanism, Shinkei-suijaku, Pexeso or simply Pairs, is a card game in which all of the cards are laid face down on a surface and two cards are flipped face up over each turn. The object of the game is to turn over pairs of matching cards.</p>
<div id="game-board" class="board clearfix"></div>
</main>
<footer>
<div class="clearfix">
<p class="copyright">Copyright 2017</p>
<p class="message">Created with ♥ by <span class="name">GA</span></p>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
If you are trying to flip both cards back, this code only flips the last card in play "cardsInPlay[1]":
this.setAttribute('src', 'images/back.png');
What you want is to flip both "cardsInPlay[0]" and "cardsInPlay[1]" so maybe somethign like this:
else {
alert("Sorry, try again.");
console.log(cardsInPlay);
cardsInPlay[0].setAttribute('src', 'images/back.png');
cardsInPlay[1].setAttribute('src', 'images/back.png');
}
EDIT: your problem with this is about closure in javascript. I recommend you use IIFE (for EcmaScript5) or let keyword (for EcmaScript6). Read more here.
Instead of doing this:
function createBoard() {
for (var i = 0; i < cards.length; i++) {
var cardElement = document.createElement('img');
[..]
cardElement.addEventListener('click', flipCard); << 'this' will refer to last cardElement at the end of the loop
Do this:
function createBoard() {
for (var i = 0; i < cards.length; i++) {
var cardElement = document.createElement('img');
[..]
cardElement.addEventListener('click', (function(x) {return function() {flipCard(x)}})(i)); // 'i' is immediately evaluated to the correct value
cards[i].element = cardElement; // Keep association with DOM here
Now you can flip cards back easily.
function flipCard (i) {
cardsInPlay.push(i);
// Flip played card
cards[i].element.setAttribute('src', cards[i].cardImage);
if (cardsInPlay.length === 1)
return; // First card: no game resolution yet
// Second card: give user 1s to see it flipped before flipping back
setTimeout(function(){
if (cards[cardsInPlay[0]].rank === cards[cardsInPlay[1]].rank)
alert("You found a match!");
else
alert("Sorry, try again.");
cardsInPlay.forEach(function(i) {
cards[i].element.setAttribute('src', 'images/back.png');
});
cardsInPlay.length = 0;
}, 1000);
}
The below snippet is modified to achieve the required functionality! can you please check?
var cards = [
{
rank: "Queen",
suit: "Hearts",
cardImage: "http://via.placeholder.com/350x150?text=QueenHeartsfront"
},
{
rank: "Queen",
suit: "Diamonds",
cardImage: "http://via.placeholder.com/350x150?text=QueenDiamondsfront"
},
{
rank: "King",
suit: "Hearts",
cardImage: "http://via.placeholder.com/350x150?text=KingHeartsfront"
},
{
rank: "King",
suit: "Diamonds",
cardImage: "http://via.placeholder.com/350x150?text=KingDiamondsfront"
}
];
//1 CREATE BOARD
function createBoard() {
for (var i = 0; i < cards.length; i++) {
var cardElement = document.createElement('img');
// console.log(cardElement);
cardElement.setAttribute('src', 'http://via.placeholder.com/350x150?text=back');
cardElement.setAttribute('data-id', i);
document.getElementById('game-board').appendChild(cardElement);
cardElement.addEventListener('click', flipCard);
cardElement.style.width = '210px';
}
}
createBoard();
var prev = "";
//2 FLIPCARD
function flipCard () {
var cardId = this.getAttribute('data-id');
cardsInPlay.push(cards[cardId].rank);
this.setAttribute('src', cards[cardId].cardImage);
console.log(cardsInPlay[0]);
console.log(cardsInPlay[1]);
if (cardsInPlay.length === 2) {
if (cardsInPlay[0] === cardsInPlay[1]) {
alert("You found a match!");
cardsInPlay = [];
}
else {
alert("Sorry, try again.");
cardsInPlay = [];
// cardsInPlay.pop();
// cardsInPlay.pop();
// console.log(cardsInPlay);
try{
prev.setAttribute('src', 'http://via.placeholder.com/350x150?text=back');
}catch(e){}
this.setAttribute('src', 'http://via.placeholder.com/350x150?text=back');
}
}
prev = this;
}
var cardsInPlay = [];
body{
text-align: center;
margin: 0;
}
h1 {
font-family: "Raleway", sans-serif;
font-weight: 400;
color: #0d2c40;
font-size: 45px;
letter-spacing: 1px;
margin: 0;
color: white;
}
p {
font-family: "Droid Serif", serif;
line-height: 26px;
font-size: 18px;
}
a {
font-family: raleway;
text-decoration: none;
color: #F15B31;
letter-spacing: 1px;
font-weight: 600;
font-size: 18px;
}
h2 {
font-family: raleway;
font-size: 20px;
color: #0d2c40;
letter-spacing: 1px;
font-weight: 600;
}
header {
background-color: #F15B31;
padding: 30px 20px 30px 20px;
}
main {
width: 850px;
margin: 35px auto
}
a {
margin: 0 20px;
color: white;
}
nav a:hover {
border-bottom: 2px solid white;
}
nav {
background-color: #00A6B3;
padding: 20px 0;
}
img {
margin: 40px 8px 0 8px;
}
footer {
text-transform: uppercase;
padding: 0 20px;
background-color: #0D2C40;
color: white;
letter-spacing: .08em;
font-weight: 500;
}
.copyright {
float: left;
}
.message {
float: right;
}
.clearfix:after {
visibility: hidden;
display: block;
content: " ";
clear: both;
height: 0;
font-size: 0;
}
.name {
color: #F15B31;
font-weight: 700;
}
#game-board{
width: 1200px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="css/style.css" rel="stylesheet" type="text/css">
<title>Memory card game</title>
</head>
<body>
<header>
<h1>Memory Game</h1>
</header>
<nav>
<p>INSTRUCTIONS GAME</p>
</nav>
<main>
<h2>INSTRUCTIONS</h2>
<p>Concentration, also known as Match Match, Memory, Pelmanism, Shinkei-suijaku, Pexeso or simply Pairs, is a card game in which all of the cards are laid face down on a surface and two cards are flipped face up over each turn. The object of the game is to turn over pairs of matching cards.</p>
<div id="game-board" class="board clearfix"></div>
</main>
<footer>
<div class="clearfix">
<p class="copyright">Copyright 2017</p>
<p class="message">Created with ♥ by <span class="name">GA</span></p>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

How can I change color efficiently for right/wrong characters in a typing speed test?

I was doing an exercise involving a typing speed test. I added a color palette that makes the whole text green if the character matches the test text and red if it does not match.
If I wanted individual characters to change though, all the ways I can think of going about this seem quite tedious. I've tried creating individual span tags every time a character is entered and assigning them to the array value of the text string but haven't been able to get it to work. Is there a more efficient way to do this or is my current method the "best" way to go about it?
I would much more prefer a general path to solve this as opposed to someone just writing out a complete solution so I can write it out on my own please.
const testWrapper = document.querySelector(".test-wrapper");
const testArea = document.querySelector("#test-area");
const originText = document.querySelector("#origin-text p").innerHTML;
const resetButton = document.querySelector("#reset");
const theTimer = document.querySelector(".timer");
var interval;
var timer = [0,0,0,0];
var timerRunning = false;
// Add leading zero to numbers 9 or below (purely for aesthetics):
function leadingZero(time) {
if (time <= 9) {
time = "0" + time;
}
return time;
}
// Run a standard minute/second/hundredths timer:
function runTimer() {
let currentTime = leadingZero(timer[0]) + ":" + leadingZero(timer[1]) + ":" + leadingZero(timer[2]);
theTimer.innerHTML = currentTime;
timer[3]++;
timer[0] = Math.floor((timer[3]/100)/60);
timer[1] = Math.floor((timer[3]/100) - (timer[0] * 60));
timer[2] = Math.floor(timer[3] - (timer[1] * 100) - (timer[0] * 6000));
}
// Match the text entered with the provided text on the page:
function spellCheck() {
let textEntered = testArea.value;
let originTextMatch = originText.substring(0,textEntered.length);
if (textEntered == originText) {
testWrapper.style.borderColor = "#429890";
clearInterval(interval);
} else {
if (textEntered == originTextMatch) {
testWrapper.style.borderColor = "#65ccf3";
testArea.style.color = "#00B400";
} else {
testWrapper.style.borderColor = "#e95d0f";
testArea.style.color = "#FF0000"
}
}
}
// Start the timer:
function start() {
let textEnteredLength = testArea.value.length;
if(textEnteredLength === 0 && !timerRunning) {
timerRunning = true;
interval = setInterval(runTimer, 10);
}
}
// Reset everything:
function reset(){
timer = [0,0,0,0];
theTimer.innerHTML = "00:00:00";
clearInterval(interval);
testArea.value = "";
timerRunning = false;
console.log("The reset button has been pressed.");
}
// Event listeners for keyboard input and the reset button:
testArea.addEventListener("keypress", start, false);
testArea.addEventListener("keyup", spellCheck, false);
resetButton.addEventListener("click", reset, false);
/*--------------------------------------------------------------
Typography
--------------------------------------------------------------*/
body,
button,
input,
select,
textarea {
font-family: 'Source Sans Pro', 'Helvetica', Arial, sans-serif;
font-size: 18px;
line-height: 1.5;
}
h1,
h2,
h3,
h4,
h5,
h6 {
clear: both;
}
p {
margin-bottom: 1.5em;
}
b,
strong {
font-weight: bold;
}
dfn,
cite,
em,
i {
font-style: italic;
}
blockquote {
margin: 0 1.5em;
}
address {
margin: 0 0 1.5em;
}
pre {
background: #eee;
font-family: "Courier 10 Pitch", Courier, monospace;
font-size: 15px;
font-size: 1.5rem;
line-height: 1.6;
margin-bottom: 1.6em;
max-width: 100%;
overflow: auto;
padding: 1.6em;
}
code,
kbd,
tt,
var {
font: 15px Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace;
}
abbr,
acronym {
border-bottom: 1px dotted #666;
cursor: help;
}
mark,
ins {
background: #fff9c0;
text-decoration: none;
}
sup,
sub {
font-size: 75%;
height: 0;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
bottom: 1ex;
}
sub {
top: .5ex;
}
small {
font-size: 75%;
}
big {
font-size: 125%;
}
/*--------------------------------------------------------------
Layout
--------------------------------------------------------------*/
body {
margin: 0;
padding: 0;
}
.masthead {
padding: 1em 2em;
background-color: #0D1B2E;
color: white;
}
.masthead h1 {
text-align: center;
}
.intro {
padding: 2em 2em;
color: #ffffff;
background: #429890;
}
.intro p,
.test-area {
margin: 0 auto;
max-width: 550px;
}
.test-area {
margin-bottom: 4em;
padding: 0 2em;
}
.test-wrapper {
border: 10px solid grey;
border-radius: 10px;
}
#origin-text {
margin: 1em 0;
padding: 1em 1em 0;
background-color: #ededed;
}
#origin-text p {
margin: 0;
padding-bottom: 1em;
}
.test-wrapper {
display: flex;
}
.test-wrapper textarea {
flex: 1;
}
.meta {
margin-top: 1em;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.timer {
font-size: 3em;
font-weight: bold;
}
#reset {
padding: .5em 1em;
font-size: 1.2em;
font-weight: bold;
color: #E95D0F;
background: white ;
border: 10px solid #E95D0F;
}
#reset:hover {
color: white;
background-color: #E95D0F;
}
<header class="masthead">
<h1>Test Your Typing Speed</h1>
</header>
<main class="main">
<article class="intro">
<p>This is a typing test. Your goal is to duplicate the provided text, EXACTLY, in the field below. The timer starts when you start typing, and only stops when you match this text exactly. Good Luck!</p>
</article><!-- .intro -->
<section class="test-area">
<div id="origin-text">
<p>The text to test.</p>
</div><!-- #origin-text -->
<div class="test-wrapper">
<textarea id="test-area" name="textarea" rows="6" placeholder="The clock starts when you start typing."></textarea>
</div><!-- .test-wrapper -->
<div class="meta">
<section id="clock">
<div class="timer">00:00:00</div>
</section>
<button id="reset">Start over</button>
</div><!-- .meta -->
</section><!-- .test-area -->
</main>
If efficiency is your question, I would say to use an MVVM framework such as KnockoutJS or Angular, then it's really easy to determine differences and keeps the code real clean.
I'd split the text to copy into two, side by side:
1. Text typed | 2. Text to copy
With a separate box to type into.
You would subtract what you type upon key press, probably bassed on string length, from 2.text to copy, and put it into 1.text typed.
Use a calculated method to determine if it matched, and set the class on 1.text typed accordingly.

Trying to hide notice for 30 days with the following

I'm trying to hide a notification bar I built after a user clicks x on the following codepen for 30 days based on cookies. I can't seem to figure out how to do this. https://codepen.io/Danskii/pen/aWpoRP
HTML:
<div id="top-site-message-wrapper">
<div id="top-site-message">
Members: you can reduce paper consumption by choosing to receive your membership package by email:
<a href="http://www.oct.ca/" id="top-site-message-CTA">
Yes Please</a>
<button id="top-site-message-hide">
x
</button>
</div>
</div>
CSS:
#top-site-message-wrapper {
background-color: #068edb;
padding: 30px 13px 30px 30px;
border-radius: 3px;
max-width: 100%;
font-size: 16px;
font-style: normal;
font-weight: 600
}
#top-site-message {
color: white;
text-align: center;
}
#top-site-message-CTA {
width: 10%;
color: white;
text-decoration: none;
background: #043d86;
padding: 10px;
border-radius: 3px;
}
#top-site-message-hide {
float: right;
border: none;
color: white;
background-color: blue;
border-radius: 3px;
}
div.yay {
display: none;
}
button.yay {display: none;
}
JS:
// Selects the FIRST occurance of <button>;
var button = document.querySelector("button");
var element = document.querySelector("div");
button.addEventListener("click", function() {
element.classList.toggle("yay");
button.classList.toggle("yay");
});
// Begin script portion for cookies
function TopMessage(){
days=30;
myDate = new Date();
myDate.setTime(myDate.getTime()+(days*24*60*60*1000));
document.cookie = 'TopMessage=Hidden; expires=' + myDate.toGMTString();
}
var cookie = document.cookie.split(';')
.map(function(x){ return x.trim().split('='); })
.filter(function(x){ return x[0]==='TopMessage'; })
.pop();
if(cookie && cookie[1]==='Accepted') {
$("div.yay").hide();
$("button.yay").hide();
}
$('.top-site-message-hide').on('click', function(){
TopMessage();
return false;
});

Categories