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
|
import opairs from require 'lib.ordered'
void_tags = { 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr' }
void_tags = { t,t for t in *void_tags }
-- convert anything to HTML string
-- val must be one of:
-- * MMMElement (have a .render method that returns a string)
-- * string
tohtml = (val) ->
if 'table' == type val
assert val.render, "Table doesn't have .render"
val = val\render!
if 'string' == type val
val
else
error "not a Node: #{val}, #{type val}"
-- overloaded append
-- see tohtml for acceptable values
g_append = append
append = (value) -> g_append tohtml value
-- shorthand to form a text node from strings
text = (...) -> table.concat { ... }, ' '
class ReactiveVar
@isinstance: (val) -> 'table' == (type val) and val.subscribe
new: (@value) =>
set: (value) =>
error "attempting to update ReactiveVar serverside"
get: => @value
subscribe: (callback) =>
warn "attempting to subscribe to ReactiveVar serverside"
map: (transform) =>
ReactiveVar transform @value
class ReactiveElement
@isinstance: (val) -> 'table' == (type val) and val.render
new: (element, ...) =>
@element = element
@attrs = { style: {} }
@children = {}
children = { ... }
-- attributes are last arguments but mustn't be a ReactiveVar
attributes = children[#children]
if 'table' == (type attributes) and
(not ReactiveElement.isinstance attributes) and
(not ReactiveVar.isinstance attributes)
table.remove children
else
attributes = {}
for k,v in pairs attributes
@set k, v if 'string' == type k
-- if there is only one argument,
-- children can be in attributes table too
if #children == 0
children = attributes
for child in *children
@append child
destroy: =>
set: (attr, value) =>
if attr == 'style' and 'table' == type value
for k,v in opairs value
@attrs.style[k] = v
return
@attrs[attr] = value
append: (child, last) =>
assert not last, "last passed to append on server"
if ReactiveVar.isinstance child
child = child\get!
child = tohtml child
table.insert @children, child
remove: (child) =>
error "remove called serverside"
render: =>
b = "<#{@element}"
for k,v in opairs @attrs
if 'table' == type v
tmp = ''
for kk, vv in opairs v
tmp ..= "#{kk}: #{vv}; "
v = tmp
b ..= " #{k}=\"#{v}\""
if void_tags[@element]
assert #@children == 0, "void tag #{element} cannot have children!"
b .. ">"
else
b .. ">"
b ..= ">" .. table.concat @children, ''
b ..= "</#{@element}>"
b
elements = setmetatable {}, __index: (name) =>
with val = (...) -> ReactiveElement name, ...
@[name] = val
get_or_create = (elem, id, ...) ->
with ReactiveElement elem, ...
\set 'id', id
{
:ReactiveVar,
:ReactiveElement,
:get_or_create,
:tohtml,
:flush,
:append,
:text,
:elements,
}
|