(Art by shahabalizadeh)
<!β
For devs who love ergonomics! You may appreciate Surreal if:
document.querySelector over.. and over..addEventListener over.. and over..document.querySelectorAll had Array functions..this would work in any inline <script> tagme() inside <script>
this but much more flexible!me in your CSS <style> tags, too? See our companion scriptme(), any(), NodeList, HTMLElement (..or arrays of these!)me()any()me() or any() can chain with any Surreal function.
me() can be used directly as a single element (like querySelector() or $())any() can use: for / forEach / filter / map (like querySelectorAll() or $())classAdd or class_add or addClass or add_class
camelCase (Javascript) or snake_case (Python, Rust, PHP, Ruby, SQL, CSS).me() / any() instead of $()me() is guaranteed to return 1 element (or first found, or null).any() is guaranteed to return an array (or empty array).Do surreal things with Locality of Behavior like:
<label for="file-input" >
<div class="uploader"></div>
<script>
me().on("dragover", ev => { halt(ev); me(ev).classAdd('.hover'); console.log("Files in drop zone.") })
me().on("dragleave", ev => { halt(ev); me(ev).classRemove('.hover'); console.log("Files left drop zone.") })
me().on("drop", ev => { halt(ev); me(ev).classRemove('.hover').classAdd('.loading'); me('#file-input').attribute('files', ev.dataTransfer.files); me('#form').send('change') })
</script>
</label>
See the Live Example! Then view source.
Surreal is only 320 lines. No build step. No dependencies.
π₯ Download into your project, and add <script src="/surreal.js"></script> in your <head>
Or, π via CDN: <script src="https://cdn.jsdelivr.net/gh/gnat/surreal@main/surreal.js"></script>
me(...)
".button", "#header", "h1", "body > .block"body, e, some_elementevent.currentTarget will be used.me(),any()document)
any('button', me('#header')).classAdd('red')
.red to any <button> inside of #headerme() β Get parent element of <script> without a .class or #id !me("body") Gets <body>me(".button") Gets the first <div class="button">...</div>. To get all of them use any()any(...)
me() but guaranteed to return an array (or empty array).any(".foo") β Get all matching elements.any(me()), me(any(".something"))me() and any()
me().classAdd('red') β Chain style. Recommended!classAdd(me(), 'red')globalsAdd() will automatically warn you of any clobbering issues!globalsAdd()
me().classAdd('red') becomes surreal.me().classAdd('red')classAdd(me(), 'red') becomes surreal.classAdd(surreal.me(), 'red')See: Quick Start and Reference and No Surreal Needed
me().classAdd('red')any("button").classAdd('red')me().on("click", ev => me(ev).fadeOut() )any('button').on('click', ev => { me(ev).styles('color: red') })any('button').run(_ => { alert(_) })me().styles('color: red')me().styles({ 'color':'red', 'background':'blue' })me().attribute('active', true)<div>I change color every second.
<script>
// On click, animate something new every second.
me().on("click", async ev => {
let el = me(ev) // Save target because async will lose it.
me(el).styles({ "transition": "background 1s" })
await sleep(1000)
me(el).styles({ "background": "red" })
await sleep(1000)
me(el).styles({ "background": "green" })
await sleep(1000)
me(el).styles({ "background": "blue" })
await sleep(1000)
me(el).styles({ "background": "none" })
await sleep(1000)
me(el).remove()
})
</script>
</div>
<div>I fade out and remove myself.
<script>me().on("click", ev => { me(ev).fadeOut() })</script>
</div>
<div>Change color every second.
<script>
// Run immediately.
(async (e = me()) => {
me(e).styles({ "transition": "background 1s" })
await sleep(1000)
me(e).styles({ "background": "red" })
await sleep(1000)
me(e).styles({ "background": "green" })
await sleep(1000)
me(e).styles({ "background": "blue" })
await sleep(1000)
me(e).styles({ "background": "none" })
await sleep(1000)
me(e).remove()
})()
</script>
</div>
<script>
// Run immediately, for every <button> globally!
(async () => {
any("button").fadeOut()
})()
</script>
any('button')?.forEach(...)
any('button')?.map(...)
Looking for DOM Selectors? Looking for stuff we recommend doing in vanilla JS?
me() and any()run
forEach but less wordy and works on single elements, too!me().run(e => { alert(e) })any('button').run(e => { alert(e) })remove
me().remove()any('button').remove()classAdd π class_add π addClass π add_class
me().classAdd('active'). is optional
me().classAdd('active') π me().classAdd('.active')classRemove π class_remove π removeClass π remove_class
me().classRemove('active')classToggle π class_toggle π toggleClass π toggle_class
me().classToggle('active')styles
me().styles('color: red') Add style.me().styles({ 'color':'red', 'background':'blue' }) Add multiple styles.me().styles({ 'background':null }) Remove style.attribute π attributes π attr
me().attribute('data-x')
any(...).run(...) or any(...).forEach(...)me().attribute('data-x', true)me().attribute({ 'data-x':'yes', 'data-y':'no' })me().attribute('data-x', null)me().attribute({ 'data-x': null, 'data-y':null })send π trigger
me().send('change')me().send('change', {'data':'thing'})dispatchEventon
me().on('click', ev => { me(ev).styles('background', 'red') })addEventListeneroff
me().off('click', fn)removeEventListeneroffAll
me().offAll()disable
me().disable()off(). Disables click, key, submit events.enable
me().enable()disable()createElement π create_element
e_new = createElement("div"); me().prepend(e_new)sleep
await sleep(1000, ev => { alert(ev) })async version of setTimeouthalt
halt(event)tick
await tick()await version of rAF / requestAnimationFrame.rAF
rAF(e => { return e })rIC
rIC(e => { return e })onloadAdd π onload_add π addOnload π add_onload
onloadAdd(_ => { alert("loaded!"); })<script>let e = me(); onloadAdd(_ => { me(e).on("click", ev => { alert("clicked") }) })</script>ready()window.onload while preventing overwrites of window.onload and predictable loading!?. example: me("video")?.requestFullscreen()<script> after the loaded element.
me('-') / me('prev')fadeOut
fadeIn
Build effects with me().styles({...}) with timelines using CSS transitioned await or callbacks.
Common effects included:
fadeOut π fade_out
remove=false.me().fadeOut()me().fadeOut(ev => { alert("Faded out!") }, 3000) Over 3 seconds then call function.fadeIn π fade_in
opacity: 0me().fadeIn()me().fadeIn(ev => { alert("Faded in!") }, 3000) Over 3 seconds then call function.More often than not, Vanilla JS is the easiest way!
Logging
console.log() console.warn() console.error()monitorEvents(me()) See: Chrome BlogBenchmarking / Time It!
console.time('name')console.timeEnd('name')Text / HTML Content
me().textContent = "hello world"
me().innerHTML = "<p>hello world</p>"me().innerText = "hello world"Children
me().childrenme().children.hidden = trueAppend / Prepend elements.
me().prepend(new_element)me().appendChild(new_element)me().insertBefore(element, other_element.firstChild)me().insertAdjacentHTML("beforebegin", new_element)AJAX (replace jQuery ajax())
fetch()
me().on("click", async event => {
let e = me(event)
// EXAMPLE 1: Hit an endpoint.
if((await fetch("/webhook")).ok) console.log("Did the thing.")
// EXAMPLE 2: Get content and replace me()
try {
let response = await fetch('/endpoint')
if (response.ok) e.innerHTML = await response.text()
else console.warn('fetch(): Bad response')
}
catch (error) { console.warn(`fetch(): ${error}`) }
})
XMLHttpRequest()
me().on("click", async event => {
let e = me(event)
// EXAMPLE 1: Hit an endpoint.
var xhr = new XMLHttpRequest()
xhr.open("GET", "/webhook")
xhr.send()
// EXAMPLE 2: Get content and replace me()
var xhr = new XMLHttpRequest()
xhr.open("GET", "/endpoint")
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) e.innerHTML = xhr.responseText
}
xhr.send()
})
## π Conventions & Tips
_ = for temporary or unused variables. Keep it short and sweet!e, el, elt = elemente, ev, evt = eventf, fn = function<script>{ let note = "hi"; function hey(text) { alert(text) }; me().on('click', ev => { hey(note) }) }
let and function is scoped within { }me()
me().hey = (text) => { alert(text) }me().on('click', (ev) => { me(ev).hey("hi") })me().on('click', ev => { /* add and call function here */ })<script type="module">
me() in modules will not see parentElement, explicit selectors are required: me(".mybutton")<input type="text" />me('-') or me('prev') or me('previous')
<input type="text" /> <script>me('-').value = "hello"</script>+ but in reverse -<form> <input type="text" n1 /> <script>me('[n1]', me()).value = "hello"</script> </form>me("#i_dont_exist")?.classAdd('active')me("#i_dont_exist", document, false)?.classAdd('active')Feel free to edit Surreal directly- but if you prefer, you can use plugins to effortlessly merge with new versions.
function pluginHello(e) {
function hello(e, name="World") {
console.log(`Hello ${name} from ${e}`)
return e // Make chainable.
}
// Add sugar
e.hello = (name) => { return hello(e, name) }
}
surreal.plugins.push(pluginHello)
Now use your function like: me().hello("Internet")
pluginEffects for a more comprehensive example.globalsAdd() If you do not want this, add it to the restricted list.Make an issue or pull request if you think people would like to use it! If itβs useful enough weβll want it in core.
document.plugins https://github.com/gnat/surreal/issues/52