Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
2071 lines (1713 sloc) 41.7 KB



A JavaScript framework for building UI, almost like writing in VanillaJS.

Documentation

Below some basic concepts:

Installation

npm install --save doz

Import library

//ES6
import Doz from 'doz'

//ES5
var Doz = require('doz');

Make an app

An app is a main component that embed other components.

import Doz from 'doz'

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <h2>Hello World</h2>
        `
    }
});

LIVE


Component definition

The method define or the alias component defines an component globally that can be added to any other component of the project. The tag name must be according to the W3C specs.

Doz.define('hello-world', class extends Doz.Component {
    template(h) {
        return h`
            <h2>Hello World</h2>
        `
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <hello-world></hello-world>
        `
    }
});

LIVE


Props

All props are stored into props (your component data) property of the component and they are accessible through a proxy that detect changes. When there are changes Doz update only the node that containing the updated prop. Doz uses template literals to build the component UI then the props are injected inside the string.

class MyClock extends Doz.Component {
    constructor(o) {
        super(o);
        this.props = {
            time: '--:--:--'
        }
    }
    template(h) {
        return h`
            <h2>${this.props.title} <span>${this.props.time}</span></h2>
        `
    }
    onMount() {
        setInterval(() => this.props.time = new Date().toLocaleTimeString(), 1000)
    }
}

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <${MyClock} title="it's"/>
        `
    }
});

LIVE


Props listener

Since 1.8.0

Doz provides an API called propsListener that allows you to associate an handler to determinate prop. The handler will be triggered to every change for given prop.

Doz.component('my-clock', {
    props: {
        time: '--:--:--'
    },
    propsListener: {
        time: function(newValue, oldValue) {
            console.log('Prop time is changed', newValue, oldValue);
        }
    },
    template(h) {
        return h`
            <h2>${this.props.title} <span>${this.props.time}</span></h2>
        `
    },
    onMount() {
        setInterval(() => this.props.time = new Date().toLocaleTimeString(), 1000)
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-clock title="it's"></my-clock>
        `
    }
});

Props computed

Since 1.9.0

This is useful for performing complex computational operations. The result will be saved in cache.

Doz.component('my-computed', {
    props: {
        aNumber: 0
    },
    propsComputed: {
        aNumber: function(v) {
            return v * Math.random();
        }
    },
    template() {
        return `
            <input type="number" value="0" min="0" d-ref="inputNumber"/>
            <button onclick="this.props.aNumber = this.ref.inputNumber.value">Compute</button>
            <h3>Result ${this.props.aNumber}</h3>
        `
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>a number multiplied by a random number</h1>
            <my-computed/>
        `
    }
});

FIDDLE


Props convert

Since 1.12.0

It is similar to propsComputed with the difference that the result will not be saved in any cache.

Doz.component('my-clock', {
    props: {
        time: '--:--:--'
    },
    propsConvert: {
        time: function(newValue) {
            return `Prepend this string before: ${newValue}`;
        }
    },
    template(h) {
        return h`
            <h2>${this.props.title} <span>${this.props.time}</span></h2>
        `
    },
    onMount() {
        setInterval(() => this.props.time = new Date().toLocaleTimeString(), 1000)
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-clock title="it's"></my-clock>
        `
    }
});

FIDDLE


Delay props update

Since 1.14.0

If you need to delay the update of the props you can use the delayUpdate property, this is useful in case of animations. The onBeforeUpdate event will be called without delay.

