forked from cordjs/core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Model.coffee
231 lines (180 loc) · 6.84 KB
/
Model.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
define [
'cord!Module'
'cord!Collection'
'underscore'
], (Module, Collection, _) ->
class Model extends Module
# list of field names actually loaded and used in this model
# only attributes with this names are treated as model fields
_fieldNames: null
# old values of changed fields (by method set()) until the model is saved
_changed: null
constructor: (attrs) ->
###
attrs could be another model as well, ctor will return a copy of the original model
###
@_fieldNames = []
if (attrs instanceof Model)
attrs = attrs.toJSON()
@_load(attrs) if attrs
@_changed = {}
_load: (attrs) ->
for key, value of attrs
@[key] = @_deepClone value
@_fieldNames.push(key)
_deepClone: (value) ->
if _.isArray(value)
result = []
for val in value
result.push @_deepClone(val)
return result
else if _.isObject(value)
result = {}
for key, val of value
result[key] = @_deepClone(val)
return result
return value
getDefinedFieldNames: ->
###
Returns list of the root names of the fields ever set for the model via constructor or set() method.
'id' special field is not included.
@return Array[String]
###
@_fieldNames
getChangedFields: ->
# todo: why not _.clone(@_changed) ?
result = {}
for key of @_changed
result[key] = @[key]
result
resetChangedFields: ->
@_changed = {}
setCollection: (collection) ->
@collection = collection
set: (key, val) ->
if _.isObject(key)
key = key.toJSON() if key instanceof Model
attrs = key
else
(attrs = {})[key] = val
for key, val of attrs
if not _.isEqual(@[key], val)
@_changed[key] = @[key] if not @_changed[key]?
@[key] = val
@_fieldNames.push(key) if @_fieldNames.indexOf(key) == -1
this
setAloud: (key, val) ->
###
Sets new value and emit change to everyone who subsribed on the Model's 'change' event
This won't change any models in other collections! Use save() or propagateModelChange() for that.
###
@set(key, val)
@collection.emit("model.#{ @id }.change", this)
refreshOnlyContainingCollections: ->
#Make all collections, containing this model refresh
#It's cheaper than Collection::checkNewModel and ModelRepo.suggestNewModelToCollections,
#because mentioned ones make almost all collections refresh
@collection.repo.refreshOnlyContainingCollections @
propagateFieldChange: (fieldName, newValue) ->
@collection.repo.propagateFieldChange @id, fieldName, newValue
propagateModelChange: ->
###
Propagate changed model in all collecion
###
@collection.repo.propagateModelChange @
emitLocalCalcChange: (path, val) ->
###
Triggers correctly formed event about changing of some locally ad-hoc calculated field values of the model.
The main purpose of this method is to propagate locally calculated value of the field to another model instances
if they care about the field. It doesn't change the model in any way. If the model cares about the field, than
the value will be changed by through the change-event listening in the model's collection.
@param String|Object path dot-separated path of the value, or structure with field values
@param Any val the new value for the field (applicable only if the first argument is String)
###
if arguments.length == 1 and _.isObject(path)
changeVal = _.clone(path)
else
# we have path -> value argument format, need to convert it to object-structure
parts = path.split('.')
lastPart = parts.pop()
# special value that'll contain only structure with the changing value without any existing siblings
changeVal = {}
changePointer = changeVal
# building structure based on dot-separated path
for part in parts
changePointer[part] = {}
changePointer = changePointer[part]
changePointer[lastPart] = val
changeVal.id = @id
@collection.repo.emit 'change', changeVal
save: (notRefreshCollections = false) ->
###
Save model via collection repo
@param notRefreshCollections - if true caller must take care of collections refreshing
###
if @collection?.repo?
@collection.repo.save(this, notRefreshCollections)
else
throw new Error('Can not save model without collection')
delete: ->
###
Delete model via collection repo
###
if @collection?.repo?
@collection.repo.delete(this)
else
throw new Error('Can not delete model without collection')
on: (topic, callback) ->
###
Subscribe for this model instance related event
@param String topic event topic (name)
@param Function(data) callback callback function
@return MonologueSubscription
###
if @collection?
if topic == 'change'
# 'change'-event is conveniently proxy-triggered by the collection @see Collection::_handleModelChange
@collection.on "model.#{ @id }.#{ topic }", callback
else
@collection.repo.on topic, (changed) =>
callback(changed) if changed.id == @id
# serialization related
toJSON: ->
result = {}
for key in @_fieldNames
value = @[key]
result[key] = value
if value instanceof Collection
result[key] = value.serializeLink()
else if value instanceof Model
result[key] = value.serializeLink()
else if _.isArray(value) and value[0] instanceof Model
result[key] = (m.serializeLink() for m in value)
result.id = @id
result
serializeLink: ->
###
Returns serialized link (address) of this model (including collection)
@return String
###
":model:#{ @id }/#{ @collection.serializeLink() }"
@isSerializedLink: (serialized) ->
###
Detects if the given value is a serialized link to model
@param Any serialized
@return Boolean
###
_.isString(serialized) and serialized.substr(0, 7) == ':model:'
@unserializeLink: (serialized, ioc, callback) ->
###
Converts serialized link to model to link of the model instance in it's collection
@param String serialized
@param Box ioc service container needed to get model repository service by name
@param Function(Model) callback "returning" callback
###
if serialized instanceof Model
callback(serialized)
else
[modelId, serializedCollection] = serialized.substr(7).split('/')
Collection.unserializeLink serializedCollection, ioc, (collection) ->
callback(collection.get(modelId))