I try to make a simple like/dislike function to my page. The image changing is working but the counter not and I do not know why. Any idea how to make it workable?
By the way I have read a bunch of questions about like/dislike system with JS but its not really works for me.
const imageChange = document.querySelector('.likeClassQ')
var countL = 0;
var buttonLike = document.getElementById("likeButton");
var displayLike = document.getElementById("likes");
buttonLike.addEventListener('click', () => {
imageChange.classList.toggle('likeClassFilled')
})
buttonLike.onclick = function() {
if (buttonLike.classList == 'likeClass') {
countL++;
buttonLike.classList.add = "likeClassFilled";
} else if (buttonLike.classList == "likeClassFilled") {
countL--;
buttonLike.classList.add = "likeClass";
}
displayLike.innerHTML = countL;
}
.likeClass {
background-color: red;
background-repeat: no-repeat;
background-size: contain;
padding: 10px;
border: none;
}
.likeClassFilled {
background-color: green;
}
<span><span id="likes">0</span> Likes</span><br>
<button id="likeButton" class="likeClass likeClassQ">Like</button>
There is no need to assign a function to onclick and use addEventListener. Just use one and stick to it.
Your CSS classes are all over the place. Use one for the general styling and another one for your state. Or better yet, use the data attribute if the element or maybe even a stylized checkbox for that. Mixing CSS classes and business logic is a slippery slope.
classList has methods like toggle, add and includes, but you have to use those fields as methods and not as simple fields. Also, you can not use the comparison operator (==) with objects. You would only use that on simple values like strings or numbers. You execute functions/methods by writing brackets after the method/function name and passing any parameters in those. When you use the assignment operator (=), you are not calling anything.
Your logic about saving the state and deriving the current state is flawed. I changed it to toggle a class on each click. Hence you will not find any classes being added or removed within the condition.
const imageChange = document.querySelector('.likeClassQ')
var countL = 0;
var buttonLike = document.getElementById("likeButton");
var displayLike = document.getElementById("likes");
buttonLike.onclick = function() {
if (buttonLike.classList.contains('likeClassFilled')) {
countL--;
} else {
countL++;
}
buttonLike.classList.toggle("likeClassFilled");
displayLike.innerHTML = countL;
}
.likeClass {
background-color: red;
background-repeat: no-repeat;
background-size: contain;
padding: 10px;
border: none;
}
.likeClassFilled {
background-color: green;
}
<span><span id="likes">0</span> Likes</span><br>
<button id="likeButton" class="likeClass likeClassQ">Like</button>
Related
Quick question here, I encountered this problem today while practicing some JS. I wanted to create a basic prototype to loop through a "div" background-color array on click, but I realized that assigning the element property to a variable (instead of using the event target) impedes me to change the actual values.
This is the JS code:
let colors = ["blue", "yellow", "orange", "red"]
let n = 1;
document.querySelectorAll('div').forEach(occurence => {
occurence.addEventListener('click', (e) => {
let classes = e.target.className;
classes = colors[n];
n++;
console.log(classes);
if (n >= 4) {n = 0;}
});
});
So, changing the actual e.target.className works just fine, but trying to change the assigned "classes" variable does nothing. I feel like this may be a matter of specificity, or JS being unable to access the actual property values, or some akin beginner mistake.
e.target.className passes by value when you have let classes = e.target.className, so classes contains a copy of its data. Changing classes just changes the copy, rather than what's stored in e.target.classname.
Actually, you are not changing the value of e.target.className. What you do, is assigning the value of e.target.className to the variable/let-binding classes. To assign one of the color values to the className property, the assignment has to be the other way around:
e.target.className = colors[n];
let classes = e.target.className will assign the current string value of className to classes. And while you can assign a new colour value to classes that won't assign the new colour value to the className property of the element. For that you want to explicitly assign it: e.target.className = colors[i].
You may also want to remove the need to add a event listener to all the elements. Event delegation allows you to add one listener to a parent element which captures events from its child elements as they "bubble up" the DOM.
Here's an over-wrought example:
const colors = ['blue', 'yellow', 'orange', 'red'];
// Cache the elements first, and add a listener to
// the container
const counter = document.querySelector('.index');
const container = document.querySelector('.container');
container.addEventListener('click', handleClick);
let count = 0;
function handleClick(e) {
// Check to see if the element that was clicked
// was a div element
if (e.target.matches('.container div')) {
// Update the counter element, the `className` value,
// and the `textContent of the "box", and then update
// the count value
counter.textContent = `Color index: ${count}`;
e.target.className = colors[count];
e.target.textContent = colors[count];
count = count < colors.length - 1 ? ++count : 0;
}
}
.container { display: grid; gap: 0.4em; grid-template-columns: repeat(2, 50px); }
.container div { display: flex; justify-content: center; align-items: center; height: 50px; width: 50px; border: 1px solid lightgray; }
.container div:hover { cursor: pointer; border: 1px solid darkgray; }
.blue { background-color: lightblue; }
.yellow { background-color: #ffff00; }
.orange { background-color: darkorange; }
.red { background-color: red; }
.index { margin-top: 0.5em; }
<div class="container">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div class="index">Color index:</div>
It's a to do list and here's the JS I'm stuck into -
function taskCheck() {
let taskCheckboxImg = this.style.backgroundImage;
if(taskCheckboxImg=="url('img/uncheck.png')") {
taskCheckboxImg="url('img/check.png')";
} else {
taskCheckboxImg="url('img/uncheck.png')";
}
}
I want to toggle between check and uncheck by changing the background image when clicked but the if statement doesn't seem to work. Perhaps it doesn't change the background image property in the 4th line.
I could have done this with a regular checkbox but it doesn't seem to have editable properties in CSS. Help!
You are assigning the value to a variable, instead you need to change the value of the CSS property after each condition like
this.style.backgroundImage = "url('img/check.png')";
Also, you are using this, which refers to the current object, so make sure you have the this context available from wherever you call taskCheck() function
You function after the change should look like below
function taskCheck() {
let taskCheckboxImg = this.style.backgroundImage;
if(taskCheckboxImg=="url('img/uncheck.png')") {
this.style.backgroundImage="url('img/check.png')";
} else {
this.style.backgroundImage="url('img/uncheck.png')";
}
}
const myDiv = document.querySelector('div')
myDiv.addEventListener('click', taskCheck)
function taskCheck(e) {
let taskCheckboxImg = e.target.style.backgroundImage;
if(taskCheckboxImg=='url("https://picsum.photos/id/200/200/200")') {
// you have to change css property like this
e.target.style.backgroundImage='url("https://picsum.photos/id/300/200/200")';
} else {
console.log(taskCheckboxImg)
e.target.style.backgroundImage='url("https://picsum.photos/id/200/200/200")';
}
}
div{
width: 200px;
height: 200px;
background-size: cover;
}
<div style="background-image:url('https://picsum.photos/id/200/200/200')"></div>
This might be a very simple oversight of something in my part, but so far I have a very simple project, that divides the screen into three parts, the first 2 parts are within a container with display: flex
and they are both given the same flex value of 1. In my JS code, when a specific boolean variable is set to false, I want one of those two parts to be hidden, and the other one to occupy its place.
This works as expected if I set 'flex: 0' in CSS, but not when I do it in JS.
// Init on page load
window.addEventListener("load", function() {
var gameContainer = document.getElementById("game-container");
if (!false) { //this is to be changed later on to check for a boolean value
gameContainer.style.flex = "0"; //this should hide the right part, but it does not
}
});
#whole-game {
display: flex;
}
#story-container {
background-color: black;
height: 80vh;
flex: 1;
}
#game-container {
background-color: blue;
height: 80vh;
flex: 1;
}
#settings-container {
background-color: rgb(83, 50, 8);
width: 100vw;
height: 20vh;
}
<body>
<div id="whole-game">
<div id="story-container"></div>
<div id="game-container"></div>
</div>
<div id="settings-container"></div>
</body>
This is how my screen looks on running the code
This is how I want it to look
As I see on your code, you have declared the "gameManager" variable as array not object. So, if you want to execute the "trial" function, you need first to access the object index in the array, then the function:
var gameManager = [{
},
{trial: function (){
var gameContainer = document.getElementById("game-container");
if(!false){ //this is to be changed later on to check for a boolean value
gameContainer.style.flex = "0"; //this should hide the right part, but it does not
}},
}]
// Init on page load
window.addEventListener("load", gameManager[1].trial);
Of course you gonna change index [1] to the actual index of the object.
Maybe it's because of the zero being a string?
Try making it gameContainer.style.flex = 0;
So this the code I'm doing
<a onclick="setStyleSheet('css/blue.css')" href="#">Switch</a>
<a onclick="setStyleSheet('css/red.css')" href="#">Switch</a>
On click it'll switch to blue theme.
But I want to keep the button same.
Instead of using 2 buttons. I just want one Switch button to change to blue, then if I click that button again, it'll change to red.
Is there anyway to do this?
THanks all in advance!
You can set a global flag
window.blueTheme = true
function setStyleSheet() {
var styleSheetToBeSet = window.blueTheme ? "css/red.css" : "css/blue.css"
window.blueTheme = !window.blueTheme
// your code here
}
Of course, you can change blueTheme to theme and store theme name instead of boolean variable.
And then just call the function without parameter:
<a onclick="setStyleSheet()" href="#">Switch</a>
Simple, use if loop to compare the argument like
function StyleSheet(arg) {
if (arg == "css/blue.css") {
var styleSheetToBeSet = "css/red.css";
}
else {
var styleSheetToBeSet = "css/blue.css";
}
//set style sheet here with js
}
I would use another helper function and set some condition first.
function setElement(){
var style = document.styleSheets[0]; //set the index
if(style.href ==='css/blue.css'){
style.href = 'css/red.css';
}else if (style.href ==='css/red.css'){
style.href = 'css/blue.css';
}else{
console.log('error');
}
}
<a onclick="setElement();" href="#">Switch</a>
I suggest not to use the href="#". This will give uggly urls.
If you use the anchor better use
<a onclick="setStyleSheet('....')" href="javascript:void(0)">Switch</a>
Another option is using event.preventDefault(); in your javascript function.
One approach, that may work (depending upon the complexity of your stylesheets) is to update a CSS custom property to update the properties you wish to change or edit:
// defining the colours to be used (the approach taken in these
// code snippets allows for other values to be added to this
// Array to extend the number of colours/values used):
let cssStyles = ['blue', 'red'],
// defining the function (using Arrow syntax, since
// we don't need to use the 'this' object within):
modifyCSS = (styles) => {
// 'styles': the Array of colours passed into the function.
// here we find the root-element of the document:
let root = document.querySelector(':root'),
// we retrieve the current value of the '--colorValue'
// CSS property defined in that Node's CSS (if present):
current = root.style.getPropertyValue('--colorValue'),
// and, because this property-value is a String, we search
// the Array of colours to retrieve the index of the current
// colour:
index = styles.indexOf(current);
// here we update the '--colorValue' custom property to the
// property held in the next index of the Array; or to the
// value held at the Array's zeroth/first index:
root.style.setProperty('--colorValue', styles[++index % styles.length]);
}
// binding the function to the click event on the '#updatecss' button:
document.querySelector('#updatecss').addEventListener('click', (e) => modifyCSS(cssStyles) );
let cssStyles = ['blue', 'red'],
modifyCSS = (event, styles) => {
let root = document.querySelector(':root'),
current = root.style.getPropertyValue('--colorValue'),
index = styles.indexOf(current);
root.style.setProperty('--colorValue', styles[++index % styles.length]);
}
document.querySelector('#updatecss').addEventListener('click', (e) => modifyCSS(e, cssStyles) );
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.wrapper {
display: grid;
grid-template-columns: 40vw 40vw;
width: 80vw;
margin: auto;
grid-gap: 4px;
}
.wrapper>div {
color: var(--colorValue, black);
border: 2px solid currentColor;
grid-column: 1/-1;
}
<div class="wrapper">
<button id="updatecss">Switch CSS</button>
<div>element 1</div>
<div>element 2</div>
<div>element 3</div>
<div>element 4</div>
</div>
JS Fiddle demo.
Given that your question requires new stylesheets, and is probably a simplified version of your requirements, it may be more difficult to update CSS custom properties to meet your needs. However, we can use a similar approach with an array of stylesheet names:
// CSS stylesheet filenames:
let stylesheets = ['blue.css', 'red.css'],
// named function, using Arrow syntax as above, to change
// the stylesheets:
modifyCSS = (sheets) => {
// finding the relevant <link> element:
let link = document.querySelector('.linkedResource'),
// finding the index of the last '/' character in
// the href property-value of the <link>; adding 1
// so that the last slash is included in the 'path'
// and not the 'file':
lastSlashIndex = link.href.lastIndexOf('/') + 1,
// here we find the substring of the href up to,
// and including, the last '/' character:
path = link.href.substring(0, lastSlashIndex),
// finding the filename (based on the assumption
// that the filename follows the last '/' character):
file = link.href.slice(lastSlashIndex),
// finding the index of the current filename in the
// Array of filenames:
currentSheetIndex = sheets.indexOf(file);
// updating the href of the <link> element to be equal
// to the concatenated value of the path and the
// filename held at the next, or first, index of the Array:
link.href = path + sheets[++currentSheetIndex % sheets.length];
};
document.querySelector('#updatecss').addEventListener('click', () => modifyCSS(stylesheets));
let stylesheets = ['blue.css', 'red.css'],
modifyCSS = (sheets) => {
let link = document.querySelector('.linkedResource'),
lastSlashIndex = link.href.lastIndexOf('/') + 1,
path = link.href.substring(0, lastSlashIndex),
file = link.href.slice(lastSlashIndex),
currentSheetIndex = sheets.indexOf(file);
link.href = path + sheets[++currentSheetIndex % sheets.length];
};
document.querySelector('#updatecss').addEventListener('click', () => modifyCSS(stylesheets));
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.wrapper {
display: grid;
grid-template-columns: 40vw 40vw;
width: 80vw;
margin: auto;
grid-gap: 4px;
}
.wrapper>div {
color: var(--colorValue, black);
border: 2px solid currentColor;
grid-column: 1/-1;
}
<link rel="stylesheet" href="https://www.davidrhysthomas.co.uk/linked/blue.css" class="linkedResource" />
<div class="wrapper">
<button id="updatecss">Switch CSS</button>
<div>element 1</div>
<div>element 2</div>
<div>element 3</div>
<div>element 4</div>
</div>
JS Fiddle demo.
References:
CSS:
CSS Custom Properties (--*).
CSS Grid Layout.
JavaScript:
Array.prototype.indexOf().
CSSStyleDeclaration.getPropertyValue().
CSSStyleDeclaration.setProperty().
document.querySelector().
document.querySelectorAll().
JavaScript remainder (%) operator.
String.prototype.lastIndexOf().
String.prototype.slice().
String.prototype.substring().
Bibliography:
Constantly loop a javascript array and display results to div?
https://plnkr.co/V14X7icWCrmUw6IrCRVV
That's the plunker for the code. I've never linked to plunker so if it doesn't work, let me know.
What it is supposed to do is when a user hovers over some of the text, that same text should appear in the yellow box.
I thought I should have been able to do it with just a few lines, and substituting the index number with a variable, and looping through them with a while loop. I couldn't quite figure it out and had to just make like 20 different functions. I got it to do what I wanted it to do, but I can't help but think there should be a simpler way to do it.
Here is the Javascript: (The plunker link has the CSS and HTML)
var gamesArray = ['Metal Gear Solid 1', 'The Last of Us', 'Uncharted', 'Snake Eater', 'Need for Speed', 'Forza', 'Halo', 'Conker\'s Bad Fur Day', 'WWF No Mercy', 'WWF Wrestlemania 2000', 'Spelunky', 'The Last of Us Part 2', 'The Walking Dead Season 1', 'The Phantom Pain', 'Ys Memories of Celceta', 'Ys Seven', 'Dragon Ball Z Tenkaichi Tag Team', 'Naruto: Ultimate Ninja Heroes', 'Mortal Kombat'];
var itemList = document.getElementsByClassName('myClass');
var box2 = document.getElementsByClassName('answerBox');
box2[0].style.borderColor = 'black';
box2[0].style.color = 'red';
//var num = 0;
//var i = itemList[num];
//var j = gamesArray[num];
function choice000(){
box2[0].textContent = gamesArray[0];
}
function choice001(){
box2[0].textContent = gamesArray[1];
}
function choice002(){
box2[0].textContent = gamesArray[2];
}
function choice003(){
box2[0].textContent = gamesArray[3];
}
function choice004(){
box2[0].textContent = gamesArray[4];
}
function choice005(){
box2[0].textContent = gamesArray[5];
}
function choice006(){
box2[0].textContent = gamesArray[6];
}
function choice007(){
box2[0].textContent = gamesArray[7];
}
function choice008(){
box2[0].textContent = gamesArray[8];
}
function choice009(){
box2[0].textContent = gamesArray[9];
}
function choice010(){
box2[0].textContent = gamesArray[10];
}
function choice011(){
box2[0].textContent = gamesArray[11];
}
function choice012(){
box2[0].textContent = gamesArray[12];
}
function choice013(){
box2[0].textContent = gamesArray[13];
}
function choice014(){
box2[0].textContent = gamesArray[14];
}
function choice015(){
box2[0].textContent = gamesArray[15];
}
function choice016(){
box2[0].textContent = gamesArray[16];
}
function choice017(){
box2[0].textContent = gamesArray[17];
}
function choice018(){
box2[0].textContent = gamesArray[18];
}
function choice019(){
box2[0].textContent = gamesArray[19];
}
Just use one function:
function choice(game){
box2[0].textContent = gamesArray[game];
}
function choice(number) {
box2[0].textContent = gamesArray[number];
}
Use this function and then replace any of the other function calls. Example choice003() is replaced with choice(3)
I like that you doubt your solution, because it is indeed overwhelming! It's a very important rule: if you see certain pattern, repetition of some kind, you can always make it shorter.
First of all, you need to decide if your items are already created in HTML or do you want to create them using JavaScript. In other words: you should have only one source of data. In your example you'd need to maintain two data sources at once — array in JavaScript and list in HTML.
HTML-driven data
It's extremely important to separate HTML and JavaScript as much as possible. Below you'll find a working example without even a smallest amount of any JS functions or events.
If you design your code that way it's easier to keep track of everything, as it stays simple. JavaScript code below has only around 6 real-lines and it can be simplified even more!
If you need to provide any additional data in the box, that are not visible to the user by default, you can use data attributes.
I've used jQuery since I'm used to it, but you can easily achieve the same effect with roughly the same amount of lines with pure JavaScript.
/*
We define every event and action only in JavaScript.
We're keeping HTML *pure* and *simple*.
*/
$(function(){
var $games = $('.gameChoiceList li');
$games.on('mouseover', function() { // After moving mouse on any [.gameChoiceList li] element
var $game = $(this);
var $result = $('.answerBox');
$result.text($game.text()); // Display current [.gameChoiceList li] text in [.answerBox]
});
});
/* Styles go here */
body {
background-color: skyblue;
font-family: Arial, sans-serif; /*
We provide feedback if user doesn't have Arial font installed:
sans-serif is a group of serif fonts, so possible replacement wont be far from Arial */
}
.answerContainer{ /*
We don't need width: auto; and height: auto; as those are the default values */
border-style: solid;
margin-top: 30px;
}
.testAnswer{
border-style: solid;
padding: 10px;
}
.answerBox{
border-style: solid;
text-align: center;
padding: 20px;
height: 200px;
width: 50%;
background-color: yellow;
}
/*/ Extra: /*/
.gameChoiceList {
float: left;
padding: 0;
width: 40%;
}
.gameChoiceList li {
list-style-type: none;
margin: 2px;
}
.gameChoiceList li a {
display: block;
border: solid 1px;
background: #fff;
text-align: center;
padding: 5px;
}
.answerBox {
float: right;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul class="gameChoiceList">
<!--
We don't need the same class for every <li>, as it is easily accesible by:
.gameChoiceList li OR .gameChoiceList > li
We don't need the same class for every <a>, as it is easily accesible by:
.gameChoiceList li a OR .gameChoiceList a
-->
<li>Metal Gear Solid 1</li>
<li>The Last of Us</li>
<li>Uncharted</li>
<li>Snake Eater</li>
<li>Need for Speed</li>
<li>Forza</li>
<li>Halo</li>
<li>Conker's Bad Fur Day</li>
<li>WWF No Mercy</li>
<li>WWF Wrestlemania 2000</li>
<li>Spelunky</li>
<li>The Last of Us Part 2</li>
<li>The Walking Dead Season 1</li>
<li>Metal Gear Solid V: The Phantom Pain</li>
<li>Ys Memories of Celceta</li>
<li>Ys Seven</li>
<li>Dragon Ball Z Tenkaichi Tag Team</li>
<li>Naruto Ultimate Ninja Heroes</li>
<li>Mortal Kombat</li>
</ul>
<p class="answerBox">Mouseover on any of the games on the side to display its name here.</p>
Extra thought
I noticed that in your JavaScript code you're changing borderColor and color of .answerBox. You should never do that.
As it's part of visual styles, you should define adequate styles beforehand in your CSS file and then, toggle certain classes as needed. For example, below code is much easier to maintain:
/*/
Of course all the code below can be simplified to just 1 line:
document.querySelector('p').className = 'important';
I wanted to show something universal that helps separate the logic even more:
You can pass any element, class and content to the make() function.
/*/
var make = function(element, className, content) {
element.className = className;
element.textContent = content;
};
var paragraph = document.querySelector('p');
setTimeout(function() {
make(paragraph, 'important', 'Important paragraph');
}, 1000); // Make important after 1 second
setTimeout(function() {
make(paragraph, 'irrelevant', 'Irrelevant paragraph. do dont read it!');
}, 2000); // Make important after 2 seconds
/*/ Since we defined every class beforehand,
it's easier to adjust styles for certain actions in the future /*/
p {
color: green;
}
p.important {
color: red;
border: solid 1px red;
padding: 10px;
}
p.irrelevant {
color: gray;
font-size: .8em;
}
<p>Very nice paragraph</p>
Stay pure!
~Wiktor