Skip to content
This repository has been archived by the owner on Feb 4, 2018. It is now read-only.

Commit

Permalink
Merge pull request #15 from skatejs/attribute-aware-h
Browse files Browse the repository at this point in the history
feat(h): make h set attributes on custom-element via attributes prop
  • Loading branch information
Hotell authored Feb 2, 2017
2 parents eb3cb24 + f0db476 commit e031e57
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 18 deletions.
30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.



Expand Down Expand Up @@ -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 = <my-skate
brand="zero" // this will always set to element property
attrs={{ // this will always set to element attributes
'arial-label':'skate',
mastery: 'half-pipe'
}}
events={{ // this will set event handlers on element
click: e => console.log('just regular click'),
kickflip: e => console.log('just did kickflip')
}}
></my-skate>
```



### `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';
Expand Down
52 changes: 43 additions & 9 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -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;
}

Expand Down
46 changes: 41 additions & 5 deletions test/unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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 = <div events={{click, custom}} />;

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)', () => {
Expand Down

0 comments on commit e031e57

Please sign in to comment.