/
each.js
238 lines (203 loc) · 7.15 KB
/
each.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/**
* Convert the item looped into an object used to extend the child tag properties
* @param { Object } expr - object containing the keys used to extend the children tags
* @param { * } key - value to assign to the new object returned
* @param { * } val - value containing the position of the item in the array
* @returns { Object } - new object containing the values of the original item
*
* The variables 'key' and 'val' are arbitrary.
* They depend on the collection type looped (Array, Object)
* and on the expression used on the each tag
*
*/
function mkitem(expr, key, val) {
var item = {}
item[expr.key] = key
if (expr.pos) item[expr.pos] = val
return item
}
/**
* Unmount the redundant tags
* @param { Array } items - array containing the current items to loop
* @param { Array } tags - array containing all the children tags
*/
function unmountRedundant(items, tags) {
var i = tags.length,
j = items.length
while (i > j) {
var t = tags[--i]
tags.splice(i, 1)
t.unmount()
}
}
/**
* Move the nested custom tags in non custom loop tags
* @param { Object } child - non custom loop tag
* @param { Number } i - current position of the loop tag
*/
function moveNestedTags(child, i) {
Object.keys(child.tags).forEach(function(tagName) {
var tag = child.tags[tagName]
if (isArray(tag))
each(tag, function (t) {
moveChildTag(t, tagName, i)
})
else
moveChildTag(tag, tagName, i)
})
}
/**
* Adds the elements for a virtual tag
* @param { Tag } tag - the tag whose root's children will be inserted or appended
* @param { Node } src - the node that will do the inserting or appending
* @param { Tag } target - only if inserting, insert before this tag's first child
*/
function addVirtual(tag, src, target) {
var el = tag._root
tag._virts = []
while (el) {
var sib = el.nextSibling
if (target)
src.insertBefore(el, target._root)
else
src.appendChild(el)
tag._virts.push(el) // hold for unmounting
el = sib
}
}
/**
* Move virtual tag and all child nodes
* @param { Tag } tag - first child reference used to start move
* @param { Node } src - the node that will do the inserting
* @param { Tag } target - insert before this tag's first child
* @param { Number } len - how many child nodes to move
*/
function moveVirtual(tag, src, target, len) {
var el = tag._root
for (var i = 0; i < len; i++) {
var sib = el.nextSibling
src.insertBefore(el, target._root)
el = sib
}
}
/**
* Manage tags having the 'each'
* @param { Object } dom - DOM node we need to loop
* @param { Tag } parent - parent tag instance where the dom node is contained
* @param { String } expr - string contained in the 'each' attribute
*/
function _each(dom, parent, expr) {
// remove the each property from the original tag
remAttr(dom, 'each')
var mustReorder = typeof getAttr(dom, 'no-reorder') !== T_STRING || remAttr(dom, 'no-reorder'),
tagName = getTagName(dom),
impl = __tagImpl[tagName] || { tmpl: dom.outerHTML },
useRoot = SPECIAL_TAGS_REGEX.test(tagName),
root = dom.parentNode,
ref = document.createTextNode(''),
child = getTag(dom),
isOption = /option/gi.test(tagName), // the option tags must be treated differently
tags = [],
oldItems = [],
hasKeys,
isVirtual = dom.tagName == 'VIRTUAL'
// parse the each expression
expr = tmpl.loopKeys(expr)
// insert a marked where the loop tags will be injected
root.insertBefore(ref, dom)
// clean template code
parent.one('before-mount', function () {
// remove the original DOM node
dom.parentNode.removeChild(dom)
if (root.stub) root = parent.root
}).on('update', function () {
// get the new items collection
var items = tmpl(expr.val, parent),
// create a fragment to hold the new DOM nodes to inject in the parent tag
frag = document.createDocumentFragment()
// object loop. any changes cause full redraw
if (!isArray(items)) {
hasKeys = items || false
items = hasKeys ?
Object.keys(items).map(function (key) {
return mkitem(expr, key, items[key])
}) : []
}
// loop all the new items
items.forEach(function(item, i) {
// reorder only if the items are objects
var _mustReorder = mustReorder && item instanceof Object,
oldPos = oldItems.indexOf(item),
pos = ~oldPos && _mustReorder ? oldPos : i,
// does a tag exist in this position?
tag = tags[pos]
item = !hasKeys && expr.key ? mkitem(expr, item, i) : item
// new tag
if (
!_mustReorder && !tag // with no-reorder we just update the old tags
||
_mustReorder && !~oldPos || !tag // by default we always try to reorder the DOM elements
) {
tag = new Tag(impl, {
parent: parent,
isLoop: true,
hasImpl: !!__tagImpl[tagName],
root: useRoot ? root : dom.cloneNode(),
item: item
}, dom.innerHTML)
tag.mount()
if (isVirtual) tag._root = tag.root.firstChild // save reference for further moves or inserts
// this tag must be appended
if (i == tags.length) {
if (isVirtual)
addVirtual(tag, frag)
else frag.appendChild(tag.root)
}
// this tag must be insert
else {
if (isVirtual)
addVirtual(tag, root, tags[i])
else root.insertBefore(tag.root, tags[i].root)
oldItems.splice(i, 0, item)
}
tags.splice(i, 0, tag)
pos = i // handled here so no move
} else tag.update(item)
// reorder the tag if it's not located in its previous position
if (pos !== i && _mustReorder) {
// update the DOM
if (isVirtual)
moveVirtual(tag, root, tags[i], dom.childNodes.length)
else root.insertBefore(tag.root, tags[i].root)
// update the position attribute if it exists
if (expr.pos)
tag[expr.pos] = i
// move the old tag instance
tags.splice(i, 0, tags.splice(pos, 1)[0])
// move the old item
oldItems.splice(i, 0, oldItems.splice(pos, 1)[0])
// if the loop tags are not custom
// we need to move all their custom tags into the right position
if (!child) moveNestedTags(tag, i)
}
// cache the original item to use it in the events bound to this node
// and its children
tag._item = item
// cache the real parent tag internally
defineProperty(tag, '_parent', parent)
}, true) // allow null values
// remove the redundant tags
unmountRedundant(items, tags)
// insert the new nodes
if (isOption) root.appendChild(frag)
else root.insertBefore(frag, ref)
// set the 'tags' property of the parent tag
// if child is 'undefined' it means that we don't need to set this property
// for example:
// we don't need store the `myTag.tags['div']` property if we are looping a div tag
// but we need to track the `myTag.tags['child']` property looping a custom child node named `child`
if (child) parent.tags[tagName] = tags
// clone the items array
oldItems = items.slice()
})
}