stimulus_reflex
Version:
Build reactive applications with the Rails tooling you already know and love.
201 lines (173 loc) • 5.48 kB
JavaScript
import Debug from './debug'
import Deprecate from './deprecate'
import Schema from './schema'
import { elementToXPath, XPathToArray } from './utils'
const multipleInstances = element => {
if (['checkbox', 'radio'].includes(element.type)) {
return (
document.querySelectorAll(
`input[type="${element.type}"][name="${element.name}"]`
).length > 1
)
}
return false
}
const collectCheckedOptions = element => {
return Array.from(element.querySelectorAll('option:checked'))
.concat(
Array.from(
document.querySelectorAll(
`input[type="${element.type}"][name="${element.name}"]`
)
).filter(elem => elem.checked)
)
.map(o => o.value)
}
// Returns a string value for the passed array.
//
// attributeValue(['', 'one', null, 'two', 'three ']) // 'one two three'
//
const attributeValue = (values = []) => {
const value = Array.from(
new Set(
values
.filter(v => v && String(v).length)
.map(v => v.trim())
)
).join(' ').trim()
return value.length > 0 ? value : null
}
// Returns an array for the passed string value by splitting on whitespace.
//
// attributeValues('one two three ') // ['one', 'two', 'three']
//
const attributeValues = value => {
if (!value) return []
if (!value.length) return []
return value.split(' ').filter(v => v.trim().length)
}
// Extracts attributes from a DOM element.
//
const extractElementAttributes = element => {
let attrs = Array.from(element.attributes).reduce((memo, attr) => {
memo[attr.name] = attr.value
return memo
}, {})
attrs.checked = !!element.checked
attrs.selected = !!element.selected
attrs.tag_name = element.tagName
if (element.tagName.match(/select/i) || multipleInstances(element)) {
const collectedOptions = collectCheckedOptions(element)
attrs.values = collectedOptions
attrs.value = collectedOptions.join(',')
} else {
attrs.value = element.value
}
return attrs
}
// Returns an array of elements for the provided tokens.
// Tokens is an array of space separated string coming from the `data-reflex-dataset`
// or `data-reflex-dataset-all` attribute.
//
const getElementsFromTokens = (element, tokens) => {
if (!tokens || tokens.length === 0) return []
let elements = [element]
const xPath = elementToXPath(element)
tokens.forEach(token => {
try {
switch (token) {
case 'combined':
if (Deprecate.enabled)
console.warn(
"In the next version of StimulusReflex, the 'combined' option to data-reflex-dataset will become 'ancestors'."
)
elements = [
...elements,
...XPathToArray(`${xPath}/ancestor::*`, true)
]
break
case 'ancestors':
elements = [
...elements,
...XPathToArray(`${xPath}/ancestor::*`, true)
]
break
case 'parent':
elements = [...elements, ...XPathToArray(`${xPath}/parent::*`)]
break
case 'siblings':
elements = [
...elements,
...XPathToArray(
`${xPath}/preceding-sibling::*|${xPath}/following-sibling::*`
)
]
break
case 'children':
elements = [...elements, ...XPathToArray(`${xPath}/child::*`)]
break
case 'descendants':
elements = [...elements, ...XPathToArray(`${xPath}/descendant::*`)]
break
default:
elements = [...elements, ...document.querySelectorAll(token)]
}
} catch (error) {
if (Debug.enabled) console.error(error)
}
})
return elements
}
// Extracts the dataset of an element and combines it with the data attributes from all specified tokens
//
const extractElementDataset = element => {
const dataset = element.attributes[Schema.reflexDataset]
const allDataset = element.attributes[Schema.reflexDatasetAll]
const tokens = (dataset && dataset.value.split(' ')) || []
const allTokens = (allDataset && allDataset.value.split(' ')) || []
const datasetElements = getElementsFromTokens(element, tokens)
const datasetAllElements = getElementsFromTokens(element, allTokens)
const datasetAttributes = datasetElements.reduce((acc, ele) => {
return { ...extractDataAttributes(ele), ...acc }
}, {})
const reflexElementAttributes = extractDataAttributes(element)
const elementDataset = {
dataset: { ...reflexElementAttributes, ...datasetAttributes },
datasetAll: {}
}
datasetAllElements.forEach(element => {
const elementAttributes = extractDataAttributes(element)
Object.keys(elementAttributes).forEach(key => {
const value = elementAttributes[key]
if (
elementDataset.datasetAll[key] &&
Array.isArray(elementDataset.datasetAll[key])
) {
elementDataset.datasetAll[key].push(value)
} else {
elementDataset.datasetAll[key] = [value]
}
})
})
return elementDataset
}
// Extracts all data attributes from a DOM element.
//
const extractDataAttributes = element => {
let attrs = {}
if (element && element.attributes) {
Array.from(element.attributes).forEach(attr => {
if (attr.name.startsWith('data-')) {
attrs[attr.name] = attr.value
}
})
}
return attrs
}
export {
attributeValue,
attributeValues,
extractElementAttributes,
extractElementDataset,
extractDataAttributes
}