Doz.component('my-delay', {
    delayUpdate: 1000, //ms

    props: {
        aNumber: 0
    },

    template() {
        return `
            <div>Number: ${this.props.aNumber}</div>
        `
    },

    onMount() {
        this.props.aNumber = 5;
    },

    onBeforeUpdate() {
        console.log('onBeforeUpdate');
        this.startUpdate = Date.now();
    },

    onUpdate() {
        const totalTime = Date.now() - this.startUpdate;
        console.log('onUpdate', totalTime);
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <my-delay/>
        `
    }
});

Load props

Since 1.16.0

If you need to load the props a runtime, you can use loadProps method.

Doz.component('a-rnd', {
    template(h){
        return h`
            <div>
                <input type="text" d-bind="num"/>
                <p>Value: ${this.props.num}</p>
                <button onclick="this.setNew()">Load props</button>
            </div>
        `
    },
    props: {
        num: 0
    },
    setNew() {
        this.loadProps({
           num: Math.random()
        });
    },
    onLoadProps() {
        console.log('props loaded');
    }
});

new Doz({
    root: '#app',
    template: `
        <h3>Input type text</h3>
        <a-rnd/>
    `
});

Reusing components

Doz applies Object.assign to props for a basic immutability. Obviously assign method is not recursive then you need define props as function, otherwise other instances of the same object will change the same data.

No problem for this scenario

props: {
    value: 0
}

This scenario could create some problems

props: {
    other: {
        value: 0
    }
}

You must convert to a function like so

props: function(){
    return {
        other: {
            value: 0
        }
    }
}

or put definition into onCreate hook

onCreate: function(){
    this.props = {
        other: {
            value: 0
        }
    }
}

But if you use the new ES6 pattern class, the problem does not arise. More info ES6 class

Methods

The methods are defined inside a single object where there are also props and events. Why this choice? Because during development it's essential to have an exact reference of this context.

Doz.component('my-component', {
    props: {
        title: 'Hello World'
    },
    template(h) {
        return h`
            <h1>${this.props.title}</h1>
        `
    },
    yourMethod() {
        // do something
    },
    anotherMethod() {
        this.yourMethod();
    }
});

Handlers

All HTML element of a component accepts standard events. It's possible also passing a component method or actions.

Doz.component('my-button', {
    template(h) {
        return h`
            <button onclick="this.clickme()">Click me!</button>
        `
    },
    clickme(e) {
        alert(e)
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-button></my-button>
        `
    }
});

FIDDLE


Inline logic

Since 1.3.2

Doz supports also inline logic. Normally this is reference of HTML element but in Doz it's every reference of component instance.

Doz.component('my-button', {
    props: {
        count: 0
    },
    template(h) {
        return h`
            <button onclick="this.props.count++">Click me!</button>
        `
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-button></my-button>
        `
    }
});

Passing arguments

Doz keeps the types passed to the function. this is a special placeholder that identify current instance.

Doz.component('my-button', {
    template(h) {
        return h`
            <button onclick="this.clickme(${'hello'}, ${true}, ${123}, ${function(){return true}}, this)">Click me!</button>
        `
    },
    clickme(myArg, otherArg, me, e) {
        alert(myArg + ' ' + otherArg);
        alert(e);
        console.log(me);
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-button></my-button>
        `
    }
});

If you want the HTMLElement reference inside the handler you can use $this.


Emitter

Any component can emit a custom event. See also component directives.

Doz.component('salutation-card', {
    template(h) {
        return h`<caller-o d:on-mycallback="aCallback"></caller-o>`
    },
    aCallback: function(arg) {
        alert('callback is called: ' + arg);
    }
});

Doz.component('caller-o', {
    template(h) {
        return h`<button onclick="this.emit('mycallback', 'hello world')">Callback</button>`
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <salutation-card></salutation-card>
        `
    }
});

FIDDLE


Lifecycle Hooks

In order all hooks:

  • onBeforeCreate: called before that instance is created.
  • onCreate: called after that instance is created.
  • onConfigCreate: called after that instance is created (Only in ES6 pattern and if config object is set) ES6 class.
  • onBeforeMount: called before that instance is mounted on DOM.
  • onAfterRender: called every time after that render() method has been called.
  • onMount: called after that instance is mounted on DOM.
  • onMountAsync: called after that instance is mounted on DOM.
  • onBeforeUpdate: called before that instance is updated.
  • onUpdate: called after that instance is updated.
  • onBeforeUnmount: called before that instance is unmounted.
  • onUnmount: called after that instance is unmounted.
  • onBeforeDestroy: called before that instance is destroyed.
  • onDestroy: called after that instance is destroyed.

Any event with prefix "onBefore" if returns false the next event will not called.

//..
    onBeforeUpdate(changes) {
        console.log('before update', changes);
        if (this.props.counter >= 10) return false;
    },
    onUpdate(changes) {
        console.log('update', this.props.counter, changes);
    },
//..

Using the argument "changes" (only for onBeforeUpdate and onUpdate) you can know the changes of props object:

[
    {
        currentPath: "salutation"
        newValue: "Ciao Mondo"
        previousValue: "Hello World"
        property: "salutation"
        type: "update"
    }
]

A complete example

Doz.component('hello-world', {
    props: {
        salutation: 'Hello World'
    },
    template(h) {
        return h`
            <h2>${this.props.salutation}</h2>
        `
    },
    onBeforeCreate() {
        console.log('before create');
    },
    onCreate() {
        console.log('create');
    },
    onBeforeMount() {
        console.log('before mount');
    },
    onMount() {
        console.log('mount');
        setTimeout(()=> this.props.salutation = 'Ciao Mondo', 1000);
    },
    onBeforeUpdate(changes) {
        console.log('before update', this.props.salutation, changes);
    },
    onUpdate(changes) {
        console.log('update', this.props.salutation, changes);
        setTimeout(()=> this.destroy(), 1000)
    },
    onBeforeUnmount() {
        console.log('before unmount');
    },
    onUnmount() {
        console.log('unmount');
    },
    onBeforeDestroy() {
        console.log('before destroy');
    },
    onDestroy() {
        console.log('destroy');
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <hello-world></hello-world>
        `
    }
});

