a small opinionated future forward front-end framework
MIT License
a small flavorful and unapologetic library built for the modern web
Feel free to fork or raise issues. Constructive criticism is always welcome
To use rilti just download /dist/rilti.min.js and pop it in a script tag. If you have any issues just tell me, I'm on it.
yarn add rilti
npm i rilti
or
<script src="https://unpkg.com/rilti"></script>
Different strokes for different folks:
Also look at rilti.on
which can be used like this on['any-event'](node, func, =options)
,
as well as like this on('any-event', node)(func, =options)
and also on(node, { click: e => {} }, =options)
.
const { component, componentReady, dom: { h1 }} = rilti
component('counter-button', {
bind: {
count: {
key: 'clicks:innerText',
val: 0,
views: {
clicks: count => `clicks: ${count}`
}
}
},
on: {
click: (e, el) => ++el.count
}
})
componentReady('body > counter-button', el => {
const tellEm = h1['tell-em'](`You just won't stop clicking huh?`)
el.$count.on.change(count => {
if (count > 20 && count < 40 && !tellEm.mounted) tellEm.render('body')
else if (count > 40 && count < 100) tellEm.txt += ' Seriously? '
else if (count > 100) tellEm.txt = 'What? You want a prize or something?'
})
})
const {databind, dom: {button, div, h1}} = rilti
div.multicounter({
render: 'body', // <- apend to <body> on load
bind: {
count: {
val: 0,
views: {
display: count => `current count is at: ${count}`
}
}
}
},
host => [ // <com-po-nents> bind natively, other elements bind to el.bind['$bind/bindValue']
h1(
host.bind.$count.text('display')
),
button({onclick: e => ++host.bind.count}, '+'),
button({onclick: e => --host.bind.count}, '-')
]
)
rilti.dom.div.pointer_tracker({
$: 'body',
css: {width: '300px', height: '300px'},
bind: {
pointer: {
key: 'location:innerText',
val: {x: 0, y: 0},
views: {
location: ({x, y}) => `Pointer is at (${x}x, ${y}y)`
},
change: ({x, y}) => ({x: x.toFixed(2), y: y.toFixed(2)})
}
},
onpointermove ({x, y}, el) {
el.bind.pointer = {x, y}
}
})
the above produces this:
<div class="pointer-tracker" style="width: 300px; height: 300px;">
Pointer is at (0x, 0y)
</div>
Stop writing html (yes JSX too)! Just generate everything, it's so simple.
const {a, button, h1, header, nav, section} = rilti.dom
section.navbar({$: 'body'}, // <- $ is shorthand for render: 'Node/Selector'
header(h1('My Wicked Website')),
nav(
'Home Blog About Contact'.split(' ').map(name =>
a['nav-bn']({href: '#/' + name.toLowerCase()}, name)
),
a['nav-bn']({
css: {backgroundColor: 'white', color: 'dimgrey'},
href: 'https://github.com/SaulDoesCode',
target: '_blank'
},
'Github'
)
)
)
The above produces this html
<section class="navbar">
<header>
<h1>My Wicked Website</h1>
</header>
<nav>
<a class="nv-btn" href="#/home">
<button>Home</button>
</a>
<a class="nv-btn" href="#/blog">
<button>Blog</button>
</a>
<a class="nv-btn" href="#/about">
<button>About</button>
</a>
<a class="nv-btn" href="#/contact">
<button>Contact</button>
</a>
<a class="nv-btn" href="https://github.com/SaulDoesCode/rilti.js" target="_blank" style="background-color: dimgrey; color: white;">
<button>Github</button>
</a>
</nav>
</section>
method | description |
---|---|
.dom(tag, =options, ...children) |
where the magic happens, define behavior for elements and see them come to life |
.dom["any-tag"](=options, ...children) |
pre-bound tag version of .dom
|
.query(string, Selector/Node) |
improved alternative to document.querySelector
|
.queryAll(string, Selector/Node) |
improved alternative to document.querySelectorAll
|
.queryAsync(string, func, Selector/Node) |
same as querySelector but async, good for pre-load logic |
.queryEach(string, Selector/Node, func) |
queries nodes returned by selector and iterates over them like .forEach would |
.dom.frag(=string) |
create a fragment or convert html text to nodes |
.on(target, type, listener, =options) |
generates event listener |
.once(target, type, listener, =options) |
generates event listener that triggers only once |
.curry(func, =argsLimit) |
curries a function |
.component(tag, {create, mount, unmount, attr, props, methods}) |
define custom elements, no polyfills needed |
.each(iterable, func) |
loop through objects, numbers, array(like)s, sets, maps... |
.extend(hostObj, obj, =safeMode) |
extends host object with all props of other object, won't overwrite if safe is true |
.flatten(arraylike) |
flattens multidimensional arraylike objects |
.render(AlmostAnything, Selector/Node, =connector = 'appendChild') |
renders things, independent of ready state |
.run(func) |
asynchronously executes a given function when the DOM is loaded |
.runAsync(func, ...args) |
run a function asynchronously |
.isMounted(node, =parentNode) |
determines whether or not the dom or other node contains a specific node |
usage : rilti.isX( {any} ) // -> boolean
isArr, isNil, isDef, isObj, isFunc, isBool, isStr, isNum, isArrlike, isNodeList, isNode, isPrimitive, isPromise, isRenderable, isRegExp, isInput, isEmpty, isEl
rilti contains a domfn
that contains several useful dom manipulation functions.
these fucntions will all return the node passed as the first argument unless specified
otherwise such as with has/get(this/that) type functions
const {
attach,
css, // (node, stylePropery, val) || (node, { styleProp:'4em' }) set element.style properties
class, // (node, class, =state) add/remove or toggle classes
hasClass, // (node, class) -> bool
attr, // (node, attrNameOrObj, =value): attr(el, 'href', '/') or attr(el, 'href') -> '/'
removeAttribute, // (node, ...attrNames) removes attributes
hasAttr, // hasAttr(node, attrName) -> bool
attrToggle, // (node, attrName, state = !hasAttr, =val = getAttr(name) || '')
emit, // (node, {type string/Event/CustomEvent}) dispatchEvents on node
append, prepend, appendTo, prependTo, // (node, selectorOrNode)
remove, // (node, =afterMS) // remove node or remove after timeout
} = rilti.domfn
everything found in rilti.domfn will be available as:
rilti.$(Node).domfnMethod(...args)
const contentCard = async (src, hidden = false) => {
const card = rilti.dom.div.card() // <- <div class="card"></div>
card.class({hidden}) // add class .hidden if hidden === true
card.class.hidden = hidden // <- this works too
card.attr['aria-hidden'] = hidden // set attribute
card.css({
'--custom-theme': 'hsl(331, 70%, 48%)',
borderTop: '2px solid var(--custom-theme, --theme-color)'
})
card.prependTo('.content-list')
try {
const res = await fetch(src)
card.append(await res.text())
card.on.click((e, card) => {
card.once.animationend(() => {
card.class('flip-animation', false)
})
card.class('flip-animation', true)
})
} catch (e) {
card.remove()
console.error('could not load content from: ' + src)
}
}
dom['any-arbitrary-tag'](=options, ...children) -> Node/Element
dom['random-tag'] // <- <random-tag class="with random chainable classes">
.with
.random
['chainable']
.classes({
// render to dom using selectors or nodes
render: '.main > header',
// add attributes
attr: {contenteditable: true},
// set classes
class: 'card active',
// or
class: ['card', 'active']
// or conditionally
class: {
card: true,
active: false // active won't be added
},
// some styles?
css: {
boxShadow: '0 2px 6px rgba(0,0,0,.12)',
'--highlight-color': 'crimson',
// ^- oh yeah, css variables work too
},
// attach properties to the element
props: {
oldtxt: '',
// create property get/set traps
accesors: {
contents: {
get: el => el.txt,
set (el, val) { el.txt = val.trim() }
},
// or as one function
innerds (el, val) {
if (val == null) return el.txt
el.txt = val.trim()
}
},
// plain getter/setters work too
get ye_old_txt () { return this.innerText },
set ye_old_txt (val) { this.innerText = val.trim() }
},
methods: {
// el will be pre-bound upon execution
// think of it like self in python classes
// or rust struct methods
warn (el, ...args) {
el.oldtxt = el.txt
el.contents = 'Sure you want to remove random-tag?'
},
reset (el) { el.contents = el.oldtxt }
},
// listen for events
on: {
click (evt, {warn}) { warn() },
mouseout (evt, {reset}) { reset() }
},
// if there's just one listener then use:
// once/onxevent: fn instead of once: { evt: fn }
oncedblclick (evt, el) { el.remove() },
// manage the element's lifecycle
cycle: {
create (el) { /*...*/ },
mount (el) { /*...*/ },
unmount (el) { /*...*/ },
remount (el) { /*...*/ }
}
},
...children // [], "", =>, Node, NodeList : should all render
)
// observe attributes with vue-like directives
rilti.directive('custom-attr', {
init (element, value) { ... },
update (element, value, oldValue) { ... },
remove (element, value) { ... }
})
// revoke a directive
rilti.directives.delete('custom-attr')
rilti.component('tick-box', {
props: {
accessors: {
ticked: {
get: el => el.attr.has('ticked'),
set: (el, state) => el.attrToggle('ticked', !!state)
}
}
},
create (el) {
el.on.click(e => el.attrToggle('ticked'))
el.css({
display: 'block',
width: '20px',
height: '20px',
margin: '5px auto',
cursor: 'pointer',
border: `1px solid ${el.ticked ? 'white' : 'dimgrey'}`,
backgroundColor: el.ticked ? 'dimgrey' : 'white'
})
},
mount (el) {
console.log('tick-box mounted to ', el.parent)
},
unmount (el) {
console.log('unmounted: tick-box is no more :(')
},
attr: {
ticked: {
update (el) {
el.css({
backgroundColor: 'dimgrey',
border: `1px solid #fff`
})
el.emit('ticked', true)
},
remove (el) {
el.css({
backgroundColor: '#fff',
border: `1px solid dimgrey`
})
el.emit('ticked', false)
}
}
}
})