└── README.md /README.md: -------------------------------------------------------------------------------- 1 | _This is currently under draft and not complete. Feedback welcome on the issue tracker or via [twitter](https://twitter.com/pemrouz)_ 2 | 3 | # Vanilla 4 | ## Ergonomic and Widely Reusable Components 5 | 6 | ![image](https://img.shields.io/badge/component-vanilla-green.svg?style=flat-square) 7 | 8 | This is a general pattern for authoring components that gives component developers the ability to create components and not worry that it can reliably reused in a wide range of contexts. This is a concern in an increasingly fragmented application framework landscape, and also with the advent of encapsulated Web Components which further tilts the balance in favour of writing reusable components rather than rewriting them for each framework. These components follow a simple unidirectional model and progressively enhanced by the Web Components specifications, but are not required for them to work. 9 | 10 | The primary design goals are to refine the boundaries/seams of components, and maximise the ergonomics of authoring components. This is to separate out the concerns of and enable further customisations and innovations to take place orthogonal to the implementation of components. For example, you may wish to take certain actions before a component renders such as pre-applying a related template or inlining styles or invoking lifecyle hooks or catch diffs after the component has run. The components are framework-agnostic, but not the lowest common denominator at the cost of being a long-term solution. 11 | 12 | * [**Authoring**](#authoring) 13 |
[i. Javascript](#1-javascript) 14 |
[ii. Styling](#2-styling) 15 |
[iii. Communication](#3-communication) 16 | 17 | * [**Using**](#using) 18 |
[i. Vanilla](#1-vanilla-component) 19 |
[ii. Ripple](#2-ripple) 20 |
[iii. D3](#3-d3) 21 |
[iv. React](#4-react) 22 |
[v. Angular](#5-angular) 23 | * [Testing](#testing) 24 | * [Performance](#performance) 25 | * [Example Repo](#example-repo) 26 | * [Footnotes](#footnotes) 27 | 28 |

29 | ## Authoring 30 | 31 | ### 1. JavaScript 32 | 33 | ```js 34 | /* Define - component.js */ 35 | export default function component(node, data){ .. } 36 | ``` 37 | 38 | **Stateless:** Components are just plain old functions. `node` is the DOM node being operated on and `data` is an object with all the state and data [1] the component requires to render. This makes components agnostic as to how or where the node/data is injected, which simplifies testing and allows frameworks to co-ordinate linking state with elements in different ways [2]. 39 | 40 | **Idempotent**: For a given dataset, the component should always result in the same representation. This means components should be written declaratively. `node.innerHTML = 'Hi!'` is perhaps the simplest example of this, but use of the `innerHTML` is not the most efficient [3]. A component should not update anything above and beyond it's own scope (`node`). 41 | 42 | **Serializable:** You should not hold any selections or state within the closure of the component (other than variables that will be used within that the cycle). These components are stamps. They will be applied to all instances of the same type. They may be invoked on the server and the client. They may be streamed over WebSockets. They may be cached in localStorage. 43 | 44 | **Declarative:** Event handlers and component API should update the state object and then call `node.draw` which will redraw the component. This is in contrast to modifying the DOM directly and greatly simplifies components by disentagling rendering logic from update logic. The `node.draw` hook can then be extended by frameworks to form their own [rendering pipeline](#rendering-middleware) or simply stubbed in tests. 45 | 46 | **Defaults:** Default values for state can simply be set using the native ES6 syntax: 47 | 48 | ```js 49 | function component(node, { color = 'red', focused = false }){ ... } 50 | ``` 51 | 52 | If you need to default and set initial values on the state object only the first time you can use a [helper function](https://github.com/utilise/utilise#--defaults). The same technique can be used to idempotently expose your component API. 53 | 54 |
55 | ### 2. Styling 56 | 57 | ```css 58 | /* Style - component.css */ 59 | :host { } 60 | ``` 61 | 62 | Component styles should be co-located with the component. Additional styles or skins can be provided as a separate file (`component-modifier.css` or `some-feature.css`). The styles should be written in the [Web Component syntax](http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/) and should work if they were used accordingly (i.e. within a Shadow DOM). There are various modules that interpret these in different ways as a separate concern: apply conventions, dedupe, compile, scope for non-shadow dom browsers, inline, etc. 63 | 64 | You will want a mix of styles to be inherited and not inherited. To make styling robust across different environments, the root element [should defensively set](https://github.com/vanillacomponents/ux-button/blob/4d98a9ae90a6d9632134cc2c0c86f8cbcb4aa311/src/ux-button.css#L1-L17) all the unwanted styles which may leak into the component. It should then [explicitly set those it does wish to inherit](https://github.com/vanillacomponents/ux-button/blob/4d98a9ae90a6d9632134cc2c0c86f8cbcb4aa311/src/ux-button.css#L17) by setting their value to `inherit`, such as `font-size: inherit` etc. Where possible, it's also recommended to [use `em`](https://github.com/vanillacomponents/ux-button/blob/4d98a9ae90a6d9632134cc2c0c86f8cbcb4aa311/src/ux-button.css#L15-L16) to make components responsive to their immediate context. 65 | 66 |
67 | ### 3. Communication 68 | 69 | ```js 70 | // Communicate 71 | node.addEventListener('event', d => { .. }) 72 | node.dispatchEvent(event) 73 | ``` 74 | 75 | The final aspect is that child components will need to communicate with parent components. This is achieved by emitting events on the host node, as this is the only visible part to the parent (the component implementation may indeed be entirely hidden away in a closed Shadow DOM). You can use the native `addEventListener` and `dispatchEvent` for this [4]. If something changes in a parent component that requires the child to update, it should redraw it with the new state (unidirectional architecture). 76 | 77 |

78 | # Using 79 | 80 | ### 1. Vanilla Component 81 | 82 | The simplest way to invoke a component is: 83 | 84 | ```js 85 | import { component } from 'component' 86 | component.call(node, data) 87 | ``` 88 | 89 | This is the pure, low-level, 100% dependency free API. You just `require`/`import` the component function and invoke it on an element with some data. Similarly, you can load the styles by just including the stylesheet via `` or `