FIDDLE


Drawing hooks

  • onAppDraw: called every time that any component (of the whole app) is drawn

    Doz.component('hello-world', {
        template(h) {
            return h`
                <h2>Hello world</h2>
            `
        },
        onAppDraw(newNode, prevNode, component) {
            console.log(newNode, prevNode, component);
            // you can also manipulate the v-dom
            if (component.tag === 'the-clock')
                newNode.children.push('ciao');
        }
    });
    
    Doz.component('the-clock', {
        props: {
            time: '--:--:--'
        },
        template(h) {
            return h`
                <h2>${this.props.time}</h2>
            `
        },
        onMount() {
            setInterval(()=> this.props.time = new Date().toLocaleTimeString(), 1000);
        }
    });
    
    new Doz({
        root: '#app',
        template(h) {
            return h`
                <h1>Welcome to my app:</h1>
                <hello-world/>
                <the-clock/>
            `
        }
    });

    FIDDLE

  • onDrawByParent: called every time that component is drawn by the parent for example in a slot scenario

    Doz.define('user-card', class extends Doz.Component {
    
        constructor(o) {
            super(o);
        }
    
        template(h) {
            return h`
                <div>
                    <slot name="name"/>
                  <div> 
                    <h2>Biography</h2>
                    <slot name="biography"/>
                    <hr/>
                    <slot name="projects">No projects</slot>
                  </div>
                </div>
            `
        }
        
        onDrawByParent(newNode, oldNode) {
            if (newNode.props.slot === 'name') {
                newNode.children.push(Doz.compile(`
                    <button onclick="console.log(scope)">Click</button>
                `));
            }
        }
    
    });
    
    new Doz({
    
        root: '#app',
    
        template(h) {
            return h`
                <user-card>
                    <h1 slot="name">Mike Ricali</h1>
                    <p slot="biography">
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
                        Quisque magna neque, pharetra ac felis ut,
                        finibus volutpat dolor. Orci varius.
                  </p>
                </user-card>
            `
        }
    
    });

    What is scope?

    <button onclick="console.log(scope)">Click</button>
    

    scope refers to this of user-card component.

    FIDDLE


Local component

Since 2.0.0

As said previously, when define a component with define or component this will be global. Doz also allows you to create local components:

// Extendig the Component class
const HelloWorld = class extends Doz.Component {
    template(h) {
        return h`
            <h2>Hello World</h2>
        `
    }
}

// Or a simple function
const Other = function (h) {
    return h`
        <h3>Other</h3>
    `
}

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <${HelloWorld}/>
            <${Other}/>
        `
    }
});

Also components supports local components:

Since 1.1.0

// First
const hello = {
    template(h) {
        return h`
            <span>Hello</span>
        `
    }
}

// Second
const world = {
    template(h) {
        return h`
            <span>World</span>
        `
    }
}

// Together...
const HelloWorld = {
    components: {
        'hello-tag': hello,
        'world-tag': world
    },
    template(h) {
        return h`
            <h2>
                <hello-tag></hello-tag>
                <world-tag></world-tag>
            </h2>
        `
    }
}

// App
new Doz({
    components: {
        'hello-world': HelloWorld
    },
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <hello-world></hello-world>
        `
    }
});

FIDDLE


Mount

Doz component instance provide a method called mount, this method allows you to "append" a new component inside another.

Doz.component('hello-world', {
    template(h) {
        return h`
            <h2>Hello World</h2>
        `
    }
});

