/once

declarative, terse and fastest approach to rendering

Primary LanguageJavaScript

Once

Coverage Status Build once

Once is a declarative, terse and efficient way to manipulate the DOM for rendering. Once stamps out elements based on some data. It only updates the DOM with the minimal changes required to make it match the data. See the main documentation here. Below is just a cheat sheet of random examples:


Create three li and set their text
once(node)
  ('li', [1, 2, 3])
    .text('foo')
<li>foo</li>
<li>foo</li>
<li>foo</li>
Create three li and set their text to their data
once(node)
  ('li', [1, 2, 3])
    .text(d => d)
<li>1</li>
<li>2</li>
<li>3</li>
Create elements from arbitrary selector strings (omitting a tag name defaults to div)
once(node)
  ('.foo[attr=val]', [1, 2, 3])
    .text(d => d)
<div class="foo" attr="val">1</div>
<div class="foo" attr="val">2</div>
<div class="foo" attr="val">3</div>
Create three td cells on three tr rows (multiple parents)
rows = [
  { cells: [1, 2, 3] }
, { cells: [4, 5, 6] }
, { cells: [7, 8, 9] }
]

once(node)
  ('tr', rows)
    ('td', d => d.cells)
      .text(String)
<tr>
  <td>1</td><td>2</td><td>3</td>
</tr>
<tr>
  <td>4</td><td>5</td><td>6</td>
</tr>
<tr>
  <td>7</td><td>8</td><td>9</td>
</tr>
Using objects, strings or numbers instead of an array is interpreted as an array of one element (i.e. create one element with the specified data)
once(node)
  ('div', { foo: 'bar' })

once(node)
  ('div', 10)

once(node)
  ('div', 'Hi!')
Using 1 as the data is a shortcut for inheriting the parent data
once(node)
  ('.parent', { foo: 'bar' })
    ('.child', 1)
Modify a selection with D3-style accessors (can use constants or functions which receive the data and index)
once(node)
  ('.parent', { foo: 'bar' })
    .text(d => d.foo)
    .classed('bar', true)
    ('.child', 1)
      .attr('key', d => d.foo)
      .html('Hi!')
<div class="parent bar">
  bar
  <div class="child" key="bar">
    Hi!
  </div>
</div>
Save a reference to a selection to spawn different elements at that level
var o = once(node)

o('header', 1)

o('article', { text: 'foo' })
  ('.content', 1)
    .text(d => d.text)

o('footer', 1)
<header></header>
<article>
  <div class="content">foo</div>
</article>
<footer></footer>
Change parent selection with a single argument to create more elements at that level
var o = once(node)

o('ul', 1)
  ('li', [1,2,3])

o('ul')
  ('footer', 1)
<ul>
  <li></li>
  <li></li>
  <li></li>
  <footer></footer>
</ul>
Semantically key elements (default is by index)
var o = once(node)

o('li', [{ id: 'a' }, { id: 'b' }, { id: 'c' }], d => d.id)
<ul>
  <li></li>
  <li></li>
  <li></li>
</ul>
Insert an element before another rather than appending at the end
var o = once(node)

o('li', [1, 2, 3])

o('header', 1, null, ':first-child')
<ul>
  <header></header>
  <li></li>
  <li></li>
  <li></li>
</ul>
Use enter and exit subselections to access newly created or newly removed elements
once(node)
  ('li', [1])

once(node)
  ('li', [1, 2])
    .enter()
    .attr('new', 'yes')
<li></li>
<li new="yes"></li>
Use .on and .emit as syntatic sugar for .addEventListener and .dispatchEvent
var o = once(node)

o('li', [{ href: '/1' }, { href: '/2' }, { href: '/3' }])
  .attr('href', d => d.href)
  .on('click', d => o(node).emit('navigate', d.href))
<a href="/1"></a>
<a href="/2"></a>
<a href="/3"></a>