|
1 | | -import { assert } from '@ember/debug'; |
2 | | -import { ENV } from '@ember/-internals/environment'; |
3 | | -import type { ElementDescriptor, ExtendedMethodDecorator } from '@ember/-internals/metal'; |
4 | | -import { |
5 | | - isElementDescriptor, |
6 | | - expandProperties, |
7 | | - setClassicDecorator, |
8 | | -} from '@ember/-internals/metal'; |
9 | | -import { getFactoryFor } from '@ember/-internals/container'; |
10 | | -import { setObservers } from '@ember/-internals/utils'; |
11 | | -import type { AnyFn } from '@ember/-internals/utility-types'; |
12 | | -import CoreObject from '@ember/object/core'; |
13 | | -import Observable from '@ember/object/observable'; |
14 | | - |
15 | | -export { |
16 | | - notifyPropertyChange, |
17 | | - defineProperty, |
18 | | - get, |
19 | | - set, |
20 | | - getProperties, |
21 | | - setProperties, |
22 | | - computed, |
23 | | - trySet, |
24 | | -} from '@ember/-internals/metal'; |
25 | | - |
26 | | -/** |
27 | | -@module @ember/object |
28 | | -*/ |
29 | | - |
30 | | -/** |
31 | | - `EmberObject` is the main base class for all Ember objects. It is a subclass |
32 | | - of `CoreObject` with the `Observable` mixin applied. For details, |
33 | | - see the documentation for each of these. |
34 | | -
|
35 | | - @class EmberObject |
36 | | - @extends CoreObject |
37 | | - @uses Observable |
38 | | - @public |
39 | | -*/ |
40 | | -// eslint-disable-next-line @typescript-eslint/no-empty-object-type |
41 | | -interface EmberObject extends Observable {} |
42 | | -class EmberObject extends CoreObject.extend(Observable) { |
43 | | - get _debugContainerKey() { |
44 | | - let factory = getFactoryFor(this); |
45 | | - return factory !== undefined && factory.fullName; |
46 | | - } |
47 | | -} |
48 | | - |
49 | | -export default EmberObject; |
50 | | - |
51 | | -/** |
52 | | - Decorator that turns the target function into an Action which can be accessed |
53 | | - directly by reference. |
54 | | -
|
55 | | - ```js |
56 | | - import Component from '@ember/component'; |
57 | | - import { tracked } from '@glimmer/tracking'; |
58 | | - import { action } from '@ember/object'; |
59 | | -
|
60 | | - export default class Tooltip extends Component { |
61 | | - @tracked isShowing = false; |
62 | | -
|
63 | | - @action |
64 | | - toggleShowing() { |
65 | | - this.isShowing = !this.isShowing; |
66 | | - } |
67 | | - } |
68 | | - ``` |
69 | | - ```hbs |
70 | | - <!-- template.hbs --> |
71 | | - <button {{on "click" this.toggleShowing}}>Show tooltip</button> |
72 | | -
|
73 | | - {{#if isShowing}} |
74 | | - <div class="tooltip"> |
75 | | - I'm a tooltip! |
76 | | - </div> |
77 | | - {{/if}} |
78 | | - ``` |
79 | | -
|
80 | | - It also binds the function directly to the instance, so it can be used in any |
81 | | - context and will correctly refer to the class it came from: |
82 | | -
|
83 | | - ```js |
84 | | - import Component from '@ember/component'; |
85 | | - import { tracked } from '@glimmer/tracking'; |
86 | | - import { action } from '@ember/object'; |
87 | | -
|
88 | | - export default class Tooltip extends Component { |
89 | | - constructor() { |
90 | | - super(...arguments); |
91 | | -
|
92 | | - // this.toggleShowing is still bound correctly when added to |
93 | | - // the event listener |
94 | | - document.addEventListener('click', this.toggleShowing); |
95 | | - } |
96 | | -
|
97 | | - @tracked isShowing = false; |
98 | | -
|
99 | | - @action |
100 | | - toggleShowing() { |
101 | | - this.isShowing = !this.isShowing; |
102 | | - } |
103 | | - } |
104 | | - ``` |
105 | | -
|
106 | | - @public |
107 | | - @method action |
108 | | - @for @ember/object |
109 | | - @static |
110 | | - @param {Function|undefined} callback The function to turn into an action, |
111 | | - when used in classic classes |
112 | | - @return {PropertyDecorator} property decorator instance |
113 | | -*/ |
114 | | - |
115 | | -const BINDINGS_MAP = new WeakMap(); |
116 | | - |
117 | | -interface HasProto { |
118 | | - constructor: { |
119 | | - proto(): void; |
120 | | - }; |
121 | | -} |
122 | | - |
123 | | -function hasProto(obj: unknown): obj is HasProto { |
124 | | - return ( |
125 | | - obj != null && |
126 | | - (obj as any).constructor !== undefined && |
127 | | - typeof ((obj as any).constructor as any).proto === 'function' |
128 | | - ); |
129 | | -} |
130 | | - |
131 | | -interface HasActions { |
132 | | - actions: Record<string | symbol, unknown>; |
133 | | -} |
134 | | - |
135 | | -function setupAction( |
136 | | - target: Partial<HasActions>, |
137 | | - key: string | symbol, |
138 | | - actionFn: Function |
139 | | -): TypedPropertyDescriptor<unknown> { |
140 | | - if (hasProto(target)) { |
141 | | - target.constructor.proto(); |
142 | | - } |
143 | | - |
144 | | - if (!Object.prototype.hasOwnProperty.call(target, 'actions')) { |
145 | | - let parentActions = target.actions; |
146 | | - // we need to assign because of the way mixins copy actions down when inheriting |
147 | | - target.actions = parentActions ? Object.assign({}, parentActions) : {}; |
148 | | - } |
149 | | - |
150 | | - assert("[BUG] Somehow the target doesn't have actions!", target.actions != null); |
151 | | - |
152 | | - target.actions[key] = actionFn; |
153 | | - |
154 | | - return { |
155 | | - get() { |
156 | | - let bindings = BINDINGS_MAP.get(this); |
157 | | - |
158 | | - if (bindings === undefined) { |
159 | | - bindings = new Map(); |
160 | | - BINDINGS_MAP.set(this, bindings); |
161 | | - } |
162 | | - |
163 | | - let fn = bindings.get(actionFn); |
164 | | - |
165 | | - if (fn === undefined) { |
166 | | - fn = actionFn.bind(this); |
167 | | - bindings.set(actionFn, fn); |
168 | | - } |
169 | | - |
170 | | - return fn; |
171 | | - }, |
172 | | - }; |
173 | | -} |
174 | | - |
175 | | -export function action( |
176 | | - target: ElementDescriptor[0], |
177 | | - key: ElementDescriptor[1], |
178 | | - desc: ElementDescriptor[2] |
179 | | -): PropertyDescriptor; |
180 | | -export function action(desc: PropertyDescriptor): ExtendedMethodDecorator; |
181 | | -export function action( |
182 | | - ...args: ElementDescriptor | [PropertyDescriptor] |
183 | | -): PropertyDescriptor | ExtendedMethodDecorator { |
184 | | - let actionFn: object | Function; |
185 | | - |
186 | | - if (!isElementDescriptor(args)) { |
187 | | - actionFn = args[0]; |
188 | | - |
189 | | - let decorator: ExtendedMethodDecorator = function ( |
190 | | - target, |
191 | | - key, |
192 | | - _desc, |
193 | | - _meta, |
194 | | - isClassicDecorator |
195 | | - ) { |
196 | | - assert( |
197 | | - 'The @action decorator may only be passed a method when used in classic classes. You should decorate methods directly in native classes', |
198 | | - isClassicDecorator |
199 | | - ); |
200 | | - |
201 | | - assert( |
202 | | - 'The action() decorator must be passed a method when used in classic classes', |
203 | | - typeof actionFn === 'function' |
204 | | - ); |
205 | | - |
206 | | - return setupAction(target, key, actionFn); |
207 | | - }; |
208 | | - |
209 | | - setClassicDecorator(decorator); |
210 | | - |
211 | | - return decorator; |
212 | | - } |
213 | | - |
214 | | - let [target, key, desc] = args; |
215 | | - |
216 | | - actionFn = desc?.value; |
217 | | - |
218 | | - assert( |
219 | | - 'The @action decorator must be applied to methods when used in native classes', |
220 | | - typeof actionFn === 'function' |
221 | | - ); |
222 | | - |
223 | | - // SAFETY: TS types are weird with decorators. This should work. |
224 | | - return setupAction(target, key, actionFn); |
225 | | -} |
226 | | - |
227 | | -// SAFETY: TS types are weird with decorators. This should work. |
228 | | -setClassicDecorator(action as ExtendedMethodDecorator); |
229 | | - |
230 | | -// .......................................................... |
231 | | -// OBSERVER HELPER |
232 | | -// |
233 | | - |
234 | | -type ObserverDefinition<T extends AnyFn> = { |
235 | | - dependentKeys: string[]; |
236 | | - fn: T; |
237 | | - sync: boolean; |
238 | | -}; |
239 | | - |
240 | | -/** |
241 | | - Specify a method that observes property changes. |
242 | | -
|
243 | | - ```javascript |
244 | | - import EmberObject from '@ember/object'; |
245 | | - import { observer } from '@ember/object'; |
246 | | -
|
247 | | - export default EmberObject.extend({ |
248 | | - valueObserver: observer('value', function() { |
249 | | - // Executes whenever the "value" property changes |
250 | | - }) |
251 | | - }); |
252 | | - ``` |
253 | | -
|
254 | | - Also available as `Function.prototype.observes` if prototype extensions are |
255 | | - enabled. |
256 | | -
|
257 | | - @method observer |
258 | | - @for @ember/object |
259 | | - @param {String} propertyNames* |
260 | | - @param {Function} func |
261 | | - @return func |
262 | | - @public |
263 | | - @static |
264 | | -*/ |
265 | | -export function observer<T extends AnyFn>( |
266 | | - ...args: |
267 | | - | [propertyName: string, ...additionalPropertyNames: string[], func: T] |
268 | | - | [ObserverDefinition<T>] |
269 | | -): T { |
270 | | - let funcOrDef = args.pop(); |
271 | | - |
272 | | - assert( |
273 | | - 'observer must be provided a function or an observer definition', |
274 | | - typeof funcOrDef === 'function' || (typeof funcOrDef === 'object' && funcOrDef !== null) |
275 | | - ); |
276 | | - |
277 | | - let func: T; |
278 | | - let dependentKeys: string[]; |
279 | | - let sync: boolean; |
280 | | - |
281 | | - if (typeof funcOrDef === 'function') { |
282 | | - func = funcOrDef; |
283 | | - dependentKeys = args as string[]; |
284 | | - sync = !ENV._DEFAULT_ASYNC_OBSERVERS; |
285 | | - } else { |
286 | | - func = funcOrDef.fn; |
287 | | - dependentKeys = funcOrDef.dependentKeys; |
288 | | - sync = funcOrDef.sync; |
289 | | - } |
290 | | - |
291 | | - assert('observer called without a function', typeof func === 'function'); |
292 | | - assert( |
293 | | - 'observer called without valid path', |
294 | | - Array.isArray(dependentKeys) && |
295 | | - dependentKeys.length > 0 && |
296 | | - dependentKeys.every((p) => typeof p === 'string' && Boolean(p.length)) |
297 | | - ); |
298 | | - assert('observer called without sync', typeof sync === 'boolean'); |
299 | | - |
300 | | - let paths: string[] = []; |
301 | | - |
302 | | - for (let dependentKey of dependentKeys) { |
303 | | - expandProperties(dependentKey, (path: string) => paths.push(path)); |
304 | | - } |
305 | | - |
306 | | - setObservers(func as Function, { |
307 | | - paths, |
308 | | - sync, |
309 | | - }); |
310 | | - return func; |
311 | | -} |
0 commit comments