This is an itch I find that needs scratching every now and then — a distraction — and a good exercise to re-familiarise myself with the some of vanilla js’s dom methods. classList’s toggle force parameter would be case in point
I have basically carried on from the small classList script I wrote the other day on here.
A script to handle a few of the more common tasks e.g. toggling classes and adding events in a jquery like fashion — if that’s your thing?
Javascript
// helper functions
import { forEachable, flattenAll } from './helpers/array-helpers.js'
/**
* array factory function
* @param {HTMLElements, nodeList, element} elements
* @returns {Object} array methods forEach etc
*/
const arrayMethods = (elements) => {
const nodes = forEachable(elements)
return {
forEach: function (fn, context) {
nodes.forEach(fn, context)
return this
}
}
}
/**
* classlist transform factory function
* @param {HTMLElements, nodeList, element} elements
* @returns {Object} classList methods add, remove, toggle
*/
const classTransforms = (elements) => {
const nodes = forEachable(elements)
// return a callback for nodes.forEach
const classListTransform = (method, classNames) =>
(elem) => elem.classList[method](...classNames)
return {
addClass: function (...classNames) {
nodes.forEach(classListTransform('add', classNames))
return this
},
removeClass: function (...classNames) {
nodes.forEach(classListTransform('remove', classNames))
return this
},
toggleClass: function (className, force) {
nodes.forEach(
(elem) => elem.classList.toggle(className, force)
)
return this
}
}
}
/**
* listeners factory function
* @param {HTMLElements, nodeList, element} elements
* @returns {Object} eventListener methods add and remove
*/
const listeners = (elements) => {
const nodes = forEachable(elements)
return {
addEvent: function (type, fn, optional) {
nodes.forEach(
element => element.addEventListener(type, fn, optional)
)
return this
},
removeEvent: function (type, fn, optional) {
nodes.forEach(
element => element.removeEventListener(type, fn, optional)
)
return this
}
}
}
/**
* Selector factory function
* @param {String, HTMLElements, nodeList or an element}
* @returns {Object} with dom manipulation methods
*/
const QueryJS = (selection, root = document) => {
// single out #id selectors for querySelector
const isId = (selector) => /^#\S+$/.test(selector.trim())
const nodes = flattenAll([
(typeof selection === 'string')
// a string selector
? (isId(selection))
? root.querySelector(selection)
: root.querySelectorAll(selection)
// or an HTMLCollection, nodeList, an element
: selection
])
// return mixin
return {
...arrayMethods(nodes),
...classTransforms(nodes),
...listeners(nodes),
get nodes () { return nodes }, // return array of all nodes
get node () { return nodes[0] } // return first node
}
}
export { QueryJS as default, classTransforms, listeners }
Chose not go down the prototype route, instead opting for mixins and modules which can be imported and used in their own right. Edit: Got a feeling that may backfire
A test script, based on a couple of the posts/assignments on here.
import queryJS, { listeners } from './js/queryJS.js'
queryJS(window).addEvent('DOMContentLoaded', () => {
const options = {
root: queryJS('.wrapper').node,
rootMargin: '300px',
threshold: .5
}
const intersecting = (entries, observer) => {
for (const { target, isIntersecting } of entries) {
queryJS(`#nav-tab-${target.dataset.section}`)
.toggleClass('active', isIntersecting)
}
}
const observer = new IntersectionObserver(intersecting, options)
const observe = elem => observer.observe(elem)
queryJS('section').forEach(observe)
// Scroll into View:
//
// A test here using the listeners module without QueryJS and passing in a nodeList.
listeners(document.querySelectorAll('.tab-menu a'))
.addEvent('click', (event) => {
event.preventDefault()
const { tab } = event.target.dataset
const options = { behavior: 'smooth', block: 'center' }
queryJS(`#section-${tab}`).node.scrollIntoView(options)
})
})
Just a bit of fun : )