Git Product home page Git Product logo

gnat / surreal Goto Github PK

View Code? Open in Web Editor NEW
1.1K 15.0 23.0 260 KB

๐Ÿ—ฟ Mini jQuery alternative. Dependency-free animations. Locality of Behavior. Use one element or arrays transparently. Pairs with htmx. Vanilla querySelector() but better!

Home Page: https://gnat.github.io/surreal/example.html

License: MIT License

JavaScript 67.40% HTML 23.34% CSS 9.26%
cash dom dom-manipulation jquery jquery-alternative jquery-like jquery-replacement queryselector queryselectorall selector

surreal's Introduction

๐Ÿ—ฟ Surreal

Tiny jQuery alternative for plain Javascript with inline Locality of Behavior!

cover (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).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').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(...)
    • 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 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>
    // 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>

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 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()
  • ๐ŸŒ sleep
    • ๐Ÿ”ฅ await sleep(1000, ev => { alert(ev) })
    • async version of setTimeout
    • Wonderful for animation timelines.
  • ๐ŸŒ tick
    • ๐Ÿ”ฅ await tick()
    • await version of rAF / 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)
    • Prevent default browser behaviors.
    • 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!"); })
    • ๐Ÿ”ฅ <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

๐Ÿ”Œ 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()
  • 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 (replace 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()
})

๐Ÿ’Ž 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 inside <script>

  • โญ On me()
    • me().hey = (text) => { alert(text) }
    • me().on('click', (ev) => { me(ev).hey("hi") })
  • โญ Use a block: { function hey(text) { alert(text) }; me().on('click', ev => { hey("hi") }) }
  • โญ Use an event: me().on('click', ev => { /* add and call function here */ })
  • Use an inline module: <script type="module">
    • Note: me() will no longer see parentElement so 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')

๐Ÿ”Œ 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 // 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 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 !

๐Ÿ“š๏ธ Inspired by

  • 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.

๐ŸŒ˜ Future

surreal's People

Contributors

gnat avatar ilkiri23 avatar kennycallado avatar klausborges avatar onlyfortesting avatar sieroslawski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

surreal's Issues

Signal value update overwrites css-scope-inline style block of html

Description:

When using preact/signals-core and surreal's companion script css-scope-inline, I encountered an issue where when updating the element's textContent the script and style blocks get overwritten. This only happens when I include a style block in the element. Here is the code to reproduce the issue:

<script>
  let mySignal = signal("");
</script>
<div>
  sample text
  <script>
    {
      let textElement = me();
      effect(() => (textElement.textContent = mySignal.value));
    }
  </script>
  <style>
    me {
      background-color: red;
    }
  </style>
</div>

In this setup, when I change the value of the signal (mySignal.value = "some new text";), the script and style blocks get overwritten.

However, if I get the firstChild of the div, this issue does not occur:

<div>
  sample text
  <script>
    {
      let textElement = me().firstChild;
      effect(() => (textElement.textContent = mySignal.value));
    }
  </script>
  <style>
    me {
      background-color: red;
    }
  </style>
</div>

Steps to Reproduce:

1. Create a signal.
2. Use the signal value to update the textContent of an element inside a div that has a nested style block.
3. Observe that the script and style blocks within the div are overwritten when the signal's value changes.

Expected Behavior:

The script and style blocks of the div should not be overwritten when updating its textContent with the signal's value.

Actual Behavior:

The script and style blocks of the div element are overwritten when the signal's value is updated.

Additional Context:

When logging textElement.textContent both inside and outside the effect, it consistently shows "sample text" as expected.

To include preact/signals-core in your project you can use this html file which contains a script tag and the compiled preact/signals-core JS code:
https://github.com/eboody/app-template/blob/main/crates/services/web-server/src/templates/modules/signals.html

no license

the project doesn't appear to have a license.

would you mind supplementing one?

Can this mod be made into a sugar?

This adds in a small modifier to pick up elementSiblings.
It's been great for picking up single tags at the same level like <input> and <img>. Putting some of the scripting outside of a tag like <td> is nice for for searching a table without creating a text node.

me(selector=null, start=document, warning=true) {
	if (selector == null) return $.sugar(start.currentScript.parentElement) // Just local me() in <script>
	if (selector instanceof Event) return selector.currentTarget ? $.me(selector.currentTarget) : (console.warn(`Surreal: Event currentTarget is null. Please save your element because async will lose it`), null) // Events try currentTarget
	// Goofy mod here.
	if (typeof selector == 'string') {
		if (selector === '<') return $.sugar(start.currentScript.previousElementSibling)
		if (selector === '>') return $.sugar(start.currentScript.nextElementSibling)
		if (isSelector(selector, start, warning)) return $.sugar(start.querySelector(selector)) // String selector.
	}
	if ($.isNodeList(selector)) return $.me(selector[0]) // If we got a list, just take the first element.
	if ($.isNode(selector)) return $.sugar(selector) // Valid element.
	return null // Invalid.
},

