dio.js

Dio is a fast and lightweight (~9kb) feature rich Virtual DOM framework.
- ~9kb minified+gzipped
- ~20kb minified

Browser Support
- Edge
- IE 8+
- Chrome
- Firefox
- Safari
Installation
direct download
<script src=dio.min.js></script>
CDN
<script src=https://cdnjs.cloudflare.com/ajax/libs/dio/1.2.3/dio.min.js></script>
<script src=https://cdn.jsdelivr.net/dio/1.2.3/dio.min.js></script>
<script src=https://unpkg.com/dio.js@1.2.3/dio.min.js></script>
bower
bower install dio.js
npm
npm install dio.js --save
You can also play with Dio on this jsbin
Getting Started
Dio is a fast and lightweight (~9kb) feature rich Virtual DOM framework
built around the concept that any function/object can become a component.
Components in Dio share the same api's as react with a few additions,
this means that you can easily port both ways between Dio and React as and when needed
without any significant changes to the code base,
the minimal change in most cases being React, ReactDOM = dio
,
ergo if you know react then you probably already know Dio and vice-versa.
Having said that dio can be used as just a "view" library but it does come
self containeed with everything you would need to build an application,
from routing, http requests, server-side rendering to testing, state stores, animations and more.
In that respect this getting started guide aims to show you how to go from zero to hello world in Dio.
Hello world
function HelloWorld () {
return {
render: function (props) {
return h('h1', props.text);
}
}
}
dio.createRender(HelloWorld)({text: 'Hello World'})
Will mount a h1 element onto the page the contents of which will be 'Hello World'.
Performance
Single File Components
Single file components are self contained components with their own stylesheets,
this is possible with the use of dio.createStyle
allowing us to create a component
within one file or function that has no external dependencies.
var namespace = '#id';
var styles = {
'h1': {
'font-size': '40px',
width: function () {
return window.innerWidth
},
'&:hover': {
color: 'blue'
},
'&:after': {
content: ' '
},
'@keyframes blink': {
0: {
backgroundColor: 'blue'
},
50: {
backgroundColor: 'black'
}
}
}
}
dio.createStyle(styles, namespace);
function HelloWorld () {
return {
render: function (props) {
return h('div'+namespace,
h('h1', props.text)
)
}
}
}
dio.createRender(HelloWorld, '.app')({text: 'Hello World'});
You can also play with this on JS Bin
API Reference
components schema
{
shouldComponentUpdate: (props, state, this) => {}
componentWillReceiveProps: (props, state, this) => {}
componentWillUpdate: (props, state, this) => {}
componentDidUpdate: (props, state, this) => {}
componentWillMount: (props, state, this) => {}
componentDidMount: (props, state, this) => {}
componentWillUnmount: (props, state, this) => {}
componentDidUnmount: (props, state, this) => {}
this.withAttr ({String|String[]}, {Function|Function[]})
this.forceUpdate: (
this?: {Object},
props|callback: {Object|Function},
children?: {Any}
)
this.setState: ({Object}, callback: {Function})
this.setProps: ({Object})
displayName: {String}
propTypes: {Object}
render: (props, state, this) => {}
}
creating hyperscript objects
h(
tag: {String},
props?|children?: {Object} | {Array|Object}...,
children?: {Array|Object}...
)
h('.wrap')
h('input[type=checkbox]')
h('input', {value: 'text', onInput: ()=>{}})
h('div', 'Text')
h('div', h('span', 'Text'))
h('div', [h('h1', '1st'), h('h1', '2nd'), ...])
h('div', {innerHTML: "<script>alert('hello')</script>"});
h(Component, {who: 'World'}, 1, 2, 3, 4)
h('div', ComponentFunction)
h('div', Component)
h('@fragment', 'Hello', 'World')
h('@foo', 'Hello', 'World')
render: function () {
return [
'Hello',
'Workd'
];
}
render: function () {
h('@', 'Hello', 'World');
}
dio.createComponent
dio.createComponent({Function|Object})
dio.createClass({Function|Object})
class Component extends dio.Component {
render() {
}
}
var myComponent = dio.createComponent({
render: () => { return h('div') }
});
var myComponent = dio.createComponent(function () {
return {
render: () => { return h('div') }
};
});
myComponent(__,__,true)
dio.createRender
dio.createRender(
component: {Function|Object},
mount?: {String|Element|Function}
)
dio.render(
component: {Function|Object},
mount?: {String|Element|Function}
)
var instance = dio.createRender(Component)
instance(props: {Object}, children: {Any}, forceUpdate: {Boolean})
instance(__, __, '@@dio/COMPONENT')
dio.createRender(h(Component, {...props}, ...children));
Components that do not return an object with a render function
or are itself an object with a render function will not feature
the component lifecycles and methods as presented above in the schema.
component examples.
function () {
return h('div', 'Hello World')
}
function () {
return {
render: function () {
return h('div', 'Hello World')
}
}
}
h('div', 'Hello World')
{
render: function () {
return h('div', 'Hello World')
}
}
dio.createComponent({
render: function () {
return h('div', 'Hello World')
}
})
dio.createComponent(function () {
return {
render: function () {
return h('div', 'Hello World')
}
}
})
class Component extends dio.Component {
constructor(props) {
super(props)
}
render() {
return h('div', 'Hello World')
}
}
Components create with .createClass/.createComponent/.Component
are
statefull by default. There are also other scenarios that pure functions
may become statefull, below are some examples.
function Pure () {
return {
render: function () {
return h('h1');
}
}
}
function Parent () {
return {
render: function () {
return h('div', Pure);
}
}
}
var render = dio.createRender(Pure);
var render = dio.createRender(Parent);
Note that in the getting started section 'Hello World'
we did not create a component with dio.createComponent()
but rather just used a pure function that we passed
to dio.createRender(here)
this is because
.createRender
will create a component if
what is passed to it is not already a component as detailed above.
mount examples.
document
document.body
document.querySelector('.myapp')
'.myapp'
document.createElement('div')
function el () {
return document.body;
}
dio.createRender(__, mount)
How do i render one component within another?
Components are for the most part functions(statefull/stateless),
to render call them h('div', A())
or place them h('div', A)
as needed with the exception of classes created with
class B extends dio.Component
that should only use placement
h('div', B)
note: render instances(created with .createRender) are not components
function Foo (props) {
return h('h1', props.text)
}
var Bar = dio.createComponent({
render: function (props) {
return h('h2', props.text)
}
});
function () {
var elementHolder = dio.createStream()
return {
render: function () {
return h('div', {ref: elementHolder},
Foo({text: 'Hello'}),
Bar({text: 'World'})
)
}
}
}
dio.createRouter
dio.createRouter(
routes: {Function|Object},
rootAddress?: {String},
onInitNavTo?: {String},
mount?: {String|Element}
)
example router
dio.createRouter({
'/': function () {
dio.createRender(home)()
},
'/user/:id': function (data) {
dio.createRender(user)(data)
}
}, '/backend', '/user/sultan')
dio.createRouter({
'/': ComponentA,
'/user/:id': ComponentA
}, null, null, document.body);
You can then assign this route to a variable and use it to navigate across views
var myrouter = ...
myrouter.nav('/user/sultan')
myrouter.go(-2)
myrouter.back()
myrouter.forward()
dio.createStore
dio.createStore(
reducer: {Function|Object},
initalState: {Any}
enhancer: {Function}
)
Alot like the redux createStore or rather it's exactly like redux createStore
with the addition of .connect
that accepts a render insance or a component
and mount with which to update everytime the store is updated.
Which is mostly a short hand for creating a listerner with .subscribe
that updates your component on state changes.
var store = dio.createStore(reducer: {Function})
var store = dio.createStore(object of reducers: {Object})
store.dispatch({type: '' ...})
store.getState()
store.subscribe(listener: {Function})
store.connect(render: {Function})
store.connect(render: {Function|Object}, element: '.myapp')
dio.createHTML
Like the name suggests this methods outputs the html
of a specific component/render instance with the props passed
to it, this is normally used within the context of server-side rendering.
When used as a server-side tool add the attribute data-hydrate
to the container you wish to ouput the html, this will tell DIO not to
hydrate the current dom on initial mount.
dio.createHTML(component, props, children);
dio.createHTML(h('div', 'Text'));
dio.createHTML(renderInstance, props, children);
renderToString();
renderToStaticMarkup();
dio.createStream
dio.createStream(store: {Any|Function}, mapper);
var alwaysString = dio.createStream(100, String);
alwaysString()
var foo = dio.createStream('initial value')
foo('changed value')
foo('changed')('again')
foo()
var bar = foo.map(function(foo){
return foo + ' and bar';
});
bar()
foo('hello world')
bar()
var faz = dio.createStream.combine(function(foo, bar, prevValueOfFaz){
return foo() + bar();
}, foo, bar);
foo(1)
bar(2)
faz()
faz.then(function(faz){
console.log(faz)
});
faz('changed')
faz.then(fn).then(fn)....
dio.createStream.all([dep1, dep2]).then(fn);
var async = dio.createStream(function (resolve, reject) {
setTimeout(resolve, 500, 'value');
});
var async = dio.createStream(function () {
setTimeout(reject, 500, 'just because');
}).then(function (value) {
console.log(value + 'resolved')
}).catch(function (reason) {
console.log('why:' + reason)
});
var foo = dio.createStream(function (resolve, reject) {
xhr.onload = resolve;
xhr.onerror = reject;
xhr.send();
});
foo
.then(function (value) { return 100 })
.then(function (value) { console.log(value+20) })
.catch(function (value) { return 100 })
.catch(function (value) { console.log(value+200) })
var numbers = createStream();
var sum = createStream.scan(function(sum, numbers) {
return sum + numbers();
}, 0, numbers);
numbers(2)(3)(5);
sum();
dio.curry
var foo = dio.curry(
fn: {Function|String},
args...: {Any[]},
preventDefault: {Boolean},
)
onClick: dio.curry(
(a) => {'look no e.preventDefault()'},
['a'],
true
)
function DoesOneThing (component, arg1, arg2) {
component.setState({...});
}
h('input', {
onInput: dio.curry(DoesOneThing, [this, 1, 2], true)
})
dio.createStyle
creates a style element that is mounted to document.head
,
the output is auto prefixed and resembles sass/scss in the use of the "&" character
in nested styles. See the Single File Components section for an example.
dio.createStyle(css: {Object}, namespace?: {String});
dio.createStyle({'p': {color:'red'}}, '#id');
dio.createFactory
dio.createFactory(any[]|...arguments);
createFactory returns or exposes a function to the window that
produces a hyperscript element of a given type.
If the last argument is a Boolean true
the element factories are added to the global namespace
otherwise an object of the element factories are returned.
If only one element is specified, only that factory is returned and not an object,
if this is coupled with true as the second argument, the factory is added to
the global namespace instead.
dio.createFactory('div', 'input', true);
h('div', 'Hello World');
div('Hello World');
input({value: 'empty'});
dio.createFactory('div', 'input', true);
var {div, input} = dio.createFactory('div', 'input');
var div = dio.createFactory('div');
dio.createFactory('div', true);
dio.request
a http helper that makes ajax requests.
dio.request(
url: {String},
payload?: {Object},
enctype?: {String},
withCredentials?: {Boolean}
)
dio.request.post('/url', {id: 1234}, 'json')
.then((res)=>{return res})
.then((res)=>{'do something'})
.catch((err)=>{throw err});
dio.request({
method: 'GET',
url: '/url',
payload: {id: 1234},
enctype: 'json',
withCredentials: false
})
.then((res)=>{return res})
.catch((err)=>{throw err});
dio.animateWith
dio.animateWith.flip(
className: {String},
duration: {Number},
transform: {String},
transformOrigin: {String},
easing: {String}
)(
element: {Element|String}
)
dio.animateWith.transitions(
className: {String},
type?: {String|Number|Boolean}
)(
element: {Element},
callback: {Function} => (element: {Element}, transitions: {Function})
)
dio.animateWith.animations(...)
for example dio.animateWith.flip
can be used within a render as follows
render: function () {
return h('.card',
{
onclick: dio.animateWith.flip('active-state', 200)
},
''
)
}
since dio.animateWith.flip(...)
returns a function this is the same as
dio.animateWith.flip('active-state', 200)(Element)
another animation helper is animateWith.transitions
and animateWith.animations
the callback function supplied after the element will execute after the
resulting animation/transition from adding/removing the class completes.
handleDelete: function (e) {
var
element = e.currentTarget,
self = this
dio.animateWith.transitions('slideUp')(element, function(el, next) {
store.dispatch({type: 'DELETE', id: 1234});
next('slideLeft')(el, function (el, next) {
next('slideLeft', -1)(el);
});
})
}
dio.PropTypes
validates props passed to components insuring they are of the the specificied type. The built in validtors work exactly as they do in react land namely.
[
number, string, bool, array, object, func, element, node
any, shape, instanceOf, oneOf, oneOfType, arrayOf, objectOf
]
and you can also create your own validators though it should be noted that propTypes/validations are only evaluated when NODE_ENV
or process.env.NODE_ENV
are defined and set to 'development'
.
dio.createComponent({
propTypes: {
id: dio.PropTypes.string.isRequired,
name: dio.PropTypes.string,
custom: function (
props,
propName,
displayName,
createInvalidPropTypeError,
createRequiredPropTypeError
) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + displayName + '`. Validation failed.'
);
}
}
}
render: function () {
}
})
createInvalidPropTypeError(
propName: {String},
propValue: {Any},
displayName: {String},
expectedType: {String}
)
createRequiredPropTypeError(
propName: {String},
displayName: {String}
)
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
return createInvalidPropTypeError(
propName, props[propName], displayName, 'expected type'
)
dio.injectWindowDependency
injects a mock window object to be used for writting tests for
features that do not exist outside of the browser enviroment,
like XMLHttpRequest
and other #document
operations.
dio.injectWindowDependency({Object})
Utilities
_: {
addEventListener: addEventListener,
assign,
keys,
forEach,
reduce,
reduceRight,
filter
map,
toArray,
isObject,
isFunction,
isString,
isArray,
isDefined
}