I'm currently trying to learn how to OOP in JavaScript by making use of ES6 classes. As an example I created this Slider using classes. I didn't find any class example where the DOM is highly involved
class Slider {
constructor(_options, _className) {
this.options = _options;
this.container = document.getElementsByClassName(_className)[0];
this.slider = new SliderBackground(this.container.getElementsByClassName("slider-background")[0]);
this.minThumb = new Thumb(this.container.getElementsByClassName("slider-min")[0]);
this.maxThumb = new Thumb(this.container.getElementsByClassName("slider-max")[0]);
this.minLabel = this.container.getElementsByClassName("input-min")[0];
this.maxLabel = this.container.getElementsByClassName("input-max")[0];
this.minDragged = false;
this.maxDragged = false;
this.setup();
}
setup() {
this.setStartValues();
this.assignEvents();
}
setStartValues() {
const minValue = this.options.defaultValue[0] > this.options.range[0] ? this.options.defaultValue[0] : this.options.range[0];
const maxValue = this.options.defaultValue[1] > this.options.range[1] ? this.options.defaultValue[1] : this.options.range[1];
const minValuePercent = this.getValueToPercent(minValue);
const maxValuePercent = this.getValueToPercent(maxValue);
this.minThumb.position = minValuePercent;
this.maxThumb.position = maxValuePercent;
this.minLabel.value = minValue;
this.maxLabel.value = maxValue;
this.slider.setBackground(minValuePercent, maxValuePercent);
if (this.options.prefix !== "" && this.options.prefix !== undefined && this.options.prefix !== null) {
let inputs = this.container.getElementsByClassName("input-value");
for (let i = 0; i < inputs.length; i++) {
let prefix = document.createElement("span");
prefix.className = "slider-input-prefix";
prefix.innerHTML = this.options.prefix;
inputs[i].parentNode.insertBefore(prefix, inputs[i]);
}
}
}
assignEvents() {
this.minThumb.element.addEventListener("mousedown", (e) => this.mouseDown(e));
this.maxThumb.element.addEventListener("mousedown", (e) => this.mouseDown(e));
this.minLabel.addEventListener("input", (e) => this.changeInputValue(e));
this.maxLabel.addEventListener("input", (e) => this.changeInputValue(e));
this.minLabel.addEventListener("focusout", (e) => this.formatInput(e));
this.maxLabel.addEventListener("focusout", (e) => this.formatInput(e));
window.addEventListener("mouseup", () => this.mouseUp());
window.addEventListener("mousemove", (e) => this.mouseMove(e));
}
mouseDown(e) {
if (e.target === this.minThumb.element) {
e.preventDefault();
this.minDragged = true;
this.setActive(this.maxThumb.element, this.minThumb.element);
} else if (e.target === this.maxThumb.element) {
e.preventDefault();
this.maxDragged = true;
this.setActive(this.minThumb.element, this.maxThumb.element);
}
}
mouseUp() {
this.minDragged = false;
this.maxDragged = false;
}
mouseMove(e) {
if (this.minDragged || this.maxDragged) {
e.preventDefault();
let input = this.minDragged ? this.minLabel : this.maxLabel;
const newValue = this.setPositions(this.getXToPercent(e.clientX), this.minDragged);
if (newValue != null) input.value = Math.round(this.getPercentToValue(newValue));
}
}
changeInputValue(e) {
const valueInput = parseFloat(e.target.value);
const value = this.getValueToPercent(valueInput);
const minActive = e.target.className.includes("min");
this.setPositions(value, minActive);
this.setActive(this.container.getElementsByClassName("active")[0], this.container.querySelector(".slider-btn:not(.active)"));
}
setPositions(inputValue, minActive) {
const percentPosMin = this.getXToPercent(this.minThumb.position);
const percentPosMax = this.getXToPercent(this.maxThumb.position);
let value;
if (minActive) {
if (inputValue >= 0 && inputValue <= percentPosMax) value = inputValue;
else if (inputValue < 0) value = 0;
else if (inputValue > percentPosMax) value = percentPosMax;
} else if (!minActive) {
if (inputValue <= 100 && inputValue >= percentPosMin) value = inputValue;
else if (inputValue > 100) value = 100;
else if (inputValue < percentPosMin) value = percentPosMin;
} else return;
let thumb = minActive ? this.minThumb : this.maxThumb;
thumb.position = value;
this.slider.setBackground(minActive ? value : percentPosMin, minActive ? percentPosMax : value);
return value;
}
formatInput(e) {
const minValue = this.minLabel.value;
const maxValue = this.maxLabel.value;
if (maxValue < minValue) this.maxLabel.value = minValue;
else if (minValue > maxValue) this.minLabel.value = maxValue;
}
getXToPercent(elmX) {
const slider = this.slider.bounding;
return (elmX - slider.left) / slider.width * 100;
}
getPercentToValue(percent) {
return (percent * (this.options.range[1] - this.options.range[0]) * 0.01 + this.options.range[0]);
}
getValueToPercent(value) {
return (value - this.options.range[0]) / (this.options.range[1] - this.options.range[0]) * 100;
}
setActive(curActiveElement, newActiveElement) {
curActiveElement.classList.remove("active");
newActiveElement.className += " active";
}
}
class Thumb {
constructor(_element) {
this.element = _element;
}
get bounding() {
return this.element.getBoundingClientRect();
}
get x() {
return this.element.getBoundingClientRect().x;
}
get position() {
let bounding = this.bounding;
return (bounding.x + bounding.width / 2);
}
set position(value) {
this.element.style.left = "calc(" + value + "% - " + this.bounding.width / 2 + "px)";
}
}
class SliderBackground {
constructor(_element) {
this.element = _element;
}
get bounding() {
return this.element.getBoundingClientRect();
}
setBackground(min, max) {
this.element.style.background = "linear-gradient(to right, var(--primary-color) " + min + "%, var(--primary-focus-color) " + min + "%, var(--primary-focus-color) " + max + "%, var(--primary-color) " + max + "%)";
}
}
let options = {
range: [0, 15000],
defaultValue: [0, 15000],
prefix: "€"
};
let slider = new Slider(options, "container-slider");
html {
--border-radius: 7px;
--border-radius-small: 3px;
--default-shadow: -3px 5px 15px 6px rgba(0, 0, 0, .35);
--btn-shadow: -1px 1px 10px 1px rgba(0, 0, 0, .35);
--hover-time: 0.2s;
--bg-color: #1F1B24;
--sf-color: #332940;
--sf2-color: #332940;
--primary-color: #1f2d82;
--primary-hover-color: #243394;
--primary-focus-color: #2E41BD;
--header-bg-color: #2C2735;
--primary-font-color: white;
--primary-font-hover-color: #BABABA;
}
body {
background-color: grey;
}
.flex {
width: 60%;
margin-left: 20%;
margin-top: 20%;
}
.container-slider {
width: 100%;
margin-top: 0.5em !important;
}
.slider-background {
width: 100%;
height: 5px;
position: relative;
display: flex;
align-items: center;
background-color: yellow;
}
.slider-btn {
cursor: pointer;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: var(--primary-font-color);
display: inline-block;
position: absolute;
left: 0;
}
.slider-btn.active {
z-index: 99;
}
.slider-labels {
margin-top: 1em;
width: 100%;
color: var(--primary-font-color);
display: flex;
justify-content: space-between;
}
.prefix_and_input {
position: relative;
display: inline-block;
color: var(--primary-font-color);
}
.input-value {
width: 4em;
background: none;
border: none;
outline: none;
color: var(--primary-font-color);
font-size: 1em;
text-align: center;
border-bottom: 1px solid var(--primary-font-color);
}
.underline-outer {
display: flex;
justify-content: center;
bottom: 0;
left: 0;
position: absolute;
width: 100%;
height: 2px;
background-color: #64e4fe;
}
.underline-inner {
transition: transform 0.15s ease-out;
width: 100%;
transform: scale(0, 1);
background-color: #1F1B24;
height: 100%;
}
.prefix_and_input>.input-value:focus+.underline-outer>.underline-inner {
transform: scale(1);
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number] {
-moz-appearance: textfield;
}
<div class="flex">
<div class="container-slider">
<div class="slider-background">
<span class="slider-btn slider-min active"></span>
<span class="slider-btn slider-max"></span>
</div>
<div class="slider-labels">
<div class="min-label_and_input">
<label class="label-value label-min" for="input-min">Min: </label>
<div class="prefix_and_input">
<input type="number" step="1" id="input-min" class="input-value input-min">
<div class="underline-outer">
<div class="underline-inner"></div>
</div>
</div>
</div>
<div class="max-label_and_input">
<label class="label-value label-max" for="input-max">Max: </label>
<div class="prefix_and_input">
<input type="number" step="1" id="input-max" class="input-value input-max">
<div class="underline-outer">
<div class="underline-inner"></div>
</div>
</div>
</div>
</div>
</div>
</div>
Problem/Question: Am I doing OOP in Javascript right? Anything else I'm doing wrong in JS?