@govbr-ds/core
Version:
Padrão Digital de Governo
222 lines (205 loc) • 6.68 kB
JavaScript
/**
* Comportamento do grupo de checkboxes
*
* Cada checkbox parent em uma cadeia de checkbox terá um objeto próprio para tratar
* o seu comportamento e de seus filhos. Os eventListeners são declarados em cada objeto,
* de modo que a execução passa por objetos diferentes quando um checkbox é pai e filho ao mesmo tempo
*
* O comportamento é uma sincronia entre os eventos e o objeto que executa o evento
*/
export default class Checkgroup {
/**
* Instancia um comportamento de grupo de checkbox
* @param {object} trigger - Elemento DOM que representa o checkbox acionador do comportamento
*/
constructor(trigger) {
this.parent = trigger
this.parent.indeterminate = this.parent.hasAttribute('indeterminate')
// this.checkedLabel = trigger.dataset.checkedLabel
// this.uncheckedLabel = trigger.dataset.uncheckedLabel
this.children = this._setChildren(trigger.dataset.parent)
}
/**
* Pega dinamincamente o valor do atributo data-checked-label
* @type {string} - O valor do atributo data-checked-label ou o valor da label, caso não tenha o atributo
* @readonly
* @public
*/
get checkedLabel() {
return this.parent.dataset.checkedLabel || this.parent.nextElementSibling.innerText
}
/**
* Pega dinamincamente o valor do atributo data-unchecked-label
* @type {string} - O valor do atributo data-unchecked-label ou o valor da label, caso não tenha o atributo
* @readonly
* @public
*/
get uncheckedLabel() {
return this.parent.dataset.uncheckedLabel || this.parent.nextElementSibling.innerText
}
/**
* Configurar o comportamento do grupo de checkbox
* @public
*/
setBehavior() {
this._setParentBehavior()
this._setChildBehavior()
}
/**
* Obtem todos os checkboxes filhos do checkbox pai
* @param {string} tag - Nome que indica a relação entre checkbox pai e checkbox filho
* @returns {array} - Array contendo os elementos DOM que representam os checkboxes filhos.
*/
_setChildren(tag) {
return document.querySelectorAll(`[data-child="${tag}"]`)
}
/**
* Trata do comportamento do checkbox pai
* @private
*/
_setParentBehavior() {
this.parent.addEventListener('click', this._handleParentClick.bind(this))
this.parent.addEventListener('change', this._handleParentChange.bind(this))
}
/**
* Handler que trata o evento click no parent
* @private
*/
_handleParentClick() {
if (!this.parent.hasAttribute('data-parent-sync')) {
this._setIndeterminateStateOnClick()
}
this._setParentCheckboxLabel()
}
/**
* Trata o estado indeterminado no evento click da etapa de interação do usuário
* @private
*/
_setIndeterminateStateOnClick() {
if (this.parent.hasAttribute('indeterminate')) {
this.parent.removeAttribute('indeterminate')
this.parent.indeterminate = false
this.parent.checked = true
}
}
/**
* Handler que trata o evento change no parent
* @private
*/
_handleParentChange() {
if (!this.parent.hasAttribute('data-parent-sync')) {
this._changeChildState()
this._setParentCheckboxLabel()
} else {
this.parent.removeAttribute('data-parent-sync')
}
}
/**
* Trata do estado do checkboxes filhos
* @private
*/
_changeChildState() {
this.children.forEach((child) => {
if (child.checked !== this.parent.checked || child.hasAttribute('indeterminate')) {
child.removeAttribute('indeterminate')
child.indeterminate = false
child.checked = this.parent.checked
child.dispatchEvent(new Event('change'))
}
})
}
/**
* Trata do comportamento dos checkboxes filhos
* @private
*/
_setChildBehavior() {
this.children.forEach((child) => {
child.addEventListener('click', this._handleChildClick.bind(this))
child.addEventListener('change', this._handleChildChange.bind(this))
})
}
/**
* Handler que trata do evento click no filho
* @param {object} event - Objeto Event
* @private
*/
_handleChildClick(event) {
event.currentTarget.setAttribute('data-child-sync', '')
}
/**
* Handler que trata do evento change no filho
* @param {object} event - Objeto Event
* @private
*/
_handleChildChange(event) {
if (event.currentTarget.hasAttribute('data-child-sync')) {
this._setIndeterminateStateOnChildChange()
this.parent.setAttribute('data-parent-sync', '')
this.parent.dispatchEvent(new Event('click'))
this.parent.dispatchEvent(new Event('change'))
event.currentTarget.removeAttribute('data-child-sync')
}
}
/**
* Trata o estado indeterminado no evento click da etapa de sincronia
* @private
*/
_setIndeterminateStateOnChildChange() {
if (this._isAllChildrenChecked()) {
this.parent.removeAttribute('indeterminate')
this.parent.indeterminate = false
this.parent.checked = true
} else if (this._isAllChildrenUnchecked()) {
this.parent.removeAttribute('indeterminate')
this.parent.indeterminate = false
this.parent.checked = false
} else {
this.parent.setAttribute('indeterminate', '')
this.parent.indeterminate = true
this.parent.checked = true
}
}
/**
* Verifica se todos os checkboxes filhos estão selecionados
* @returns {boolean} - true se todos os checkboxes filhos estão selecionados
* false se pelo menos 1 checkbox filho não está selecionado
* @private
*/
_isAllChildrenChecked() {
let allChildChecked = true
this.children.forEach((child) => {
if (!child.checked || child.indeterminate) {
allChildChecked = false
}
})
return allChildChecked
}
/**
* Verifica se todos so checkboxes filhos estão desselecionados
* @returns {boolean} - true se todos os checkboxes filhos estão desselecionados
* false se pelo menos 1 checkbox filho está selecionado
* @private
*/
_isAllChildrenUnchecked() {
let allChildUnchecked = true
this.children.forEach((child) => {
if (child.checked) {
allChildUnchecked = false
}
})
return allChildUnchecked
}
/**
* Configura a label do checkbox pai
* @private
*/
_setParentCheckboxLabel() {
if (this.parent.checked && !this.parent.indeterminate) {
this.parent.nextElementSibling.innerHTML = this.uncheckedLabel
this.parent.setAttribute('aria-label', this.uncheckedLabel)
} else {
this.parent.nextElementSibling.innerHTML = this.checkedLabel
this.parent.setAttribute('aria-label', this.checkedLabel)
}
}
}