Make onclick function in HTML to do 2 functions - javascript

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?

Related

JS Variable Cannot Change Element Properties

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>

How can I make a function divide the total money by the total of names entered on the button?

I only need a function so that I can divide #total by the total of names that were entered on the button. If you know the answer please help me, I'm a newbie in Javascript.
function ir() {
const nombre = document.getElementById("nombre").value;
let monto = document.getElementById("monto").value;
const total = document.getElementById("total");
const final = document.getElementById("final");
const aporte = document.getElementById("aporte");
const lineBreak = document.createElement("br");
let newTotal = document.createTextNode(` ${nombre} : ${monto} `);
total.appendChild(lineBreak);
total.appendChild(newTotal);
monto = Number(monto) + Number(final.innerHTML);
final.innerHTML = `${monto}`;
aporte.innerHTML = `${monto}`;
};
<h1>Expenses app</h1>
<p>Enter how much each person spent</p>
<p>Nombre</p>
<input type="text" id="nombre">
<br>
<p>Monto</p>
<input type="number" id="monto">
<br>
<button onclick="ir()">Enviar</button>
<br>
<p>Total: <span id="final"></span> </p>
<div id="total">
</div>
<p>A cada uno le toca aportar: <span id="aporte"></span></p>
Sorry I can't fully understand the meanings of your attributes' name, I guess 'nombre' is names of people and 'monto' means amount of all money. If so:
Maybe you need split method to count the amount of names, like:
const names = 'name1,name2,name3'
// divide above string by ','
const nameArray = names.split(',') // ['name1', 'name2', 'name3']
// get the amount of names
const count = nameArray.length // 3
then use arithmetic operators to get what you need.
If you want to show everyone's money, you can use forEach (different from map) to do something, like:
const nameArray = ['name1', 'name2', 'name3']
const money = { name1: 10, name2: 20, name3: 30 }
// you can use forEach
nameArray.forEach((name) => {
// this function will run 3 times,
// and the name attribute will be 'name1' 'name2' 'name3' respectively
console.log(`${name}: ${money[name]}`)
// you can do something here, for example, operate document
})
// or use map
const resultStringArray = nameArray.map((name) => {
return `${name}: ${money[name]}`
})
// resultStringArray = ['name1: 10', 'name2: 20', 'name3: 30']
While you've already accepted an answer, I wanted to take the time to show another approach; hopefully anticipating some of your requirements. Explanatory comments are in the code itself:
// I create a few utility functions and variables to make things a
// little easier; here we cache the document variable in order to
// reduce typing the whole word:
const D = document,
// creating an alias for document.createElement():
create = (elem) => D.createElement(elem),
// a simple alias for both document.querySelector() or
// Element.querySelector(); with the default value of
// context being equal to document:
get = (selector, context = D) => context.querySelector(selector),
// an alias - as above - for document.querySelectorAll() or
// Element.querySelectorAll(), again with the default context
// argument set to document; here though we return an Array
// of element nodes in order to utilise Array methods:
getAll = (selector, context = D) => [...context.querySelectorAll(selector)],
// a simple function to handle the removal of a person once added, this
// function (as do all event-handler functions) pass in a reference to
// the Event Object ('evt') to the function-body:
removePerson = (evt) => {
// the Event Object's currentTarget property-value is a reference to
// the Element to which the event-handler (this function) was bound:
let currentTarget = evt.currentTarget,
// the Event Object's target property-value is the Element that
// initially triggered the event:
target = evt.target;
// if the user has clicked on the <ul> we return false, and effectively
// do nothing:
if (target === currentTarget) {
return false;
// otherwise we check if the target matches the supplied CSS selector,
// using Element.matches(), which returns a Boolean true (if the
// Element does match the selector) or false (if the Element does not
// match that selector):
} else if (target.matches('span.delete')) {
// if the element matches, then we navigate the DOM from that target
// Element, and find the closest ancestor element that matches the
// supplied CSS selector; if no such element is found then null is
// returned (and this has no sanity checks to ensure that an error
// is safely handled); that <li> element is then removed:
target.closest('li').remove();
}
},
// this function is another event-handler, and again passes in a reference
// to the Event Object:
addPeople = (evt) => {
// the element to which the function was bound as an event-handler:
let activated = evt.currentTarget,
// here we use the utility function to find the element with an
// 'id' of 'name':
name = get('#name'),
// from that element we retrieve the value, and then use
// String.prototype.trim() to remove leading and trailing white-space:
givenName = name.value.trim(),
// we use another utility function to create an <li> element:
li = create('li'),
// here we use the getAll() utility function to search for <option>
// elements in the <datalist> associated with the name element
allNames = getAll('option', name.list)
// we then use Array.prototype.map() to iterate over that Array
// of element-nodes to create a new Array:
.map(
// using an Arrow function, passing in a reference to the current
// element ('el') of the Array of elements, and here we return
// the textContent of the current <option> element:
(el) => el.textContent
),
// creating the remove button:
removeButton = create('span');
// adding the 'delete' class-name, using the Element.classList API:
removeButton.classList.add('delete');
// assigning the template-literal string - interpolating the value
// of the givenName variable - as the title property of the
// created 'button':
removeButton.title = `Click to remove ${givenName}`;
// I could have used the optional chaining parameter to avoid errors:
// if (givenName?.length) {
// but writing this 'quickly' in JS Fiddle, which doesn't understand
// that operator, was causing issues every time I formatted, so I went
// with the 'traditional' approach:
if (givenName && givenName.length) {
// setting the text-content of the <li> to the givenName:
li.textContent = givenName;
// appending the removeButton element:
li.append(removeButton);
// finding the element matching the CSS selector, within the <form>
// associated with the activated element, and then appending the <li>:
get('ul.people', activated.form).append(li);
// if the list of names held in the <datalist> element's <option> elements
// does not include the givenName:
if (allNames.includes(givenName) === false) {
// we then append a new Option element, with the givenName as its text:
name.list.append(new Option(givenName))
}
// and here we set the value of the name element to be an empty string:
name.value = '';
}
},
// the function to handle calculating the total, again an event-handler passing
// in a reference to the Event Object:
total = (evt) => {
// finding the element to which the function was bound:
let activated = evt.currentTarget,
// finding the associated <form> of that element:
form = activated.form,
// finding the <li> elements which match the selector inside
// of the <form> element:
participants = getAll('ul.people li', form),
// the number of participants:
numberOfParticipants = participants.length,
// finding the element with the id of 'total':
total = get('#amount'),
// calling parseFloat on the trimmed-value of that element:
totalValue = parseFloat(total.value.trim()),
// finding the <output> and <aside> elements within the <form>:
output = get('output', form),
aside = get('aside', form);
// if the value is less than zero:
if (totalValue < 0) {
// we convert the value of the <input> to the absolute-value given:
total.value = Math.abs(totalValue);
// we update the <aside> element to leave a "helpful" message:
aside.textContent = "No, we don't use negative numbers."
// if the totalValue is not a number (isNaN), or is potentially infinite (divide-by-zero),
// or is somehow falsey (!totalValue) or is equal to zero:
} else if (isNaN(totalValue) || !isFinite(totalValue) || !totalValue || totalValue === 0) {
// we update the value of the <input> to zero:
total.value = 0;
// and leave a "helpful" message:
aside.textContent = "Honestly, we'd rather work with sensible numbers."
// if there are no participants, or the number of participants is falsey:
} else if (numberOfParticipants === 0 || !numberOfParticipants) {
// we do nothing, except leave a helpful message:
aside.textContent = "If no-one's splitting the bill, what are we doing? Is anybody there?"
// if we get to this point:
} else {
// and the number of participants is only 1:
if (numberOfParticipants === 1) {
// again, "helpful" message (it was around this point I realised I was getting
// hungry):
aside.textContent = "We're glad to help, but this does seem a bit unnecessary."
// otherwise:
} else {
// we remove the text of the <aside>:
aside.textContent = '';
}
// here is where we set the <output>:
output.textContent = (
// using Math.ceil() to round up to the next/nearest integer (of cents):
Math.ceil(
// because we're dealing with currency I multiply the number by 100,
// to convert €9.99 to 999, this allows us to use Math.ceil() to
// round up to the nearest cent:
100 * (totalValue / numberOfParticipants)
// here we divide by 100 to allow for the value to be returned in € and c
) / 100
// and because - again - this is currency, we convert the number to
// a String, with two decimal places (for the cents):
).toFixed(2);
}
};
// finding the element that matches the selector, and calling
// EventTarget.addEventListener() to bind the addPeople() function
// as the 'click' event-handler:
get('button.minor-button').addEventListener('click', addPeople);
// here we find the element with the id of 'name', and again we use
// EventTarget.addEventListener() but this time we use its anonymous
// function to evaluate the keypress event:
get('#name').addEventListener('keypress', (evt) => {
// if the key is 'Enter' or has the keyCode of 13:
if ('Enter' === evt.key || 13 === evt.keyCode) {
// we then call the addPeople function, and supply the Event Object:
addPeople(evt);
}
});
// exactly as above, but binding the total() function as the 'click'
// event-handler:
get('#calculate').addEventListener('click', total);
// getting the element matching the selector, chaining addEventListener()
// to bind the removePerson() function as the 'click' event-handler:
get('form ul.people').addEventListener('click', removePerson);
:root {
--border: 1px solid hsl(120 40% 40%);
--color: hsl(120 40% 20%);
--hint-color: hsl(120 40% 40%);
--spacing: 0.5rem;
--numColumns: 2;
--maxColSpan: 2;
--rowHeight: 1fr;
}
*,
::before,
::after {
box-sizing: border-box;
color: var(--color);
margin: 0;
padding: 0;
}
::before,
::after {
color: var(--hint-color);
font-style: italic;
}
form {
display: grid;
gap: var(--spacing);
grid-template-columns: repeat(var(--numColumns), 1fr);
grid-template-rows: repeat(3, var(--rowHeight));
margin-block: calc(2*var(--spacing));
margin-inline: auto;
width: clamp(30em, 80vw, 1000px);
}
form>* {
border: var(--border);
grid-row: span 1;
}
form> :empty {
border-color: transparent;
}
fieldset {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-row: span var(--maxColSpan);
gap: var(--spacing);
padding: var(--spacing);
}
legend {
display: block;
}
button {
grid-column: span 2;
}
.minor-button {
grid-column: 2;
}
legend::after,
label::after {
content: ':';
}
ul.people {
border-color: var(--border);
display: flex;
flex-flow: row wrap;
gap: var(--spacing);
align-content: start;
list-style-type: none;
padding: var(--spacing);
}
.people:empty {
border: var(--border);
}
.people:empty::before {
content: 'No people added.';
}
li {
border: var(--border);
display: flex;
gap: var(--spacing);
padding-block: calc(var(--spacing)/2);
padding-inline: var(--spacing);
}
.delete {
cursor: pointer;
display: inline-block;
overflow: hidden;
position: relative;
width: 1em;
height: 1em;
background-image: url(https://www.davidrhysthomas.co.uk/img/delete.svg);
}
output {
display: grid;
grid-column: span var(--maxColSpan);
place-items: center;
}
output::after {
content: 'per person';
}
output:empty::after {
content: '';
}
#media screen and (max-width: 900px) {
:root {
--numColumns: 1;
--maxColSpan: 1;
--rowHeight: min-content;
}
}
<datalist id="people">
<option>Ana</option>
<option>Carmen</option>
<option>Daniel</option>
<option>Dolores</option>
<option>Josefa</option>
<option>Juan</option>
<option>Luis</option>
<option>Maria</option>
<option>Ramón</option>
</datalist>
<form action="#">
<fieldset>
<label for="names">Names</label>
<input id="name" type="text" name="name" placeholder="Juan, Federico, Ana..." list="people">
<button type="button" class="minor-button">Add person</button>
<label>Total amount to share (€)</label>
<input id="amount" type="number" name="amount" value="0" min="0" step="0.01">
<button type="button" id="calculate">Calculate</button>
</fieldset>
<ul class="people"></ul>
<aside></aside>
<output></output>
</form>
JS Fiddle demo
References:
Array literals ([...]).
Array.prototype.includes().
Array.prototype.map().
Arrow functions.
document.createElement().
document.querySelector().
document.querySelectorAll().
Element.classList API.
Element.closest().
Element.matches().
Element.querySelector().
Element.querySelectorAll().
Element.remove().
Event.currentTarget.
Event.target.
EventTarget.addEventListener().
isFinite().
isNaN().
Number.prototype.toFixed().
parseFloat().
Spread syntax (...).
String.prototype.trim().
i hope this fast written solution helps you.
var Persons = [];
window.onload = () => {
var PersonHTMLElement = document.getElementById("person");
var AmountHTMLElement = document.getElementById("amount");
var TotalHTMLElement = document.getElementById("total");
var PersonListHTMLElement = document.getElementById("final");
var TotalDividedHTMLElement = document.getElementById("aporte");
document.getElementById("calc").addEventListener("click", setPerson);
function setPerson(){
let person = PersonHTMLElement.value;
let amount = AmountHTMLElement.value;
Persons.push({
Name:person,
Amount:parseFloat(amount)
});
PersonHTMLElement.value = "";
AmountHTMLElement.value = "";
setTotal();
}
function setTotal(){
TotalHTMLElement.innerHTML = "";
let PersonsList = "";
let Total = 0;
for(let i = 0;i < Persons.length; i++){
Total += Persons[i].Amount;
PersonsList += `${Persons[i].Name}: ${Persons[i].Amount} <br>`;
}
TotalHTMLElement.innerHTML = PersonsList;
PersonListHTMLElement.innerHTML = Total;
TotalDividedHTMLElement.innerHTML = Total / Persons.length;
}
}
<html>
<head>
<script src="myscript.js"></script>
</head>
<body>
<h1>Expenses app</h1>
<p>Enter how much each person spent</p>
<p>Nombre</p>
<input type="text" id="person">
<br>
<p>Monto</p>
<input type="number" id="amount">
<br>
<br>
<button id="calc">Enviar</button>
<br>
<p>Total: <span id="final"></span> </p>
<div id="total">
</div>
<p>A cada uno le toca aportar: <span id="aporte"></span></p>
</body>
</html>

