Skip to content

Commit 4797b2e

Browse files
committed
[ Refactor ] Sub View rendering is based on ViewList class now
[ Add ] 3 inserting utility methods
1 parent 25221dc commit 4797b2e

File tree

10 files changed

+379
-71
lines changed

10 files changed

+379
-71
lines changed

ReadMe.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
}
2828
```
2929

30-
`source/index.html` [**Template syntax**](https://web-cell.tk/DOM-Renderer/manual/Template.html)
30+
`source/index.html` [**Template syntax**](https://web-cell.dev/DOM-Renderer/manual/Template.html)
3131

3232
```HTML
3333
<template>
@@ -167,7 +167,7 @@ console.log( view.name ); // 'TechQuery'
167167

168168
console.log( view.profile ); // View {}
169169

170-
console.log( view.job ); // [View {}, View {}, View {}]
170+
console.log( view.job ); // ViewList [View {}, View {}, View {}]
171171
```
172172

173173
### Setter
@@ -187,4 +187,4 @@ nextTick().then(() => {
187187

188188
## Typical cases
189189

190-
1. [WebCell](https://web-cell.tk/)
190+
1. [WebCell](https://web-cell.dev/)

manual/Template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@ Source code of **DOM-Renderer** templates is legal [HTML 5 markups][1] with lega
4343
[1]: https://developer.mozilla.org/en-US/docs/Web/HTML
4444
[2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
4545
[3]: https://developer.mozilla.org/en-US/docs/Web/API/element
46-
[4]: https://web-cell.tk/DOM-Renderer/class/source/view/View.js~View.html
46+
[4]: https://web-cell.dev/DOM-Renderer/class/source/view/View.js~View.html

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dom-renderer",
3-
"version": "0.9.0",
3+
"version": "1.0.0-alpha",
44
"description": "Template engine based on HTML 5, ECMAScript 6 & MVVM",
55
"keywords": [
66
"template",
@@ -11,7 +11,7 @@
1111
],
1212
"license": "LGPL-3.0",
1313
"author": "shiy2008@gmail.com",
14-
"homepage": "https://web-cell.tk/DOM-Renderer/",
14+
"homepage": "https://web-cell.dev/DOM-Renderer/",
1515
"repository": {
1616
"type": "git",
1717
"url": "git+https://github.com/EasyWebApp/DOM-Renderer.git"
@@ -35,7 +35,7 @@
3535
"debug": "npm run pack && mocha --inspect-brk --no-timeouts",
3636
"test": "npm run build && mocha --exit && esdoc",
3737
"prepublishOnly": "npm test",
38-
"help": "esdoc && opn docs/index.html"
38+
"help": "esdoc && open-cli docs/index.html"
3939
},
4040
"husky": {
4141
"hooks": {
@@ -58,11 +58,11 @@
5858
"esdoc-external-webapi-plugin": "^1.0.0",
5959
"esdoc-standard-plugin": "^1.0.0",
6060
"eslint": "^5.16.0",
61-
"husky": "^2.1.0",
62-
"jsdom": "^15.0.0",
61+
"husky": "^2.3.0",
62+
"jsdom": "^15.1.0",
6363
"mocha": "^6.1.4",
64-
"opn-cli": "^4.1.0",
65-
"prettier": "^1.17.0",
64+
"open-cli": "^5.0.0",
65+
"prettier": "^1.17.1",
6666
"should": "^13.2.3",
6767
"should-sinon": "0.0.6",
6868
"sinon": "^7.3.2"

source/DOM/insert.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { parseDOM } from './parser';
2+
3+
/**
4+
* @param {Node} node
5+
* @param {Boolean} [inNodes] - Seek in all kinds of `Node`
6+
*
7+
* @return {Number} The index of `node` in its siblings
8+
*/
9+
export function indexOf(node, inNodes) {
10+
var key = `previous${inNodes ? '' : 'Element'}Sibling`,
11+
index = 0;
12+
13+
while ((node = node[key])) index++;
14+
15+
return index;
16+
}
17+
18+
/**
19+
* @param {Node[]} list
20+
* @param {Number} [index]
21+
*
22+
* @return {Number}
23+
*/
24+
export function insertableIndexOf(list, index) {
25+
return !(index != null) || index > list.length
26+
? list.length
27+
: index < 0
28+
? list.length + index
29+
: index;
30+
}
31+
32+
/**
33+
* @param {ParentNode} parent
34+
* @param {Node|String} child - https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append#Parameters
35+
* @param {Number|Node} [position]
36+
* @param {Boolean} [inNodes] - Seek in all kinds of `Node`
37+
*/
38+
export function insertTo(parent, child, position, inNodes) {
39+
const list = Array.from(parent[`child${inNodes ? 'Nodes' : 'ren'}`]);
40+
41+
position =
42+
position instanceof Node
43+
? indexOf(position, inNodes)
44+
: insertableIndexOf(list, position);
45+
46+
const point = list.slice(position)[0];
47+
48+
if (point) point.before(child);
49+
else parent.append(child);
50+
}
51+
52+
/**
53+
* @param {String|Node[]} fragment
54+
*
55+
* @return {?DocumentFragment}
56+
*/
57+
export function makeNode(fragment) {
58+
if (fragment instanceof Node) return fragment;
59+
60+
if (Object(fragment) instanceof String) return parseDOM(fragment);
61+
62+
let node = document.createDocumentFragment();
63+
64+
node.append.apply(
65+
node,
66+
Array.from(fragment, item =>
67+
item.parentNode ? item.cloneNode(true) : item
68+
)
69+
);
70+
71+
return node;
72+
}

source/DOM/polyfill.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const { window } = new JSDOM('', {
1111
'self',
1212
'document',
1313
'Node',
14+
'Element',
1415
'HTMLElement',
1516
'DocumentFragment',
1617
'DOMParser',

source/view/View.js

Lines changed: 18 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010

1111
import Template from './Template';
1212

13+
import ViewList from './ViewList';
14+
1315
import { debounce, nextTick } from '../DOM/timer';
1416

1517
import CustomInputEvent, { watchInput } from '../DOM/CustomInputEvent';
@@ -22,7 +24,6 @@ const view_template = Symbol('View template'),
2224
view_top = new Map(),
2325
view_injection = Symbol('View injection'),
2426
view_varible = ['view', 'scope'],
25-
element_view = new WeakMap(),
2627
top_input = new WeakMap();
2728

2829
export default class View extends Model {
@@ -79,7 +80,7 @@ export default class View extends Model {
7980
* @return {String} HTML/XML source code of this View
8081
*/
8182
toString() {
82-
return stringifyDOM(this.topNodes);
83+
return stringifyDOM(this.topNodes).replace(/\s+$/gm, '');
8384
}
8485

8586
/**
@@ -125,12 +126,14 @@ export default class View extends Model {
125126
return;
126127
}
127128

128-
const sub_view = [];
129-
130-
element_view.set(element, sub_view);
129+
const view_list = new ViewList(
130+
element,
131+
this.data,
132+
this[view_injection]
133+
);
131134

132135
this.watch(name, {
133-
get: () => (sub_view[1] ? sub_view : sub_view[0])
136+
get: () => (view_list[1] ? view_list : view_list[0])
134137
});
135138
}
136139

@@ -272,11 +275,18 @@ export default class View extends Model {
272275
template.evaluate.apply(template, [element].concat(injection));
273276
});
274277

275-
for (let { type, element, template, name } of this)
278+
for (let { type, element, name } of this)
276279
if (type === 'View') {
277280
await nextTick();
278281

279-
await this.renderSub(temp[name], name, element, template);
282+
const view_list = new ViewList(element),
283+
data = temp[name];
284+
285+
if (data === null) view_list.clear();
286+
else if (data)
287+
await view_list.render(
288+
data instanceof Array ? data : [data]
289+
);
280290
}
281291

282292
if (root)
@@ -293,40 +303,4 @@ export default class View extends Model {
293303

294304
view_top.delete(this);
295305
}
296-
297-
/**
298-
* @protected
299-
*
300-
* @param {Object} data
301-
* @param {String} name
302-
* @param {Element} element
303-
* @param {String} template
304-
*/
305-
async renderSub(data, name, element, template) {
306-
if (!data && data !== null) return;
307-
308-
const sub = element_view.get(element),
309-
isArray = data instanceof Array,
310-
_data_ = this.data;
311-
312-
data = isArray ? Array.from(data) : data ? [data] : [];
313-
314-
sub.splice(data.length, Infinity).forEach(view => view.destroy());
315-
316-
for (let i = 0; data[i]; i++) {
317-
sub[i] =
318-
sub[i] || new View(template, this.data, this[view_injection]);
319-
320-
if (isArray) _data_[name][i] = sub[i].data;
321-
else _data_[name] = sub[i].data;
322-
323-
await sub[i].render(data[i]);
324-
}
325-
326-
if (data[0])
327-
element.append.apply(
328-
element,
329-
concat.apply([], sub.map(view => view.topNodes))
330-
);
331-
}
332306
}

source/view/ViewList.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import View from './View';
2+
3+
import { insertableIndexOf, insertTo, makeNode } from '../DOM/insert';
4+
5+
const root_list = new WeakMap(),
6+
list_root = new WeakMap(),
7+
template = Symbol('View template'),
8+
child_offset = Symbol('Child offset');
9+
10+
export default class ViewList extends Array {
11+
/**
12+
* @param {Element} root
13+
* @param {Object} scope
14+
* @param {String[]} injection
15+
*/
16+
constructor(root, scope, injection) {
17+
super();
18+
19+
if (!(root instanceof Element)) return;
20+
21+
const that = root_list.get(root);
22+
23+
if (that) return that;
24+
25+
root_list.set(root, this), list_root.set(this, root);
26+
27+
this[template] = [View.getTemplate(root), scope, injection];
28+
29+
this[child_offset] = root.childNodes.length;
30+
}
31+
32+
/**
33+
* @param {Number} [from=0] - View index
34+
*/
35+
clear(from = 0) {
36+
this.splice(from, Infinity).forEach(view => view.destroy());
37+
}
38+
39+
set length(value) {
40+
this.clear(value);
41+
}
42+
43+
/**
44+
* @type {Element}
45+
*/
46+
get root() {
47+
return list_root.get(this);
48+
}
49+
50+
/**
51+
* @type {String}
52+
*/
53+
get name() {
54+
return this.root.dataset.view;
55+
}
56+
57+
/**
58+
* @type {Object}
59+
*/
60+
get data() {
61+
return (this[template][1] || '')[this.name];
62+
}
63+
64+
/**
65+
* @return {String} HTML source
66+
*/
67+
toString() {
68+
return this.join('').replace(/\s+$/gm, '');
69+
}
70+
71+
/**
72+
* @return {Object[]} JSON data
73+
*/
74+
valueOf() {
75+
return Array.from(this, view => view.valueOf());
76+
}
77+
78+
/**
79+
* @param {Object} item
80+
* @param {?Number} index
81+
*
82+
* @return {View}
83+
*/
84+
async insert(item, index) {
85+
index = insertableIndexOf(this, index);
86+
87+
const view = new View(...this[template]),
88+
{ data } = this;
89+
90+
this.splice(index, 0, view);
91+
92+
await view.render(item);
93+
94+
if (data instanceof Array) data[index] = view.data;
95+
else if (data) this[template][1][this.name] = view.data;
96+
97+
const { topNodes } = view;
98+
99+
insertTo(
100+
this.root,
101+
makeNode(topNodes),
102+
topNodes.length * index + this[child_offset],
103+
true
104+
);
105+
106+
return view;
107+
}
108+
109+
/**
110+
* @param {Object[]} list
111+
*/
112+
async render(list) {
113+
this.clear(list.length);
114+
115+
for (let i = 0; list[i]; i++)
116+
await (this[i] ? this[i].render(list[i]) : this.insert(list[i], i));
117+
}
118+
}

0 commit comments

Comments
 (0)