DIO

dio is a fast javascript framework for building applications.

Support
- Edge
- IE 9+
- Chrome
- Firefox
- Safari
- Node.JS
Installation
direct download
<script src=dio.min.js></script>
cdn
<script src=https://cdnjs.cloudflare.com/ajax/libs/dio/5.0.2/dio.min.js></script>
<script src=https://cdn.jsdelivr.net/dio/5.0.2/dio.min.js></script>
<script src=https://unpkg.com/dio.js@5.0.2/dio.min.js></script>
bower
bower install dio.js
npm
npm install dio.js --save
or play with dio on jsbin
Getting Started
const Main = h => props => h('h1', props.value);
dio.render(Hello)({name: 'World'});
will mount a h1 element to the page the content of which will be 'Hello World'.
API
Components
You can create a component in the following 3 ways.
class Hello extends dio.Component {
render () {
}
}
function Hello () {
return {
render () {
}
}
}
var Hello = h => ({
render () {
}
});
var Hello = h => props => ();
var Hello = dio.createClass({
render () {
}
});
render
The render method tells dio what the component will render. This method recieves props
, state
& context
as its arguments and can return a VNode h(...)
, an array of VNodes [h()...]
or nothing(null/empty render). This is the only required method to render a component.
class Hello extends dio.Component {
render () {
return h('div', 'Text') || [h('div'), h('div')]
}
}
stylesheet
The stylesheet method tells dio how the component appearance will look like(its style/css). This method receives no arguments but returns a string of css. This css will get namespaced to the component and prefixed where required.
class Hello extends dio.Component {
stylesheet () {
return `
color: red;
> h1 { font-size: 20px; }
`
}
}
componentWillUpdate
componentWillUpdate is a method called before the component updates and receives as arguments the new props and new state.
class Hello extends dio.Component {
componentWillUpdate (newProps, newState) {
}
}
componentDidUpdate
similar to componentWillUpdate, componentDidUpdate is called after the component updates and receives as arguments the new props and new state.
class Hello extends dio.Component {
componentDidUpdate (newProps, newState) {
}
}
componentWillMount
componentWillMount is a method called before the component is mounted to the document and recieves as an argument the element being added to the document.
class Hello extends dio.Component {
componentWillMount (element) {
}
}
componentDidMount
similar to componentWillMount, componentDidMount is called after the component is mounted to the document and recieves as an argument the element that was added to the document.
class Hello extends dio.Component {
componentDidMount (element) {
}
}
componentWillUnmount
similar to componentWillMount, componentWillUnmount is called before the component is un-mounted from the document and receives as an argument the element that is about to be removed.
class Hello extends dio.Component {
componentWillUnmount (element) {
}
}
componentWillReceiveProps
componentWillRecieveProps is called before the component recieves new props.
class Hello extends dio.Component {
componentWillReceiveProps () {
}
}
shouldComponentUpdate
shouldComponentUpdate is a method that is called before the component is updated and determines if the component should update.
class Hello extends dio.Component {
shouldComponentUpdate () {
return false;
}
}
setState
setState is used when a component requires a state update. setState accepts two arguments the last of which is an optional callback that will get called within the context of the component if passed after the state update. The first argument is an {Object}
representing the update to the components state. Alternatively you can update the state directly this.state.id = 1
and call this.forceUpdate
manually. It should however be noted that setState is synchronous.
class Hello extends dio.Component {
handleClick () {
this.setState({id: 1})
}
render () {
return h('div', {onClick: this.handleClick})
}
}
forceUpdate
forceUpdate is a method you can call when the component requires a render update, this method unlike setState
by passes shouldComponentUpdate
if defined.
class Hello extends dio.Component {
handleClick () {
this.forceUpdate();
}
render () {
return h('div', {onClick: this.handleClick})
}
}
getDefaultProps
getDefaultProps is a method used to assign the default props of a component when the component does not receive props. This method receives no arguments and returns an {Object}
. This method is supported by components created with createClass functions and classes. Alternatively you could also specify defaultProps
for function and class components.
getInitialProps
similar to getDefaultProps, getInitialProps is a method used to assign the initial props of the component. This method receives the props passed to the component if any and returns an {Object}
or sets the props manually via this.props = {}
.
getInitialState
similar to getDefaultProps, getInitialState is a method used to assign the initial state of the component. This method receives no arguments and returns an {Object}
or sets the state manually via this.state = {}
.
state
describes the current state of the component, you can declare the initial state of a component in 3 ways using any of the conventions of creating a component.
class Hello extends dio.Component {
getInitialState () {
return {id: 1234}
}
}
function Hello {
return {
constructor () {
this.state = {id: 1234}
}
}
}
function Hello {
return {
state: {id: 1234}
}
}
props
props are the values passed down to a component, the convention is that they should be treated as immutable objects(read-only), that is you shouldn't mutate them - this.props.value = 1
, though nothing will break if you do and nothing stops you from doing that.
events
events are props prefixed with on
and case insensitive. The value of an event prop can either be a function or an object.
class Hello extends dio.Component {
handleClick (e) {
}
render () {
return h('div', {
onClick: this.handleClick
})
}
}
Object values are used to specifiy bindings, this allows for either creating a twa-way binding system between a property of the component and a property of the element or binding a method to a specific context or data.
For example if we wanted to bind the handler
function to the components instance.
const handler = component => component.state.id = 1;
class Hello extends dio.Component {
render () {
return h('div', {
onClick: {
bind: handler,
with: this
}
})
}
}
The handler
function will receive two props, the value of this
and a second argument that is the event object, if handler
was not an arrow function the this
context would also be the components this
context that the value of with: this
.
But if we wanted to bind a property of the component to a property of the element we could do it as follows, where the this
property is the object you want to bind.
class Hello extends dio.Component {
render (props, state) {
return h('div', {
onClick: {
bind: {
this: state,
property: 'foo'
},
with: 'value'
}
})
}
}
By default two-way data binding will envoke e.preventDefault
unless specified otherwise by assigning false
to a property preventDefault
on the object passed. Optionally if the property of the element and component you are binding to use the same name then creating an object for the bind property can be ommited.
class Hello extends dio.Component {
render (props, state) {
return h('div', {
onClick: {
bind: state,
with: 'value'
}
})
}
}
Elements
Elements are of three types, VComponent
, VElement
, VText
and VSvg
. The h
(read as hyperscript) and createElement
functions hide away these low-level types that you can access if the need arises. In a browser enviroment h
is also exposed to the global scope.
class Hello extends Component {
render () {
return h('div');
}
}
var component = h(Hello, {id: 1}, 1, 2)
var element = h('div', 'Hello', 'World')
var h('svg', h('path'))
var special = h('input.green[type=checkbox][checked]')
var element = h('div', Hello);
Refs
refs are an escape hatches used to extract the DOMNode the a Virtual Element represents
h('div', {ref: (el) => el instanceof Element});
The only argument passed to a function ref is the DOMNode the Element represents. The ref function will be called within the this context of its root component if it has any.
class Hello extends Component {
render () {
return h('div', {
ref: function () {
this instanceof Hello;
}
});
}
}
Strings can also represent refs.
class Hello extends Component {
onClick () {
this.refs.div.nodeName === 'DIV';
},
render () {
return h('div', {ref: 'div', onClick: this.onClick.bind(this)});
}
}
createFactory
createFactory creates a new element constructor/factory. This method receives two arguments the second of which is an optional props and the first the type of the element.
var h1 = dio.createFactory('h1', {class: 'headings'});
var welcome = h1('Welcome');
var goodby = h1('GoodBye');
DOM
DOM is similar to createFactory except it receieves one argument that is an array of strings used to create multiple factories.
var {h1, p, div} = dio.DOM(['h1', 'p', 'div']);
var heading = h1({class: 'h1'}, 'Heading');
var paragraph = p({class: 'p'}, 'Call Me');
isValidElement
isValidElement checks if the passed argument is an virtual element.
dio.isValidElement(h('h1'));
cloneElement
cloneElement accpets 3 arguments, (element, newProps, newChildren)
the last two of which are optional, when newProps are passed it shallow clones newProps and replaces the elements children with newChildren if passed as part of the arguments.
render
Not to be confused with the Component method render dio.render
is used to mount a Component/Element to the document and create render factories. As you will recall in the getting started section we did something like this.
function Hello () {
return {
render () {
return h('h1' 'Hello ' + this.props.name);
}
}
}
dio.render(Hello)({name: 'World'});
The render method however also accepts a second argument, a mount element/mount selector defaulting to document.body
if omitted.
dio.render(Hello)({name: 'World'}, '.container');
dio.render(Hello, document.body)({name: 'World'}, '.container');
The return value is a render instance that can be used to re-render as needed.
var hello = dio.render(Hello)({name: 'World'}, '.container');
hello({name: 'Mars'});
Will mount Hello then update it with {name: 'World'}
then update it again with the props {name: 'Mars'}
the result will go from // nothing
to <h1>Hello World</h1>
to <h1>Hello Mars</h1>
.
The render method also accepts Object
that you may pass to createClass
dio.render({
state: {name: 'World'},
render: (props, state) => h('h1', 'Hello' + state.name)
})
The render method is non-destructive, that is it will not destroy the contents of the element you wish to mount to. If you wanted to retrieve the root node that is mounted you can pass a function as a third argument that will recieve a single argument that is the root node after the Element/Component has mounted. The callback function will execute with the this refering to the component instance if a component was passed to render.
dio.render(Hello)({name: 'World'}, document.body, function (el) { this instanceof Hello; } );
The last argument the render method accepts is a {boolean}
that signals whether to hydrate the DOM true
or active a destructive render false
. This is usefull in the context of Server Side Rendering when coupled with renderToString
or renderToStream
.
renderToString
similar to render but renders the output to a string. This is usefull in a server-side enviroment.
function Hello () {
return {
render () {
return h('h1' 'Hello ' + this.props.name);
}
}
}
dio.renderToString(Hello, template)
This method accepts two arugments the second of which is an optional template
string/function that is used to determine where to render the output. for example you could use it in the following ways
var output = dio.renderToString(Hello);
var output = dio.renderToString(Hello, `
<html>
<title>Hello</title>
<body>@body</body>
</html>
`);
var output = dio.renderToString(Hello, function (body, stylesheet) {
return `
<html>
<title>Hello</title>
@stylesheet
<body>@body</body>
</html>
`
})
renderToStream
similar to renderToString except this method renders the output to a stream. This is usefull in a server-side enviroment as well and has the advantage over renderToString in that it is non-blocking and delivers chunks of bytes to the client for great "time to first bytes" and overall constant CPU load.
dio.renderToStream(Hello, template)
This method like renderToString accepts 2 arguments the second of which is differs from renderToString in that it expects template
to be a string if passed where as renderToString also accepts functions.
createStore
dio.createStore(
reducer: {(function|Object)},
initalState: {Any}
enhancer: {Function}
)
This method creates a store similar to redux createStore
with the different of .connect
that accepts a component & mount/callback
with which to update everytime the store is updated.
Which is 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(callback: {function})
store.connect(callback: {(function|Object)}, element: {(string|Node)})
applyMiddleware
although you can archive the same effect with the third argument of createStore()
applyMiddleware is exposed as a public api to allow manual handling of the above proxy detailed in createStore. applyMiddleware accepts a variable number of function arguments that are composed into middlewares.
dio. applyMiddleware(fn, fn, fn...);
combineReducers
This method allows you manually handle combinding multiple reducers into one. It accepts one {Object<string, function>}
argument.
var reducer = dio.combindReducers({
add: () => {},
subtract: () => {}
})
request
request is a method that provides a http layer to help make ajax calls to api end points. For example if i wanted to make a get request to google.com
i could do it in one of the following ways
dio.request('google.com');
dio.request.get('google.com');
dio.request({url: 'google.com'})
Below is a full run down of all the arguments the request method accepts, all of which are optional with the exception of the url
.
dio.request(
url: {string},
payload?: {Object},
enctype: {string=},
responseType: {string='(html|document)'|'json'|'text'}
withCredentials: {boolean=},
initial: {any=},
headers: {Object=},
config: {function=},
username: {string=},
password: {string=}
)
examples creating POST
and GET
requests.
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,
config: function (xhr) {
xhr.setRequestHeader('x-auth-token',"xxxx");
}
}).then((res)=>{return res})
.catch((err)=>{throw err});
router
This methods accepts two arguments the last of which is optional. In the below example /user/dio
is the initial route to navigate to and /catagory
represents the root address of the app. The arguments passed to the middleware if specified are ``component/function,
dataand
element, where data is an
{url: '' ...}representing the data attached to the route. If a route is not matched the
404` method will get called if defined.
var router = dio.router({
'/': function (data) {
dio.render(h(home, data))
},
'/user/:id': function (data) {
dio.render(H(user, data));
}
}, {
initial: '/catagory',
directory: '/user/dio',
middleware: function (fn, data, el) {},
'404': function (data) {
}
})
The above example handles mounting manually alternatively you can also create a router using a mount element and Components in the following fashion.
var router = dio.router({
'/': Bar,
'/user/:id': Foo
}, {
mount: document.body
});
In both cases :id
variables are passed to Foo and Bar as props when the route is activated or to the function specified such that in the case where the url /user/1234
is matched the object passed will be represented as {url: '/user/1234', id: 1234}
.
When called this method will return an object with resolve
, routes
, back
, foward
, link
, resume
, pause
, destroy
, set
and location
properties. The location property can be used to navigate between views router.location = '/user'
or link it to an action with router.link
in the following way.
h('h1', {onClick: router.link('href'), href: '/'}, 'Home')
h('h1', {onClick: router.link('/')}, 'Home')
h('h1', {onClick: router.link(el => {
return el.getAttribute('href')
}), href: '/'}, 'Home')
The pause
method pauses listerning to route changes, resume
does the opposite and can be used after destroy
to start things up a fresh. The methods back
and foward
are proxies to history.back
& history.foward
and the destroy method stops the router and clears all registered routes. The set method is used interally to regsiter a route, it can be used at any point to assign/re-assign/add routes as needed. If you wanted to access the registered routes you can access the routes
property, it is immutable so you cannot change the already registered routes without using set
or destroy
. The resolve
method exists for cases where you might want to resolve a route without relying on window.location
or changing location manually with location
.
router.resolve('/user/1234');
router.location = '/user/1234';
routes.pause();
routes.location = '/en/';
routes.resume();
routes.set('/:user/:name', (data) => data);
stream
streams in dio are convinient getters/setters that also act like promises. For example var foo = dio.stream(10)
. The variable foo now holds a function that when called without any arguments returns 10 foo()
and updates the value that foo holds when called with an argument foo(20)
.
The second argument in dio.stream
is a optional argument representing a mapper, this allows use to extend streams in different ways.
Before we go into extending stream it's important to know that like promises streams have .then
, .done
and .catch
methods that can be used to listen for the resolution and/or rejection of a stream.
var foo = dio.stream(10).then(foo => console.log(foo))
foo(20);
var foo = dio.stream(function (resolve, reject) {
setTimeout(resolve, 0, 20);
}).then(foo => console.log(foo)).catch(msg => console.log(msg))
Which allows the above piece of code to log the value of foo everytime the value successfully resolves and log the error message anytime it rejects.
If you wanted to insure that the value returned by the stream is always of a certain type you can pass a second function to stream
that will be used to resolve the value through before retrieving the value of the stream.
var string = dio.stream(100, String);
string()
string(1)(2)
You could also pass a {boolean}
as the second argument if you wanted to pass a function as a stream that should return the returned value when retreiving the value of the stream, this can come in handy in some cases one of which includes extending streams.
Similar to .then
streams also have a .map
method which allows you to create a stream that is dependent on another streams value such that when the value of one stream changes so does the other.
var foo = dio.stream(10);
var bar = foo.map(function (foo) {
return foo * 2;
});
bar()
foo(2)
bar()
Since streams a similar to promises creating a stream in a resolved/rejected state applies to streams as well though creating streams in a resolved state is identical to creating streams, for example the following to are the same.
var foo = dio.stream.resolve(10);
var foo = dio.stream(10);
the following is a stream created in a rejected state
var foo = dio.stream.reject('reason');
The above stream api allow us to extend streams as needed. To showcase this i will try to extend the streams with a .scan
method that creates streams that accumulates everytime it is called such that calling foo(1)(1)(2)
will result in a stream bar
holding the value 4
.
dio.stream.scan = function (reducer, accumulated, stream) {
return Stream(function (resolve) {
stream.then(function (value) {
accumulated = reducer(accumulated, value);
resolve(accumulated);
});
});
};
var foo = dio.stream();
var bar = dio.stream.scan((sum, n) => {
sum + n
}, 0, foo);
foo(1)(1)(2)
bar()
FAQ
Is this like react?
Yes, dio shares a great deal of its api with react.
What is the nano package
The nano is dio core (4kb) without the extras.