Doz.component('my-wrapper', {
    template(h) {
        return h`
            <div>
                <button onclick="this.showHello()">Mount</button>
            </div>
        `
    },
    
    showHello() {
    	this.mount('<hello-world/>');
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-wrapper></my-wrapper>
        `
    }
});

FIDDLE


Mount component in a specific root inside a parent:

Doz.component('hello-world', {
    template(h) {
        return h`
            <h2>Hello World</h2>
        `
    }
});

Doz.component('my-wrapper', {
    template(h) {
        return h`
            <div>
                <button onclick="this.append()">Mount</button>
                <div class="my-root" style="border: 1px solid #000"></div>
            </div>
        `
    },
    append() {
        this.mount('<hello-world></hello-world>', {selector: '.my-root'});
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-wrapper></my-wrapper>
        `
    }
});

FIDDLE


Unmount

You can also unmount a component.

Doz.component('my-wrapper', {
    template(h) {
        return h`
            <div>
                <h2>Hello World</h2>
                <button onclick="this.unmount()">Unmount</button>
            </div>
        `
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-wrapper></my-wrapper>
        `
    }
});

FIDDLE


Empty attributes in HTML element

In HTML there are some attributes such as "disabled" that can be defined without specifying any value. In Doz this is not possible, so for example disabled must be defined disabled="true".

Doz.component('my-button', {
    props: {
        buttonDisabled: true
    },
    template(h) {
        return h`
            <div>
                <button onclick="this.props.buttonDisabled = false">Enable button below</button>
                <button disabled="${this.props.buttonDisabled}">This button is disabled</button>
            </div>
        `
    }
});

Directives

The directives are special attributes that are specified inside component tag. There are two types:

HTML element

Directives that works only on HTML element.

d-bind

This directive bind an input element to a props:

Doz.component('input-message', {
    template(h){
        return h`
            <div>
                <input type="text" d-bind="message" placeholder="${this.props.placeholder}"/>
                <p>${this.props.message}</p>
            </div>
        `
    },
    props: {
        message: ''
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <input-message placeholder="write a message"></input-message>
        `
    }
});

FIDDLE


d-ref

Sometimes it's necessary to have a easy reference to an HTML element in your component, this directive does it.

Doz.component('my-button', {
    template(h) {
        return h`
            <h2 d-ref="title">I'm a title</h2>
            <button onclick="this.clickme()">Get H2 ref!</button>
        `
    },
    clickme(e) {
        alert(this.ref.title);
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-button></my-button>
        `
    }
});

FIDDLE


d-is

Since 1.6.1

Sometimes it is necessary to render a component inside tags like UL which only accepts LI as child nodes. The "d-is" directive can identify any HTML element as a Doz component.

Doz.component('my-item', {
    template(h) {
        return h`
            <span>${this.props.color}</span>
        `
    }
});

Doz.component('my-list', {
    props: {
        colors: [
            {
                name: 'Red'
            },
            {
                name: 'Green'
            },
            {
                name: 'Orange'
            }
        ]
    },
    template(h) {
        return h`
            <ul>
                ${this.each(this.props.colors, color => h`
                    <li d-is="my-item" color="${color.name}"></li>
                `)}
            </ul>
        `
    }
});

new Doz({
    root: '#app',
    template(h){
        return h`
            <h1>Welcome to my app:</h1>
            <my-list></my-list>
        `
    }
});

d-show

Since 1.25.0

Show or hide an element.

new Doz({
    root: '#app',
    template(h){
        return h`
            <h1>Welcome to my app:</h1>
            <h2 d-show=${false}>Wow!</h2>
        `
    }
});

Custom directives

Since 1.25.0

If you need to create a custom directive please look this directory.

https://github.com/dozjs/doz/tree/master/src/directive/built-in


DOZ component

Directives that works only on component

d:alias

This attribute allows you to define an unique name that identify the component locally inside component parent.

Doz.component('my-label', {
    template(h) {
        return h`
            <label>I'm a label</label>
        `
    }
});

Doz.component('my-button', {
    template(h) {
        return h`
            <button onclick="this.clickme()">Get component by alias!</button>
            <my-label d:alias="foo"></my-label>
        `
    },
    clickme(e) {
        alert(this.children.foo);
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-button></my-button>
        `
    }
});
d:id

This attribute allows you to define an unique name that identify the component globally.

Doz.component('my-label', {
    template(h) {
        return h`
            <label>I'm a label</label>
        `
    }
});

