@sncf/bootstrap-sncf.metier
Version:
SNCF frontend framework
235 lines (199 loc) • 7.61 kB
JavaScript
import $ from 'jquery'
import findIndex from 'lodash/findIndex'
import getSelectedOptions from './../utils/getSelectedOptions'
import includes from 'lodash/includes'
import pull from 'lodash/pull'
const ACTIVE_CLASS = 'active'
const INDETERMINATE_CLASS = 'indeterminate'
/**
* ------------------------------------------------------------------------
* Class Definition
* ------------------------------------------------------------------------
*/
class SelectMultiple {
constructor(element) {
this.element = element
this.btn = element.querySelector('[data-role=btn]')
this.toggle = element.querySelector('[data-role=select-toggle]')
this.input = element.querySelector('[data-role=input]')
this.placeholder = element.querySelector('[data-role=placeholder]')
const groups = element.querySelectorAll('[data-role=group]')
this.collapses = element.querySelectorAll('[data-role=collapse]')
this.selectedOptions = getSelectedOptions(this.input)
this.store = {}
this.count = {
values: 0,
currentValues: 0
}
this._addEventListeners()
groups.forEach((group) => {
const id = group.dataset.id
const labelNode = group.querySelector('[data-role=counter]')
const valueNodeList = group.querySelectorAll('[data-role=value]')
this.store[id] = {
values: [],
currentValues: [],
labelNode,
valueNodeList
}
if (labelNode) {
labelNode.addEventListener('click', () => {
this._onGroupChange(id, (id) => this._addCurrentValues(id), (id) => this._removeCurrentValues(id))
})
}
valueNodeList.forEach((value) => {
this.store[id].values.push(value.dataset.target)
this.count.values += 1
const valueId = value.dataset.target
this._setDefaultSelectedOptions(id, valueId, (isSelected) => this._toggleValue(id, valueId, value, isSelected))
value.addEventListener('click', () => {
this._onValueChange(id, valueId, (isSelected) => this._toggleValue(id, valueId, value, isSelected))
})
})
this._updatePlaceholderNodeStyle()
this._updateGroupNodeStyle(id)
})
this.placeholder.addEventListener('click', (event) => {
event.stopPropagation()
this._onPlaceholderChange((id) => this._addCurrentValues(id), (id) => this._removeCurrentValues(id))
})
}
// Private
_addEventListeners() {
this.toggle.addEventListener('click', (event) => {
event.stopPropagation()
this.element.classList.toggle(ACTIVE_CLASS)
this.btn.classList.toggle(ACTIVE_CLASS)
if (this.btn.getAttribute('aria-expanded') === 'true') {
this.btn.setAttribute('aria-expanded', 'false')
} else {
this.btn.setAttribute('aria-expanded', 'true')
}
})
this.collapses.forEach((item) => {
item.addEventListener('click', (event) => {
event.target.classList.toggle(ACTIVE_CLASS)
// bootstrap jQuery collapse
$(event.target.dataset.target).collapse('toggle')
})
})
this.element.addEventListener('click', (event) => {
event.stopPropagation()
})
document.addEventListener('click', () => {
this.element.classList.remove(ACTIVE_CLASS)
this.btn.classList.remove(ACTIVE_CLASS)
this.btn.setAttribute('aria-expanded', 'false')
})
}
_setDefaultSelectedOptions(groupId, valueId, toggleCallback) {
/* eslint-disable arrow-body-style */
const isSelected = findIndex(this.selectedOptions, (o) => {
return o.dataset.id === valueId
})
/* eslint-enable arrow-body-style */
toggleCallback(isSelected < 0)
}
_onValueChange = (groupId = 0, valueId, toggleCallback) => {
toggleCallback(includes(this.store[groupId].currentValues, valueId))
}
_toggleValue = (groupId, valueId, valueNode, isSelected) => {
if (!isSelected) {
this.store[groupId].currentValues.push(valueId)
this.count.currentValues += 1
valueNode.classList.add(ACTIVE_CLASS)
valueNode.setAttribute('aria-checked', 'true')
} else {
pull(this.store[groupId].currentValues, valueId)
this.count.currentValues -= 1
valueNode.classList.remove(ACTIVE_CLASS)
valueNode.setAttribute('aria-checked', 'false')
}
this._updatePlaceholderNodeStyle()
this._updateGroupNodeStyle(groupId)
}
_updateGroupNodeStyle(id = 0) {
const labelNode = this.store[id].labelNode
const valuesLength = this.store[id].values.length
const currentValuesLength = this.store[id].currentValues.length
if (labelNode) {
if (currentValuesLength > 0) {
if (currentValuesLength === valuesLength) {
labelNode.classList.add(ACTIVE_CLASS)
labelNode.classList.remove(INDETERMINATE_CLASS)
} else {
labelNode.classList.remove(ACTIVE_CLASS)
labelNode.classList.add(INDETERMINATE_CLASS)
}
} else {
labelNode.classList.remove(ACTIVE_CLASS, INDETERMINATE_CLASS)
}
}
}
_onGroupChange = (id, addCallback, removeCallback) => {
if (this.store[id].currentValues.length === this.store[id].values.length) {
this.store[id].labelNode.classList.remove(ACTIVE_CLASS, INDETERMINATE_CLASS)
removeCallback(id)
} else {
this.store[id].labelNode.classList.add(ACTIVE_CLASS)
this.store[id].labelNode.classList.remove(INDETERMINATE_CLASS)
addCallback(id)
}
this._updatePlaceholderNodeStyle()
}
_addCurrentValues = (id) => {
this.count.currentValues += this.store[id].values.length - this.store[id].currentValues.length
this.store[id].currentValues = [...this.store[id].values]
this.store[id].valueNodeList.forEach((value) => {
value.classList.add(ACTIVE_CLASS)
value.classList.remove(INDETERMINATE_CLASS)
value.setAttribute('title', `${value.innerHTML} active`)
this.input[value.dataset.target].selected = true
})
}
_removeCurrentValues = (id) => {
this.count.currentValues -= this.store[id].currentValues.length
this.store[id].currentValues = []
this.store[id].valueNodeList.forEach((value) => {
value.classList.remove(ACTIVE_CLASS, INDETERMINATE_CLASS)
value.removeAttribute('title')
this.input[value.dataset.target].selected = false
})
}
_onPlaceholderChange = (addCallback, removeCallback) => {
if (this.count.currentValues > 0) {
for (const key in this.store) {
if (Object.prototype.hasOwnProperty.call(this.store, key)) {
this.store[key].currentValues = []
this._updateGroupNodeStyle(key)
removeCallback(key)
}
}
this.count.currentValues = 0
} else {
for (const key in this.store) {
if (Object.prototype.hasOwnProperty.call(this.store, key)) {
this.store[key].currentValues = this.store[key].values
this._updateGroupNodeStyle(key)
addCallback(key)
}
}
this.count.currentValues = this.count.values
}
this._updatePlaceholderNodeStyle()
}
_updatePlaceholderNodeStyle() {
if (this.count.currentValues > 0) {
if (this.count.values === this.count.currentValues) {
this.placeholder.classList.add(ACTIVE_CLASS)
this.placeholder.classList.remove(INDETERMINATE_CLASS)
} else {
this.placeholder.classList.remove(ACTIVE_CLASS)
this.placeholder.classList.add(INDETERMINATE_CLASS)
}
} else {
this.placeholder.classList.remove(ACTIVE_CLASS, INDETERMINATE_CLASS)
}
}
}
export default SelectMultiple