npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@mojule/dom-mapper

v0.1.0

Published

Map values to and from DOM nodes

Downloads

25

Readme

dom-mapper

Map from a value to the DOM, or from the DOM to a value

npm install @mojule/dom-mapper

const Mapper = require( '@mojule/dom-mapper' )

/*
  requires a document instance - can be window.document in the browser, or you
  can use one created by JSDOM et al.
*/
const options = { document }

const { from, to } = Mapper( options )

let value = { foo: 1, bar: 'baz' }

/*
<table data-type="object">
  <tr>
    <th>foo</th>
    <td data-type="number" data-value="1" data-name="foo">1</td>
  </tr>
  <tr>
    <th>bar</th>
    <td data-type="string" data-value="baz" data-name="bar">"baz"</td>
  </tr>
<table>
*/
const dom = to( value )

/*
  {
    "foo": 1,
    "bar": "baz"
  }
*/
value = from( dom )

Mapping to the DOM

Will convert any JSON-compatible value to a DOM representation

The DOM representation is intended to be both semantic and human readable, eg an object maps to a two column name/value table, an array maps to a zero-indexed ordered list etc.

const el = mapper.to( value )

Mapping from the DOM

Compatible with (but much looser than) the mapper that maps to the DOM. Your DOM elements can encode JSON-compatible values in a fairly free form way, you just add the required data- attributes to the nodes you want to take part in the mapping.

const value = mapper.from( el )

Primitives

Elements with a data-type attribute of string, number, boolean, null, array or object will be converted to their matching value.

The primitives string, number and boolean should have a data-value attribute. If the data-value attribute is missing, they will return null.

For string, the resultant string will be the value of the attribute.

For number, the result of passing the attribute value string to parseFloat will be used, with null returned if the result is NaN, Infinity or -Infinity.

For boolean, true will be returned if and only if the attribute value exactly matches "true", otherwise false will be returned.

Arrays

Elements with data-type="array" will return an array containing the mapped results of any descendant elements with a data-type attribute; excluding any descendants of those descendants, to allow for nesting of arrays and objects.

This allows you to have child elements with varying amounts of nesting for display or semantic purposes and still get the expected result:

<div data-type="array">
  <p>
    <img src="1.png" alt="" />
    <span data-type="number" data-value="1">One</span>
  </p>
  <p data-type="string" data-value="foo">Foo</p>
</div>
[ 1, "foo" ]

Objects

Elements with data-type="object" are mapped in the same way as array, except descendant nodes are expected to also have a data-name attribute. Any nodes without this attribute are skipped:

<div data-type="object">
  <div>
    <p data-type="string" data-name="foo" data-value="Foo">Foo</p>
    <p data-type="string" data-name="bar" data-value="Bar">Bar</p>
  </div>
  <div>
    <p data-type="string" data-value="baz">Baz</p>
  </div>
</div>
{ "foo": "Foo", "bar": "Bar" }

Finding data elements

If the root node you pass in doesn't match any of the above patterns, it will be searched for descendants that do in the same manner as mapping an array, except with the caveat that if only a single value is found, that value is returned rather than an array with a length of one.

Single matching value

<div>
  <p>Hello World</p>
  <p><span data-type="number" data-value="1">One</span></p>
</div>
1

Multiple values

<div>
  <p>Hello World</p>
  <p><span data-type="number" data-value="1">One</span></p>
  <p><span data-type="number" data-value="2">Two</span></p>
</div>
[ 1, 2 ]

Other uses

You can override one or both of the mappers to customise the mapping:

const to = require( './path/to/some/custom/to' )
const from = require( './path/to/some/custom/from' )

const mapper = Mapper( { to, from } )

See the defaults under /src/ to see how they are implemented, or mojule mapper for more documentation on how the mapper works.

Some use cases that this enables include:

  • adding types other than just JSON-compatible values
  • using your own markup structure to represent the values
  • scraping data from existing web pages with custom predicates

Complex example, JSON to DOM

Input

{
  "string": "foo",
  "emptyString": "",
  "number": -1.5,
  "true": true,
  "false": false,
  "null": null,
  "array": [ 1, 2, 3 ],
  "emptyArray": [],
  "object": { "foo": "bar", "baz": "qux" },
  "emptyObject": {},
  "objectArray": [
    { "foo": 1, "bar": 2, "baz": 3, "qux": 4 },
    { "foo": 5, "bar": 6, "baz": 7 },
    { "foo": 5, "bar": "foo" }
  ],
  "mixedArray": [ {}, { "foo": 1, "bar": [] } ],
  "nestedArray": [ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ]
}

Output

