elem-vdom
Simple and idiotic lib to build UI components. It's a component library promoting functional composition with the full expressiveness of JavaScript and support for all existing JavaScript libraries. elem-vdom is just a quick and dirty experiment to avoid string templates, string concat and manual mutations when modifying the DOM. elem-vdom
is fully written in ES2015
Install
with npm do :
npm install elem-vdom --save
Build
If you want to build the project yourself, you need to install io.js and do the following :
git clone https://github.com/mathieuancelin/elem-vdom.git elem-vdom
cd elem-vdom
npm install
npm run build
Examples
If you want to run examples, just clone the project then :
git clone https://github.com/mathieuancelin/elem-vdom.git elem-vdom
cd elem-vdom
npm install
npm start
open http://localhost:8080/examples
API
Elem.el(name, attributes, children)
: Create a representation of an HTML element. Children can be a string/number/boolean, anElem.el
, an array ofElem.el
or a__asHtml
object.Elem.svg(name, attributes, children)
: Create a representation of a simple SVG elementElem.render(elem, container)
: render an element to a container in the DOMElem.renderToString(elem)
: render an element as an HTML stringElem.renderToJson(elem)
: render an element as JSON objectElem.findDOMNode(ref)
: return an actual DOM node based on an elementref
Elem.stylesheet(obj)
: create an extendable set of CSS inline stylesElem.nbsp(times)
: creates a<span>
containing one or more
Elem.text(value)
: creates a<span>value</span>
Elem.predicate(predicate, elem)
: return element if predicate is true or undefined if false. Predicate can be a function
A few other APIs are also available but none are mandatory to use :
Elem.Perf
: performance measurement tools (used in the examples to craft the Perf monitor)Elem.Store
: tools to create flux like store (heavily inspired by Redux)Elem.Devtools
: tools for DX
Dependencies
elem-vdom depends on virtual-dom
, babel-runtime
and error-stack-parser
but these dependencies are bundled in the distribution package.
How can I use elem-vdom ?
First imports Elem.min.js
in your page. Then you will be able to build your first node
const MyAwesomeNode = Elem;Elem;
Of course, you can build much more complicated nodes
const node = Elem;Elem;
As you construct the node tree with functions and arrays, it is pretty easy to map and filter model objects to render your components easily.
Attributes use camel case shaped keys, so something like backgroundColor
will be rendered as background-color
. Also, you can notice that the class
attribute is named className
. Also, you can provide an object for className
value with boolean as values. Every key with a false value will not be rendered.
const shouldDisplayDarkBackground = true;const shouldDisplayBrightBackground = !shouldDisplayDarkBackground;Elem;
will produce
Hello
As children are just nodes in an array, it is really easy to add or remove elements from your UI. You can also pass undefined elements or functions that can return undefined to not render nodes.
If you want to provide a child as HTML value, just pass an object like {__asHtml: ' ;-)'}
.
You can also attach callback to event on elements like
{ ;} const node = Elem;Elem;
And no, the output WILL NOT BE
Hello World Say Something Lorem ipsum ....
but the following with an event listener on the root element of the component listening to click
events on the button.
Hello World Say Something Lorem ipsum ....
Supported events are
wheel scroll touchcancel touchend touchmove touchstart click doubleclick
drag dragend dragenter dragexit dragleave dragover dragstart drop
change input submit focus blur keydown keypress keyup copy cut paste
SVG
You can also simply use SVG with elem-vdom, using the dedicated API :
{ return Elem;}Elem;
I just want functions everywhere man ...
Pretty easy actually, Elem is made for that :-)
{ return Elem;} { return Elem;} { const hoursRotation = 'rotate(' + 30 * + / 2 + ')'; const minutesRotation = 'rotate(' + 6 * + / 10 + ')'; const secondsRotation = 'rotate(' + 6 * + ')'; return Elem;} { if thiscontextinterval === null thiscontextinterval = ; return Elem;} Elem;
The whole context of the components are also available on this
. Props are different for each components instance (each function call), the context is the same for the whole tree. The context contains the following.
props: 'props of the current element'refs: 'refs of DOM nodes inside the current render tree'state: 'mutable state of the current render tree'refresh or redraw: 'rerender the current function at the same place'setState: 'mutate the state with diff and trigger a refresh'replaceState: 'mutate the whole state and trigger a refresh'getDOMNode: 'return root DOM node of the current render tree'context: 'a multipurpose object shared by all the components in the tree'
What about state and functions
Stateless functions as components is the key pattern of elem-vdom
, but sometimes you need more.
When you render a function using elem-vdom
with Elem.render(Function, container)
, the function owns a context including a state.
This state will trigger a rendering of the root function each time it is changed with setState
and replaceState
. The state is common to the whole component tree. If you want a component (a function) to get its own state, you just have to assign a key to it.
Elem
Then the this.state
passed to the function MyButton
will be its own. You just have to be careful to assign the same key to the same component each time you render the tree so elem-vdom
can track down the right sub state and pass it to the component.
You can also specify an initialState to the sub-state of a component
{ this; // only set once ...}
it work also with the main render state
{ this; // only set once ...}Elem;
But, I like jsx syntax, how can I use it ?
If you use babel, add jsxPragma=Elem.jsx to options in your .babelrc or babel-loader.
see
https://github.com/mathieuancelin/elem-vdom/blob/master/.babelrc#L5
or
https://github.com/mathieuancelin/elem-vdom/blob/master/webpack.config.js#L53
then you can write stuff like :
{ return <div style= borderStyle: 'solid' borderColor: thispropscolor || 'black' borderWidth: thispropswidth || '1px'> thispropschildren </div> ;} { return <small>Just a child</small>;} { return <div> <h1>Hello World!</h1> <Bordered color="blue"> <Child /> </Bordered> </div> ;} Elem;
Really, there is no 'actual' component API ?
Actually, there is one
const Clicker = Elem; Elem; // or Elem;
you can also use module syntax
const name = 'Clicker'; { // do whatever you want here} { return count: 0 ;} { return text: 'You clicked' ;} { return clickerService: ClickerService ;} { thiscontextclickerService; this;} { return <div> <span>thispropstext : thisstatecount</span> <button type="button" onClick=click>Click</button> </div> ;}
to use it
; const Clicker = Elem; Elem; // or Elem;
But, how can I get an actual DOM node from inside my component ?
That's pretty easy, you just have to use refs. Refs give you access to any node inside your component that has been marked with a ref
parameter.
{ const clickMe => console; return Elem;} Elem;
But you can't render that stuff server side (universal apps), right ?
Actually you can and it's pretty easy.
First you can use Elem.renderToString
on any Elem.el
node you want.
But you can also do the same on components, let's write a funny clock component;
{ const seconds = % 60 * 6; const minutes = % 60 * 6; const hours = % 12 * 30; return Elem;}
Now we can instanciate it on the server side, and render it as an HTML string :
const express = ;const app = ;const Clock = ; app; const server = app;
on the client side, you just have to re-render the component at the same div dans Elem while re-attach itself generated DOM.
What about webcomponents ?
Just use Elem.registerWebComponent(name-with-a-dash, component)
and use it like an average HTML element
How can I test my components
We recommand to write your tests with mocha
and chai
with a headless browser named jsdom
. You will need to use io.js
instead of node
because jsdom
requires it. First, install everything :
npm install --save-dev mocha chai jsdom simulant
then create a folder for your tests and create a main file that will run all the tests
const env = ; env; const tests = ;
you need to do so, because the setupEnv
function need to be called before the first loading of Elem
. This will setup everything you need to create global window
and document
objects based on working with jsdom
. If you want to use a real browser or something like phantomjs
, you don't need to call setupEnv
, just use an HTML document with a <div id="app"></div>
.
Then in you test file, you can test the behavior of your components :
;;;; ; ;
Here we use a custom it
function that is written like :
{ global;}
so if you need to write an async test and use the done
function when you need it, don't use that, but don't forget to call DOM.cleanup()
to be sure to work on a clean page.
The DOM API is just a bunch of helpers to find nodes in the current document. If you're comfortable with those API, you are not forced to use the DOM API. Just mount your component in the #app node and don't forget to unmount everything between each test. The API is the following :
appHtml()
: return '#app' innerHTMLhtml(of)
: return innerHTML of the selected nodechildren(of)
: return children of the selected node as an arraynode(of)
: return selected nodenodes(of)
: return selected nodes as an arrayrenderComponent(component)
: render anElem
component in the '#app' nodecleanup()
: empty the '#app' nodeon(selector).simulate('keyup', { charCode: 42 })
: fire an event on the selected nodechange(selector, value)
: fire a change event on the selected inputclick(selector)
: click on the selected nodeselect(selector)
: return a fluent API to assert on selected nodeid()
get()
: return the id of the selected nodeshouldBe(value)
: assert on the id of the selected node
className()
get()
: return the className of the selected nodeshouldBe(value)
: assert on the className of the selected node
html()
get()
: return the innerHTML of the selected nodeshouldBe(value)
: assert on the innerHTML of the selected node
children()
get()
: return the children of the selected nodeshouldBe(value)
: assert on the children of the selected nodecount()
get()
: return the number of children of the selected nodeshouldBe(value)
: assert on the number of children of the selected node
click()
: fire a click event on the selected nodechange(value)
: fire a change event on the selected nodeshouldExist()
: assert if the selected node existsshouldNotExist()
: assert if the selected node does not existsshouldBe(value)
: assert if the innerHTML of the parent of the selected node is equals to the value (string or el)simulate(name, event)
: simulate any event on the selected node
Elem.predicate
About You can use Elem.predicate
to render element if a predicate is true
const somethingTrue = true; { return false;} Elem;
will produce :
1 2 4
Elem.stylesheet
About The Elem.stylesheet
function allows you to create something like
var Style = Elem;
And use parts for inline styles of your component. Every stylesheet and stylesheet element owns an extend
function to customize your styles on demand. You can also specify an extend
member in a stylesheet element for declarative customization.
let Style = Elem;let CustomBackground = StylewithBackground;let CustomStyle = Elem;
You can also use it as an actual stylesheet for your pages, just call mount()
on a stylesheet object to mount it in the DOM. You can call unmount()
to remove it.
About Elem.Perf
Elem.Perf
is a performance measurement tool available through
const Perf = ElemPerf;// orconst Perf = ;// or;
The API is the following :
start
: enable performance measurementstop
: disable performance measurementmarkStart(name)
: mark the start of a measuremarkStop(name)
: mark the stop of a measuremark(name, function)
: mark the start and stop of a measure around a block of codemeasures
: return all collected measurescollectMeasures(clear)
: return all collected measures and maybe clear the measures storeprintMeasures
: print collected measures and clear the measures store
You can use the Perf API to build great perf analysis tools like here : https://github.com/mathieuancelin/elem-vdom/blob/master/src/devtools/perfmonitor.js
You can use it with
const Monitor = ;// or; { return Elem;} Elem;
About Elem.Store
Just a bunch of tools to create stores (heavily inspired by Redux) available through
const Store = ElemStore;// orconst Store = ;// or;
The API is the following :
createStore(reducer, initialState)
: Create a store from one or more reducershandleActions(actions, initialState)
: same than createStore with helpers to avoid switch casewithInitialState(initialState)
: builder forhandleActions
enrichCreateStoreWith(plugins...)
: return a newcreateStore
function with enhanced dispatchPlugins
: Plugins to enhance stores withenrichCreateStoreWith
.Provider
: Component that will feed the context with the store and some actionsSelector
: Component that will be use to wrap sub components to pass them state parts and actions
const INCREMENT_COUNTERS = 'INCREMENT_COUNTERS';const DECREMENT_COUNTERS = 'DECREMENT_COUNTERS'; { return type: INCREMENT_COUNTERS ;} { return type: DECREMENT_COUNTERS ;} const counters = Store; { } let store = Store; { return counter: statecountersvalue1 // state.counters because we target the counters reducer ;} { return counter: statecountersvalue2 // state.counters because we target the counters reducer ;} { // here this.props.counter come from the store, throught the selector // here this.prop.perform come from actions on the Store.Selector return <p onClick=thispropsperform>thispropsname : thispropscounter + ''</p>;} { return <StoreProvider store=store actions= increments decrements render= <div> <StoreSelector selector=CounterSelector1 actions= perform: increments name="count1" render=CountLine /> <StoreSelector selector=CounterSelector2 actions= perform: decrements name="count2" render=CountLine /> <button type="button" onClick=thiscontextactionsincrements>+1</button> <button type="button" onClick=thiscontextactionsdecrements>-1</button> </div> /> ;} Elem;
If you want to play with enhanced store, write something like the following
const INCREMENT_COUNTERS = 'INCREMENT_COUNTERS';const DECREMENT_COUNTERS = 'DECREMENT_COUNTERS'; { return type: INCREMENT_COUNTERS ;} { return type: DECREMENT_COUNTERS ;} const counters = Store; let createStore = Store;let store = ; ...
The Logger plugin will be plugged into the dispatch
function of the store and will be executed before the actual dispatch. This allows you to control the actions, pass it to the dispatch, or stop dispatch, or dispatch later.
You can require the plugins from
const Plugins = ElemStorePlugins;// orconst Devtools = ;// or;
A simple example is action logging
const logger = { console; return ;};
You can check out the code of existing plugins at https://github.com/mathieuancelin/elem-vdom/blob/master/src/storeplugins.js
About Elem.Devtools
Just a bunch of tools for DX available through
const Devtools = ElemDevtools;// orconst Devtools = ;// or;
The API is the following :
Elem.Devtools.Perf
: the perf API as explained in About Elem.PerfElem.Devtools.PerfMonitor
: the perf monitor as explained in About Elem.Perf. Also available atelem-vdom/lib/devtools/perfmonitor
Elem.Devtools.Redbox(error)
: A component that will display a JavaScript Error in a red box. Also available atelem-vdom/lib/devtools/redbox
Elem.Devtools.ErrorMonitor(function)
: A function to wrap a function that can throw errors. If so, the Redbox is displayed instead of the wrapped function return. Also available atelem-vdom/lib/devtools/errormonitor
{ return <h1>Hello thispropswho!</h1>;} { return Elem;} Elem;
Elem.Devtools.Inspector
: An inspector element to browse component tree created by elem, inspect props and state, updating state live, trigger redraw, etc ... Just use it withElem.render(Elem.Devtools.Inspector, '#inspector', { __inspectorSilent: true })
. The '__inspectorSilent' is very important at the root of the tree that include the inspector to avoid the inspector to auto refresh itself.
Elem.Devtools.InspectorAPI
: API to get internal data about the rendered Elem trees. You can use the API to build tools like the Inpector.start()
: start inspectionstop()
: stop inspectionisEnabled()
: is inspection startedgetExposedStateAndProps()
: get the current internal datacleanup()
: cleanup current internal datasubscribe(listener)
: subscribe notification of changes of the internal dataephemeralSubscribe(listener)
: subscribe notification of changes of the internal data, but one shot