Browse Source

POC ui module

ui-module
Artur Arseniev 5 years ago
parent
commit
c1c57ac0a1
  1. 1
      src/editor/index.js
  2. 1
      src/editor/model/Editor.js
  3. 103
      src/ui/elements/Base.js
  4. 26
      src/ui/elements/InputField.js
  5. 259
      src/ui/elements/vdom.js
  6. 52
      src/ui/index.js

1
src/editor/index.js

@ -144,6 +144,7 @@ export default (config = {}) => {
[
'I18n',
'UI',
'Utils',
'Config',
'Commands',

1
src/editor/model/Editor.js

@ -16,6 +16,7 @@ Backbone.$ = $;
const deps = [
require('utils'),
require('i18n'),
require('ui'),
require('keymaps'),
require('undo_manager'),
require('storage_manager'),

103
src/ui/elements/Base.js

@ -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();
}
});

26
src/ui/elements/InputField.js

@ -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 })
})
]);
}
});

259
src/ui/elements/vdom.js

@ -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
);

52
src/ui/index.js

@ -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…
Cancel
Save