1 | // Generated by CoffeeScript 1.12.7
|
2 | (function() {
|
3 | var cloneStructure, concatInto, defineModule, each, formattedInspect, isBoolean, isFunction, isNumber, isPlainArray, isPlainObject, isString, log, lowerCamelCase, merge, mergeInto, object, ref, upperCamelCase,
|
4 | extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
5 | hasProp = {}.hasOwnProperty,
|
6 | slice = [].slice;
|
7 |
|
8 | ref = require('art-standard-lib'), defineModule = ref.defineModule, log = ref.log, object = ref.object, upperCamelCase = ref.upperCamelCase, lowerCamelCase = ref.lowerCamelCase, each = ref.each, isPlainObject = ref.isPlainObject, isPlainArray = ref.isPlainArray, isFunction = ref.isFunction, isNumber = ref.isNumber, isBoolean = ref.isBoolean, cloneStructure = ref.cloneStructure, isString = ref.isString, mergeInto = ref.mergeInto, concatInto = ref.concatInto, formattedInspect = ref.formattedInspect, merge = ref.merge;
|
9 |
|
10 |
|
11 | /*
|
12 | Todo:
|
13 | validatedDeclarable / validatedExtendableProperty
|
14 | Which use Art.Validation
|
15 |
|
16 | TODO:
|
17 | When we switch to ES6, we should make the
|
18 | class API look identical to the current instance API.
|
19 |
|
20 | That means declarable API looks like this:
|
21 | @extendableProperty foo: {}
|
22 |
|
23 | * extend:
|
24 | @foo: hi: 123
|
25 |
|
26 | The differnce is we add a ":".
|
27 |
|
28 | The benefit is it's a normal getter/setter pair:
|
29 |
|
30 | @foo = hi: 123
|
31 |
|
32 | log @foo
|
33 |
|
34 | The one diference is the "setter" is really an
|
35 | "extender"
|
36 | */
|
37 |
|
38 | defineModule(module, function() {
|
39 | return function(superClass) {
|
40 | var ExtendablePropertyMixin;
|
41 | return ExtendablePropertyMixin = (function(superClass1) {
|
42 | var arrayPropertyExtender, defaultExtender, getOwnProperty, noOptions, objectPropertyExtender, optimizedInitFunction;
|
43 |
|
44 | extend1(ExtendablePropertyMixin, superClass1);
|
45 |
|
46 | function ExtendablePropertyMixin() {
|
47 | return ExtendablePropertyMixin.__super__.constructor.apply(this, arguments);
|
48 | }
|
49 |
|
50 |
|
51 | /*
|
52 | IN
|
53 | object: any object
|
54 | property: string, property name
|
55 | init:
|
56 | (object) -> returning initial value for object
|
57 | OR
|
58 | initial value is computed by:
|
59 | cloneStructure object[property] || init
|
60 |
|
61 | EFFECT:
|
62 | if object.hasOwnProperty property, return its current value
|
63 | otherwise, initialize and return it with init()
|
64 | */
|
65 |
|
66 | ExtendablePropertyMixin.getOwnProperty = getOwnProperty = function(object, internalName, init) {
|
67 | if (object.hasOwnProperty(internalName)) {
|
68 | return object[internalName];
|
69 | } else {
|
70 | return object[internalName] = init(object, internalName);
|
71 | }
|
72 | };
|
73 |
|
74 | optimizedInitFunction = function(internalName, init) {
|
75 | switch (false) {
|
76 | case !isFunction(init):
|
77 | return init;
|
78 | case !(isString(init) || isNumber(init) || isBoolean(init)):
|
79 | return function(object) {
|
80 | var ref1;
|
81 | return (ref1 = object[internalName]) != null ? ref1 : init;
|
82 | };
|
83 | default:
|
84 | return function(object) {
|
85 | var ref1;
|
86 | return cloneStructure((ref1 = object[internalName]) != null ? ref1 : init);
|
87 | };
|
88 | }
|
89 | };
|
90 |
|
91 |
|
92 | /*
|
93 | objectPropertyExtender
|
94 |
|
95 | IN: @ is set to the property-value to extend
|
96 |
|
97 | API 1:
|
98 | IN: map
|
99 | EFFECT: mergeInto propValue, map
|
100 |
|
101 | API 2:
|
102 | IN: key, value
|
103 | EFFECT: propValue[key] = valuee
|
104 |
|
105 | OUT: ignore
|
106 | */
|
107 |
|
108 | ExtendablePropertyMixin.objectPropertyExtender = objectPropertyExtender = function(toExtend, mapOrKey, value) {
|
109 | if (mapOrKey === void 0 || mapOrKey === null) {
|
110 | return toExtend;
|
111 | }
|
112 | if (isString(mapOrKey)) {
|
113 | toExtend[mapOrKey] = value;
|
114 | } else if (isPlainObject(mapOrKey)) {
|
115 | mergeInto(toExtend, mapOrKey);
|
116 | } else {
|
117 | log({
|
118 | mapOrKey: mapOrKey,
|
119 | value: value,
|
120 | type: mapOrKey != null ? mapOrKey.constructor : void 0
|
121 | });
|
122 | throw new Error("first value argument must be a plain object or string: " + (formattedInspect({
|
123 | key: mapOrKey,
|
124 | value: value
|
125 | })));
|
126 | }
|
127 | return toExtend;
|
128 | };
|
129 |
|
130 |
|
131 | /*
|
132 | arrayPropertyExtender
|
133 |
|
134 | IN: valueToExtend, value
|
135 | value:
|
136 | array: concatInto propValue, array
|
137 | non-array: propValue.push value
|
138 |
|
139 | NOTE: if you want to concat an array-as-a-value to the end of propValue, do this:
|
140 | arrayPropertyExtender.call propValue, [arrayAsValue]
|
141 |
|
142 | OUT: ignore
|
143 | */
|
144 |
|
145 | ExtendablePropertyMixin.arrayPropertyExtender = arrayPropertyExtender = function(toExtend, arrayOrValue) {
|
146 | if (isPlainArray(arrayOrValue)) {
|
147 | concatInto(toExtend, arrayOrValue);
|
148 | } else {
|
149 | toExtend.push(arrayOrValue);
|
150 | }
|
151 | return toExtend;
|
152 | };
|
153 |
|
154 |
|
155 | /*
|
156 | Extendable Properties
|
157 |
|
158 | EXAMPLE:
|
159 | class Foo extends BaseClass
|
160 | @extendableProperty foo: {}
|
161 |
|
162 | Extendable properties work like inheritance:
|
163 |
|
164 | When any subclass or instance extends an extendable property, they
|
165 | inherit a cloneStructure of the property from up the inheritance tree, and then
|
166 | add their own extensions without effecting the parent copy.
|
167 |
|
168 | With Object property types, this can just be a parallel prototype chain.
|
169 | (It isn't currently: if you modify a parent after extending it to a child,
|
170 | the child won't get updates.)
|
171 |
|
172 | BUT, you can also have array or other types of extend-properties, which
|
173 | JavaScript doesn't have any built-in mechanisms for inheriting.
|
174 |
|
175 | BASIC API:
|
176 | @extendableProperty: (map, options) -> ...
|
177 |
|
178 | IN:
|
179 | map: name: defaultValue
|
180 | options:
|
181 | declarable: true/false
|
182 | if true, slightly alters the created functions:
|
183 | for: @extendableProperty foo: ...
|
184 | generates:
|
185 | @foo
|
186 |
|
187 | extend:
|
188 | DEFAULTS:
|
189 | switch defaultValue
|
190 | when is Object then objectPropertyExtender
|
191 | when is Array then arrayPropetyExtender
|
192 | else defaultExtender
|
193 |
|
194 | (extendable, extendWithValues...) -> newExtendedOwnPropertyValue
|
195 | IN:
|
196 | extendable: the current, extended value, already cloned, so direct mutation is OK
|
197 | extendWithValues: 1 or more values passed into the extend funtion by the client.
|
198 | Ex: for an array, this is either a single value or an array
|
199 | Ex: for an object, this is either a single object or two args: key, value
|
200 | OUT: new property value to set own-property to
|
201 | EFFECT:
|
202 | Can be pure functional and just return the new, extended data.
|
203 | OR
|
204 | Can modify extendable directly, since it is an object/array/atomic value unique to the current class/instance.
|
205 | If modifying extendable directly, be sure to return extendable.
|
206 | Regardless, the returned value becomes the new extendable prop's value.
|
207 |
|
208 |
|
209 |
|
210 | EFFECT: for each {foo: defaultValue} in map, extendableProperty:
|
211 | WARNING:
|
212 | !!! Don't modify the object returned by a getter !!!
|
213 |
|
214 | Getters only return the current, most-extended property value. It may not be extended to the
|
215 | current subclass or instance! Instead, call @extendFoo() if you wish to manually modify
|
216 | the extended property.
|
217 |
|
218 | declarable:
|
219 | getters:
|
220 | @getFoo:
|
221 | getFoo:
|
222 |
|
223 | extenders:
|
224 | @foo:
|
225 | foo:
|
226 |
|
227 | non-declarable:
|
228 |
|
229 | getters:
|
230 | @getFoo:
|
231 | @getter foo:
|
232 |
|
233 | extenders:
|
234 | @foo:
|
235 | @extendFoo:
|
236 | extendFoo:
|
237 |
|
238 | IN:
|
239 | 0-args: nothing happens beyond the standard EFFECT
|
240 | 1+args: passed to the "extend" function
|
241 |
|
242 | EFFECT: creates a extension (cloneStructure) of the property for the currnet class, subclass or instance
|
243 | OUT: the current, extendedPropValue
|
244 |
|
245 | API 1: IN: 0 args
|
246 | NO ADDITIONAL EFFECT - just returns the extended property
|
247 | API 2: IN: 1 or more args
|
248 | In addition to extending and returning the extended property:
|
249 | calls: propExtender extendedPropValue, args...
|
250 |
|
251 | NOTE: gthe prototype getters call the class getter for extension purposes.
|
252 | The result is each instance won't get its own version of the property.
|
253 | E.G. Interitance is done at the Class level, not the Instance level.
|
254 | */
|
255 |
|
256 | defaultExtender = function(toExtend, v) {
|
257 | if (v === void 0) {
|
258 | throw new Error("not expecting undefined");
|
259 | }
|
260 | return v;
|
261 | };
|
262 |
|
263 | noOptions = {};
|
264 |
|
265 | ExtendablePropertyMixin.extendableProperty = function(map, options) {
|
266 | var declarable, extend, noSetter, oldExtender;
|
267 | if (options == null) {
|
268 | options = noOptions;
|
269 | }
|
270 | if (isFunction(oldExtender = options)) {
|
271 | log.error("DEPRICATED customPropertyExtender not supported, use extend: option ");
|
272 | options = {
|
273 | extend: function() {
|
274 | var args, extendable;
|
275 | extendable = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
276 | return oldExtender.apply(extendable, args);
|
277 | }
|
278 | };
|
279 | }
|
280 | extend = options.extend, declarable = options.declarable, noSetter = options.noSetter;
|
281 | return each(map, (function(_this) {
|
282 | return function(defaultValue, name) {
|
283 | var extenderName, getterName, instanceExtender, instanceGetter, internalName, propertyExtender, ucProp;
|
284 | name = lowerCamelCase(name);
|
285 | ucProp = upperCamelCase(name);
|
286 | internalName = _this.propInternalName(name);
|
287 | getterName = "get" + ucProp;
|
288 | extenderName = "extend" + ucProp;
|
289 | propertyExtender = (function() {
|
290 | if (extend != null) {
|
291 | return extend;
|
292 | } else if (isPlainObject(defaultValue)) {
|
293 | return objectPropertyExtender;
|
294 | } else if (isPlainArray(defaultValue)) {
|
295 | return arrayPropertyExtender;
|
296 | } else {
|
297 | if (defaultValue === void 0) {
|
298 | throw new Error("defaultValue must not be undefined");
|
299 | }
|
300 | return defaultExtender;
|
301 | }
|
302 | })();
|
303 | _this[getterName] = function() {
|
304 | var ref1;
|
305 | return (ref1 = this.prototype[internalName]) != null ? ref1 : defaultValue;
|
306 | };
|
307 | _this[name] = _this[extenderName] = function(value) {
|
308 | var extendablePropValue;
|
309 | extendablePropValue = getOwnProperty(this.prototype, internalName, optimizedInitFunction(internalName, defaultValue));
|
310 | if (arguments.length > 0 && value !== void 0) {
|
311 | this.prototype[internalName] = propertyExtender.apply(null, [extendablePropValue].concat(slice.call(arguments)));
|
312 | }
|
313 | return extendablePropValue;
|
314 | };
|
315 | instanceGetter = function() {
|
316 | var ref1;
|
317 | return (ref1 = this[internalName]) != null ? ref1 : defaultValue;
|
318 | };
|
319 | instanceExtender = _this.prototype[extenderName] = function(value) {
|
320 | var extendablePropValue;
|
321 | extendablePropValue = getOwnProperty(this, internalName, optimizedInitFunction(internalName, defaultValue));
|
322 | if (arguments.length > 0 && value !== void 0) {
|
323 | this[internalName] = propertyExtender.apply(null, [extendablePropValue].concat(slice.call(arguments)));
|
324 | }
|
325 | return extendablePropValue;
|
326 | };
|
327 | if (declarable) {
|
328 | _this.prototype[getterName] = instanceGetter;
|
329 | return _this.prototype[name] = instanceExtender;
|
330 | } else {
|
331 | if (!noSetter) {
|
332 | _this.addSetter(name, instanceExtender);
|
333 | }
|
334 | return _this.addGetter(name, instanceGetter);
|
335 | }
|
336 | };
|
337 | })(this));
|
338 | };
|
339 |
|
340 | ExtendablePropertyMixin.declarable = function(map, options) {
|
341 | return this.extendableProperty(map, merge(options, {
|
342 | declarable: true
|
343 | }));
|
344 | };
|
345 |
|
346 | return ExtendablePropertyMixin;
|
347 |
|
348 | })(superClass);
|
349 | };
|
350 | });
|
351 |
|
352 | }).call(this);
|
353 |
|
354 | //# sourceMappingURL=ExtendablePropertyMixin.js.map
|