Sorry if it's a dumb question, currently making a tic tac toe game, I made the user able to click the board which are buttons. When clicked upon, it will place the players marker("X" or "O").
Problem: Once a player has pressed it, I do not want that textContent to change at all. Is there a way to achieve that result?
const playRound = (e) => {
let playerInput = e.currentTarget.dataset.number
placeMarker(playerInput, getActivePlayer().marker);
e.currentTarget.textContent = getActivePlayer().marker;
_switchPlayerTurn();
printNewRound();
console.log(`${board}`);
console.log(typeof board);
console.log(GameBoard.getBoard());
}
//Event Listener
const ScreenController = (function ScreenController() {
const {
playRound
} = Game;
const board = document.querySelectorAll('.square');
board.forEach(element => {
element.addEventListener('click', playRound);
})
})();
Codepen:https://codepen.io/jimmyjimenez2400/pen/ZEjvrPq
I did try to look up of how I can make it stay permanent with no luck.
I did try to use innerHTML but knowing it can cause issues for security. I just avoid it.
Feeling that I probably have to make an if statement, where "If textContent changed, then somehow lock it". I'm expecting the textContent to not change on second click on the element.
I think setting the once option to true is the most concise:
element.addEventListener('click', playRound, { once: true });
Although it is not supported by IE: https://caniuse.com/once-event-listener
docs: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#parameters
Example:
let count = 0;
const playRound = (e) => {
const letter = count++ % 2 == 0 ? "O" : "X";
e.target.textContent = letter;
console.log(`Turn ${count} complete`);
}
const board = document.querySelectorAll('.square');
board.forEach(element => {
element.addEventListener('click', playRound, { once: true });
})
.row {
display: flex
}
.square {
display: flex;
cursor: pointer;
width: 40px;
height: 40px;
border: 1px solid black;
align-items: center;
justify-content: center;
}
div.as-console-wrapper {
max-height: 66px;
}
<div class="row">
<span class="square"></span>
<span class="square"></span>
<span class="square"></span>
</div>
<div class="row">
<span class="square"></span>
<span class="square"></span>
<span class="square"></span>
</div>
<div class="row">
<span class="square"></span>
<span class="square"></span>
<span class="square"></span>
</div>
As suggested in the comments, here is a slightly less concise method that supports IE. It does the exact same thing as above, removes the event listener at the end of the callback function.
let count = 0;
const playRound = (e) => {
const letter = count++ % 2 == 0 ? "O" : "X";
e.target.textContent = letter;
console.log(`Turn ${count} complete`);
}
const board = document.querySelectorAll('.square');
board.forEach(element => {
const callback = (e) => {
playRound(e);
element.removeEventListener('click', callback);
}
element.addEventListener('click', callback);
})
.row {
display: flex
}
.square {
display: flex;
cursor: pointer;
width: 40px;
height: 40px;
border: 1px solid black;
align-items: center;
justify-content: center;
}
div.as-console-wrapper {
max-height: 66px;
}
<div class="row">
<span class="square"></span>
<span class="square"></span>
<span class="square"></span>
</div>
<div class="row">
<span class="square"></span>
<span class="square"></span>
<span class="square"></span>
</div>
<div class="row">
<span class="square"></span>
<span class="square"></span>
<span class="square"></span>
</div>
Simply add an if clause right at the beginning of your event listener function, like
const playRound = (e) => {
if (e.currentTarget.textContent) return;
...
}
This will effectively "lock" any already filled out cell. An empty cell will have a .textContent of "" which in JavaScript will be seen as falsy.
Ok, if the initial content is numeric, then you should of course check differently, like:
if ("XO".includes(e.currentTarget.textContent)) return;
Related
I'm currently doing a Javascript challenge on Scrimba that requires you to recreate a Basketball scoreboard. I've gotten the design down but i'm having trouble with increment buttons to add either 1,2, or 3 points to either teams score. Each team's scoreboard has 3 buttons underneath that can add 1,2, or 3 points. Originally i was just going to write 6 functions, 3 for each team that would function based on which increment button you select for which team. I figured i could probably just write the three separate increment functions and find a way to pass in an argument to direct which team was getting the points. This worked except that the functions all target a 'points' variable so they end up incrementing off of each other when you add points to the opposite team.
Here is the HTML
<div class="container">
<div class="column">
<h3 class="title">HOME</h3>
<h2 class="score" id="home-score">0</h2>
<div>
<button class="increment-btn" onclick="add1Point('home-score')">+1</button>
<button class="increment-btn" onclick="add2Points('home-score')">+2</button>
<button class="increment-btn" onclick="add3Points('home-score')">+3</button>
</div>
</div>
<div class="column">
<h3 class="title">GUEST</h3>
<h2 class="score" id="guest-score">0</h2>
<div>
<button class="increment-btn" onclick="add1Point('guest-score')">+1</button>
<button class="increment-btn" onclick="add2Points('guest-score')">+2</button>
<button class="increment-btn" onclick="add3Points('guest-score')">+3</button>
</div>
And here is the JS
let points = 0
function add1Point(idValue){
let teamId = document.getElementById(idValue)
points += 1
teamId.textContent = points
}
function add2Points(idValue){
let teamId = document.getElementById(idValue)
points += 2
teamId.textContent = points
}
function add3Points(idValue){
let teamId = document.getElementById(idValue)
points += 3
teamId.textContent = points
}
I know i need to find a way to have two separate point variables for each team but I'm not sure how i can point the individual functions to a specific variable base on which teams button is selected. Not without creating a whole new function specifically for that variable. If possible i would like a solution with the most basic vanilla JS possible, I know there are more complex ways to solve this but im only so far with my learning. Thanks in advance!
use closures
function score(points = 0) {
return function(value) {
points += value;
return points;
}
}
const $homeScore = document.getElementById("home-score");
const $guestScore = document.getElementById("guest-score");
const homeScore = score();
const guestScore = score();
const $homeButtons = document.querySelectorAll("#home-buttons button");
const $guestButtons = document.querySelectorAll("#guest-buttons button");
for(let i = 0; i < $homeButtons.length; i++) {
$homeButtons[i].addEventListener("click", () => {
$homeScore.innerText = homeScore(i + 1);
});
}
for(let i = 0; i < $guestButtons.length; i++) {
$guestButtons[i].addEventListener("click", () => {
$guestScore.innerText = guestScore(i + 1);
});
}
.container {
display: flex;
justify-content: space-between;
background-color: black;
color: white;
font-family: Courier, Courier New, monospace;
padding: 2px 5px;
}
.container .column .score {
border: 1px solid white;
border-radius: 2px;
padding: 2px 5px;
text-align: center;
}
<div class="container">
<div class="column">
<h3 class="title">HOME</h3>
<h2 class="score" id="home-score">0</h2>
<div id="home-buttons">
<button class="increment-btn">+1</button>
<button class="increment-btn">+2</button>
<button class="increment-btn">+3</button>
</div>
</div>
<div class="column">
<h3 class="title">GUEST</h3>
<h2 class="score" id="guest-score">0</h2>
<div id="guest-buttons">
<button class="increment-btn">+1</button>
<button class="increment-btn">+2</button>
<button class="increment-btn">+3</button>
</div>
</div>
</div>
As far as I understand, you'd probably need 2 individual variables to hold the value for each team, here's an example for add 1 point
let guest = 0;
let home = 0;
function add1Point(idValue){
let teamId = document.getElementById(idValue)
if (idValue === 'guest-score') { //Assume your element has name to tell them apart
guest += 1;
teamId.textContent = guest;
} else {
home += 1;
teamId.textContent = home;
}
}
In the other hand, you should make your method reusable and flexible a little like this
function addPoints(idValue, point) {
let teamId = document.getElementById(idValue)
if (idValue === 'guest-score') { //Assume your element has name to tell them apart
guest += point;
teamId.textContent = guest;
} else {
home += point;
teamId.textContent = home;
}
}
then your code will look cleaner
<button class="increment-btn" onclick="addPoints('guest-score', 1)">+1</button>
<button class="increment-btn" onclick="addPoints('guest-score', 2)">+2</button>
<button class="increment-btn" onclick="addPoints('guest-score', 3)">+3</button>
You can simplify it to one function only:
var homeScore = 0;
var guestScore = 0;
var homeScoreEl = document.getElementById('home-score');
var guestScoreEl = document.getElementById('guest-score');
function addPoints(isHome, points = 1) {
window[isHome ? 'homeScore' : 'guestScore'] += points
window[isHome ? 'homeScoreEl' : 'guestScoreEl'].textContent = window[isHome ? 'homeScore' : 'guestScore']
}
.container {
display: flex;
justify-content: space-between;
background-color: black;
color: white;
font-family: Courier, Courier New, monospace;
padding: 2px 5px;
}
.container .column .score {
border: 1px solid white;
border-radius: 2px;
padding: 2px 5px;
text-align: center;
}
<div class="container">
<div class="column">
<h3 class="title">HOME</h3>
<h2 class="score" id="home-score">0</h2>
<div>
<button class="increment-btn" onclick="addPoints(true)">+1</button>
<button class="increment-btn" onclick="addPoints(true, 2)">+2</button>
<button class="increment-btn" onclick="addPoints(true, 3)">+3</button>
</div>
</div>
<div class="column">
<h3 class="title">GUEST</h3>
<h2 class="score" id="guest-score">0</h2>
<div>
<button class="increment-btn" onclick="addPoints(false)">+1</button>
<button class="increment-btn" onclick="addPoints(false, 2)">+2</button>
<button class="increment-btn" onclick="addPoints(false, 3)">+3</button>
</div>
I am making a javascript shopping cart. When you click on an item, the contents of that item are appended on the DOM via javascript. Having some difficulty with my code, specifically the sections where I bolded with '** **'
For my 'dropDown-amount' class, I have a number as the value but i cant seem to access that value later on in the code (see dropDown-price).
Also I have a For statement on my if/else statement that tries to loop through each index in 'items' array and to essentially count how many times one specific item is inside the list (so I can update quantity of x item on DOM) but having trouble getting everything to work. I know what I have to do to solve this problem and I know all 3 of these issues are linked together, just don't know what is exactly causing this to fail.
//dropdown menu hidden
const cartDropdown = document.querySelector('.cart-dropDown-items');
//every single + symbol
const addToCartButtons = document.querySelectorAll('.addToCart');
//price of item
const foodPrices = document.querySelectorAll('.selection-row-title');
//name of item
const foodNames = document.querySelectorAll('.selection-row-foodName');
//weight of item
const foodWeights = document.querySelectorAll('.selection-row-weight');
const items = [];
let total = 0;
for (let i = 0; i < addToCartButtons.length; i++) {
addToCartButtons[i].addEventListener('click', function() {
const newItem = document.createElement('div');
newItem.className = 'dropDown-item';
let amountItems = document.querySelector('.amount-items');
newItem.innerHTML =
`<div class='dropDown-title dropDown-info'>
${foodNames[i].innerHTML}
</div>
<div class='dropDown-amount dropDown-info'>
**<p class='amount-items'>${1}</p>**
</div>
<div class='dropDown-price dropDown-info'>
**${Number(foodPrices[i].innerHTML.substring(1)) * Number(amountItems.textContent)}**
</div>`;
console.log(newItem)
// if item currently exists in array, just update amount in checkout and increase count++
if (items.includes(addToCartButtons[i].value)) {
items.push(addToCartButtons[i].value); **
for (let i = 0; i < items.length; i++) {
if (items[i].includes(addToCartButtons[i].value)) {
Number(amountItems.innerHTML) + 1;
}
} **
}
// if items does not exist in array, update dom with new item UI and count = 1 by default
else {
items.push(addToCartButtons[i].value);
cartDropdown.appendChild(newItem);
}
console.log(items)
})
}
.cart-dropDown-items {}
.dropDown-title {}
.dropDown-item {
display: flex;
align-items: center;
text-align: center;
background-color: orange;
margin: 3px;
padding: 4px;
}
.dropDown-info {}
.dropDown-title {
width: 40%;
}
.dropDown-amount {
width: 30%;
text-align: center;
}
.dropDown-amount p {
border: 1px solid black;
width: 35%;
padding: 8px;
margin: 0 auto;
background-color: white;
}
.dropDown-price {
width: 30%;
}
<!--cart dropDown-->
<div class='cart-dropDown'>
<div class='cart-dropDown-header'>
<p>My Carts</p>
<p>Personal Cart</p>
<p class='cart-dropDown-close'>Close</p>
</div>
<div class='cart-dropDown-items'>
<!--
<div class='dropDown-item'>
<div class='dropDown-title dropDown-info'>Mixed bell pepper, 6 ct</div>
<div class='dropDown-amount dropDown-info'>1</div>
<div class='dropDown-price dropDown-info'>$9.84</div>
</div>
next unique item...
-->
</div>
<div class='cart-dropDown-checkout'>
<div class='cart-dropDown-checkout1'>
<p>Go to Checkout</p>
</div>
<div class='cart-dropDown-checkout2'>
<p>$0</p>
</div>
</div>
</div>
</div>
I mean is it possible to connect an existing button with a newly created button that will work as the existing button?
Reference site: https://www.ninetypercent.com/products/sleeveless-gather-maxi-dress-c5?color=red
Please paste the below code to the browser console after opening the above link:
let title = document.querySelector(".product-details__name").textContent;
let price;
let oldPrice = document.querySelector(".product-details__old-price");
if (oldPrice) {
price = oldPrice.nextSibling.textContent.trim();
} else {
price = document.querySelector(".js-product-price").textContent;
}
let div = document.createElement("div");
let path = document.querySelector("header");
div.innerHTML = `
<div
id="sticky-nav"
style="position: fixed;
top:110px;
display: none;
padding: 0 10px;
justify-content: space-between;
align-items: center;
background-color:#e4e4e4;
width: 100vw;"
class="sticky-wrapper"
>
<div
style="width:80%; display: flex; justify-content: space-between; align-items:center"
class="sticky-text-wrapper"
>
<p style="margin: 0;" class="sticky-text">${title}</p>
<p style="margin: 0;" class="sticky-price">${price}</p>
</div>
<span style="width: 18%; display: flex; justify-content: space-around;" class="button"></span>
</div>`;
path.appendChild(div);
const sourceElement = document.querySelector(".js-product-details-submit-wrapper button");
const destination = document.querySelector(".button");
const copy = sourceElement.cloneNode(true);
destination.appendChild(copy);
window.addEventListener("scroll", () => {
const scrollPos = window.scrollY;
if (scrollPos > 1500) {
document.getElementById("sticky-nav").style.display = "flex";
} else {
document.getElementById("sticky-nav").style.display = "none";
}
});
I want to connect the sticky-nav button to the existing "Add to Bag" button and the functionality of both button will be the same. The same messaging as in control will appear on the PDP(Product details page) and basket icon quantity will increase. How can I do that?
Within the click-handler for the one button you could trigger the other, like so (names are made up of course):
stickyNavBtn.onclick = () => addToBagBtn.click();
You can create a function (in JS) and call it from both the original button (addToBagBtn in your case) and newly created button (stickyNavBtn in your case).
Like:-
/*JavaScript*/
function doSomething() {
alert("Replace with whatever task you like!");
}
var button = document.createElement("button");
button.innerText = "Newly Created Button";
button.onclick = function() {
doSomething();
}
document.getElementById("container").appendChild(button);
<!--HTML-->
<button id="button1" onclick="doSomething()">Original Button</button>
<br><br>
<div id="container">
<!--New button will be created here from JS-->
</div>
First mouse click gives me the expected 'x' marker but the next mouse click results in the alert ("Player o: tile has already been selected) even though the grid-item is blank. What am I doing wrong?
In the HTML body, I have created a 3x3 grid, 9 gridItems in total.
$(document).ready(function(){
let grid = $("#grid-container")
let gridItem = $(".grid-item")
function select(){
for (i = 0; i < gridItem.length; i++){
gridItem[i].addEventListener("click", function(){
if ($(this).html() == ""){
$(this).html("x");
nextSelect();
} else {alert ("Player X: tile has already been selected"); select()}
})
}
}
function nextSelect(){
for (i = 0; i < gridItem.length; i++){
gridItem[i].addEventListener("click", function(){
if ($(this).html() == ""){
$(this).html("o");
select();
} else {alert ("Player o: tile has already been selected"); nextSelect()}
})
}
}
select()
});
The addEventListener should be executed only once. Your problem is that you're adding multiple event handlers. A possible version in the snippet bellow.
The idea is that you only need one function and attach it to click event only once per cell, which is done with jQuery .click(callback). To manage the turns you can use a boolean variable (isTurnOfPlayerOne).
$(document).ready(function() {
let isTurnOfPlayerOne = true;
$(".grid-item").click(handleClick);
function handleClick() {
if ($(this).html() == " ") {
let symbol = (isTurnOfPlayerOne ? "x" : "o");
$(this).html(symbol);
} else {
alert("Tile has already been selected");
}
isTurnOfPlayerOne = !isTurnOfPlayerOne;
}
});
.grid-item {
width: 40px;
height: 40px;
display: inline-block;
border: solid 1px #000;
text-align: center;
margin-bottom: 3px;
line-height: 30px;
font-size: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="grid-container">
<div class="grid-item"> </div>
<div class="grid-item"> </div>
<div class="grid-item"> </div>
<br/>
<div class="grid-item"> </div>
<div class="grid-item"> </div>
<div class="grid-item"> </div>
<br/>
<div class="grid-item"> </div>
<div class="grid-item"> </div>
<div class="grid-item"> </div>
</div>
I need to animate the change of these 3 characters with the data of the array, when I click this blue button on the right.
This is the HTML code I have written so far.
<div class="cards">
<div id="card1" class="card">L</div>
<div id="card2" class="card">A</div>
<div id="card3" class="card">X</div>
<a class="btn btn-flat shuffle" href="#" onclick="travelSurprise();">
</a>
</div>
Javascript code is here.
function travelSurprise() {
let cities = ["NYC", "PTH", "SDN", "KTY", "PRT"];
let card1 = document.getElementById("card1");
let card2 = document.getElementById("card2");
let card3 = document.getElementById("card3");
for (let i = 0; i < cities.length; i++) {
setInterval(function shuffle() {
card1.innerHTML = cities[i].charAt(0);
card2.innerHTML = cities[i].charAt(1);
card3.innerHTML = cities[i].charAt(2);
}, 500);
}
}
I need to change these three letters when I click the button. Animation should be like the 3 letters will change to all the elements of the array for 3,4 seconds and stop at a random position.
Try using setTimeout() instead, where the second argument is the 500ms time multiplied by an index of every array item. For the shuffle, use Fisher-Yates algorithm as described here and just do it every time the a is clicked.
function shuffle(array) {
// shuffle your array code
return array;
}
function travelSurprise(event) {
let cities = ["NYC", "PTH", "SDN", "KTY", "PRT"];
let card1 = document.getElementById("card1");
let card2 = document.getElementById("card2");
let card3 = document.getElementById("card3");
shuffle(cities);
cities.forEach((city, index) => {
setTimeout(() => {
card1.innerHTML = city.charAt(0);
card1.innerHTML = city.charAt(1);
card2.innerHTML = city.charAt(2);
}, (index * 500));
});
}
.cards > div {
display: inline-block;
padding: 2rem;
background: lightblue;
border-radius: .3rem;
}
a {
display: inline-block;
background: #f2f2f2;
width: 1rem;
height: 1rem;
}
<div class="cards">
<div id="card1" class="card">L</div>
<div id="card2" class="card">A</div>
<div id="card3" class="card">X</div>
<a class="btn btn-flat shuffle" href="#" onclick="travelSurprise();">
</a>
</div>