Below some basic concepts:
- Installation
- Make an app
- Web Component: Doz Component as Web Component
- Sharing things
- Conditional statements
- Loops
- Slots
- Scoped style
- Inline style
- Actions
- Mixins
- Plugins
- ES6 class
- SFC: Single Function Component
- Component logic inside Doz constructor
- Develop and production
- Write app or component
- API
- Component structure
- Reserved tags
npm install --save doz
//ES6
import Doz from 'doz'
//ES5
var Doz = require('doz');
An app is a main component that embed other components.
import {appCreate, Component, tag} from 'doz'
@tag('hello-world')
class HelloWorld extends Component {
template(h) {
return h`
<h2>Hello World</h2>
`
}
}
appCreate('#app', HelloWorld);
The tag name must be according to the W3C specs.
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.
import {appCreate, Component, tag} from 'doz'
@tag('my-clock')
class MyClock extends 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)
}
}
@tag('my-app')
class MyApp extends Component {
template(h) {
return h`
<${MyClock} title="it's"/>
`
}
}
appCreate('#app', MyApp)
Since 3.10.0
This API called propsPropagation allows you to propagate your props to child components.
class MyChild1 extends Component {
template(h) {
//language=HTML
return h`
<div>
<span>My Child 1</span>
<div>${this.props.description}</div>
<${MyChild2}/>
</div>
`;
}
}
class MyChild2 extends Component {
constructor(o) {
super(o);
this.props = {
description: 'excluded from props propagation'
}
// this exclude this component from props propagation
this.excludeFromPropsPropagation = true;
}
template(h) {
return h`
<div>
<span>My Child 2</span>
<div>${this.props.description}</div>
<${MyChild3}/>
</div>
`;
}
}
class MyChild3 extends Component {
template(h) {
return h`
<div>
<span>My Child 3</span>
<div>${this.props.description}</div>
<button onclick="${() => this.destroy()}">destroy</button>
</div>
`;
}
}
class MyParent extends Component{
constructor(obj) {
super(obj);
this.props = {
description: 'wow',
title: 'lorem'
}
this.propsPropagation = [
'description'
] // if set to true all props will be propagated
}
template(h){
return h`
<div>
<h3>props propagation example</h3>
<div>
<input placeholder="description" d-bind="description">
</div>
<h4>Child component</h4>
<${MyChild1}/>
</div>
`
}
}
appCreate('#app', MyParent)
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>
`
}
});
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/>
`
}
});
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>
`
}
});
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/>
`
}
});
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/>
`
});
Since 1.20.0
propsType allows you to convert a prop to a specific type. This is very useful when using the d-bind directive since the value of the INPUT, SELECT etc. elements is always a string.
Doz.component('my-types', {
propsType: {
myParam: 'string'
},
template() {
return `
<h3>This is number but string: ${this.props.myParam}</h3>
`
}
});
new Doz({
root: '#app',
template(h) {
return h`
<my-types my-param="10"/>
`
}
});
string
number
boolean
object
array
date
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
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();
}
});
Since 2.2.0
All HTML element of a component accepts standard events.
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>
`
}
});
Before 2.2.0
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>
`
}
});
Before 2.2.0
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>
`
}
});
Before 2.2.0
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
.
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>
`
}
});
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 ifconfig
object is set) ES6 class.onBeforeMount
: called before that instance is mounted on DOM.onAfterRender
: called every time after thatrender()
method has been called.onMount
: 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>
`
}
});
-
onAppDraw
: called every time any component (of the whole app) is drawnDoz.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/> ` } });
-
onDrawByParent
: called every time component is drawn by the parent for example in a slot scenarioDoz.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 tothis
of user-card 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>
`
}
});
suspendContent
a mechanism that suspends the creation of children nodes and saves them in a component property called suspendNodes
.
It is possible to use it via:
- special prop
<${MyComponent} suspendcontent/>
- class property
...suspendContent = true...
Doz component instance provides a method called inject
a better alternative to mount
(below),
this method allows you to "append" a new component inside another.
Why is it better than mount
?
Because the component or html will be added directly inside the virtual dom object of the parent component.
In this way it will not insert other parents tag like "dz-mount".
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() {
let result = this.inject('<hello-world/>');
// inject returns an object with 2 properties
// {cmp, id}
// cmp is the component instance injected and the id is the symbol to identify the injection
// You can use the id or the entire object to remove the injection.
// For example
// destroy after 5s with eject method
setTimeout(() => {
this.eject(result)
}, 5000);
}
});
new Doz({
root: '#app',
template(h) {
return h`
<h1>Welcome to my app:</h1>
<my-wrapper></my-wrapper>
`
}
});
Use inject
instead of mount
.
Doz component instance provides 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>
`
}
});
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>
`
}
});
If you need to mount your component deferred.
Doz.component('hello-world', {
waitMount: true,
template(h) {
return h`
<h2>Hello World</h2>
`
},
async onWaitMount() {
await foo();
this.runMount()
}
});
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>
`
}
});
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>
`
}
});
The directives are special attributes that are specified inside component tag. There are two types:
Directives that usually work only with HTML elements but there are
exceptions such as d-show
and d-animate
which work with Doz components.
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>
`
}
});
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>
`
}
});
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.props.colors.map(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>
`
}
});
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>
`
}
});
Since 2.3.0
This directive allows you to add CSS animations to components and HTML elements. By default, it is set to use the animate.css library, but you can also use custom animations or other libraries.
new Doz({
root: '#app',
template(h){
return h`
<h1>Welcome to my app:</h1>
<h2 d-animate="${{show: 'fadeIn'}}">Wow!</h2>
`
}
});
The d-animate directive accepts an object as a configuration. The main properties of the object are two: "show" and "hide". The show property defines the CSS class will be used when an element will be displayed in the DOM. While the hide property will be used when an element will be hidden or removed from the DOM Both show and hide properties can accept values as a string (just animation name) or object.
Below is an example of object configuration:
const animationConfig = {
show: {
name: 'vanishIn',
duration: '500ms',
delay: '1000s',
cb: () => console.log('animation show ends')
},
hide: {
name: 'vanishOut',
duration: '500ms',
delay: '1000s',
cb: () => console.log('animation hide ends')
},
// classLib defines the css class name of the framework that use for animations.
// default is "animated", the class name of animate.css
classLib: 'magictime' // For example, it will use another animation framework and relative animation names
};
new Doz({
root: '#app',
template(h){
return h`
<h1>Welcome to my app:</h1>
<h2 d-animate="${animationConfig}">Wow!</h2>
`
}
});
PS: d-show
supports the animation with d-directive
.
Important Remember that to use animations you need to include CSS frameworks.
Directives that works only on component
This attribute allows you to define a 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>
`
}
});
This attribute allows you to define a 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>
`
}
});
This attribute allows you to define a 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>
`
}
});
This attribute allows you to define a 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>
`
}
});
With this directive the component will only be rendered if necessary. Necessary means that the component fits within the visible portion of the screen..
new Doz({
root: '#app',
template(h) {
return h`
<salutation-card d-lazy></salutation-card>
`
}
});
Since 1.25.0
If you need to create a custom directive please look this directory.
https://github.com/dozjs/doz/tree/master/src/directives/built-in
Since 1.15.0
A listener can be associated with these directives:
d:oncreate
d:onconfigcreate
d:onmount
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)
}
});
Since 2.5.0
If you need to use a doz component outside your JavaScript app then this is for you.
Calling the Doz.defineWebComponent
method will create a web component that will incorporate your doz component.
const myCmp1 = {
template(h) {
return h`
<div>Hello</div>
<button onclick="this.myClick()">Set random to myCmp2</button>
`
},
myClick() {
this.getWebComponentById('my-cmp2').props.title = Math.random();
}
}
const myCmp2 = class extends Doz.Component {
constructor(o) {
super(o);
props = {
title: 'WoW'
}
}
template(h) {
return h`
<div>${this.props.title}</div>
`
}
myMethod(value) {
console.log('hello ' + value);
this.app.emit('myListener', 'hello', 'another param')
}
}
defineWebComponent('dwc-my-cmp1', myCmp1, ['title']);
defineWebComponent('dwc-my-cmp2', myCmp2, ['title'], ['myMethod'], ['myListner']);
Important note: The third parameter of defineWebComponent
is referred to "observedAttributes",
an array of attributes names linked to doz component props, so you can update the props also
using native method document.getElementById('foo-my-wc').setAttribute('title', 'hello')
while the fourth parameter is
referred to "exposedMethods", for example you can call a method in this way document.getElementById('foo-my-wc').myMethod('bar foo')
.
HTML
<html>
<head>
<!-- doz library file-->
<script src="path/to/doz.js"></script>
</head>
<body>
<dwc-my-cmp1 title="lorem"></dwc-my-cmp1>
<dwc-my-cmp2 title="ipsum" data-id="my-cmp2"></dwc-my-cmp2>
<!-- doz components file -->
<script src="path/to/your-component.js"></script>
</body>
</html>
Super Important: Keep in mind to add a prefix like this: 'dwc-' to the name of the component you are going to define, this to prevent Doz from processing possible web components that have the same name as the doz components.
The "data-id" attribute allows us to access a Web Component from other components
using the method (in this case) this.getWebComponentById ('my-cmp2')
.
Sometimes the initial rendering of a Web Component by the browser could generate an unpleasant effect as if for example
the style was missing. To solve this problem you can use the data-soft-entrance
attribute.
<html>
<head>
<!-- doz library file-->
<script src="path/to/doz.js"></script>
</head>
<body>
<dwc-my-cmp data-soft-entrance>
<foo-bar1></foo-bar1>
<foo-bar2></foo-bar2>
<foo-bar3></foo-bar3>
</dwc-my-cmp>
<!-- doz components file -->
<script src="path/to/your-component.js"></script>
</body>
</html>
If you want disable the shadow root you can use data-no-shadow
attribute:
<dwc-my-cmp data-no-shadow>
<foo-bar1></foo-bar1>
<foo-bar2></foo-bar2>
<foo-bar3></foo-bar3>
</dwc-my-cmp>
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/>
`
}
});
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>
`
}
});
Same situation like conditional statements but using the array method map
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.props.colors.map((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>
`
}
});
Very important: It is always necessary for the loop to be contained by a tag with no other child elements.
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.
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>
`
}
});
Named:
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>
`
}
});
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>
`
}
});
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>
Since 1.3.4
Doz provides 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>
`
}
});
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>
`
}
});
Since 1.5.0
Doz provides to you two-way for add reusable functions to components:
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() {}
}
]);
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.
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 provides to you "Doz.use". Write a plugin is very easy:
const myPlugin = function(Doz, app, options) {
// You can add 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>
`
}
});
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 ofcomponent
for don't confuse you withComponent
subclass
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"/>
`
}
});
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)
}
});
The best way to build app with Doz is using the great Parcel Bundler with zero configuration.
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>
`
}
});
You can use doz-cli at the moment the right way to creating a component or an app.
The tags you shouldn't use:
- dz-app
- dz-empty
- dz-mount
- dz-root
- dz-slot
- dz-text-node
- dz-iterate-node
DOZ is open-sourced software licensed under the MIT license