-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstate.coffee
166 lines (136 loc) · 4.45 KB
/
state.coffee
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
class State
constructor: (saved) ->
@children = saved?.children ? {}
@vars = saved?.vars ? {}
toJSON: ->
children: do =>
x = {}
for own name, child of @children
c = {}
for own route, state of child
c[route] = state.toJSON?() ? state
x[name] = c
x
vars: do =>
x = {}
x[k] = v for own k,v of @vars when k isnt 'value'
x
class Binding
constructor: (@var) ->
toString: -> "#{@var.get()}"
toJSON: -> @var.get()
Template::onStateRequested = (handler) ->
@onStateRequestedHandler = handler
Blaze.TemplateInstance::requestState = ->
h = @view.template.onStateRequestedHandler
if h? then h.call(@) else null
Template::onStateUpdated = (handler) ->
handlers = @onStateUpdatedHandlers ?= []
handlers.push handler
Blaze.TemplateInstance::triggerOnStateUpdated = ->
@stateParent.triggerOnStateUpdated() if @stateParent?
# We must run setTimeout here to group update requests.
unless @stateUpdatePending
@stateUpdatePending = true
setTimeout =>
h.call(@) for h in @view.template.onStateUpdatedHandlers ? []
@stateUpdatePending = false
, 10
Template::initState = (initializers) ->
makeState = (value, saved) ->
state = new State saved
if value instanceof Binding
state.vars.value = value
value = value.toJSON()
else
state.vars.value ?= value
for key, init of initializers
state.vars[key] ?= if typeof init is 'function' then init.call value else init
state
@helpers
vars: -> Template.instance().vars
bind: -> Template.instance().bindings
@onCreated ->
vars = @vars = {}
bindings = @bindings = {}
@state = new State
template = @
## Creating reactive vars and subscription for reactive changes.
## State here is fake one, just to allow first autorun to run without errors.
prop = (name) =>
v = new ReactiveVar
v.descriptor =
get: -> v.get()
set: (value) ->
# TODO Move it in more proper way whey will check changes from database.
template.triggerOnStateUpdated()
v.set value
enumerable: true
configurable: true
b = new Binding v
b.descriptor =
get: -> b
enumerable: true
configurable: true
@autorun =>
@state.vars[name] = v.get()
# All properties of vars are reactive
Object.defineProperty vars, name, v.descriptor
Object.defineProperty bindings, name, b.descriptor
prop name for name of initializers
prop "value"
@state = null
@stateParent = template
loop
@stateParent = parentTemplate @stateParent
break unless @stateParent?
if @stateParent.state instanceof State
break
parent = @stateParent
@autorun ->
data = Blaze.getData(template.view) ? {}
if data.constructor isnt Object
console.error 'CURRENT DATA isnt Object', data, template
throw new Error 'CURRENT DATA isnt Object'
value = data.value
name = data.name ? 'default'
route = data.route ? '*'
if parent?
r = parent.state.children[route] ?= {}
saved = r[name]
template.state = if saved instanceof State
saved
else
r[name] = makeState value, saved
else
## POTENTIAL RESTORE FROM user defined source
template.state = Tracker.nonreactive -> template.requestState()
unless template.state instanceof State
template.state = makeState value, template.state
for key, init of template.state.vars
if init instanceof Binding
Object.defineProperty vars, key, init.var.descriptor
Object.defineProperty bindings, key, init.descriptor
else
vars[key] = init
parentView = (view) -> view.originalParentView ? view.parentView
parentTemplate = (template) ->
view = parentView(template.view)
while view and (!view.template or view.name in ['(contentBlock)', '(elseBlock)'])
view = parentView(view)
view?.templateInstance?()
Template::methods = (methods) ->
helpers = {}
for name, method of methods
do (method) ->
helpers[name] = ->
tpl = Template.instance()
original = tpl.context # Save context if somebody already used it.
tpl.context = @
result = if typeof method is 'function'
method.call tpl
else
method
tpl.context = original # Restore original context
return result
@helpers helpers