JavaScript onclick change image and increase the counter

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>

CSS flex property is not responding correctly when set to 0 from JS code

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;

Generate css dynamically via templates and placeholders

I want to generate css dynamically at run time.
Initially I had used sass and defined some variables and was using those variables. But css has to be generated first from the scss. Sass had given me flexibility to use variables and functions but still I was not able to changes them at run time via javascript.
One way was to change the inline styles via javascript but that approach was not completly flexible.
document.getElementById("myDiv").style.color = "red";
I don't want to do above, neither I want to attach any <style> attribute via javascript.
I want to use javascript but not for chaniging each and every style properties. I want to achieve scss like effect using css and javascript but at run time i.e dynamically.
E.g. suppose I got the color information from the ajax call now I want to change the whole theme of website based on that color received immediately without restarting or re-deploying my application.
e.g
as done in scss
.myClass {
background:$color;
// Update color value dynamically at run-time
}
Is it even possible or I am thinking in wrong direction!
Wound up playing with this and CSS variables. I'm adding a second answer because it's very different method from my first answer and it better aligns with your original question (updating CSS variables with JS).
BUT... don't do this. :) Browser support in IE < Edge doesn't exist and it is almost certainly slower than updating an on-page <style> element though I haven't tested it. This jsperf tests various style update methods. It doesn't include innerHTML on a single style element (likely the fastest) but you can see that the following CSS DOM methods are slower than the rest.
// get the stylesheet
// array position depends on how many style sheets you're loading.
// adjust as needed.
var sheet = document.styleSheets[0];
// simplest method: insertRule()
// setTimeout only for demo so you can see the change
window.setTimeout(function(){
// #media all {} is a trick to insert more than one
// selector and/or properties at once. Otherwise it's:
// sheet.insertRule(":root", "--header-color: green"); ...repeat...
sheet.insertRule("#media all { :root { --header-color: green; --main-color: orange; } }", 1);
}, 1200);
// SAFER method via addCSSRule.
// button and getAjaxStyles are just placeholders, obviously
var btn = document.querySelector('button');
btn.addEventListener("click", getAjaxStyles);
function getAjaxStyles() {
// success callback... break apart the json and update the CSS variables
addCSSRule(sheet, ":root", "--header-color: orange");
addCSSRule(sheet, ":root", "--main-color: blue");
addCSSRule(sheet, ":root", "--alt-color: red");
addCSSRule(sheet, ":root", "--borderColorA: lavender");
// or go with a single big string. definitely faster:
// addCSSRule(sheet, ":root", "--alt-color: red; --borderColorA: #0ff; ")
}
// Credit for addCSSRule() goes to Diego Flórez in a comment on
// https://davidwalsh.name/add-rules-stylesheets
var addCSSRule = function(sheet, selector, rules) {
//Backward searching of the selector matching cssRules
var index = sheet.cssRules.length - 1;
for (var i = index; i > 0; i--) {
var current_style = sheet.cssRules[i];
if (current_style.selectorText === selector) {
//Append the new rules to the current content of the cssRule;
rules = current_style.style.cssText + rules;
sheet.deleteRule(i);
index = i;
}
}
if (sheet.insertRule) {
sheet.insertRule(selector + "{" + rules + "}", index);
} else {
sheet.addRule(selector, rules, index);
}
return sheet.cssRules[index].cssText;
}
/* Set initial CSS variables */
:root {
--header-color: #333;
--main-color: #888;
--alt-color: #bbb;
--borderColorA: #ccc;
}
h1 {
color: var(--header-color);
}
p {
border-bottom: 1px solid var(--borderColorA);
color: var(--main-color);
}
p+p {
color: var(--alt-color);
}
<h1>header</h1>
<p>paragraph 1</p>
<p>paragraph 2</p>
<button>Update CSS Variables</button>
To expand on the information that is provided in the linked "possible duplicate" question, you could easily set up a "default" set of styles in your page CSS file and then create a inline <style> ekement containing any overrides based on the response from your AJAX call. As long as the element/class/id definitions are the same in the two locations (i.e., CSS file and inline style section), specificity will cause the inline definitions to override the CSS ones.
So, using your example, your static CSS file would contain:
.myClass {
background: #FFFFFF;
}
. . . so that there is a default value if the AJAX call were to fail, and then your dynamically created <style> section would contain:
.myClass {
background: THE_AJAX_RESPONSE_VALUE;
}
. . . which would override the default value.
UPDATE #1:
Based on your sample JSON, this would be REALLY easy . . . you would loop through each top-level property of the JSON and create this:
KEY_NAME {
. . .
}
Then, within that block, loop through each property within that property and add the keys and values to create the style definitions:
KEY_NAME {
key1: value1,
key2: value2,
. . .
keyN: valueN
}
UPDATE #2:
You can also use StyleSheet and CSSStyleSheet interfaces to access the rules that are in the existing stylesheets, but, given that it uses an array-like structure, that means looping through all of the CSS definitions to find the one that you want and alter it. An example of how to do that can be found in this answer to another SO question: Is it possible to alter a CSS stylesheet using JavaScript? (NOT the style of an object, but the stylesheet itself)
Between the two approaches, though, creating an overriding <style> section seems like the easier approach.
Since the JSON has both the element names and the related styles, refreshing an on page stylesheet (vs inline element styles) would probably be the fastest since it uses innerHTML and only requires a single DOM lookup.
You'll need to loop through your JSON to create CSS compatible strings and then just dump it into the onpage style element. You can append CSS by concatenating the existing innerHTML with the new CSS string. I added an ID to the stylesheet for simplicity but you could also generate the style element when needed.
var StringifiedAjaxStyleObject = "h1 {background-color: #ecc; color: #633}";
var styleSheet = document.getElementById("style-update");
// some additional fake test style returns...
var testStyle1 = "h1 {background-color: #ccc; color: #333}";
var testStyle2 = "h1 {background-color: #667; color: #bbc}";
var testStyle3 = "h1 {background-color: #fee; color: #b00}";
// some fake ajax returns...
window.setTimeout(function() {
styleSheet.innerHTML = StringifiedAjaxStyleObject;
}, 1000);
window.setTimeout(function() {
styleSheet.innerHTML = testStyle1;
}, 2000);
window.setTimeout(function() {
styleSheet.innerHTML = testStyle2;
}, 3000);
window.setTimeout(function() {
styleSheet.innerHTML = testStyle3;
}, 4000);
/* base styles ... */
h1 {
padding: 5px;
background-color: #eef;
color: #007
}
<!-- empty stylesheet -->
<style id="style-update" type="text/css" rel="stylesheet"></style>
<h1>Hi, mom</h1>
<button>Update Styles<button>
EDIT:
Here's a slightly more real-world version based on the JSON object in your comment. Trigger it via the button.
var styleSheet = document.getElementById("style-update");
var btn = document.querySelector('button');
btn.addEventListener("click", updateStyles);
function updateStyles() {
var StringifiedAjaxStyleObject
, newCSS
, ajaxReturn
;
// ...your ajax method to get the new styles...
// on success...
ajaxReturn = {
".base": {
"background-color": "#b83605",
"border-color": "#543927",
"color": "gray",
"text-shadow": "0 -1px 0 rgba(0, 0, 0, 0.15)"
},
".overlay": {
"background": "rgba(76, 65, 80, 0.2)",
"color" : "#ddd"
}
};
// Convert the object to a string
newCSS = cssStringFromJson(ajaxReturn);
// Update the stylesheet
styleSheet.innerHTML = newCSS;
}
function cssStringFromJson(cssJSON) {
var styleStr = "",
i, j;
for (i in cssJSON) {
styleStr += i + " {\n"
for (j in cssJSON[i]) {
styleStr += "\t" + j + ": " + cssJSON[i][j] + ";\n"
}
styleStr += "}\n"
}
return styleStr;
}
/* base styles ... */
.base {
border: 1px solid #ccf;
background-color: #eef;
color: #000;
padding: 15px;
}
.overlay {
padding: 5px 15px;
background: rgba(96, 95, 180, 0.2);
}
body {
font-family: sans-serif;
}
button {
margin-top: 1em;
font-size: 1em;
}
<!-- empty stylesheet -->
<style id="style-update" type="text/css" rel="stylesheet"></style>
<div class="base">
<p>.base</p>
<div class="overlay">
<p>.overlay</p>
</div>
</div>
<button>Update Styles</button>
You can try angular templates.
It is going to break your previous sass, but it will work out later.

Categories