🗿 Surreal
Tiny jQuery alternative for plain Javascript with inline Locality of Behavior!
(Art by shahabalizadeh)
Why does this exist?
For devs who love ergonomics! You may appreciate Surreal if:
- You want to stay as close as possible to Vanilla JS.
- Hate typing
document.querySelector over… and over…
- Hate typing
addEventListener over… and over…
- Really wish
document.querySelectorAll had Array functions…
- Really wish
this would work in any inline <script> tag
- Enjoyed using jQuery selector syntax.
- Animations, timelines, tweens with no extra libraries.
- Only 320 lines. No build step. No dependencies.
- Pairs well with htmx
- Want fewer layers, less complexity. Are aware of the cargo cult. ✈️
✨ What does it add to Javascript?
- ⚡️ Locality of Behavior (LoB) Use
me() inside <script>
- No .class or #id needed! Get an element without creating a unique name.
this but much more flexible!
- Want
me in your CSS <style> tags, too? See our companion script
- 🔗 Call chaining, jQuery style.
- ♻️ Functions work seamlessly on 1 element or arrays of elements!
- All functions can use:
me(), any(), NodeList, HTMLElement (…or arrays of these!)
- Get 1 element:
me()
- …or many elements:
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 $())
- 🌗 No forced style. Use:
classAdd or class_add or addClass or add_class
- Use
camelCase (Javascript) or snake_case (Python, Rust, PHP, Ruby, SQL, CSS).
🤔 Why use me() / any() instead of $()
- 💡 Solves the classic jQuery bloat problem: Am I getting 1 element or an array of elements?
me() is guaranteed to return 1 element (or first found, or null).
any() is guaranteed to return an array (or empty array).
- No more checks = write less code. Bonus: Reads more like self-documenting english.
👁️ How does it look?
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.
🎁 Install
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>
⚡ Usage
🔍️ DOM Selection
- Select one element:
me(...)
me() 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()
- Can also be any of:
- CSS selector:
".button", "#header", "h1", "body > .block"
- Variables:
body, e, some_element
- Events:
event.currentTarget will be used.
- Surreal selectors:
me(),any()
- Choose a start location in the DOM with parameter 2. (Default:
document)
any('button', me('#header')).classAdd('red')
- Add
.red to any <button> inside of #header
- Select one or more elements as an array:
any(...)
any(".foo") Get all matching elements.
- Like
me() but guaranteed to return an array (or empty array).
- Easily convert between arrays of elements and single elements:
any(me()), me(any(".something"))
✏️ DOM Functions
- ♻️ All functions work on single elements or arrays of elements.
- 🔗 Start a chain using
me() and any()
- 🟢 Style A:
me().classAdd('red') ⭐ Recommended! Chain style.
- 🔵 Style B:
classAdd(me(), 'red')
- Global conveniences help you write less code.
globalsAdd() will automatically warn you of any clobbering issues.
- If you do not want global conveniences, delete
globalsAdd()
- 🟠 Style C:
surreal.me().classAdd('red')
- 🟡 Style D:
surreal.classAdd(surreal.me(), 'red')
See: Quick Start and Reference and No Surreal Needed
⚡ Quick Start
- Add a class
me().classAdd('red')
any("button").classAdd('red')
- Events
me().on("click", ev => me(ev).fadeOut() )
any('button').on('click', ev => { me(ev).styles('color: red') })
- Run functions over elements.
any('button').run(_ => { alert(_) })
- Styles / CSS
me().styles('color: red')
me().styles({ 'color':'red', 'background':'blue' })
- Attributes
me().attribute('active', true)
Timeline animations without any libraries.
<div>I change color every second.
<script>
me().on("click", async ev => {
let el = me(ev)
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>
(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>
(async () => {
any("button").fadeOut()
})()
</script>
Array methods
any('button')?.forEach(...)
any('button')?.map(...)
👁️ Functions
Looking for DOM Selectors?
Looking for stuff we recommend doing in vanilla JS?
🧭 Legend
- 🔗 Chainable off
me() and any()
- 🌐 Global
- 🌗 Alias
- 🔌 Built-in Plugin
👁️ At a glance
- 🔗
run
- It’s
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')
- Leading
. is optional
- Same thing:
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
- Get:
me().attribute('data-x')
- For single elements.
- For many elements, wrap it in:
any(...).run(...) or any(...).forEach(...)
- Set:
me().attribute('data-x', true)
- Set multiple:
me().attribute({ 'data-x':'yes', 'data-y':'no' })
- Remove:
me().attribute('data-x', null)
- Remove multiple:
me().attribute({ 'data-x': null, 'data-y':null })
- 🔗
send 🌗 trigger
me().send('change')
me().send('change', {'data':'thing'})
- Wraps
dispatchEvent
- 🔗
on
me().on('click', ev => { me(ev).styles('background', 'red') })
- Wraps
addEventListener
- 🔗
off
me().off('click', fn)
- Wraps
removeEventListener
- 🔗
offAll
- 🔗
disable
me().disable()
- Easy alternative to
off(). Disables click, key, submit events.
- 🔗
enable
me().enable()
- Opposite of
disable()
- 🌐
createElement 🌗 create_element
- 🌐
sleep
await sleep(1000, ev => { alert(ev) })
async version of setTimeout
- Wonderful for animation timelines.
- 🌐
halt
halt(event)
- When recieving an event, stop propagation, and prevent default actions (such as form submit).
- Wrapper for stopPropagation and preventDefault
- 🌐
tick
await tick()
await version of rAF / requestAnimationFrame.
- Waits for 1 frame (browser paint).
- Useful to guarantee CSS properties are applied, and events have propagated.
- 🌐
rAF
rAF(e => { return e })
- Calls after 1 frame (browser paint). Alias of requestAnimationFrame
- Useful to guarantee CSS properties are applied, and events have propagated.
- 🌐
rIC
- 🌐
onloadAdd 🌗 onload_add 🌗 addOnload 🌗 add_onload
onloadAdd(_ => { alert("loaded!"); })
<script>let e = me(); onloadAdd(_ => { me(e).on("click", ev => { alert("clicked") }) })</script>
- Execute after the DOM is ready. Similar to jquery
ready()
- Add to
window.onload while preventing overwrites of window.onload and predictable loading!
- Alternatives:
- Skip missing elements using
?. example: me("video")?.requestFullscreen()
- Place
<script> after the loaded element.
- 🔌
fadeOut
- 🔌
fadeIn
🔌 Built-in Plugins
Effects
Build effects with me().styles({...}) with timelines using CSS transitioned await or callbacks.
Common effects included:
-
🔗 fadeOut 🌗 fade_out
- Fade out and remove element.
- Keep element with
remove=false.
me().fadeOut()
me().fadeOut(ev => { alert("Faded out!") }, 3000) Over 3 seconds then call function.
-
🔗 fadeIn 🌗 fade_in
- Fade in existing element which has
opacity: 0
me().fadeIn()
me().fadeIn(ev => { alert("Faded in!") }, 3000) Over 3 seconds then call function.
⚪ No Surreal Needed
More often than not, Vanilla JS is the easiest way!
Logging
console.log() console.warn() console.error()
monitorEvents(me())
Benchmarking
console.time('name')
console.timeEnd('name')
Text / HTML Content
me().textContent = "hello world"
me().innerHTML = "<p>hello world</p>"
me().innerText = "hello world"
Child Elements
me().children
me().children.hidden = true
Appending / Prepending Elements
me().prepend(new_element)
me().appendChild(new_element)
me().insertBefore(element, other_element.firstChild)
me().insertAdjacentHTML("beforebegin", new_element)
🔄 AJAX (replace jQuery ajax())
me().on("click", async event => {
let e = me(event)
if((await fetch("/thing")).ok) console.log("Got thing.")
try {
let response = await fetch('/thing')
if (response.ok) e.innerHTML = await response.text()
else console.warn('fetch(): Bad response')
}
catch (error) { console.warn(`fetch(): ${error}`) }
})
- Example using
XMLHttpRequest()
me().on("click", event => {
let e = me(event)
var xhr = new XMLHttpRequest()
xhr.open("GET", "/thing")
xhr.send()
var xhr = new XMLHttpRequest()
xhr.open("GET", "/thing")
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status < 300) e.innerHTML = xhr.responseText
}
xhr.send()
})
💎 Conventions & Tips
- Many ideas can be done in HTML / CSS (ex: dropdowns)
_ = for temporary or unused variables. Keep it short and sweet!
e, el, elt = element
e, ev, evt = event
f, fn = function
Scoping
- ⭐ Scope with block
{ … }
{ let note = "hi"; function hey(text) { alert(text) }; me().on('click', ev => { hey(note) }) }
let and function are scoped within { }
- ⭐ Scope with
me() block
me().hey = (text) => { alert(text) }
me().on('click', (ev) => { me(ev).hey("hi") })
- ⭐ Scope with event block
me().on('click', event => { /* ... */ })
- Scope with inline module
<script type="module">
- Warning:
me() will not see parentElement. Explicit selector required: me(".mybutton")
Selecting void elements (<input type="text" />)
- Use:
me('-') or me('prev') or me('previous')
<input type="text" /> <script>me('-').value = "hello"</script>
- Inspired by the CSS “next sibling” combinator
+ but in reverse -
- Or, use a relative start.
<form> <input type="text" n1 /> <script>me('[n1]', me()).value = "hello"</script> </form>
Ignoring missing elements (Null safety)
me("#i_dont_exist")?.classAdd('active')
- Silent warnings:
me("#i_dont_exist", document, false)?.classAdd('active')
🔌 Your own plugin
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
}
e.hello = (name) => { return hello(e, name) }
}
surreal.plugins.push(pluginHello)
Now use your function like: me().hello("Internet")
- See the included
pluginEffects for a more comprehensive example.
- Your functions are added globally by
globalsAdd() If you do not want this, add it to the restricted list.
- Refer to an existing function to see how to make yours work with 1 or many elements.
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.
🌘 Change Log
1.3.4
📚️ Inspired by
⭐ Check out awesome-surreal for extra examples, plugins, and resources !
评论