AI prompts
base on 🗿 Mini jQuery alternative. Dependency-free animations. Locality of Behavior. Use one element or arrays transparently. Pairs with htmx. Vanilla querySelector() but better! # 🗿 Surreal
### Tiny jQuery alternative for plain Javascript with inline [Locality of Behavior](https://htmx.org/essays/locality-of-behaviour/)!
![cover](https://user-images.githubusercontent.com/24665/171092805-b41286b2-be4a-4aab-9ee6-d604699cc507.png)
(Art by [shahabalizadeh](https://www.deviantart.com/shahabalizadeh))
<!--
<a href="https://github.com/gnat/surreal/archive/refs/heads/main.zip"><img src="https://img.shields.io/badge/Download%20.zip-ff9800?style=for-the-badge&color=%234400e5" alt="Download badge" /></a>
<a href="https://github.com/gnat/surreal"><img src="https://img.shields.io/github/workflow/status/gnat/surreal/ci?label=ci&style=for-the-badge&color=%237d91ce" alt="CI build badge" /></a>
<a href="https://github.com/gnat/surreal/releases"><img src="https://img.shields.io/github/workflow/status/gnat/surreal/release?label=Mini&style=for-the-badge&color=%237d91ce" alt="Mini build badge" /></a>
<a href="https://github.com/gnat/surreal/blob/main/LICENSE"><img src="https://img.shields.io/github/license/gnat/surreal?style=for-the-badge&color=%234400e5" alt="License badge" /></a>-->
## 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](#-quick-start) with no extra libraries.
* Only 320 lines. No build step. No dependencies.
* Pairs well with [htmx](https://htmx.org)
* Want fewer layers, less complexity. Are aware of the cargo cult. ✈️
## ✨ What does it add to Javascript?
* ⚡️ [Locality of Behavior (LoB)](https://htmx.org/essays/locality-of-behaviour/) 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](https://github.com/gnat/css-scope-inline)
* 🔗 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](https://htmx.org/essays/locality-of-behaviour/) like:
```html
<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](https://gnat.github.io/surreal/example.html)! Then [view source](https://github.com/gnat/surreal/blob/main/example.html).
## 🎁 Install
Surreal is only 320 lines. No build step. No dependencies.
[📥 Download](https://raw.githubusercontent.com/gnat/surreal/main/surreal.js) 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
### <a name="selectors"></a>🔍️ DOM Selection
* 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()`
* Choose the start location in the DOM with the 2nd arg. (Default: `document`)
* 🔥 `any('button', me('#header')).classAdd('red')`
* Add `.red` to any `<button>` inside of `#header`
* `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()`
* Select **one or more** elements as an array: `any(...)`
* Like `me()` but guaranteed to return an array (or empty array).
* `any(".foo")` ⭐ Get all matching elements.
* 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')` ⭐ Chain style. Recommended!
* 🟠 Style B: `classAdd(me(), 'red')`
* 🌐 Global conveniences help you write less code.
* `globalsAdd()` will automatically warn you of any clobbering issues!
* 💀🩸 If you want 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')`
See: [Quick Start](#quick-start) and [Reference](#reference) and [No Surreal Needed](#no-surreal)
## <a name="quick-start"></a>⚡ 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)`
<a name="timelines"></a>
#### Timeline animations without any libraries.
```html
<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>
```
```html
<div>I fade out and remove myself.
<script>me().on("click", ev => { me(ev).fadeOut() })</script>
</div>
```
```html
<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>
```
```html
<script>
// Run immediately, for every <button> globally!
(async () => {
any("button").fadeOut()
})()
</script>
```
#### Array methods
```js
any('button')?.forEach(...)
any('button')?.map(...)
```
## <a name="reference"></a>👁️ Functions
Looking for [DOM Selectors](#selectors)?
Looking for stuff [we recommend doing in vanilla JS](#no-surreal)?
### 🧭 Legend
* 🔗 Chainable off `me()` and `any()`
* 🌐 Global shortcut.
* 🔥 Runnable example.
* 🔌 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`
* 🔥 `me().offAll()`
* 🔗 `disable`
* 🔥 `me().disable()`
* Easy alternative to `off()`. Disables click, key, submit events.
* 🔗 `enable`
* 🔥 `me().enable()`
* Opposite of `disable()`
* 🌐 `createElement` 🌗 `create_element`
* 🔥 `e_new = createElement("div"); me().prepend(e_new)`
* Alias of [document.createElement](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement)
* 🌐 `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](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation) and [preventDefault](https://developer.mozilla.org/en-US/docs/Web/API/Event/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](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)
* Useful to guarantee CSS properties are applied, and events have propagated.
* 🌐 `rIC`
* 🔥 `rIC(e => { return e })`
* Calls when Javascript is idle. Alias of [requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback)
* 🌐 `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.
* See `me('-')` / `me('prev')`
* 🔌 `fadeOut`
* See below
* 🔌 `fadeIn`
* See below
### <a name="plugin-included"></a>🔌 Built-in Plugins
### Effects
Build effects with `me().styles({...})` with timelines using [CSS transitioned `await` or callbacks](#timelines).
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.
## <a name="no-surreal"></a>⚪ No Surreal Needed
More often than not, Vanilla JS is the easiest way!
Logging
* 🔥 `console.log()` `console.warn()` `console.error()`
* Event logging: 🔥 `monitorEvents(me())` See: [Chrome Blog](https://developer.chrome.com/blog/quickly-monitor-events-from-the-console-panel-2/)
Benchmarking / Time It!
* 🔥 `console.time('name')`
* 🔥 `console.timeEnd('name')`
Text / HTML Content
* 🔥 `me().textContent = "hello world"`
* XSS Safe! See: [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent)
* 🔥 `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 (replace jQuery `ajax()`)
* Use [htmx](https://htmx.org/) or [htmz](https://leanrada.com/htmz/) or [fetch()](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or [XMLHttpRequest()](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
* Example using `fetch()`
```js
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}`) }
})
```
* Example using `XMLHttpRequest()`
```js
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
* 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
#### Scope functions and variables inside `<script>`
* ⭐ Use a block `{ let note = "hi"; function hey(text) { alert(text) }; me().on('click', ev => { hey(note) }) }`
* `let` and `function` is scoped within `{ }`
* ⭐ Use `me()`
* `me().hey = (text) => { alert(text) }`
* `me().on('click', (ev) => { me(ev).hey("hi") })`
* ⭐ Use an event `me().on('click', ev => { /* add and call function here */ })`
* Use an inline module: `<script type="module">`
* Note: `me()` in modules will not see `parentElement`, explicit selectors are required: `me(".mybutton")`
#### Select a void element like `<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>`
#### Ignore call chain when element is missing.
* 🔥 `me("#i_dont_exist")?.classAdd('active')`
* No warnings: 🔥 `me("#i_dont_exist", document, false)?.classAdd('active')`
## <a name="plugins"></a>🔌 Your own plugin
Feel free to edit Surreal directly- but if you prefer, you can use plugins to effortlessly merge with new versions.
```javascript
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")`
* 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](https://github.com/gnat/surreal/issues) or [pull request](https://github.com/gnat/surreal/pulls) 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](https://github.com/gnat/awesome-surreal) !
## 📚️ Inspired by
* [jQuery](https://jquery.com/) for the chainable syntax we all love.
* [BlingBling.js](https://github.com/argyleink/blingblingjs) for modern minimalism.
* [Bliss.js](https://blissfuljs.com/) for a focus on single elements and extensibility.
* [Hyperscript](https://hyperscript.org) for Locality of Behavior and awesome ergonomics.
* Shout out to [Umbrella](https://umbrellajs.com/), [Cash](https://github.com/fabiospampinato/cash), [Zepto](https://zeptojs.com/)- Not quite as ergonomic. Requires build step to extend.
## 🌘 Future
* Always more `example.html` goodies!
* Automated browser testing perhaps with:
* [Fava](https://github.com/fabiospampinato/fava). See: https://github.com/avajs/ava/issues/24#issuecomment-885949036
* [Ava](https://github.com/avajs/ava/blob/main/docs/recipes/browser-testing.md)
* [jsdom](https://github.com/jsdom/jsdom)
* [jsdom notes](https://github.com/jsdom/jsdom#executing-scripts)
", Assign "at most 3 tags" to the expected json: {"id":"3332","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"