Tiny jQuery alternative for plain Javascript featuring inline Locality of Behavior!
(Art by shahabalizadeh)
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 340 lines. No build step. No dependencies.
- Pairs well with htmx
- Want fewer layers, less complexity. Are aware of the cargo cult.
โ๏ธ
- โก๏ธ Locality of Behavior (LoB) Use
me()
inside<script>
- Get an element without creating a unique name: No .class or #id needed!
this
but better!- 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()
orany()
can chain with any Surreal function.me()
can be used directly as a single element (likequerySelector()
or$()
)any()
can use:for
/forEach
/filter
/map
(likequerySelectorAll()
or$()
)
- All functions can use:
- ๐ No forced style. Use:
classAdd
orclass_add
oraddClass
oradd_class
- Use
camelCase
(Javascript) orsnake_case
(Python, Rust, PHP, Ruby, SQL, CSS).
- Use
- ๐ก We solve the classic jQuery code 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 = you write less code. Bonus: Code reads more like self-documenting english.
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).classAdd('.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').trigger('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, ๐ use the CDN: <script src="https://cdn.jsdelivr.net/gh/gnat/surreal/surreal.js"></script>
- Select one element:
me(...)
- Can 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()
- Adding a
start=
parameter provides a starting DOM location to select from. Default isdocument
โถ๏ธ any('button', start='header').classAdd('red')
- CSS selector:
me()
Get current element for Locality of Behavior in<script>
without an explicit .class or #idme("body")
Gets<body>
me(".button")
Gets the first<div class="button">...</div>
. To get all of them useany()
- Can be any of:
- Select one or more elements as an array:
any(...)
- Similar to
me()
but guaranteed to return an array (or empty array). any(".foo")
Gets all matching elements, such as:<div class="foo">...</div>
- Feel free to convert between arrays of elements and single elements:
any(me())
,me(any(".something"))
- Similar to
- โป๏ธ All functions work on single elements or arrays of elements.
- ๐ Start a chain using
me()
andany()
- ๐ข Style A
me().classAdd('red')
โญ Chain style, recommended! - ๐ Style B:
classAdd(me(), 'red')
- ๐ข Style A
- ๐ Global conveniences help you write less code.
globalsAdd()
will automatically warn about any clobbering issues.- If you prefer no conveniences, or are a masochist, delete
globalsAdd()
me().classAdd('red')
becomes:surreal.me().classAdd('red')
classAdd(me(), 'red')
becomes:surreal.classAdd(surreal.me(), 'red')
- If you prefer no conveniences, or are a masochist, delete
See: Quick Start and Reference and No Surreal Needed
- Add a class
me().classAdd('red')
any("button").classAdd('red')
- Events
me().on("click", ev => me(ev).fadeOut() )
on(any('button'), '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)
<div>I change color every second.
<script>
// Every second animate something new.
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>I 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?
- ๐ Chainable off
me()
andany()
- ๐ Global shortcut.
โถ๏ธ Runnable example.- ๐ Built-in Plugin
- ๐
run
- It's
forEach
but less wordy and works on single elements, too! โถ๏ธ me().run(e => { alert(e) })
โถ๏ธ any('button').run(e => { alert(e) })
- It's
- ๐
remove
โถ๏ธ me().remove()
โถ๏ธ any('button').remove()
- ๐
classAdd
๐class_add
๐addClass
๐add_class
โถ๏ธ me().classAdd('active')
- Leading
.
is optional for all class functions, and is removed automatically.- These are the same:
me().classAdd('active')
๐me().classAdd('.active')
- These are the same:
- ๐
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')
- Get is only for single elements. For many, wrap the call in
any(...).run(...)
orany(...).forEach(...)
.
- Get is only for single elements. For many, wrap the call in
- 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 })
- Get:
- ๐
trigger
โถ๏ธ me().trigger('hello')
- Wraps
dispatchEvent
- ๐
on
โถ๏ธ me().on('click', ev => { me(ev).styles('background', 'red') })
- Wraps
addEventListener
- ๐
off
โถ๏ธ me().remove('click')
- Wraps
removeEventListener
- ๐
offAll
โถ๏ธ me().offAll()
- ๐
disable
โถ๏ธ me().disable()
- Easy alternative to
off()
. Disables click, key, submit events.
- ๐
enable
โถ๏ธ me().enable()
- Opposite of
disable()
- ๐
sleep
โถ๏ธ await sleep(1000, ev => { alert(ev) })
async
version ofsetTimeout
- Wonderful for animation timelines.
- ๐
tick
โถ๏ธ await tick()
await
version ofrAF
/requestAnimationFrame
.- Animation tick. Waits 1 frame.
- Great if you need to wait for events to propagate.
- ๐
rAF
โถ๏ธ rAF(e => { return e })
- Animation tick. Fires when 1 frame has passed. Alias of requestAnimationFrame
- Great if you need to wait for events to propagate.
- ๐
rIC
โถ๏ธ rIC(e => { return e })
- Great time to compute. Fires function when JS is idle. Alias of requestIdleCallback
- ๐
halt
โถ๏ธ halt(event)
- Great to prevent default browser behavior: such as displaying an image vs letting JS handle it.
- Wrapper for preventDefault
- ๐
createElement
๐create_element
โถ๏ธ e_new = createElement("div"); me().prepend(e_new)
- Alias of vanilla
document.createElement
- ๐
onloadAdd
๐onload_add
๐addOnload
๐add_onload
โถ๏ธ onloadAdd(_ => { alert("loaded!"); })
- Execute after the DOM is ready. Similar to jquery
ready()
- Queues functions onto
window.onload
- Why? So you don't overwrite
window.onload
, also predictable sequential loading!
- ๐
fadeOut
- See below
- ๐
fadeIn
- See below
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.
- Fade in existing element which has
More often than not, Vanilla JS is the easiest way!
Logging
- ๐
console.log()
console.warn()
console.error()
- Event logging:
โถ๏ธ monitorEvents(me())
See: Chrome Blog
Benchmarking / Time It!
โถ๏ธ console.time('name')
โถ๏ธ console.timeEnd('name')
Text / HTML Content
โถ๏ธ me().textContent = "hello world"
- XSS Safe! See: MDN
โถ๏ธ me().innerHTML = "<p>hello world</p>"
โถ๏ธ me().innerText = "hello world"
Children
โถ๏ธ me().children
โถ๏ธ me().children.hidden = true
Append / Prepend elements.
โถ๏ธ me().prepend(new_element)
โถ๏ธ me().appendChild(new_element)
โถ๏ธ me().insertBefore(element, other_element.firstChild)
โถ๏ธ me().insertAdjacentHTML("beforebegin", new_element)
Ajax (alternatives to jquery ajax()
)
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}`) }
})
- Using 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()
})
- Many ideas can be plain HTML / CSS (ex: dropdowns).
_
= for temporary or unused variables. Keep it short and sweet!e
,el
,elt
= elemente
,ev
,evt
= eventf
,fn
= function- Scoping functions inside
<script>
(..or anything not scoped byme()
)- โญ Inside a
me()
event:me().on('click', ev => { /* add and call function here */ })
- Or, use an inline module:
<script type="module">
- Note:
me()
will no longer seeparentElement
so explicit selectors are required:me(".mybutton")
- Note:
- Or, use backend code to generate unique names for anything not scoped by
me()
- โญ Inside a
Feel free to modify Surreal for a project any way you like- but you can use plugins to effortlessly merge functions 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)
You can now use it like: me().hello("Internet")
- See the included
pluginEffects
for a more comprehensive example. - Your functions will be 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.
โญ Awesome Surreal examples, plugins, and resources: awesome-surreal !
- jQuery for the chainable syntax we all love.
- BlingBling.js for modern minimalism.
- Bliss.js for a focus on single elements and extensibility.
- Hyperscript for Locality of Behavior and awesome ergonomics.
- Shout out to Umbrella, Cash, Zepto- Not quite as ergonomic. Requires build step to extend.
- Always more
example.html
goodies! - Automated browser testing perhaps with: