spect

DOM aspects - pieces of logic defined with CSS rules

Usage no npm install needed!

<script type="module">
  import spect from 'https://cdn.skypack.dev/spect';
</script>

README

subscript spect    npm bundle size npm

DOM aspects: pieces of logic declared with CSS rules.

spect( [ container=document, ] selector, handler? )

Observe selector within container, call handler when matching elements found.
Handler can return a teardown function, called for unmatched elements.
Returns live collection of elements SelectorCollection.

import spect from 'spect'

const foos = spect('.foo', el => {
  console.log('active')
  return () => console.log('inactive') // teardown
})

const foo = document.createElement('div')
foo.className = 'foo'
document.body.append(foo)
// ... "active"

foo.remove()
// ... "inactive"

SelectorCollection

Extends Array, implements Set, HTMLCollection methods, and provides Observable, AsyncIterable, Disposable interfaces.

foos[idx]                                     // Array
foos.has(el), foos.add(el), foos.delete(el)   // Set
foos.item(idx), foos.namedItem(elementId)     // HTMLCollection
foos.subscribe(fn)                            // Observable
foos.dispose()                                // destroy selector observer / unsubscribe

Technique

It combines selector parts indexing from selector-observer for simple queries and animation events insertionQuery for complex selectors.

Examples

Hello World
<div class="user">{{ user.name || "Loading..." }}</div>

<script type="module">
  import spect from 'spect'
  import templize from 'templize'

  // initialize template
  spect('.user', async el => templize(el, {
    user: (await fetch('/user')).json() // value is available when resolved
  }))
</script>
Timer
<time class="timer">{{ count }}</time>
<time class="timer">{{ count }}</time>

<script type="module">
  import spect from 'spect'
  import templize from 'templize'

  spect('.timer', timer => {
    const params = templize(timer, { count: 0 })
    const id = setInterval(() => params.count++, 1000)
    return () => clearInterval(id)
  })
</script>
Counter
<output id="count">{{ count }}</output>
<button id="inc" onclick="{{ inc }}">+</button>
<button id="dec" onclick="{{ dec }}">-</button>

<script type="module">
  import spect from 'spect'
  import v from 'value-ref'
  import templize from 'templize'

  const count = v(0)
  spect('#count', el => templize(el, { count }))

  // bind events via HTML template
  spect('#inc', el => templize(el, { inc: () => count.value++ }))
  spect('#dec', el => templize(el, { dec: () => count.value-- }))
</script>
Todo list
<form class="todo-form">
  <label for="add-todo">
    <span>Add Todo</span>
    <input name="text" required>
  </label>
  <button type="submit">Add</button>
  <ul class="todo-list">{{ todos }}<ul>
</form>

<script type="module">
  import spect from 'spect'
  import v from 'value-ref'
  import h from 'hyperf'
  import tpl from 'templize'

  const todos = v([])

  spect('.todo-list', el => tpl(el, {
    todos: v.from(todos, item => h`<li>${ item.text }</li>`)
  }))

  spect('.todo-form', form => form.addEventListener('submit', e => {
    e.preventDefault()
    if (!form.checkValidity()) return
    todos.value = [...todos.value, { text: form.text.value }]
    form.reset()
  }))
</script>
Form validator
<form id="email-form">
  <label for="email">Please enter an email address:</label>
  <input id="email" onchange={{ validate }}/>
  The address is {{ valid ? "valid" : "invalid" }}
</form>

<script type="module">
  import spect from 'spect'
  import templize from 'templize'

  const isValidEmail = s => /.+@.+\..+/i.test(s)

  spect('#email-form', form => {
    const params = templize(form, {
      valid: false,
      validate: () => params.valid = isValidEmail(e.target.value)
    })
  })
</script>
Prompt
<dialog class="dialog" open={{showPrompt}}>
  Proceed?
  <menu>
    <button onclick={{cancel}}>Cancel</button>
    <button onclick={{confirm}}>Confirm</button>
  </menu>
</dialog>

<script>
import v from 'value-ref'
import spect from 'spect'

spect('.dialog', el => {
  const showPrompt = v(false), proceed = v(false)
  templize(el, {
    showPrompt, proceed,
    cancel() {showPrompt.value = proceed.value = false;},
    confirm() {showPrompt.value = false; proceed.value = true;}
  })
})
</script>

See all examples.

Best Buddies

  • value-ref − value container with observable interface. Indispensible for reactive data.
  • templize − DOM buddy - hooks up reactive values to template parts.
  • hyperf − builds HTML fragments with reactive fields.
  • subscribable-things − collection of observables for different browser APIs.

Refs

fast-on-load, selector-set, insertionQuery, selector-observer, reuse, aspect-oriended-programming, qso, pure-js, element-observer, livequery, selector-listener, mutation-summary, rkusa/selector-observer.