I have this let's say quiz maker where I create questions. It looks like this:
The user can choose to display this rating system in 4 different ways:
- 1 2 3 4 5 -> 1-5 rating
- 1 2 3 4 5 6 7 8 9 10 -> 1-10 rating
- ⭐ ⭐ ⭐ ⭐ ⭐ -> 1-5 stars
- ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ -> 1-10 stars
The default rating system is from 1 to 5 numbers. But the user has the option to make it from 1 to 10 using the toggle switch as well as stars instead of numbers. My problem is that I don't know how to add 5 more numbers when the switch is turned on and then when it turned off remove them again, or remove the numbers and add stars when the other toggle switch is turned on.
I imagine that I need something like:
depending on the current state:
if (1-10_switch.isChanged){
(addMore(stars || numbers) || remove )
}
else if (stars_switch.isChanged){
(changeIconTo(stars || numbers))
}
I have the code to check if the switch goes on and off:
oneToTenButtonCheckbox.addEventListener("change", function () {
OneToTen = OneToTen ? false : true;
});
starsButtonCheckbox.addEventListener("change", function () {
isStars = isStars ? false : true;
});
And this is the code where I add the numbers:
var numbersDiv = document.createElement("div");
numbersDiv.className = "row";
for (let i = 0; i < 5; i++) {
var num = document.createElement("div");
num.insertAdjacentText("beforeend", i + 1);
if (i == 0) num.className = "col-1 offset-1 mb-2";
else num.className = "col-1 mb-2";
numbersDiv.appendChild(num);
}
Does anyone know what approach I can use to solve this? Or maybe someone has done it and know how to do? Thanks.
I have this let's say quiz maker where I create questions. It looks like this:
The user can choose to display this rating system in 4 different ways:
- 1 2 3 4 5 -> 1-5 rating
- 1 2 3 4 5 6 7 8 9 10 -> 1-10 rating
- ⭐ ⭐ ⭐ ⭐ ⭐ -> 1-5 stars
- ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ -> 1-10 stars
The default rating system is from 1 to 5 numbers. But the user has the option to make it from 1 to 10 using the toggle switch as well as stars instead of numbers. My problem is that I don't know how to add 5 more numbers when the switch is turned on and then when it turned off remove them again, or remove the numbers and add stars when the other toggle switch is turned on.
I imagine that I need something like:
depending on the current state:
if (1-10_switch.isChanged){
(addMore(stars || numbers) || remove )
}
else if (stars_switch.isChanged){
(changeIconTo(stars || numbers))
}
I have the code to check if the switch goes on and off:
oneToTenButtonCheckbox.addEventListener("change", function () {
OneToTen = OneToTen ? false : true;
});
starsButtonCheckbox.addEventListener("change", function () {
isStars = isStars ? false : true;
});
And this is the code where I add the numbers:
var numbersDiv = document.createElement("div");
numbersDiv.className = "row";
for (let i = 0; i < 5; i++) {
var num = document.createElement("div");
num.insertAdjacentText("beforeend", i + 1);
if (i == 0) num.className = "col-1 offset-1 mb-2";
else num.className = "col-1 mb-2";
numbersDiv.appendChild(num);
}
Does anyone know what approach I can use to solve this? Or maybe someone has done it and know how to do? Thanks.
Share Improve this question edited Jul 29, 2022 at 8:35 Chris Costa asked Jul 29, 2022 at 8:05 Chris CostaChris Costa 6911 gold badge6 silver badges19 bronze badges 20- Can we see HTML part, of the numbersDiv? – aca Commented Jul 29, 2022 at 8:08
- 1 Why do these numbers need to get displayed at this point to begin with? The user that is creating the question here in this place, is not the one who will actually be rating it - no? If this is pure display at this point, and there is no more functionality attached to these numbers - then I would simply toggle a class on the parent element, that shows the second half ... – C3roe Commented Jul 29, 2022 at 8:08
- @aca i added it but i don't think it matters that much – Chris Costa Commented Jul 29, 2022 at 8:10
- @CBroe You are right, the user who is creating is not the one who would be rating. There is no functionality at this point. Only visual – Chris Costa Commented Jul 29, 2022 at 8:12
- 1 It was an element not strictly bound to the question that confused me... but indeed you didn't mention it expicitely in your logic so it was fair enough. My bad. Anyway I know you may prefer to go with the strategy you already decided.. but seriously.. you might just have a sequence of elements for 1 to 10 and embed the segment 6-10 inside a container that will be toggled a css class having display:none. Wouldn't it be enough? no because you are asking how to create and remove.. ok. :) fiy I will add here the template option developer.mozilla/en-US/docs/Web/HTML/Element/template – Diego D Commented Jul 29, 2022 at 8:30
6 Answers
Reset to default 3I would not generate that HTML dynamically. Create it upfront, and hide the last 5 elements with a CSS class. The numbers can be generated with a CSS counter at the start as well.
With this set up the code only needs to toggle CSS classes:
const answer = document.querySelector("#answer");
document.querySelector("#oneToTenButtonCheckbox").addEventListener("change", () => {
answer.children[1].classList.toggle("hidden");
});
document.querySelector("#starsButtonCheckbox").addEventListener("change", () => {
answer.classList.toggle("stars");
});
body {
counter-reset: star-num;
}
#answer.stars > span > span::after {
content: "⭐";
}
#answer > span > span::after {
counter-increment: star-num;
content: counter(star-num);
}
#answer > span > span {
display: inline-block;
text-align: center;
width: 30px;
}
.hidden {
display: none
}
<label><input type=checkbox id="oneToTenButtonCheckbox">1 - 10</label><br>
<label><input type=checkbox id="starsButtonCheckbox">Stars</label><br>
<div id="answer">
<span><span></span><span></span><span></span><span></span><span></span></span><span class="hidden"><span></span><span></span><span></span><span></span><span></span></span>
</div>
You can take the logic of creating the numbers and enter it into a function:
var numbersDiv = document.createElement("div");
numbersDiv.className = "row";
function createNumbers(startNumber, endNumber)
for (let i = startingNumber; i <= endNumber; i++) {
var num = document.createElement("div");
// add id so you can delete later
num.id = "ratingNumber" + i;
num.insertAdjacentText("beforeend", i);
if (i == 1) num.className = "col-1 offset-1 mb-2";
else num.className = "col-1 mb-2";
numbersDiv.appendChild(num);
}
}
//initial call to create 1-5
createNumbers(1, 5)
Then, he event handler receives the event itself as an argument, and in a case of a change event for HTML checkbox (I assume this is a checkbox due to the variable name), the event consists a checked
property, so you can change the event listener:
oneToTenButtonCheckbox.addEventListener("change", function (event) {
event.checked ? createNumbers(6, 10) : removeNumbers(6, 10);
});
Then create a removeNumbers
method:
function removeNumbers(startNumber, endNumber) {
for (let i = startNumber; i <= endNumber; i++) {
const numToRemove = document.getElementById("ratingNumber" + i);
numToRemove.remove()
}
}
add new elements:
// first: make a new element
let li = document.createElement('li')
// second:add new element to Parent Node(such as ul)
//Insert at the end of the parent element
ul.appendChild(li)
//Insert into the specified position of the parent element
ul.insertBefore(li, ul.children[0])
//Please note that before you add the DOM child nodes (step 2) please plete the processing you need to do for the child nodes
remove elements:
ul.removeChild(li)
try use this in your code ,Hope its could help you
A simple/straightforward way is to :
- Store the status of
isTen
andisStar
, which are both default tofalse
- Create a
toggle()
function to change these status when the switch is clicked. - Generate the content according to these two flag (i.e.
display()
).
isTen = false;
isStar = false;
display()
function toggle(flag){
/* toggle the corresponding flag */
if (flag == "isTen"){
isTen = !isTen;
}else{
isStar = !isStar;
}
/* generate display with latest flag */
display()
}
function display(){
/* generate the output string with isTen and isStar flag*/
n = isTen ? 10 : 5;
output = "";
for (let i = 0; i < n; i++) {
if (isStar){
output += "⭐";
} else {
output += i+1;
}
}
/* clear the display span first*/
document.getElementById("display").innerHTML = "";
/* show output string on display span */
document.getElementById("display").innerHTML = output;
showStatus();
}
function showStatus(){
document.getElementById("status").innerHTML = "";
document.getElementById("status").innerHTML = "isTen : "+isTen+"<br>isStar :"+isStar;
}
<label>
<input type="checkbox" onclick="toggle('isTen')">
isTen
</label>
<br>
<label>
<input type="checkbox" onclick="toggle('isStar')">
isStar
</label>
<br>
<span id="display"></span>
<br><br><br>
<span id="status"></span>
This version uses a <form>
and renders multiple questions.
Details are mented in example
// Reference <form>
const form = document.forms.interface;
/**
* @desc Renders the HTML of each question up to the given number
* @param {number} items - Number of questions to render
*/
function addQuestions(items) {
const qar = form.qar;
for (let i = 0; i < items; i++) {
let q = `
<li class='item${i + 1}'>
<input id='que${i + 1}' name='que' class='que' type='text' placeholder='Enter question'>
<label style='display: inline-block'>Modify
<input name='mod' type='radio' value='${i + 1}'>
</label>
<fieldset id='row${i + 1}' name='row' class='row'>
<a></a><a></a><a></a><a></a><a></a>
<a class='extra hide'></a><a class='extra hide'></a>
<a class='extra hide'></a><a class='extra hide'></a>
<a class='extra hide'></a>
</fieldset>
</li>`;
qar.insertAdjacentHTML('beforeend', q);
const item = document.querySelector(`.item${i + 1}`);
[...item.querySelectorAll('a')].forEach((a, idx) => {
a.textContent = idx + 1;
});
}
form.elements.mod[0].checked = true;
}
addQuestions(3);
// Bind change event to <form>
form.onchange = modify;
// This modifies the question that has the checked mod radio button
function modify(e) {
// Reference the checkbox the user clicked
const clk = e.target;
// Reference all form controls
const fc = this.elements;
// Get the value of the mod radio button group
const mod = fc.mod.value;
// Reference <input> and <fieldset>
const que = document.getElementById('que' + mod);
const row = document.getElementById('row' + mod);
// If the user clicked the required checkbox...
if (clk.id === 'req') {
//...toggle the required attribute to the selected <input>
que.toggleAttribute('required');
}
// If the user clicked the 10 checkbox...
if (clk.id === 'qty') {
// ...toggle the selected row 5 a.extra .hide and .show classes
row.querySelectorAll('.extra').forEach(ex => {
ex.classList.toggle('hide');
ex.classList.toggle('show');
});
}
// If the user clicked the stars checkbox...
if (clk.id === 'str') {
// ...toggle the .stars class of row
row.classList.toggle('stars');
}
// Reset the checkboxes immediately
Array.from(fc.panel.elements).forEach(chx => chx.checked = false);
}
input {
font: inherit;
display: inline-block;
}
#panel,
label {
display: flex;
justify-content: space-around;
align-items: center;
width: max-content;
padding: 5px;
}
#qar {
list-style-type: decimal;
border: 0;
}
.que {
margin: 8px 0;
}
.row {
display: flex;
flex-flow: row nowrap;
justify-content: space-evenly;
align-items: center;
border: 0;
}
.row.stars a {
color: transparent;
}
.row.stars a::before {
content: '⭐';
color: black;
}
.hide {
display: none;
}
.show {
display: block;
}
[required] {
outline: red 1px solid;
}
<form id='interface'>
<fieldset>
<legend>Q & A/Rating</legend>
<fieldset id='panel'>
<label>Required <input id='req' type='checkbox'></label>
<label>10 <input id='qty' type='checkbox'></label>
<label>Stars <input id='str' type='checkbox'></label>
</fieldset>
<fieldset id='qar'>
</fieldset>
</fieldset>
</form>
Here's my pletely dynamic and general solution. I came up with a dynamic solution because I'm assuming that you'll need to create multiple quizzes thus multiple rating selectors.
We can create a rating selector element with the createRatingSelector
function and it returns a HTMLDivElement.
const stateElement = document.getElementById("state");
const quizRatingSelector = createRatingSelector({
altRatingScale: 10,
defaultRatingScale: 5,
altRatingType: "star",
defaultRatingType: "number",
altRatingTypeLabel: "Star",
defaultRatingTypeLabel: "Number Scale",
// listen to change event
onChange: ({ratingScale, ratingType}) => {
const stateString = `scale: ${ratingScale}, type: ${ratingType}`;
stateElement.innerText = stateString;
},
});
document.body.appendChild(quizRatingSelector);
// we can get the current state as
// console.log(quizRatingSelector.getState());
// ------------------------------------------
// returns a <div> element
// see the argument destructuring below to understand function API
function createRatingSelector(arg = {}) {
const {
altRatingType,
altRatingScale,
defaultRatingType,
altRatingTypeLabel,
defaultRatingScale,
// this function will be called whenever the rating scale or type changes
onChange = () => {},
defaultRatingTypeLabel,
NON_NUMBER_RATING_CHAR = "⭐",
parentClassName = "rating-selector",
typeSwitchClassName = "rating-type-switch",
scaleSwitchClassName = "rating-scale-switch",
outputElementClassName = "rating-selector-output",
} = arg;
const ratingSelector = document.createElement("div");
ratingSelector.setAttribute("class", parentClassName);
// we'll store the ratingType and ratingScale in the dataset of this selector
// in case you need it.
ratingSelector.dataset.ratingType = defaultRatingType;
ratingSelector.dataset.ratingScale = defaultRatingScale;
ratingSelector.getState = function () {
return {
ratingType: ratingSelector.dataset.ratingType,
ratingScale: ratingSelector.dataset.ratingScale,
};
};
const ratingOutput = document.createElement("div");
ratingOutput.setAttribute("class", outputElementClassName);
// this function is needed in the onClick function of switches
const showResultLocal = () =>
showResult({
outputElement: ratingOutput,
nonNumberValue: NON_NUMBER_RATING_CHAR,
ratingType: ratingSelector.dataset.ratingType,
ratingScale: ratingSelector.dataset.ratingScale,
});
const ratingScaleSwitch = createSwitch({
name: "rating-scale",
onValue: altRatingScale,
offValue: defaultRatingScale,
className: scaleSwitchClassName,
onLabel: `Rating Scale: 1 - ${altRatingScale}`,
offLabel: `Rating Scale: 1 - ${defaultRatingScale}`,
onClick: ({ value }) => {
ratingScale = value;
ratingSelector.dataset.ratingScale = value;
showResultLocal();
onChange(ratingSelector.getState());
},
});
const ratingTypeSwitch = createSwitch({
name: "rating-type",
className: typeSwitchClassName,
onValue: altRatingType,
offValue: defaultRatingType,
onLabel: `Rating type: ${altRatingTypeLabel}`,
offLabel: `Rating type: ${defaultRatingTypeLabel}`,
onClick: ({ value }) => {
ratingSelector.dataset.ratingType = value;
showResultLocal();
onChange(ratingSelector.getState());
},
});
ratingSelector.appendChild(ratingScaleSwitch);
ratingSelector.appendChild(ratingTypeSwitch);
ratingSelector.appendChild(ratingOutput);
showResultLocal();
return ratingSelector;
}
/**
* Creates a __input__ and __label__ element and wraps it with a <div>
* e.g.,
* <div>
* <label>The Label</label>
* <input type="checkbox" ...other attributes... />
* </div>
*
* see the argument destructuring below to understand function API
* */
function createSwitch(arg = {}) {
const {
onLabel,
offLabel,
onValue,
offValue,
id = "",
name = "",
className = "",
onClick = () => {},
} = arg;
const switchName = name;
const switchAttributes = {
id,
name: switchName,
class: className,
type: "checkbox",
};
const toggleSwitch = document.createElement("input");
for (const [name, value] of Object.entries(switchAttributes))
toggleSwitch.setAttribute(name, value);
const switchLabel = document.createElement("label");
switchLabel.setAttribute("for", switchName);
switchLabel.innerText = offLabel;
// click event handling
toggleSwitch.addEventListener("click", () => {
switchLabel.innerText = toggleSwitch.checked ? onLabel : offLabel;
onClick({
id,
name: switchName,
active: toggleSwitch.checked,
value: toggleSwitch.checked ? onValue : offValue,
});
});
const switchWrapper = document.createElement("div");
switchWrapper.appendChild(toggleSwitch);
switchWrapper.appendChild(switchLabel);
return switchWrapper;
}
// see the argument destructuring below to understand function API
function showResult(arg = {}) {
const { outputElement, ratingScale, ratingType, nonNumberValue } = arg;
while (outputElement.childNodes.length > ratingScale)
outputElement.removeChild(outputElement.childNodes[0]);
while (outputElement.childNodes.length < ratingScale)
outputElement.appendChild(document.createElement("span"));
outputElement.childNodes.forEach((child, index) => {
child.innerText = ratingType === "number" ? index + 1 : nonNumberValue;
});
}
.rating-selector-output > span {
width: 2ch;
text-align: center;
display: inline-block;
}
<body>
<p id="state">State...</p>
<hr>
</body>