Is it possible to use the sugar system for this?

Warning cannot be surpressed due to incorrect parameter order

I have this code:

me().on('click', (ev) => { me('#sidebar-toggle.initially-closed', document, false)?.send('click'); });

The third parameter should supress warning in console when the class is not set. But the warning is generated to console:

Surreal: "#sidebar-toggle.initially-closed" was not found. Missing a character? (. #)

QUESTION: How to do keydown event on a btn?

Hey mate,

First of all, what a genius plugin!!!

I'm trying to get rid of a bunch of my js files with Surreal. How would I do me().on("multipleevents?"), like click and keydown (Esc) at the same time? For example, how would I achieve something like this but keep the locality of behaviour effect?

<button id="cancelBtnDrawerForm"/>
const cancelBtnDrawerForm = document.getElementById('cancelBtnDrawerForm')
const cancelDrawerForm = () => {
  form.classList.remove('show')
  bodyWrapper.classList.remove('scaled')
  body.classList.remove('scaled')
  setTimeout(() => {
    form.style.display = 'none'
    backdrop.style.display = 'none'
  }, 200)
}

cancelBtnDrawerForm.addEventListener('click', cancelDrawerForm)
document.addEventListener('keydown', (event) => {
  if (event.key === 'Escape') {
    cancelDrawerForm();
  }
});

Tree Traversal?

I'm not fully sure this is a good idea, or there is a nice way to do it. More a discussion point at the moment. I found myself needing to do a bit of modification of parent, child, or sibling elements. Having me() is still nice, but the moment you need to do any tree traversal from there you lose the sugar methods, or end up with some awkward constructs to get them back (or you just go with vanilla js).

I was thinking the sugar call could automatically sugar some attributes like parentElement nextElementSibling and methods like querySelector to make tree traversal chainable while retaining the sugar methods.
e.g.

  • me(me().parentElement).classToggle("something")->me().parentElement.classToggle("something")
  • me(".things", me()).fadeOut()->me().querySelector(".things").fadeOut()

PS: After having written this out, and come up with the examples, I don't mind this syntax and nested me calls as much. The only thing that's weird to me is the use of the name me whenever we provide a selector. It no longer has anything to do with the current location, and is just adding the sugar methods, which makes the me name feel awkward.

me(event) behavior with children

I noticed that the behavior for me(anEvent) returns the event's target. This means that you will often get a child of the element you attached the event handler to (if it has children.) I think maybe returning the currentTarget is the more logical choice, so that you always get the element back that you added the handler to.

Surreal errors when rerunning function after HTMX adds row to table.

I'm currently trying to use Surreal and HTMX to create a simple datatable. The datatable works fine, but when a new row is added I want to rerun the surreal function so that the new row will sort and filter as well.

The two things I've tried is to do so far is:

  • Wrap the function in htmx.onLoad()
  • Name the function datatable() call the function in the same script tag then use hx-on::after-request="datatable()".

Both result in a Uncaught TypeError: start.currentScript is null

Here is the console error:

Uncaught TypeError: start.currentScript is null
    me http://127.0.0.1:3000/js/surreal.js:56
    <anonymous> http://127.0.0.1:3000/test:100
    value http://127.0.0.1:3000/js/htmx.js:473
    triggerEvent http://127.0.0.1:3000/js/htmx.js:2145
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3781
    setTimeout handler* http://127.0.0.1:3000/js/htmx.js:3780
    ready http://127.0.0.1:3000/js/htmx.js:3716
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3749
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3788
    <anonymous> http://127.0.0.1:3000/js/htmx.js:16
    <anonymous> http://127.0.0.1:3000/js/htmx.js:18
surreal.js:56:25
    me http://127.0.0.1:3000/js/surreal.js:56
    <anonymous> http://127.0.0.1:3000/test:100
    value http://127.0.0.1:3000/js/htmx.js:473
    triggerEvent http://127.0.0.1:3000/js/htmx.js:2145
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3781
    (Async: setTimeout handler)
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3780
    (Async: EventListener.handleEvent)
    ready http://127.0.0.1:3000/js/htmx.js:3716
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3749
    <anonymous> http://127.0.0.1:3000/js/htmx.js:3788
    <anonymous> http://127.0.0.1:3000/js/htmx.js:16
    <anonymous> http://127.0.0.1:3000/js/htmx.js:18

Here is a working example of the datatable minus the button hx-get="/rows".
Though one could simply copy and paste a new row in the inspector.

TEST.html:

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="utf-8" />
	<script src="https://unpkg.com/[email protected]"></script>
	<script src="https://cdn.jsdelivr.net/gh/gnat/surreal/surreal.js"></script>
	<style>
		table {
			width:100%;
		}
		table, th, td {
			border: 1px solid black;
			border-collapse: collapse;
		}
		input {
			margin: 1rem;
		}
	</style>
</head>
<body>

<h2>Table</h2>

<button hx-get="/row" hx-target="table" hx-swap="beforeend">ADD ROW</button>

<table>
	SEARCH: <input type="text" />
	<tr>
		<th>Firstname</th>
		<th>Lastname</th>
		<th>Age</th>
	</tr>
	<tr>
		<td>Jill</td>
		<td>Smith</td>
		<td>50</td>
	</tr>
	<tr>
		<td>Eve</td>
		<td>Jackson</td>
		<td>94</td>
	</tr>
	<tr>
		<td>Dork</td>
		<td>Lee</td>
		<td>32</td>
	</tr>
	<tr>
		<td>Fork</td>
		<td>Bee</td>
		<td>69</td>
	</tr>
	<script>
		(_=>{
			// SETUP
			let table = me()
			let rows = Array.from(table.rows)
			let header = rows.shift()
			let search = me('input')

			// SEARCH
			search.on('keyup', _=>{
				const term = search.value.toLowerCase()
				rows.forEach((row,i)=>{ 
					const found = Array.from(row.cells).some(cell=>cell.innerText.toLowerCase().includes(term))
					row.style.display = found ? '' : 'none'
				})
			})

			// SORT
			Array.from(header.cells).forEach((cell,i)=>{
				cell.innerText += ' โ–บ'
				cell.addEventListener('click', _=>{
					const arrow = cell.innerText.substr(-1)
					Array.from(header.cells).forEach(cell=>cell.innerText = cell.innerText.slice(0, -1) + 'โ–บ')
					if (arrow === 'โ–ผ') {
						rows.sort((a,b)=>b.cells[i].innerText.localeCompare(a.cells[i].innerText))
						cell.innerText = cell.innerText.slice(0, -1) + 'โ–ฒ'
					}
					else {
						rows.sort((a,b)=>a.cells[i].innerText.localeCompare(b.cells[i].innerText))
						cell.innerText = cell.innerText.slice(0, -1) + 'โ–ผ'
					}
					rows.forEach(row=>table.appendChild(row))
				})
			})
		})()
	</script>
</table>

</body>
</html>

My question would be what is the proper way to rerun a surreal function?
Also thank you for the library! It's been super convenient!

CDN issue

How can I download a particular version?

for example version 1.2.1 does not download from this:
https://cdn.jsdelivr.net/gh/gnat/[email protected]/surreal.js

Also, can you, please, offer a minified version (no comments inside, at least)?

Also, can you, please, do the same for you other awesome project css-scope-inline

ReferenceError: restricted is not defined

Getting this in Chrome dev tools console โ€“ haven't done anything at all except include the CDN script in my head tag.

ReferenceError: restricted is not defined
    at Object.globalsAdd (surreal.js:213:14)
    at surreal.js:238:3
    at surreal.js:241:3

usage with astro

Really love the simplicity.
I tried using this inside Astro and was having some issues getting things to work.
I am hitting an error with

globalsAdd() {
		console.log(`Surreal: adding convenience globals to window`)
		restricted = ['$', 'sugar']

restricted is not defined
at Object.globalsAdd (surreal.js:214:14)
at surreal.js:286:3

Any insights would be helpful

BTW, i am using it inside a header tag to set attribute

<header> 
<div>
<script is:inline>
		me().attribute('data-open' , false)
	</script>
</div>
<div>
...
...
</header>
```

Payload for custom event

It would be great if we could pass payload to the trigger function (something like trigger('click', { value: 42 })) and get it in the handler

me().on('click', (event) => { 
  console.log(event.detail.value) // 42
})

If you agree with that, I can create a PR

Comparison using the "===" operator here is always false

I get this warning from esbuild:

โ–ฒ [WARNING] Comparison using the "===" operator here is always false [equals-new-object]

    deps/.downloaded/surreal/script.js:92:22:
      92 โ”‚     if (e === null || e === []) return null
         โ•ต                         ~~~

  Equality with a new object is always false in JavaScript because the equality operator tests
  object identity. You need to write code to compare the contents of the object instead. For
  example, use "Array.isArray(x) && x.length === 0" instead of "x === []" to test for an empty
  array.

I wonder if there's a particular reason you included it in there?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.