Doz.component('my-button', {
    template(h) {
        return h`
            <button onclick="this.clickme()">Get component by alias!</button>
        `
    },
    clickme(e) {
        alert(this.getComponentById('wowo'));
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-label d:id="wowo"></my-label>
            <my-button></my-button>
        `
    }
});

FIDDLE


d:store

This attribute allows you to define an unique name that expose your component props globally.

Doz.component('my-label', {
    props: {
        title: "I'm a label"
    },
    template(h) {
        return h`
            <label>${this.props.title}</label>
        `
    }
});

Doz.component('my-button', {
    template(h) {
        return h`
            <button onclick="this.clickme()">Update label</button>
        `
    },
    clickme(e) {
        this.getStore('wowo').title = 'Hello world!';
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-label d:store="wowo"></my-label>
            <my-button></my-button>
        `
    }
});

FIDDLE


d:on

This attribute allows you to define an event name.

Doz.component('salutation-card', {
    template(h) {
        return h`<div>${this.props.salutation} ${this.props.title} ${this.props.name} <caller-o d:on-mycallback="aCallback"></caller-o></div>`
    },
    aCallback: function(newSalutation) {
        this.props.salutation = newSalutation;
        alert(newSalutation);
    }
});

Doz.component('caller-o', {
    template(h) {
        return h`<div>This component emit an event</div>`
    },
    onCreate() {
        setTimeout(()=>{
            this.emit('mycallback', 'Ciao');
        },1000);
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <salutation-card
                salutation="Hello"
                title="MR."
                name="Doz">
            </salutation-card>
        `
    }
});

FIDDLE


Hooks directives

Since 1.15.0

A listener can be associated with these directives:

  • d:oncreate
  • d:onconfigcreate
  • d:onmount
  • d:onmountasync
  • d:onupdate
  • d:onunmount
  • d:ondestroy
  • d:onloadprops (since 1.16.0)
Doz.component('my-label', {
    template(h) {
        return h`
            <div>${this.props.title}</div>
        `
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <my-label
                title="Mr. Robyn"
                d:oncreate="myLabelOnCreate"
                d:onmount="myLabelOnMount"
                d:onupdate="myLabelOnUpdate"
            />
        `
    },
    
    myLabelOnCreate(cmp) {
        console.log(cmp.tag, 'is created')
    },
    
    myLabelOnMount(cmp) {
        console.log(cmp.tag, 'is mounted')
    },
    
    myLabelOnUpdate(cmp, changes) {
        console.log(cmp.tag, 'is updated with this changes', changes)
    }
});

Sharing things

Since 1.8.0

Doz provides an API called shared that allows you of sharing things between components.

Doz.component('my-button', {
    template(h) {
        return h`
            <button>Click ${this.shared.foo}</button>
        `
    }
});

new Doz({
    root: '#app',
    shared: {
        foo: 'bar'
    },
    template(h) {
        return h`
            <my-button/>
        `
    }
});

Conditional statements

As said previously Doz use template literals:

Doz.component('my-button', {
    props: {
        done: true
    },
    template(h) {
        return h`
            <h2 style="color:${this.props.done ? 'red' : 'green' }">I'm a color</h2>
            <button onclick="this.clickme()">Toggle color</button>
        `
    },
    clickme(e) {
        this.props.done = !this.props.done;
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-button></my-button>
        `
    }
});

FIDDLE


Loops

Same situation like conditional statements but using the method each provided by component:

Doz.component('my-list', {
    props: {
        colors: [
            {
                id: 1,
                name: 'Red'
            },
            {
                id: 2,
                name: 'Green'
            },
            {
                id: 3,
                name: 'Orange'
            }
        ]
    },
    template(h) {
        return h`
            <ul>
                ${this.each(this.props.colors, (color,  i) => h`
                    <li key="${color.id}">${i}) ${color.name}</li>
                `)}
            </ul>
        `
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-list></my-list>
        `
    }
});

Important: The attribute key help Doz identify which items have changed, are added, or are removed.

Important: Without key, the onBeforeUnmount, onUnmount and onDestroy events will not be called.

FIDDLE


Slots

Since 1.23.0

Doz supports unnamed and named slots:

Unnamed:

Doz.define('user-card', class extends Doz.Component {

    constructor(o) {
        super(o);
    }

    template(h) {
        return h`
            <div>
            	<h1>${this.props.name}</h1>
              <div> 
              	<h2>Biography</h2>
              	<slot/>
              </div>
            </div>
        `
    }

});

