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>