dominic
v0.1.47
Published
Helper to quickly build up DOM in simple javascript object format.
Downloads
71
Readme
Dominic
Helper to quickly build up dom in javascript object format
- v.0.1.42 contains breaking changes. See changelog
Basic feature list:
Just dom
Basic dom construction by javascript object format
Event
Reference
Template by function
Components
~~Server side render to Html (with helper, see API)~~ (temporarily)
Version: 0.1.47
Outline
- 1. Basic
- 2. Div all the elements
- 3a Share configs
- 3b Share configs with extra classes
- 4. Condition
if
|hide
- 5. Attributes
- 6. Reference
- 7. Direct Reference
- 8. Events 1: Basic
- 9. Events 2: Delegate
- 10 Events 3: Key Code Hook
- 11 Events 4: Counter & Validator
- 12 Template 1: Basic
- 13 Template 2: Object loop
- 14 Template 3: Data change reaction
- 15 Template 4: All functions
- 16 Component: Basic 1
- 17 Component: Basic 2
- Installation
- API
- Plan
- License MIT
And here's some code! :+1:
Basic 1:
var root = Dominic.create({
cls: 'root', // or className: 'root' | cls as alias for className
parent: document.body,
width: 300,
height: 300,
background: 'darkgreen',
items: [ // or: children
// also accept string, number as children, will be converted to text node
{ tag: 'div', width: 50, height: 50, text: 'Intro', display: 'inline-block', background: 'yellowgreen' },
{ tag: 'div', width: 200, background: 'lightgreen',
items: [
{ tag: 'div', width: 20, height: 20, background: 'red' },
{ tag: 'div', width: 20, height: 20, background: 'orange' },
],
created: function(el, root) {
// el: the div element with background: 'lightgreen' and 2 children 'red' & 'orange'
// root: the root elemnt directly created by Dominic.create
// this: = root element
}
}
],
created: function (el, root) {
// called when finished every setup
},
appended: function () {
// called when
// 1. element is root (created by Dominic.create)
// 2. parent is Node and appended element successfully
}
})
Result
Styling
color
,backgroundColor
,background
,display
,position
,border
,transform
,opacity
,fontSize
will be applied directly to element style without the need of putting them in a style object. Same with:width
,height
,minHeight
,maxHeight
,minWidth
,maxWidth
margin
,padding
,margin
andpadding
+(Top - Left - Right - Bottom)
will be converted to proper CSS value if value is number
Basic 2: Function as item & div all the elements
- Tag name is 'div' by default
var outerScopeDataSource = [
{ name: 'yellow' },
{ name: 'green' },
{ name: 'pink' }
]
var root = Dominic.create({
cls: 'root',
parent: document.body,
width: 300,
height: 300,
background: 'darkgreen',
items: [
{ width: 50, height: 50, text: 'Intro', display: 'inline-block', background: 'yellowgreen',
items: [
function () {
return outerScopeDataSource.map(function (data) {
return { tag: 'custom-el', text: data.name }
})
}
]
},
{ width: 200, background: 'lightgreen',
items: ['color', 'material'].map(function (val) {
return { text: val }
})
}
]
})
Result
Basic 3a: Share configs
var root = Dominic.create({
cls: 'root',
parent: document.body,
width: '100%',
height: '100%',
defaults: {
display: 'inline-block',
height: '100%',
cls: 'default-class',
style: {
verticalAlign: 'top'
}
},
items: [
{ cls: 'sidebar', width: 200, ref: 'sidebar', background: 'lightgreen' },
{ cls: 'main', width: 'calc(100% - 200px)', ref: 'main', background: 'lightblue',
defaults: {
background: 'tomato',
margin: 5,
height: 50,
width: 50,
display: 'inline-block'
},
items: [
{ text: 1 },
{ text: 2 },
[3,4,5,6].map(function (v) { return { text: v } }),
function () {
return [5,6,7,8].map(function (v) { return { text: v } })
}
]
},
{ tag: 'test' }
]
})
- All direct children of root will have
display = 'inline-block', height = '100%'
- Only last child of root will have
class = 'default-class'
Result
Basic 3b: Share configs with extra class
xtraCls
, xCls
: string
var root = Dominic.create({
cls: 'root',
parent: document.body,
width: '100%',
height: '100%',
defaults: {
cls: 'default-class',
},
items: [
{ xCls: 'sidebar' },
{ xtraCls: 'main' },
{ tag: 'test' }
]
})
Result
Basic 4: Condition if
/ hide
var root = Dominic.create({
className: 'root',
parent: document.body,
width: '100%',
height: '100%',
defaults: {
display: 'inline-block',
height: '100%',
className: 'default-class',
style: {
verticalAlign: 'top'
}
},
items: [
{ cls: 'sidebar', width: 200, ref: 'sidebar', background: 'lightgreen' },
{ cls: 'main',
width: 'calc(100% - 200px)',
ref: 'main',
background: 'lightblue',
defaults: {
background: 'tomato',
margin: 5,
height: 50,
width: 50,
display: 'inline-block'
},
items: [
{ text: 'First' },
{ text: 'Second' },
[3,4,5,6].map(function (v, i) {
return { text: 'Value is: ' + v, if: v < 4 }
}),
function () {
return [5,6,7,8].map(function (v) { return { text: v, hide: v > 6 } })
}
]
}
]
})
Result
Attributes
var root = Dominic.create({
className: 'root',
id: 'root',
parent: document.body,
width: 300,
height: 300,
background: 'darkgreen',
padding: 5,
attrs: {
class: 'original',
dataTooltip: 'halo this is tip',
'data-id': 5
}
})
Result
Reference 1
var root = Dominic.create({
className: 'root',
parent: document.body,
width: 300,
height: 300,
background: 'darkgreen',
items: [
{ width: 50, height: 50, text: 'Intro', display: 'inline-block' },
{ width: 200, ref: 'orange', // access by root.refs.orange
items: [
{ width: 20, height: 20, background: 'red',
ref: 'lightgreen' // access by root.refs.lightgreen
},
{ width: 20, height: 20, background: 'orange',
// access by root.refs.orange.refs.orange
ref: 'orange', refScope: 'parent'
},
]
}
]
})
Reference 2: Direct reference
var root = Dominic.create({
className: 'root',
parent: document.body,
width: 300,
height: 300,
background: 'darkgreen',
items: [
{ width: 50, height: 50, text: 'Intro', display: 'inline-block' },
{ width: 200, ref: 'orange', // access by root.refs.orange
items: [
{ width: 20, height: 20, background: 'red',
ref: 'lightgreen' // access by root.refs.lightgreen
},
{ width: 20, height: 20, background: 'orange',
// access by root['orange-f2']
// ref: 'orange', // using ref will get dominic ignore directRef
directRef: 'orange-f2'
},
]
}
]
})
// example
Dominic.register('input', function(defs) {
return {
items: [
{ tag: 'span', text: defs.label },
{ tag: 'input', placeholder: 'Choose name...' }
]
}
})
// usage
var root = Dominic.create({
cls: 'root',
items: [
// accessed by root.refs.nameInput
// previously have to define ref in component definitions
{ ctype: 'input', ref: 'nameInput', label: 'Name: ' },
// accessed by root.nameInput2
{ ctype: 'input', directRef: 'nameInput2', label: 'Name 2: ' }
]
})
Events 1:
Reserved keyword for events:
- Mouse:
click
mousedown
mouseup
mouseover
mouseout
mouseenter
mouseleave
mousemove
- Drag:
dragstart
dragend
drag
dragover
dragenter
dragleave
drop
- Focus:
blur
focus
- Keyboard:
keydown
keypress
keyup
- Form:
change
input
submit
- Touch:
touchstart
touchmove
touchend
- Scroll:
wheel
scroll
var root = Dominic.create({
className: 'root',
id: 'root',
parent: document.body,
width: 300,
height: 300,
background: 'darkgreen',
items: [
{ tag: 'div', width: 50, height: 50, text: 'Intro', display: 'inline-block', background: 'yellowgreen' },
{ tag: 'div', width: 200, background: 'lightgreen',
items: [
{ tag: 'div', className: 'red', width: 20, height: 20, background: 'red',
// normal click handler
click: {
handler: function (e) {
// div.red
console.log('This is:', this.localName + '.' + this.className)
}
}
},
{ tag: 'div', className: 'orange', width: 20, height: 20, background: 'orange',
click: {
// change scope to root element
scope: 'root',
handler: function (e) {
// div.root
console.log('This is:', this.localName + '.' + this.className)
}
},
events: [
{ type: 'custom:event', handler: function () {
console.log('This is div.orange')
}}
]
},
{ tag: 'div', className: 'yellow', width: 20, height: 20, background: 'yellow',
click: {
scope: 'root',
// Will look up for `onClickYellow` on root element
// Throw error if not found
handler: 'onClickYellow',
capture: true
}
}
]
}
],
onClickYellow: function (e) {
var t = e.target
// From div.yellow to div.root
console.log('From ', t.localName + '.' + t.className + ' to ' + this.localName + '.' + this.className)
},
events: [
{ type: 'mouseout', handler: function (e) {
console.log('Out of:', e.target)
}}
]
})
Events 2: Delegate
var root = Dominic.create({
cls: 'root',
id: 'root',
parent: document.body,
width: 300,
height: 300,
background: 'darkgreen',
items: [
{ width: 50, height: 50, text: 'Intro', display: 'inline-block', background: 'yellowgreen' },
{ width: 200, background: 'lightgreen',
items: [
{ cls: 'child red', width: 20, height: 20, background: 'red' },
{ cls: 'child orange', width: 20, height: 20, background: 'orange' },
{ cls: 'child yellow', width: 20, height: 20, background: 'yellow' }
],
click: { scope: 'root', handler: 'onClickLightgreen', delegate: '.child' }
}
],
onClickLightgreen: function (e, match, delegate) {
// this: scope when register event listener. Default: element has listener
// e: event object
// match: element matching delegate
// delegate: delegate css selector passed when register event listener
console.log('This is: ' + match.localName + '.' + match.className.replace(' ', '.'))
}
})
Events 3: Key code hook
- Only trigger key event with specified key codes
var root = Dominic.create({
cls: 'root',
parent: document.body,
width: '100%',
height: '100%',
defaults: {
cls: 'default-class',
},
items: [
{ xCls: 'sidebar' },
{ xtraCls: 'main' },
{ tag: 'input',
keydown: { scope: 'root', handler: 'sayHelo', key: 13 }
},
{ tag: 'input',
keydown: { scope: 'root', handler: 'onArrowKey', key: [37,38,39,40] }
}
],
sayHelo: function (e) {
console.log('helo', e.target.value)
},
onArrowKey: function (e) {
console.log('Navigating:', e.keyCode)
}
})
Events 4: Event counter
, finishCount
and validator
// In this example, second input only let user navigate by arrow key if
// user has already 'sayHelo' with a validated name
var root = Dominic.create({
cls: 'root',
parent: document.body,
width: '100%',
height: '100%',
defaults: {
cls: 'default-class',
},
items: [
{ xCls: 'sidebar' },
{ xtraCls: 'main' },
{ tag: 'input',
keydown: { scope: 'root', handler: 'sayHelo', key: 13,
// count 1 to remove this listener after user say helo
// to not let user change name
count: 1,
// validator's logic
// * validator will run right before real handler,
// * return false to stop handler
// * counter will not decrease if validator returns false
// * if event is delgated to child, then validator will have same
// parameters with handler (event, match, delegate)
validator: function(e) {
return e.target.value
},
finishCount: function DoSomethingAfterInputName(e) {
// hide input maybe
}
}
},
{ tag: 'input',
keydown: { scope: 'root', handler: 'onArrowKey', key: [37,38,39,40],
// check if user is allowed to go by validator
validator: 'isAllowedToGo'
// only let user navigate 10 times
count: 10,
}
}
],
sayHelo: function (e) {
this.name = e.target.value
console.log('helo', e.target.value)
},
onArrowKey: function (e) {
console.log('Navigating:', e.keyCode)
},
isAllowedToGo: function() {
return this.name
}
})
// Bigger example to show the purpose of [finishCount] together with [count] and [validator]
var root = Dominic.create({
cls: 'root',
parent: document.body,
point: 0,
items: [
{ tag: 'input', placeholder: 'Choose a name to start the game...',
display: 'block',
width: 300,
keydown: { scope: 'root', handler: 'sayHelo', key: 13,
count: 1,
validator: function(e) {
return e.target.value
},
// start the game after input name
finishCount: function hideInput(e) {
e.target.style.display = 'none'
this.guessInput.disabled = false
this.guessInput.focus()
}
}
},
{ tag: 'input',
cls: 'guess-name',
// use directRef for faster reference
directRef: 'guessInput',
placeholder: 'Guess the name 3 times...',
// dont let user guess when they haven't entered name
disabled: true
}
],
keydown: {
delegate: '.guess-name',
// only execute handler 3 times
count: 3,
// put a validator to avoid user mistakenly guess empty name
// or user haven't submitted a name to start the game
validator: 'validateGuess',
// call this on every guess
handler: 'onGuess',
// if finished 3 tries, call this
finishCount: 'gameFinish',
// only guess when hit enter key
key: 13
},
sayHelo: function (e) {
this.name = e.target.value
console.log('helo', e.target.value, '\nLets start')
},
validateGuess: function(e, match) {
// e.target === this.guessInput === match
return match.value
},
onGuess: function(e, match) {
console.log('Your guess:', match.value)
var point = match.value === this.name ? 1 : 0
console.log('You got:', point, 'point')
this.point += point
// empty input to guess new round
match.value = ''
},
gameFinish: function(e, match) {
console.log('----\nFinish.\nYour points:', this.point)
if (this.point < 3)
return console.info('Game over. Did you type in the name?')
console.info('You won!!!')
// disable to make it look like a game
match.disabled = true
}
})
Template 1a
for
: data sourceTplFn
: function (item, itemIndex)
- If data source provided is an array, item is record of array and itemIndex is record index
- If data source provided is an object, item is data object and itemIndex will be undefined
var root = Dominic.create({
className: 'root',
id: 'root',
parent: document.body,
width: 300,
height: 300,
background: 'darkgreen',
padding: 5,
items: {
// data source
for: [
{ name: 'apple', cost: 0.5 },
{ name: 'mango', cost: 0.5 },
{ name: 'grape', cost: 0.6,
suppliers: { data: [
{ name: 'US', time: 5 },
{ name: 'UK', time: 4 }
]}
}
],
tplFn: function (item, itemIdx) {
return { tag: 'div', text: item.name, padding: 5, margin: '5px 0 0 5px', background: 'tomato',
items: {
for: item.suppliers,
root: 'data', // specify which property to look for data
tplFn: function (sup, supIdx) {
return { tag: 'div',
padding: 5,
background: 'lightblue',
text: sup.name + '. Time: ' + sup.time + ' days'
}
}
}
}
}
}
})
Result
Template 1b: Object loop
- Can also loop through object property if specified with
alwaysIterate
.
var root = Dominic.create({
cls: 'root',
parent: document.body,
defaults: {
cls: 'default-class'
},
items: [
{ for: { a: 5, b: 6, c: 7},
observeProp: 'data1',
alwaysIterate: true,
// value & key instead value & index
tplFn: function (v, key) {
return { text: 'Value is: [' + v + ']. Key is: [' + key + ']' }
}
},
{ xCls: 'sidebar' },
{ xtraCls: 'main', items: {
for: [5,6,7,8],
observeProp: 'data2',
tplFn: function (v) { return v }
}},
]
})
// change:
root.observe.data1 = { name: 'Dominic', purpose: 'Helper', target: 'quick dom for test' }
root.observe.data2 = [ 'Helo ', 'This ', 'is ', 'a ', 'test ' ]
Result
Template with data change reaction
var src = [
{ name: 'apple', cost: 0.5 },
{ name: 'mango', cost: 0.5 },
{ name: 'grape', cost: 0.6,
suppliers: { data: [
{ name: 'US', time: 5 },
{ name: 'UK', time: 4 }
]}
}
]
var root = Dominic.create({
className: 'root',
id: 'root',
parent: document.body,
width: 300,
height: 300,
background: 'darkgreen',
padding: 5,
items: {
// data source
for: null,
// update this when root.observe.data = src
observeProp: 'data',
tplFn: function (item, itemIdx) {
return { tag: 'div', text: item.name, padding: 5, margin: '5px 0 0 5px', background: 'tomato',
items: {
for: item.suppliers,
root: 'data', // specify which property to look for data
tplFn: function (sup, supIdx) {
return {
tag: 'div',
padding: 5,
background: 'lightblue',
text: sup.name + '. Time: ' + sup.time + ' days',
click: { scope: 'root', handler: 'onClickSupplier' }
}
}
}
}
}
},
onClickSupplier: function (e) {
// Do something
},
events: [
{ type: 'mouseout', handler: function (e) {
console.log('Out of:', e.target)
}}
]
})
// first change
root.observe.data = src
// add more value
root.observe.push('data', {
name: 'mangox',
cost: 0.5,
suppliers: { data: [
{ name: 'Russia', time: 4 },
{ name: 'China', time: 5 }
]}
})
All template functions
- Only work when template data is array
- Now support multiple templates observing same property
- APIs:
- push (now real push, was previously reset)
- insert
- remove
- pop
- shift
- unshift
- Example:
/**
* @param observeProperty {string}
* @param data {any}
*/
root.observe.push(observeProperty, data)
/**
* If index is absent, insert at the end, same behavior with push
* @param index? {int}
*/
root.observe.insert(observeProperty, index, data)
/**
* @param indexes {int[] | int}
*/
root.observe.remove(observeProperty, indexes)
/**
* These following methods have same behavior like in an array
*/
root.observe.pop(observeProperty)
root.observe.shift(observeProperty)
root.observe.unshift(observeProperty, data)
Component (v.0.1.42)
Basic 1
Dominic.register('input', function Input(defs) {
return {
tag: 'label',
parent: defs.parent,
display: 'block',
items: [
{ tag: 'span', items: { tag: 'b', text: 'Label name:' } },
{ tag: 'input', placeholder: 'Choose label name' }
]
}
})
Dominic.create({
ctype: 'input',
parent: document.body
})
Result
Basic 2
// Define
Dominic.register('input', function Input(defs) {
return {
tag: 'label',
display: 'block',
items: [
{ tag: 'span', items: { tag: 'b', text: defs.label } },
{ tag: 'input', placeholder: 'Choose label name' }
]
}
})
// Define
Dominic.register('tab', function Tab(defs) {
var configs = {
cls: 'd-tab-ct',
parent: defs.parent,
items: [
{ cls: 'd-tab-btns',
defaults: {
tag: 'span',
display: 'inline-block',
height: 22,
padding: 4
},
items: {
for: defs.tabs,
observeProp: 'tabs',
tplFn: function(tab, i) {
return { text: tab.name }
}
}
},
{ cls: 'd-tab-tabs', items: {
for: defs.tabs,
observeProp: 'tabs',
tplFn: function(tab, i) {
console.log('tab data is', tab)
// Usage
return { ctype: 'input', label: 'Tab number ' + i }
}
}},
]
}
if (defs.created)
configs.created = defs.created
if (defs.appended)
configs.appended = defs.appended
return configs
})
Dominic.create({
ctype: 'tab',
parent: document.body,
tabs: [
{ name: 'Tab 1' },
{ name: 'Tab 2' },
{ name: 'Tab 3' }
],
created: function() {
console.log('finished initiating tab')
},
appended: function() {
console.log('Now in document')
}
})
Result
Motivation
Prototyping some design and testing event/ interaction made a bit more convinient when
- No dependencies & everything in javascript
- There are Events & reference & template supports
Installation
<script src="dominic.min.js"></script>
npm i dominic
API
- Create new DOM element
/**
* @param defs {Object} opts options for root element, className, id, children etc...
* @return {DOM}
*/
Dominic.create(defs)
- Register a component
/**
* @param name {string} component name
* @param fn {Function} function used by dominic to create component. should return an definition object
*/
Dominic.register(name, fn)
Plan
- [x] Have mixed components and normal elements
- [ ] Have data binding/ linking
- [ ] Have diffing when updating in template
- [ ] Have basic layouts:
facebook
,twitter
,pinterest
- [ ] Have basic components:
tab
,combobox
,table
License
.MIT