<table data-type="object">
  <tr>
    <th>string</th>
    <td data-type="string" data-value="foo" data-name="string">"foo"</td>
  </tr>
  <tr>
    <th>emptyString</th>
    <td data-type="string" data-value="" data-name="emptyString">""</td>
  </tr>
  <tr>
    <th>number</th>
    <td data-type="number" data-value="-1.5" data-name="number">-1.5</td>
  </tr>
  <tr>
    <th>true</th>
    <td data-type="boolean" data-value="true" data-name="true">true</td>
  </tr>
  <tr>
    <th>false</th>
    <td data-type="boolean" data-value="false" data-name="false">false</td>
  </tr>
  <tr>
    <th>null</th>
    <td data-type="null" data-value="null" data-name="null">null</td>
  </tr>
  <tr>
    <th>array</th>
    <td>
      <ol data-type="array" start="0" data-name="array">
        <li data-type="number" data-value="1">1</li>
        <li data-type="number" data-value="2">2</li>
        <li data-type="number" data-value="3">3</li>
      </ol>
    </td>
  </tr>
  <tr>
    <th>emptyArray</th>
    <td>
      <ol data-type="array" start="0" data-name="emptyArray"></ol>
    </td>
  </tr>
  <tr>
    <th>object</th>
    <td>
      <table data-type="object" data-name="object">
        <tr>
          <th>foo</th>
          <td data-type="string" data-value="bar" data-name="foo">"bar"</td>
        </tr>
        <tr>
          <th>baz</th>
          <td data-type="string" data-value="qux" data-name="baz">"qux"</td>
        </tr>
      </table>
    </td>
  </tr>
  <tr>
    <th>emptyObject</th>
    <td>
      <table data-type="object" data-name="emptyObject"></table>
    </td>
  </tr>
  <tr>
    <th>objectArray</th>
    <td>
      <ol data-type="array" start="0" data-name="objectArray">
        <li>
          <table data-type="object">
            <tr>
              <th>foo</th>
              <td data-type="number" data-value="1" data-name="foo">1</td>
            </tr>
            <tr>
              <th>bar</th>
              <td data-type="number" data-value="2" data-name="bar">2</td>
            </tr>
            <tr>
              <th>baz</th>
              <td data-type="number" data-value="3" data-name="baz">3</td>
            </tr>
            <tr>
              <th>qux</th>
              <td data-type="number" data-value="4" data-name="qux">4</td>
            </tr>
          </table>
        </li>
        <li>
          <table data-type="object">
            <tr>
              <th>foo</th>
              <td data-type="number" data-value="5" data-name="foo">5</td>
            </tr>
            <tr>
              <th>bar</th>
              <td data-type="number" data-value="6" data-name="bar">6</td>
            </tr>
            <tr>
              <th>baz</th>
              <td data-type="number" data-value="7" data-name="baz">7</td>
            </tr>
          </table>
        </li>
        <li>
          <table data-type="object">
            <tr>
              <th>foo</th>
              <td data-type="number" data-value="5" data-name="foo">5</td>
            </tr>
            <tr>
              <th>bar</th>
              <td data-type="string" data-value="foo" data-name="bar">"foo"</td>
            </tr>
          </table>
        </li>
      </ol>
    </td>
  </tr>
  <tr>
    <th>mixedArray</th>
    <td>
      <ol data-type="array" start="0" data-name="mixedArray">
        <li>
          <table data-type="object"></table>
        </li>
        <li>
          <table data-type="object">
            <tr>
              <th>foo</th>
              <td data-type="number" data-value="1" data-name="foo">1</td>
            </tr>
            <tr>
              <th>bar</th>
              <td>
                <ol data-type="array" start="0" data-name="bar"></ol>
              </td>
            </tr>
          </table>
        </li>
      </ol>
    </td>
  </tr>
  <tr>
    <th>nestedArray</th>
    <td>
      <ol data-type="array" start="0" data-name="nestedArray">
        <li>
          <ol data-type="array" start="0">
            <li>
              <ol data-type="array" start="0">
                <li data-type="number" data-value="1">1</li>
                <li data-type="number" data-value="2">2</li>
              </ol>
            </li>
            <li>
              <ol data-type="array" start="0">
                <li data-type="number" data-value="3">3</li>
                <li data-type="number" data-value="4">4</li>
              </ol>
            </li>
          </ol>
        </li>
        <li>
          <ol data-type="array" start="0">
            <li>
              <ol data-type="array" start="0">
                <li data-type="number" data-value="5">5</li>
                <li data-type="number" data-value="6">6</li>
              </ol>
            </li>
            <li>
              <ol data-type="array" start="0">
                <li data-type="number" data-value="7">7</li>
                <li data-type="number" data-value="8">8</li>
              </ol>
            </li>
          </ol>
        </li>
      </ol>
    </td>
  </tr>
</table>

Suggested CSS

table[data-type="object"] {
  border: 1px solid #ccc;
  border-bottom: 0;
  border-collapse: collapse;
}

table[data-type="object"] th {
  text-align: left;
  vertical-align: top;
  border-right: 1px solid #ccc;
}

table[data-type="object"] td,
table[data-type="object"] th {
  border-bottom: 1px solid #ccc;
  padding: 0.5rem;
}

ol[data-type="array"] > li {
  border: 1px dotted #ccc;
  border-bottom: 0;
  padding: 0.5rem;
  list-style-type: decimal;
}

ol[data-type="array"] > li:last-child {
  border: 1px dotted #ccc;
}

ol[data-type="array"]:empty {
  margin: 0;
  padding: 0;
}

ol[data-type="array"]:empty:before {
  content: 'empty array';
  font-style: italic;
}

table[data-type="object"]:empty {
  border: 0;
}

table[data-type="object"]:empty:before {
  content: 'empty object';
  font-style: italic;
}