-
Notifications
You must be signed in to change notification settings - Fork 477
/
BaseClass.coffee
218 lines (158 loc) · 6.08 KB
/
BaseClass.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
{_} = require "./Underscore"
Utils = require "./Utils"
{EventEmitter} = require "./EventEmitter"
CounterKey = "_ObjectCounter"
DefinedPropertiesValuesKey = "_DefinedPropertiesValuesKey"
ObjectDescriptors = []
ObjectDescriptorsChanged = true
# Theoretically this should be an array per class, but as long as we don't do weird stuff
# like depending properties of subclasses in a different order then superclasses this will work
DefaultPropertyOrder = []
class exports.BaseClass extends EventEmitter
#################################################################
# Framer object properties
@define = (propertyName, descriptor) ->
# See if we need to add this property to the internal properties class
if @ isnt BaseClass
@_addDescriptor(propertyName, descriptor)
if descriptor.readonly
descriptor.set = (value) ->
throw Error("#{@constructor.name}.#{propertyName} is readonly")
# Define the property on the prototype
Object.defineProperty(@prototype, propertyName, descriptor)
@undefine = (propertyName) ->
if _.isArray propertyName
propertyName.map((prop) => @undefine(prop))
else
@define propertyName, @simpleProperty propertyName, undefined,
importable: false
exportable: false
enumerable: false
@_addDescriptor: (propertyName, descriptor) ->
# for key in ["enumerable", "exportable", "importable"]
# if descriptor.hasOwnProperty(key)
# throw Error("woops #{propertyName} #{descriptor[key]}") if not _.isBoolean(descriptor[key])
descriptor.propertyName = propertyName
# Have the following flags set to true when undefined:
descriptor.enumerable ?= true
descriptor.exportable ?= true
descriptor.importable ?= true
descriptor.readonly ?= not descriptor.set?
# We assume we don't import if there is no setter, because we can't
descriptor.importable = descriptor.importable and not descriptor.readonly
# We also assume we don't export if there is no setter, because
# it is likely a calculated property, and we can't set it.
descriptor.exportable = descriptor.exportable and not descriptor.readonly
# We assume that every property with an underscore is private
return if _.startsWith(propertyName, "_")
ObjectDescriptors.push([@, propertyName, descriptor])
ObjectDescriptorsChanged = true
# Only retain options that are importable, exportable or both:
if descriptor.exportable or descriptor.importable
if descriptor.depends
for depend in descriptor.depends
if depend not in DefaultPropertyOrder
DefaultPropertyOrder.push(depend)
index = DefaultPropertyOrder.indexOf(propertyName)
if index isnt -1
DefaultPropertyOrder.splice(index, 1)
DefaultPropertyOrder.push(propertyName)
if propertyName not in DefaultPropertyOrder
DefaultPropertyOrder.push(propertyName)
@simpleProperty = (name, fallback, options={}) ->
return _.extend options,
default: fallback
get: -> @_getPropertyValue(name)
set: (value) ->
@_setPropertyValue(name, value)
options?.didSet?(@, value)
@proxyProperty = (keyPath, options={}) ->
# Allows to easily proxy properties from an instance object
# Object property is in the form of "object.property"
objectKey = keyPath.split(".")[0]
descriptor = _.extend options,
get: ->
return unless _.isObject(@[objectKey])
Utils.getValueForKeyPath(@, keyPath)
set: (value) ->
return unless _.isObject(@[objectKey])
Utils.setValueForKeyPath(@, keyPath, value)
options?.didSet?(@, value)
proxy: true
_setPropertyValue: (k, v) =>
@[DefinedPropertiesValuesKey][k] = v
_getPropertyValue: (k) =>
Utils.valueOrDefault @[DefinedPropertiesValuesKey][k],
@_getPropertyDefaultValue k
_getPropertyDefaultValue: (k) ->
@_propertyList()[k]?["default"]
_propertyList: ->
if not @_propertyListCache or ObjectDescriptorsChanged
@_propertyListCache = @__propertyList()
ObjectDescriptorsChanged = false
return @_propertyListCache
__propertyList: ->
result = {}
for k in ObjectDescriptors
[Class, name, descriptor] = k
if @ instanceof Class
if descriptor.exportable or descriptor.importable
result[name] = descriptor
else
delete result[name]
return result
keys: -> _.keys(@props)
@define "props",
importable: false
exportable: false
get: ->
keys = []
propertyList = @_propertyList()
for key, descriptor of propertyList
if descriptor.exportable
keys.push key
_.pick(@, keys)
set: (value) ->
propertyList = @_propertyList()
for k, v of value
# We only apply properties that we know and are marked to be
# importable.
@[k] = v if propertyList[k]?.importable
@define "id",
get: -> @_id
toInspect: =>
"<#{@constructor.name} id:#{@id or null}>"
onChange: (name, cb) -> @on("change:#{name}", cb)
#################################################################
# Base constructor method
constructor: (options) ->
super
@_context = Framer?.CurrentContext
# Create a holder for the property values
@[DefinedPropertiesValuesKey] = {}
@_applyDefaults(options)
# Count the creation for these objects and set the id
@constructor[CounterKey] ?= 0
@constructor[CounterKey] += 1
# We set this last so if we print a layer during construction
# we don't get confused because the id changes from global to context
@_id = @constructor[CounterKey]
_applyDefaults: (options, proxy = false) ->
return unless options
propertyList = @_propertyList()
for k in DefaultPropertyOrder
descriptor = propertyList[k]
if descriptor?
continue if proxy and not (descriptor.proxy is true)
@_applyDefault(descriptor, k, options[k])
_applyProxyDefaults: (options) ->
@_applyDefaults(options, true)
_applyDefault: (descriptor, key, optionValue) ->
# For each known property (registered with @define) that has a setter, fetch
# the value from the options object, unless the prop is not importable.
# When there's no user value, apply the default value:
return if descriptor.readonly
value = optionValue if descriptor.importable
value = Utils.valueOrDefault(optionValue, @_getPropertyDefaultValue(key))
return if value in [null, undefined]
@[key] = value