aboutsummaryrefslogtreecommitdiffstats
path: root/lib/component.server.moon
blob: 91858c0e74905965e4bda7d90cb908e2421e16d9 (plain)
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
import opairs from require 'lib.ordered'

-- 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) =>
    error "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}\""
    b ..= ">" ..  table.concat @children, ''
    b ..= "</#{@element}>"
    b

elements = setmetatable {}, __index: (name) =>
  with val = (...) -> ReactiveElement name, ...
    @[name] = val

{
  :ReactiveVar,
  :ReactiveElement,
  :tohtml,
  :flush,
  :append,
  :text,
  :elements,
}