forked from ManuelDeLeon/viewmodel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
viewmodel.coffee
288 lines (249 loc) · 8.3 KB
/
viewmodel.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
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
class ViewModel
@all = new ReactiveArray()
@byId = (id) ->
for vm in @all.list()
return vm.vm if vm.id is id
byTemplate = @byTemplate(id)
return byTemplate[0] if byTemplate.length is 1
undefined
@byTemplate = (template) ->
(vm.vm for vm in @all.list() when vm.template is template)
@binds = {}
@hasBind = (bindName) -> ViewModel.binds[bindName]?
@addBind = (bindName, func) -> ViewModel.binds[bindName] = func
@parseBind = VmHelper.parseBind
constructor: (p1, p2) ->
self = this
_defaultComputation = null
@_vm_id = ''
@dispose = ->
Session.set @_vm_id, undefined
_defaultComputation.stop() if _defaultComputation
thisId = @_vm_id
self.parent()._vm_children.remove(self) if self.parent?()
ViewModel.all.remove((vm) -> vm.vm._vm_id is thisId)
self
if p1 and p2
@_vm_hasId = true
@_vm_id = '_vm_' + p1
else
@_vm_id = '_vm_' + Math.random()
obj = p2 || p1
dependencies = {}
values = {}
initialValues = {}
@_vm_properties = []
dependenciesDelayed = {}
valuesDelayed = {}
@_vm_delayed = {}
propertiesDelayed = []
@_vm_children = new ReactiveArray()
@children = (predicate) ->
list = self._vm_children?.list()
return list if not (predicate and list)
test = null
if _.isFunction(predicate)
test = predicate
else if _.isString(predicate)
test = (vm) ->
predicate is vm.templateInstance.view.name.substring("Template.".length)
return (c for c in list when test(c))
addRawProperty = (p, value, vm, values, dependencies) ->
dep = dependencies[p] || (dependencies[p] = new Tracker.Dependency())
vm[p] = (e) ->
if VmHelper.isArray(e)
values[p] = new ReactiveArray(e)
dep.changed()
else if arguments.length
if VmHelper.isObject(values[p]) or values[p] isnt e
values[p] = e
dep.changed()
else
dep.depend()
if values[p] instanceof ReactiveArray
values[p].list()
else
values[p]
if VmHelper.isArray(value)
values[p] = new ReactiveArray(value)
else
values[p] = value
@_vm_reservedWords = VmHelper.reservedWords
addProperty = (p, value, vm) ->
if _.has(values, p)
vm[p] value
else
if p not in self._vm_reservedWords
vm._vm_properties.push p
initialValues[p] = value
addRawProperty p, value, vm, values, dependencies
@_vm_addDelayedProperty = (p, value, vm) ->
propertiesDelayed.push p
addRawProperty p, value, vm._vm_delayed, valuesDelayed, dependenciesDelayed
addProperties = (propObj, that) ->
for p of propObj
value = propObj[p]
if value instanceof Function or p in self._vm_reservedWords
that[p] = value
else
addProperty p, value, that
if VmHelper.isObject(obj)
addProperties obj, @
@_vm_addParent = (vm, template) ->
if not vm.parent
parentView = template.view.parentView
t = null
while parentView
t = parentView.templateInstance() if parentView.templateInstance
break if t?.viewmodel
parentView = parentView.parentView
vm.parent = -> t?.viewmodel
if t?.viewmodel
t.viewmodel._vm_children.push vm
template.viewmodel = vm if not template.viewmodel
@bind = (template) =>
vm = @
db = '[data-bind]:not([data-bound])'
[container, dataBoundElements] = if VmHelper.isString(template)
if Template[template]
@_vm_addParent vm, Template[template]
[Template[template], Template[template].$(db)]
else
[$(template), $(template).find(db)]
else if VmHelper.isElement(template)
[$(template), $(template).find(db)]
else if template instanceof jQuery
[template, template.find(db)]
else
@_vm_addParent vm, template
[template, template.$(db)]
vmForAll =
vm: @
id: @_vm_id.substring("_vm_".length)
if template instanceof Blaze.TemplateInstance
vmForAll.template = template.view.name.substring("Template.".length)
ViewModel.all.push vmForAll
if @_vm_hasId and container?.autorun
_defaultComputation.stop() if _defaultComputation
container.autorun (c) ->
js = self._vm_toJS()
return if c.firstRun
Session.set self._vm_id, js
dataBoundElements.each ->
element = $(this)
elementBind = VmHelper.parseBind element.data('bind')
element.attr "data-bound", true
for bindName of elementBind
bindFunc = ViewModel.binds[bindName] || ViewModel.binds.default
bindFunc
vm: vm
element: element
elementBind: elementBind
bindName: bindName
property: elementBind[bindName]
container: container
autorun: (f) ->
fun = (c) -> f(c)
if container.autorun
container.autorun fun
else
Tracker.autorun fun
@
@extend = (newObj) =>
addProperties newObj, @
objInSession = null
Tracker.nonreactive ->
objInSession = Session.get(self._vm_id)
if @_vm_hasId and objInSession
objMatch = {}
objMatch[p] = objInSession[p] for p of newObj when _.has(objInSession, p)
if not _.isEmpty(objMatch)
self.fromJS objMatch
@
_addHelper = (name, template, that) ->
obj = {}
obj[name] = -> that[name]()
if template instanceof Blaze.Template
template.helpers obj
else if template instanceof Blaze.TemplateInstance
template.view.template.helpers obj
else
Template[template].helpers obj
@addHelper = (helper, template) ->
_addHelper helper, template, @
@
@addHelpers = (p1, p2) =>
if p2
helpers = p1
template = p2
if helpers instanceof Array
for p in helpers when p not in @_vm_reservedWords
_addHelper p, template, @
else
_addHelper helpers, template, @
else
template = p1
for p of @ when p not in @_vm_reservedWords
_addHelper p, template, @
@
@_vm_toJS = (includeFunctions) =>
ret = {}
if includeFunctions
for p of @ when p not in @_vm_reservedWords and p not in propertiesDelayed
ret[p] = @[p]()
else
for p in self._vm_properties when p not in propertiesDelayed
value = @[p]()
if value instanceof ReactiveArray
ret[p] = value.array()
else
ret[p] = value
for p in propertiesDelayed
ret[p] = this._vm_delayed[p]()
ret
@toJS = (includeFunctions) =>
ret = {}
if includeFunctions
for p of @ when p not in @_vm_reservedWords
ret[p] = @[p]()
else
for p in self._vm_properties
value = @[p]()
if value instanceof ReactiveArray
ret[p] = value.array()
else
ret[p] = value
ret
@fromJS = (obj) =>
for p of values when typeof obj[p] isnt "undefined"
value = obj[p]
if value instanceof Array
values[p] = new ReactiveArray(value)
else
values[p] = value
valuesDelayed[p] = obj[p]
for p of values when typeof obj[p] isnt "undefined"
dependencies[p].changed()
dependenciesDelayed[p].changed() if dependenciesDelayed[p]
@
@reset = ->
for p in self._vm_properties
if initialValues[p] instanceof Array
values[p] = new ReactiveArray initialValues[p]
valuesDelayed[p] = new ReactiveArray initialValues[p]
else
values[p] = initialValues[p]
valuesDelayed[p] = initialValues[p]
for p of values
dependencies[p].changed()
dependenciesDelayed[p].changed() if dependenciesDelayed[p]
@
if @_vm_hasId
if Session.get(self._vm_id)
self.fromJS Session.get(self._vm_id)
else
Session.setDefault self._vm_id, self._vm_toJS() if not Session.get(self._vm_id)?
_defaultComputation = Tracker.autorun (c) ->
obj = self._vm_toJS()
if not c.firstRun
Session.set self._vm_id, obj