Created
January 8, 2020 22:41
-
-
Save nxta/af8fbcd28f4cd1f2884fe67ffd3f25d1 to your computer and use it in GitHub Desktop.
Helper function for Flarum to traverse a Mithril Virtual DOM using basic CSS selector syntax
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* // Desired usage: | |
* import vdom from 'flarum/helpers/vdom'; | |
* import {find, vdom} from 'flarum/helpers/vdom'; | |
* | |
* // Supports tagname, class and id selectors | |
* vdom('.container ul#logo li.item.item-one a') // Returns array of vnodes | |
* find('.username') // Returns array of objects containing the vnode and more information {vnode, parent, index} | |
* | |
* // Example: | |
* vdom('.Avatar', vnode).forEach((vnode) => { | |
* vnode.attrs.style.borderColor = '#f00'; | |
* }); | |
* | |
* // Example: inserting something before a vnode | |
* find('.username', vnode).forEach((info) => { | |
* const {vnode, parent, index} = info; | |
* const el = avatar(user); | |
* parent.children.splice(index, 0, el); | |
* }); | |
*/ | |
function matchTag(tag) { | |
return node => node && node.tag && node.tag === tag; | |
} | |
function matchClass(className) { | |
className = className.substr(1); | |
return node => node && node.attrs && node.attrs.className && node.attrs.className.split(' ').includes(className); | |
} | |
function matchId(id) { | |
id = id.substr(1); | |
return node => node && ( (node.id && node.id === id) || (node.attrs && node.attrs.id && node.attrs.id === id) ); | |
} | |
function matchAll() { | |
return node => node && node.tag; | |
} | |
function matchesMultiple(s) { | |
s = s.substr(1); | |
return s.includes('.') || s.includes('#') | |
} | |
function multiMatcher(s) { | |
// get all parts starting with ^, # or . | |
const parts = s.match(/(^\w|(#|\.))[^(#|\.|\b)]*/gi); | |
const matchers = parts.map(getMatcher); | |
return node => !parts.filter((selector, i) => !matchers[i](node)).length; | |
} | |
function getMatcher(selector) { | |
const type = (matchesMultiple(selector)) ? '!' : selector[0]; | |
return ({'#': matchId, '.': matchClass, '*': matchAll, '!': multiMatcher}[type] || matchTag)(selector); | |
} | |
function parseQuery(q) { | |
return q.split(' ').map(getMatcher); | |
} | |
function findMatchingChildrensIndices(vnode, matcher) { | |
return vnode.children.reduce(function(a, child, i) { | |
if (matcher(child)) | |
a.push(i); | |
return a; | |
}, []); | |
} | |
export function find(query, vnode) { | |
const matchers = parseQuery(query); | |
return (vnode.children || []).reduce( | |
(function dig(depth, parent) { | |
parent = parent || vnode; | |
depth = depth || 0; | |
const matcher = matchers[depth]; | |
return (result, vnode, index, arr) => { | |
if (!vnode) | |
return result; | |
let found = 0; | |
if (matcher && matcher(vnode)) { | |
found = 1; | |
if (matchers.length === depth+found) | |
result.push({vnode, parent, index}); | |
} | |
if (vnode.children && Array.isArray(vnode.children)) | |
return vnode.children.reduce(dig(depth+found, vnode), result); | |
return result; | |
} | |
})(), | |
[] | |
); | |
} | |
export function vdom(query, vnode) { | |
return find(query, vnode).map(v => v.vnode); | |
} | |
export default vdom; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment