-
Notifications
You must be signed in to change notification settings - Fork 0
/
vnode.go
362 lines (317 loc) · 9.39 KB
/
vnode.go
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/*
This file is responsible for implementing the bulk of the so-called
"virtual DOM". In order to keep matching efficient, the VNode struct
pretty much follows the implementation of the standard library HTML
Node struct exactly.
*/
package zephyr
import (
// TODO: remove
"math/rand" // TODO: rmeove
"strconv"
)
type VNodeType int
// (follows html package exactly)
const (
ErrorNode VNodeType = iota
TextNode
DocumentNode
ElementNode
CommentNode
DoctypeNode
// RawNode nodes are not returned by the parser, but can be part of the
// Node tree passed to func Render to insert raw HTML (without escaping).
// If so, this package makes no guarantee that the rendered HTML is secure
// (from e.g. Cross Site Scripting attacks) or well-formed.
RawNode
// The following are types that don't follow the Go HTML package. They are
// used for conditional and iterative rendering
ConditionalNode
IterativeNode
)
// ZephyrAttrs represents the HTML attributes for
// a VNode in a key/value map.
// e.g. <input type="text" /> -> "type": "text"
type ZephyrAttr struct {
// Namespace is currently unused
Namespace, Key string
// Value should be a regular Go
// data type.
Value interface{}
}
// VNode struct is a simple intermediary between the stdlib html.Node
// and a Zephyr Component instance. There are also a few extra fields for
// optimizing patching. Its all on the heap :( I feel like this is bad, but
// I don't know how else to have ref variables inited in the func. FP maybe?
// Will investigate.
type VNode struct {
// NodeType is the virtual nodes DOM node type
NodeType VNodeType
// Tag is the HTML tag - only used for ElementNodes
Tag string
// DOM_ID is the auto-generated ID that is used to update the node
DOM_ID string
// Content
Content interface{}
// Attrs stores the attributes for the ZNode
Attrs map[string]interface{}
// ParsedAttrs is a map[string]string of ready-to-render
// attributes.
ParsedAttrs map[string]string
events map[string]func(e *DOMEvent)
listeners map[string]Listener
// Listener is the nodes listener that
// triggers a comparison whenever updated.
// May want to add a way to tell exactly WHAT
// needs to be updated.
// Listener *VNodeListener
RenderChan chan DOMUpdate
// HTMLNode is the Go representation of the currently rendered
// HTML tree
// HTMLNode *html.Node
// Other node refs
Parent, FirstChild, LastChild, PrevSibling, NextSibling *VNode
// Flags
Static bool
Component bool
// Special fields - may want to interface it up
ConditionalRenders []ConditionalRender
CurrentCondition int
ConditionUpdated bool
Keys []interface{}
key interface{}
IterRender func(int, interface{}) *VNode
}
type ConditionalRender struct {
Condition interface{}
Render *VNode
}
// the js part of this is very very temporary, in fact the whole function is
// going to just try and build it up.
// func (node *VNode) BuildAttrs(attrs map[string]interface{}) {
// // rand.Seed(time.Now().Unix())
// zAttrs := []ZephyrAttr{}
// createdFuncs := map[string]string{}
// for key, val := range attrs {
// // dont redefine the funcs, silly zephyr!
// if _, ok := createdFuncs[node.Tag]; ok {
// // zAttrs[key] = jsFunc
// continue
// }
// zAttrs = append(zAttrs, ZephyrAttr{Key: key, Value: val})
// }
// node.Attrs = zAttrs
// }
func (node *VNode) GetOrCreateListener(lID string) Listener {
if node.listeners == nil {
node.listeners = map[string]Listener{}
}
if val, ok := node.listeners[lID]; ok {
return val
}
var newListener Listener
switch lID {
case "attr":
newListener = VNAttrListener{node: node, id: node.GetDOMSelector() + "__attrL"}
case "content":
newListener = VNContentListener{node: node, id: node.GetDOMSelector() + "__contentL"}
case "prop":
newListener = VNPropListener{node: node, id: node.GetDOMSelector() + "__propL"}
case "calculator":
newListener = VNCalculatorListener{node: node, id: node.GetDOMSelector() + "__calculatorL"}
case "conditional":
newListener = VNConditionalListener{node: node, id: node.GetDOMSelector() + "__conditionalL"}
case "iterator":
newListener = VNIteratorListener{node: node, id: node.GetDOMSelector() + "__iteratorL"}
default:
panic("unknown listener type")
}
node.listeners[lID] = newListener
return newListener
}
func GetElID(nodeTag string) string {
// 0-127
return "znode" + strconv.Itoa(int(rand.Uint32()>>25))
}
func (node *VNode) BindEvent(event string, callback func(e *DOMEvent)) *VNode {
if node.events == nil {
node.events = map[string]func(*DOMEvent){}
}
// add event to vnode
if node.Component {
// TODO
// c := node.Content.(Component).getBase()
// c.events
return node
}
// domEvents in events.go
if _, ok := domEvents[event]; !ok {
panic("that event is not real dawg")
}
node.events[event] = callback
return node
}
func (node *VNode) GetDOMSelector() string {
if node != nil {
if node.DOM_ID == "" {
return node.Parent.GetDOMSelector()
}
if node.Parent != nil && node.Parent.NodeType == IterativeNode {
return node.Parent.GetDOMSelector() + "[data-key='" + node.key.(string) + "']"
}
var retStr string
retStr = node.Tag + "." + node.DOM_ID
parentStr := node.Parent.GetDOMSelector()
if parentStr == "" {
return retStr
}
return parentStr + " " + retStr
}
return ""
}
// CONDITIONAL RENDERING
func RenderIf(condition interface{}, r *VNode) *VNode {
// set up listener
vnode := &VNode{NodeType: ConditionalNode, Component: false, CurrentCondition: 0}
vnode.ConditionalRenders = []ConditionalRender{ConditionalRender{Condition: condition, Render: r}}
return vnode
}
func (vnode *VNode) RenderElseIf(condition interface{}, r *VNode) *VNode {
vnode.ConditionalRenders = append(vnode.ConditionalRenders, ConditionalRender{Condition: condition, Render: r})
return vnode
}
func (vnode *VNode) RenderElse(r *VNode) *VNode {
vnode.ConditionalRenders = append(vnode.ConditionalRenders, ConditionalRender{Condition: true, Render: r})
return vnode
}
// ITERATIVE RENDERING
func RenderFor(iterator LiveArray, r func(index int, val interface{}) *VNode) *VNode {
vnode := &VNode{NodeType: IterativeNode, Component: false, Static: false, Content: iterator, IterRender: r}
return vnode
}
func (vnode *VNode) Key(keyVal interface{}) *VNode {
vnode.key = keyVal
return vnode
}
func (node *VNode) getNewKeys() (keys []interface{}) {
if node.NodeType != IterativeNode {
panic("must be used only on iterativenodes")
}
iListener := node.GetOrCreateListener("iterator")
val := node.Content.(LiveArray).Value(iListener)
r := node.IterRender
switch val.(type) {
case []LiveStruct:
for i, v := range val.([]LiveStruct) {
c := r(i, v)
keys = append(keys, c.key)
}
}
return keys
}
func (node *VNode) parseIter() (keys []interface{}) {
if node.NodeType != IterativeNode {
panic("must be used only on iterativenodes")
}
iListener := node.GetOrCreateListener("iterator")
val := node.Content.(LiveArray).Value(iListener)
r := node.IterRender
switch val.(type) {
case []LiveStruct:
valArr := val.([]LiveStruct)
var prev *VNode
for i, v := range valArr {
c := r(i, v)
if c.Key == nil {
panic("iterators must have a key")
}
keys = append(keys, c.key)
if i == 0 {
node.FirstChild = c
prev = nil
} else {
prev.NextSibling = c
}
c.PrevSibling = prev
// on the heap, oh well, root elements will be stack
c.Parent = node
c.Attrs["data-key"] = c.key
c.DOM_ID = node.DOM_ID
prev = c
node.LastChild = c
}
// node.LastChild.NextSibling = node.NextSibling
default:
panic("fuck")
}
return keys
}
// Element will create VNodes for the element and all of its children
func Element(tag string, attrs map[string]interface{}, children []*VNode) *VNode {
vnode := VNode{NodeType: ElementNode, Tag: tag, Component: false}
var prev *VNode = nil
var next *VNode = nil
static := true
// linked list of children
for i, curr := range children {
if i < len(children)-1 {
next = children[i+1]
} else {
next = nil
vnode.LastChild = curr
}
if i == 0 {
vnode.FirstChild = curr
}
curr.PrevSibling = prev
curr.NextSibling = next
if !curr.Component {
switch curr.NodeType {
case TextNode:
curr.DOM_ID = "zText" + strconv.Itoa(i)
case ElementNode, DocumentNode, DoctypeNode:
curr.DOM_ID = "zEl" + strconv.Itoa(i)
case IterativeNode:
curr.DOM_ID = "zIterativeEl" + strconv.Itoa(i)
case ConditionalNode:
curr.DOM_ID = "zConditionalEl" + strconv.Itoa(i)
}
}
curr.Parent = &vnode
if curr.NodeType == IterativeNode {
keys := curr.parseIter()
curr.Keys = keys
}
// on the heap, oh well, root elements will be stack
static = static && curr.Static
prev = curr
}
vnode.Attrs = attrs
vnode.Static = static
// fmt.Println(vnode.Attrs)
return &vnode
}
func StaticText(content string) *VNode {
vnode := VNode{NodeType: TextNode, Content: content, Static: true, Component: false}
return &vnode
}
// works with computed props!
func DynamicText(dynamicData interface{}) *VNode {
// dynamicVal := evalFunc()
var vnode *VNode
vnode = &VNode{NodeType: TextNode, Component: false, Static: false, DOM_ID: GetElID("dynamicText")}
vnode.Content = dynamicData
// fmt.Println(vnode)
return vnode
// switch dynamicVal.(type) {
// case *[]int:
// default:
// return &VNode{NodeType: TextNode}
// }
}
func Comment(commentMsg string) VNode {
vnode := VNode{NodeType: CommentNode, Content: commentMsg}
return vnode
}
// func Child() VNode {
// }