new Doz({

    root: '#app',

    template(h) {
        return h`
            <user-card name="Mike Ricali">
            	<p>
                    Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
                    Quisque magna neque, pharetra ac felis ut,
                    finibus volutpat dolor. Orci varius.
                </p>
            </user-card>
        `
    }

});

FIDDLE

Named:

Doz.define('user-card', class extends Doz.Component {

    constructor(o) {
        super(o);
    }

    template(h) {
        return h`
            <div>
            	<h1>${this.props.name}</h1>
              <div> 
              	<h2>Biography</h2>
              	<slot/>
              </div>
            </div>
        `
    }

});

new Doz({

    root: '#app',

    template(h) {
        return h`
            <user-card name="Mike Ricali">
            	<p>
                    Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
                    Quisque magna neque, pharetra ac felis ut,
                    finibus volutpat dolor. Orci varius.
                </p>
            </user-card>
        `
    }

});

FIDDLE


Scoped style

Since 1.8.0

Doz allows you to add the style inside template function, this emulate a scoped style.

Doz.component('my-salutation', {
    template(h) {
        return h`

            <style>
                /* :wrapper rule referring to container in this case my-salutation */
                :wrapper {
                    border: 1px solid #ff0000;
                }

                /* :global rule refering to global item */
                :global button {
                    background: #ffcc00;
                }

                h1 {
                    color: red;
                    font-weight: bold;
                }

                .foo{
                    display: inline;
                }

               @media only screen and (max-width: 600px) {
                    h1 {
                        color: white;
                        background-color: green;
                    }
                }
            </style>

            <h1>Hello Doz</h1>
            <div class="foo">foo</div>

        `
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <my-salutation/>
        `
    }
});

Since 1.26.0

Adding "scoped" attribute to tag "style" it's possible to reset css inheritance.

class MyComponent1 extends Doz.Component {
    template(h) {
        return h`
            <style scoped>
                h1 {
                    font-size: 12px;
                }
            </style>

            <h1>Hello Doz</h1>
        `
    }
}

class MyComponent2 extends Doz.Component {
    template(h) {
        return h`
            <style scoped>
                h1 {
                    color: green;
                }
            </style>
            
            <!-- This will be green -->
            <h1>Hello Doz</h1>
            <!-- This will be black -->
            <${MyComponent1}/>
        `
    }
}

Since 1.19.0

Doz adds a data-uid (unique ID) attribute to node HTML of component and inject the style in to DOM in this way:

<head>
...
 <style> [data-uid="1"] .myClass{color:#000}</style>
</head>

Inline style

Since 1.3.4

Doz provide a method called toStyle that allows you to transform an object to inline style string.

const css = {
    background: '#000',
    color: '#fff'
};

Doz.component('my-button', {
    template(h) {
        return h`
            <button
                ${this.toStyle(css)}
            >Hello button</button>
        `
    }
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-button></my-button>
        `
    }
});

Actions

The actions allows to better organize the logic of the various components in the app, and they are global so can be called in all components.

const actions = {
    toggleColor() {
        const store = this.getStore('my-button');
        store.done = !store.done;
    }
};

Doz.component('my-button', {
    store: 'my-button',
    props: {
        done: true
    },
    template(h) {
        return h`
            <h2 style="color:${this.props.done ? 'red' : 'green' }">I'm a color</h2>
            <button onclick="this.action.toggleColor()">Toggle color</button>
        `
    }
});

new Doz({
    actions,
    root: '#app',
    template(h) {
        return h`
            <h1>Welcome to my app:</h1>
            <my-button></my-button>
        `
    }
});

FIDDLE


Mixins

Since 1.5.0

Doz provide you two way for add reusable functions to components:

Global mixin

The functions are available for every component.

Doz.mixin({
   sum(a, b) {
       return a + b;
   }
});

Doz.component('a-component', {
    template(h) {
        return h`
            <div>Sum of 4 + 5 = ${this.sum(4, 5)}</div>
        `
    }
});

Mix multiple object:

Doz.mixin([
    {
        method1() {},
        method2() {}
    },
    {
        method3() {},
        method4() {}
    }
]);

Local mixin

The functions are available only for a component.

const myFunctions = {
   sum(a, b) {
       return a + b;
   }
};

