mirror of https://github.com/artf/grapesjs.git
6 changed files with 442 additions and 0 deletions
@ -0,0 +1,103 @@ |
|||
import { View, Model } from 'backbone'; |
|||
import { isString, flatten } from 'underscore'; |
|||
import { h, text, patch } from './vdom'; |
|||
|
|||
export default View.extend({ |
|||
__h: h, |
|||
__text: text, |
|||
__patch: patch, |
|||
render: 0, |
|||
|
|||
initialize({ tag, props = {}, children = [] }) { |
|||
console.log('INIT', { tag, props, children }); |
|||
this.model = new Model({ |
|||
...(this.props || {}), |
|||
...props |
|||
}); |
|||
this.tag = tag; |
|||
this.children = flatten(children, 1); |
|||
this.listenTo(this.model, 'change', this.__create); |
|||
this.init && this.init(); |
|||
}, |
|||
|
|||
__rndrProps() { |
|||
return { |
|||
props: this.get(), |
|||
set: p => this.set(p) |
|||
}; |
|||
}, |
|||
|
|||
__createVDom() { |
|||
const rendered = |
|||
this.render && this.render(this.__rndrProps()).__createVDom(); |
|||
this._vdom = |
|||
rendered || |
|||
this.__h( |
|||
this.tag, |
|||
this.get(), |
|||
this.children.map(chl => |
|||
isString(chl) ? this.__text(chl) : chl.__createVDom() |
|||
) |
|||
); |
|||
return this._vdom; |
|||
}, |
|||
|
|||
__getRoot() { |
|||
const { el, _el, _vdom } = this; |
|||
const dom = _vdom && _vdom.dom; |
|||
|
|||
if (!dom && !_el && !el.firstChild) { |
|||
el.appendChild(document.createElement('div')); |
|||
} |
|||
|
|||
return dom || _el || el.firstChild; |
|||
}, |
|||
|
|||
__create(model) { |
|||
const prev = model.previousAttributes(); |
|||
this.__chn = { |
|||
...(this.__chn || {}), |
|||
...model.changed |
|||
}; |
|||
this.__prev = { |
|||
...Object.keys(this.__chn).reduce((o, i) => ((o[i] = prev[i]), o), {}), |
|||
...(this.__prev || {}) |
|||
}; |
|||
this.__tmo && clearTimeout(this.__tmo); |
|||
this.__tmo = setTimeout(() => { |
|||
const { __chn, __prev } = this; |
|||
this.trigger('change', __chn, __prev); |
|||
Object.keys(__chn).forEach(i => |
|||
this.trigger(`change:${i}`, __chn[i], __prev[i]) |
|||
); |
|||
this.__chn = {}; |
|||
this.__prev = {}; |
|||
this.create(); |
|||
}); |
|||
}, |
|||
|
|||
get(prop) { |
|||
const attrs = this.model.attributes; |
|||
return prop ? attrs[prop] : attrs; |
|||
}, |
|||
|
|||
set(props) { |
|||
return this.model.set(props); |
|||
}, |
|||
|
|||
create() { |
|||
const root = this.__getRoot(); |
|||
console.log('CREATE prepatch for', this.tag, { |
|||
_el: this._el, |
|||
_dom: this._vdom && this._vdom.dom |
|||
}); |
|||
const vdom = this.__createVDom(); |
|||
this.__patch(root, vdom); |
|||
this._el = vdom.dom; |
|||
console.log('CREATE Patch', { |
|||
innerHTML: this.__getRoot().outerHTML, |
|||
vdomDOM: vdom.dom |
|||
}); |
|||
return this.__getRoot(); |
|||
} |
|||
}); |
|||
@ -0,0 +1,26 @@ |
|||
export default ({ el }) => ({ |
|||
props: { |
|||
myLabel: 'Hello', |
|||
value: '', |
|||
changed: 0 |
|||
}, |
|||
|
|||
render({ props, set }) { |
|||
return el('div', { class: 'input-field' }, [ |
|||
el( |
|||
'label', |
|||
{ class: 'my-label' }, |
|||
`My label: ${props.myLabel} Value: ${props.value} changed: ${props.changed} rnd: ${props.rnd}` |
|||
), |
|||
el('input', { |
|||
class: 'my-input', |
|||
value: props.value, |
|||
oninput: ev => { |
|||
set({ value: ev.target.value }); |
|||
set({ rnd: Math.random() }); |
|||
}, |
|||
onchange: () => set({ changed: props.changed + 1 }) |
|||
}) |
|||
]); |
|||
} |
|||
}); |
|||
@ -0,0 +1,259 @@ |
|||
var SSR_NODE = 1; |
|||
var TEXT_NODE = 3; |
|||
var EMPTY_OBJ = {}; |
|||
var EMPTY_ARR = []; |
|||
var SVG_NS = 'http://www.w3.org/2000/svg'; |
|||
|
|||
var getKey = vdom => (vdom == null ? vdom : vdom.key); |
|||
|
|||
var listener = function(event) { |
|||
this.tag[event.type](event); |
|||
}; |
|||
|
|||
var patchProp = (dom, key, oldVal, newVal, isSvg) => { |
|||
if (key === 'key') { |
|||
} else if (key[0] === 'o' && key[1] === 'n') { |
|||
if (!((dom.tag || (dom.tag = {}))[(key = key.slice(2))] = newVal)) { |
|||
dom.removeEventListener(key, listener); |
|||
} else if (!oldVal) { |
|||
dom.addEventListener(key, listener); |
|||
} |
|||
} else if (!isSvg && key !== 'list' && key !== 'form' && key in dom) { |
|||
dom[key] = newVal == null ? '' : newVal; |
|||
} else if (newVal == null || newVal === false) { |
|||
dom.removeAttribute(key); |
|||
} else { |
|||
dom.setAttribute(key, newVal); |
|||
} |
|||
}; |
|||
|
|||
var createNode = (vdom, isSvg) => { |
|||
var props = vdom.props; |
|||
var dom = |
|||
vdom.tag === TEXT_NODE |
|||
? document.createTextNode(vdom.type) |
|||
: (isSvg = isSvg || vdom.type === 'svg') |
|||
? document.createElementNS(SVG_NS, vdom.type, { is: props.is }) |
|||
: document.createElement(vdom.type, { is: props.is }); |
|||
|
|||
for (var k in props) patchProp(dom, k, null, props[k], isSvg); |
|||
|
|||
vdom.children.map(kid => |
|||
dom.appendChild(createNode((kid = vdomify(kid)), isSvg)) |
|||
); |
|||
|
|||
return (vdom.dom = dom); |
|||
}; |
|||
|
|||
var patchDom = (parent, dom, oldVdom, newVdom, isSvg) => { |
|||
if (oldVdom === newVdom) { |
|||
} else if ( |
|||
oldVdom != null && |
|||
oldVdom.tag === TEXT_NODE && |
|||
newVdom.tag === TEXT_NODE |
|||
) { |
|||
if (oldVdom.type !== newVdom.type) dom.nodeValue = newVdom.type; |
|||
} else if (oldVdom == null || oldVdom.type !== newVdom.type) { |
|||
dom = parent.insertBefore( |
|||
createNode((newVdom = vdomify(newVdom)), isSvg), |
|||
dom |
|||
); |
|||
if (oldVdom != null) { |
|||
parent.removeChild(oldVdom.dom); |
|||
} |
|||
} else { |
|||
var tmpVKid, |
|||
oldVKid, |
|||
oldKey, |
|||
newKey, |
|||
oldProps = oldVdom.props, |
|||
newProps = newVdom.props, |
|||
oldVKids = oldVdom.children, |
|||
newVKids = newVdom.children, |
|||
oldHead = 0, |
|||
newHead = 0, |
|||
oldTail = oldVKids.length - 1, |
|||
newTail = newVKids.length - 1; |
|||
|
|||
isSvg = isSvg || newVdom.type === 'svg'; |
|||
|
|||
for (var i in { ...oldProps, ...newProps }) { |
|||
if ( |
|||
(i === 'value' || i === 'selected' || i === 'checked' |
|||
? dom[i] |
|||
: oldProps[i]) !== newProps[i] |
|||
) { |
|||
patchProp(dom, i, oldProps[i], newProps[i], isSvg); |
|||
} |
|||
} |
|||
|
|||
while (newHead <= newTail && oldHead <= oldTail) { |
|||
if ( |
|||
(oldKey = getKey(oldVKids[oldHead])) == null || |
|||
oldKey !== getKey(newVKids[newHead]) |
|||
) { |
|||
break; |
|||
} |
|||
|
|||
patchDom( |
|||
dom, |
|||
oldVKids[oldHead].dom, |
|||
oldVKids[oldHead++], |
|||
(newVKids[newHead] = vdomify(newVKids[newHead++])), |
|||
isSvg |
|||
); |
|||
} |
|||
|
|||
while (newHead <= newTail && oldHead <= oldTail) { |
|||
if ( |
|||
(oldKey = getKey(oldVKids[oldTail])) == null || |
|||
oldKey !== getKey(newVKids[newTail]) |
|||
) { |
|||
break; |
|||
} |
|||
|
|||
patchDom( |
|||
dom, |
|||
oldVKids[oldTail].dom, |
|||
oldVKids[oldTail--], |
|||
(newVKids[newTail] = vdomify(newVKids[newTail--])), |
|||
isSvg |
|||
); |
|||
} |
|||
|
|||
if (oldHead > oldTail) { |
|||
while (newHead <= newTail) { |
|||
dom.insertBefore( |
|||
createNode((newVKids[newHead] = vdomify(newVKids[newHead++])), isSvg), |
|||
(oldVKid = oldVKids[oldHead]) && oldVKid.dom |
|||
); |
|||
} |
|||
} else if (newHead > newTail) { |
|||
while (oldHead <= oldTail) { |
|||
dom.removeChild(oldVKids[oldHead++].dom); |
|||
} |
|||
} else { |
|||
for (var keyed = {}, newKeyed = {}, i = oldHead; i <= oldTail; i++) { |
|||
if ((oldKey = oldVKids[i].key) != null) { |
|||
keyed[oldKey] = oldVKids[i]; |
|||
} |
|||
} |
|||
|
|||
while (newHead <= newTail) { |
|||
oldKey = getKey((oldVKid = oldVKids[oldHead])); |
|||
newKey = getKey((newVKids[newHead] = vdomify(newVKids[newHead]))); |
|||
|
|||
if ( |
|||
newKeyed[oldKey] || |
|||
(newKey != null && newKey === getKey(oldVKids[oldHead + 1])) |
|||
) { |
|||
if (oldKey == null) { |
|||
dom.removeChild(oldVKid.dom); |
|||
} |
|||
oldHead++; |
|||
continue; |
|||
} |
|||
|
|||
if (newKey == null || oldVdom.tag === SSR_NODE) { |
|||
if (oldKey == null) { |
|||
patchDom( |
|||
dom, |
|||
oldVKid && oldVKid.dom, |
|||
oldVKid, |
|||
newVKids[newHead], |
|||
isSvg |
|||
); |
|||
newHead++; |
|||
} |
|||
oldHead++; |
|||
} else { |
|||
if (oldKey === newKey) { |
|||
patchDom(dom, oldVKid.dom, oldVKid, newVKids[newHead], isSvg); |
|||
newKeyed[newKey] = true; |
|||
oldHead++; |
|||
} else { |
|||
if ((tmpVKid = keyed[newKey]) != null) { |
|||
patchDom( |
|||
dom, |
|||
dom.insertBefore(tmpVKid.dom, oldVKid && oldVKid.dom), |
|||
tmpVKid, |
|||
newVKids[newHead], |
|||
isSvg |
|||
); |
|||
newKeyed[newKey] = true; |
|||
} else { |
|||
patchDom( |
|||
dom, |
|||
oldVKid && oldVKid.dom, |
|||
null, |
|||
newVKids[newHead], |
|||
isSvg |
|||
); |
|||
} |
|||
} |
|||
newHead++; |
|||
} |
|||
} |
|||
|
|||
while (oldHead <= oldTail) { |
|||
if (getKey((oldVKid = oldVKids[oldHead++])) == null) { |
|||
dom.removeChild(oldVKid.dom); |
|||
} |
|||
} |
|||
|
|||
for (var i in keyed) { |
|||
if (newKeyed[i] == null) { |
|||
dom.removeChild(keyed[i].dom); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return (newVdom.dom = dom); |
|||
}; |
|||
|
|||
var vdomify = vdom => |
|||
vdom !== true && vdom !== false && vdom ? vdom : text(''); |
|||
|
|||
var recycleNode = dom => |
|||
dom.nodeType === TEXT_NODE |
|||
? text(dom.nodeValue, dom) |
|||
: createVdom( |
|||
dom.nodeName.toLowerCase(), |
|||
EMPTY_OBJ, |
|||
EMPTY_ARR.map.call(dom.childNodes, recycleNode), |
|||
dom, |
|||
null, |
|||
SSR_NODE |
|||
); |
|||
|
|||
var createVdom = (type, props, children, dom, key, tag) => ({ |
|||
type, |
|||
props, |
|||
children, |
|||
dom, |
|||
key, |
|||
tag |
|||
}); |
|||
|
|||
export var text = (value, dom) => |
|||
createVdom(value, EMPTY_OBJ, EMPTY_ARR, dom, null, TEXT_NODE); |
|||
|
|||
export var h = (type, props, ch) => |
|||
createVdom( |
|||
type, |
|||
props, |
|||
Array.isArray(ch) ? ch : ch == null ? EMPTY_ARR : [ch], |
|||
null, |
|||
props.key |
|||
); |
|||
|
|||
export var patch = (dom, vdom) => ( |
|||
((dom = patchDom( |
|||
dom.parentNode, |
|||
dom, |
|||
dom.v || recycleNode(dom), |
|||
vdom |
|||
)).v = vdom), |
|||
dom |
|||
); |
|||
@ -0,0 +1,52 @@ |
|||
import { bindAll } from 'underscore'; |
|||
import Base from './elements/Base'; |
|||
import InputField from './elements/InputField'; |
|||
|
|||
const buildIn = { |
|||
InputField |
|||
}; |
|||
|
|||
export default () => ({ |
|||
name: 'UI', |
|||
|
|||
init(opts = {}) { |
|||
this.els = {}; |
|||
this.em = opts.em; |
|||
bindAll(this, 'el'); |
|||
Object.keys(buildIn).forEach(name => { |
|||
this.add(name, buildIn[name]({ el: this.el })); |
|||
}); |
|||
return this; |
|||
}, |
|||
|
|||
destroy() { |
|||
['em', 'els'].forEach(i => (this[i] = {})); |
|||
}, |
|||
|
|||
add(name, definition) { |
|||
this.els[name] = Base.extend(definition); |
|||
}, |
|||
|
|||
get(name) { |
|||
return this.els[name]; |
|||
}, |
|||
|
|||
el(tag, props, ...children) { |
|||
const Element = this.get(tag) || Base; |
|||
return new Element({ tag, props, children }); |
|||
} |
|||
}); |
|||
|
|||
/* |
|||
let { el } = editor.UI; |
|||
let appEl = document.getElementById('app'); |
|||
let inpEl = el('InputField', { myLabel: 'Hello2' }); |
|||
inpEl.on('change', (next, prev) => console.log('Changed InputField', { next, prev })) |
|||
inpEl.on('change:value', (next, prev) => console.log('Changed InputField value', { next, prev })) |
|||
let mainEl = el('main', {}, [ |
|||
el('h1', {}, 'My text h1'), |
|||
el('p', {}, 'My text p'), |
|||
inpEl, |
|||
]) |
|||
appEl.appendChild(mainEl.create()) |
|||
*/ |
|||
Loading…
Reference in new issue