diff --git a/README.md b/README.md index e10a568..cc75985 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Since web components are an extension of the HTML standard, Bore inherently work 1. The custom element polyfill is supported by calling `flush()` after mounting the nodes so things appear synchronous. 2. Nodes are mounted to a fixture that is always kept in the DOM (even if it's removed, it will put itself back). This is so that custom elements can go through their natural lifecycle. 3. The fixture is cleaned up on every mount, so there's no need to cleanup after your last mount. -4. The `attachShadow()` method is overridden to *always* provide an `open` shadow root so that there is always a `shadowRoot` property and it can be queried against. +4. The `attachShadow()` method is overridden to *always* provide an `open` shadow root so that there is always a `shadowRoot` property and it can be queried against. @@ -71,15 +71,37 @@ This can probably be confusing to some, so this is only recommended as a last re -#### Setting attributes vs properties +#### Setting attributes vs properties vs events -The `h` function prefers props unless it's something that *must* be set as an attribute, such as `aria-` or `data-`. As a best practice, your web component should be designed to prefer props and reflect to attributes only when it makes sense. +The `h` function sets always props. If you wanna set something as an attribute, such as `aria-` or `data-` or anything else `h` accepts special `attrs` prop. +For setting event handlers use `events` property. + +> As a best practice, your web component should be designed to prefer props and reflect to attributes only when it makes sense. + +*Example:* + +```js +/* @jsx h */ +import { h } from 'bore'; + +const dom = console.log('just regular click'), + kickflip: e => console.log('just did kickflip') + }} +> +``` ### `mount(htmlOrNode)` -The mount function takes a node, or a string - and converts it to a node - and returns a wrapper around it. +The mount function takes a node, or a string - and converts it to a node - and returns a wrapper around it. ```js import { mount, h } from 'bore'; diff --git a/src/index.js b/src/index.js index 91e358e..cfb9c20 100644 --- a/src/index.js +++ b/src/index.js @@ -1,25 +1,59 @@ const { DocumentFragment, Node, Promise } = window; const { slice } = []; -function startsWith (key, val) { - return key.indexOf(val) === 0; +function isAttr (key) { + return key === 'attrs'; } -function shouldBeAttr (key, val) { - return startsWith(key, 'aria-') || startsWith(key, 'data-'); +function isEvent (key) { + return key === 'events'; } function handleFunction (Fn) { return Fn.prototype instanceof HTMLElement ? new Fn() : Fn(); } +function setAttrs (node, attrs) { + Object.keys(attrs) + .forEach(key => node.setAttribute(key, attrs[key])); +} + +function setEvents (node, events) { + Object.keys(events) + .forEach(key => node.addEventListener(key, events[key])); +} + +function setProp (node, attrName, attrValue) { + node[attrName] = attrValue; +} + +function setupNodeAttrs (node, attrs) { + Object.keys(attrs || {}) + .forEach(attrName => { + const attrValue = attrs[attrName]; + + if (isAttr(attrName)) { + setAttrs(node, attrValue); + return; + } + + if (isEvent(attrName)) { + setEvents(node, attrValue); + return; + } + + setProp(node, attrName, attrValue); + }); +} + +function setupNodeChildren (node, children) { + children.forEach(child => node.appendChild(child instanceof Node ? child : document.createTextNode(child))); +} + export function h (name, attrs, ...chren) { const node = typeof name === 'function' ? handleFunction(name) : document.createElement(name); - Object.keys(attrs || []).forEach(attr => - shouldBeAttr(attr, attrs[attr]) - ? node.setAttribute(attr, attrs[attr]) - : (node[attr] = attrs[attr])); - chren.forEach(child => node.appendChild(child instanceof Node ? child : document.createTextNode(child))); + setupNodeAttrs(node, attrs); + setupNodeChildren(node, chren); return node; } diff --git a/test/unit.js b/test/unit.js index dacc1a8..4d42e8b 100644 --- a/test/unit.js +++ b/test/unit.js @@ -12,7 +12,7 @@ import '@webcomponents/shadydom'; // eslint-disable-next-line no-unused-vars import { h, mount } from '../src'; -const { customElements, DocumentFragment, HTMLElement, Promise } = window; +const { customElements, DocumentFragment, HTMLElement, Promise, Event, CustomEvent } = window; describe('bore', () => { it('creating elements by local name', () => { @@ -32,15 +32,51 @@ describe('bore', () => { data-test='data something' test1='test something' test2={1} + attrs={{ + 'aria-who': 'Tony Hawk', + who: 'Tony Hawk', + deck: 'birdhouse', + rating: 10 + }} />; - expect(div.getAttribute('aria-test')).to.equal('aria something'); - expect(div.getAttribute('data-test')).to.equal('data something'); + expect(div.hasAttribute('aria-test')).to.equal(false); + expect(div.hasAttribute('data-test')).to.equal(false); expect(div.hasAttribute('test1')).to.equal(false); expect(div.hasAttribute('test2')).to.equal(false); - expect(div['aria-test']).to.equal(undefined); - expect(div['data-test']).to.equal(undefined); + + expect(div.hasAttribute('aria-who')).to.equal(true); + expect(div.hasAttribute('who')).to.equal(true); + expect(div.hasAttribute('deck')).to.equal(true); + expect(div.hasAttribute('rating')).to.equal(true); + + expect(div['aria-test']).to.equal('aria something'); + expect(div['data-test']).to.equal('data something'); expect(div.test1).to.equal('test something'); expect(div.test2).to.equal(1); + + expect(div['aria-who']).to.equal(undefined); + expect(div.who).to.equal(undefined); + expect(div.deck).to.equal(undefined); + expect(div.rating).to.equal(undefined); + }); + + it('setting events', () => { + const click = (e) => { e.target.clickTriggered = true; }; + const custom = (e) => { e.target.customTriggered = true; }; + + const dom =
; + + dom.dispatchEvent(new Event('click')); + dom.dispatchEvent(new CustomEvent('custom')); + + expect(dom.onclick).to.equal(null); + expect(dom.click).to.not.equal(undefined); + expect(dom.getAttribute('click')).to.equal(null); + expect(dom.clickTriggered).to.equal(true); + + expect(dom.custom).to.equal(undefined); + expect(dom.getAttribute('custom')).to.equal(null); + expect(dom.customTriggered).to.equal(true); }); it('mount: all(string)', () => {