Doz.component('a-component', {
    mixin: myFunctions,
    template(h) {
        return h`
            <div>Sum of 4 + 5 = ${this.sum(4, 5)}</div>
        `
    }
});

// Sum method is undefined
Doz.component('other-component', {
    template(h) {
        return h`
            <div>Sum of 10 + 10 = ${this.sum(10, 10)}</div>
        `
    }
});

Ps: Mixins don't overwrite existing functions, since 1.7.0 a warning message will be showed into console.

Plugins

Since 1.6.0

Sometimes it's useful to have methods at global level that extend new functionality to framework, for this, Doz since 1.6.x version provide you "Doz.use". Write a plugin is very easy:

const myPlugin = function(Doz, app, options) {

    // You can adds mixin function
    Doz.mixin({
        localTime() {
            return new Date().toLocaleTimeString();
        }
    });

    // Manipulate virtual dom like:
    // Add a button to component that have an attribute "with-button"
    function addButton(child) {
        child.children.forEach(el => {
            if (el.props && el.props['with-button'] !== undefined) {

                // Doz.compile transforms HTML string to tree object
                const compiled = Doz.compile(`
                    <button onclick="console.log($this)">${options.buttonLabel}</button>
                `);

                el.children.push(compiled)
            }
        })
    }

    // This event is called to every change of the whole app
    app.on('draw', (next, prev, componentInstance) => {
        addButton(next);
    });

};

// Add plugin to Doz passing some options
Doz.use(myPlugin, {
    buttonLabel: 'click me'
});

Doz.component('my-component', {
    template(h) {
        return h`
            <div with-button>
                current time: ${this.currentTime()}
            </div>
        `
    }
});

ES6 class

Since 1.5.0

If you prefer programming with ES6 class syntax:

Doz.define('a-component', class extends Doz.Component{
    template(h){
        return h`<div>Hello ES6</div>`
    }
});

Define default props

Doz.define('a-component', class extends Doz.Component{
    constructor(o) {
        super(o);

        this.props = {
            name: 'ES6'
        };
    }

    template(h){
        return h`<div>Hello ${this.props.name}</div>`
    }
});

Define default config with config property (available only for this pattern)

Doz.define('a-component', class extends Doz.Component{
    constructor(o) {
        super(o);

        this.config = {
            store: 'myStoreName',
            id: 'myComponentId'
        };
    }

    template(h){
        return h`<div>Hello ${this.props.name}</div>`
    }
});

All the properties that need to be defined in config:

  • mixin
  • components
  • store
  • id
  • autoCreateChildren
  • updateChildrenProps

To registering a global component now we use define an alias of component for don't confuse you with Component subclass


SFC: Single Function Component

Since 1.11.0

SFC gives you the ability to define simple components that do not need configurations, such as default props, methods or events. Define a component with a single function:

Doz.component('my-sfc', function(h) {
    return h`<div>Hello ${this.props.name}</div>`
});

new Doz({
    root: '#app',
    template(h) {
        return h`
            <my-sfc name="Doz"/>
        `
    }
});

Component logic inside Doz constructor

It's also possible creating an app with component logic inside Doz constructor like so:

new Doz({
    root: '#app',
    props: {
        name: 'super DOZ'
    },
    template(h) {
        return h`
            <h1>Welcome to ${this.props.name}</h1>
            <my-button onclick="this.$clickMe()"></my-button>
        `
    },
    $clickMe() {
        alert(this.props.name)
    }
});

Develop and production

The best way to build app with Doz is using the great Parcel Bundler with zero configuration.

Hot module replacement and state preservation

If you need preserve component state add just module to config:

Doz.component('my-counter', {
    module,
    props: {
        count: 0
    },
    template(h) {
        return h`
            <button onclick="this.props.count++">Count ${this.props.count}</button>
        `
    }
});

Write app or component

You can use doz-cli at the moment the right way to creating a component or an app.


IE11 support

Doz uses ES6 proxy that unfortunately is not supported by IE11. You must include two polyfills in your document.

<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=default,Array.prototype.includes"></script>
<script src="https://unpkg.com/doz/polyfill/proxy.js"></script>

Reserved tags

The tags you shouldn't use:

  • dz-app
  • dz-empty
  • dz-mount
  • dz-root
  • dz-slot
  • dz-text-node
  • dz-iterate-node

License

DOZ is open-sourced software licensed under the MIT license

Author

Fabio Ricali

You can’t perform that action at this time.