UNPKG

31.5 kBTypeScriptView Raw
1declare module '@ember/application' {
2 /**
3 @module @ember/application
4 */
5 import { setOwner as actualSetOwner } from '@ember/owner';
6 import { _loaded, onLoad, runLoadHooks } from '@ember/application/lib/lazy_load';
7 import { RSVP } from '@ember/-internals/runtime';
8 import { EventDispatcher } from '@ember/-internals/views';
9 import Router from '@ember/routing/router';
10 import ApplicationInstance from '@ember/application/instance';
11 import Engine from '@ember/engine';
12 import type { BootOptions } from '@ember/engine/instance';
13 import type { Container, Registry } from '@ember/-internals/container';
14 import type { EngineInstanceOptions } from '@ember/engine/instance';
15 import type { SimpleDocument, SimpleElement } from '@simple-dom/interface';
16 /**
17 * @deprecated Use `import { getOwner } from '@ember/owner';` instead.
18 */
19 export const getOwner: (object: object) => import('@ember/owner').default | undefined;
20 /**
21 * @deprecated Use `import { setOwner } from '@ember/owner';` instead.
22 */
23 export const setOwner: typeof actualSetOwner;
24 /**
25 An instance of `Application` is the starting point for every Ember
26 application. It instantiates, initializes and coordinates the
27 objects that make up your app.
28
29 Each Ember app has one and only one `Application` object. Although
30 Ember CLI creates this object implicitly, the `Application` class
31 is defined in the `app/app.js`. You can define a `ready` method on the
32 `Application` class, which will be run by Ember when the application is
33 initialized.
34
35 ```app/app.js
36 export default class App extends Application {
37 ready() {
38 // your code here
39 }
40 }
41 ```
42
43 Because `Application` ultimately inherits from `Ember.Namespace`, any classes
44 you create will have useful string representations when calling `toString()`.
45 See the `Ember.Namespace` documentation for more information.
46
47 While you can think of your `Application` as a container that holds the
48 other classes in your application, there are several other responsibilities
49 going on under-the-hood that you may want to understand. It is also important
50 to understand that an `Application` is different from an `ApplicationInstance`.
51 Refer to the Guides to understand the difference between these.
52
53 ### Event Delegation
54
55 Ember uses a technique called _event delegation_. This allows the framework
56 to set up a global, shared event listener instead of requiring each view to
57 do it manually. For example, instead of each view registering its own
58 `mousedown` listener on its associated element, Ember sets up a `mousedown`
59 listener on the `body`.
60
61 If a `mousedown` event occurs, Ember will look at the target of the event and
62 start walking up the DOM node tree, finding corresponding views and invoking
63 their `mouseDown` method as it goes.
64
65 `Application` has a number of default events that it listens for, as
66 well as a mapping from lowercase events to camel-cased view method names. For
67 example, the `keypress` event causes the `keyPress` method on the view to be
68 called, the `dblclick` event causes `doubleClick` to be called, and so on.
69
70 If there is a bubbling browser event that Ember does not listen for by
71 default, you can specify custom events and their corresponding view method
72 names by setting the application's `customEvents` property:
73
74 ```app/app.js
75 import Application from '@ember/application';
76
77 export default class App extends Application {
78 customEvents = {
79 // add support for the paste event
80 paste: 'paste'
81 }
82 }
83 ```
84
85 To prevent Ember from setting up a listener for a default event,
86 specify the event name with a `null` value in the `customEvents`
87 property:
88
89 ```app/app.js
90 import Application from '@ember/application';
91
92 export default class App extends Application {
93 customEvents = {
94 // prevent listeners for mouseenter/mouseleave events
95 mouseenter: null,
96 mouseleave: null
97 }
98 }
99 ```
100
101 By default, the application sets up these event listeners on the document
102 body. However, in cases where you are embedding an Ember application inside
103 an existing page, you may want it to set up the listeners on an element
104 inside the body.
105
106 For example, if only events inside a DOM element with the ID of `ember-app`
107 should be delegated, set your application's `rootElement` property:
108
109 ```app/app.js
110 import Application from '@ember/application';
111
112 export default class App extends Application {
113 rootElement = '#ember-app'
114 }
115 ```
116
117 The `rootElement` can be either a DOM element or a CSS selector
118 string. Note that *views appended to the DOM outside the root element will
119 not receive events.* If you specify a custom root element, make sure you only
120 append views inside it!
121
122 To learn more about the events Ember components use, see
123
124 [components/handling-events](https://guides.emberjs.com/release/components/handling-events/#toc_event-names).
125
126 ### Initializers
127
128 To add behavior to the Application's boot process, you can define initializers in
129 the `app/initializers` directory, or with `ember generate initializer` using Ember CLI.
130 These files should export a named `initialize` function which will receive the created `application`
131 object as its first argument.
132
133 ```javascript
134 export function initialize(application) {
135 // application.inject('route', 'foo', 'service:foo');
136 }
137 ```
138
139 Application initializers can be used for a variety of reasons including:
140
141 - setting up external libraries
142 - injecting dependencies
143 - setting up event listeners in embedded apps
144 - deferring the boot process using the `deferReadiness` and `advanceReadiness` APIs.
145
146 ### Routing
147
148 In addition to creating your application's router, `Application` is
149 also responsible for telling the router when to start routing. Transitions
150 between routes can be logged with the `LOG_TRANSITIONS` flag, and more
151 detailed intra-transition logging can be logged with
152 the `LOG_TRANSITIONS_INTERNAL` flag:
153
154 ```javascript
155 import Application from '@ember/application';
156
157 let App = Application.create({
158 LOG_TRANSITIONS: true, // basic logging of successful transitions
159 LOG_TRANSITIONS_INTERNAL: true // detailed logging of all routing steps
160 });
161 ```
162
163 By default, the router will begin trying to translate the current URL into
164 application state once the browser emits the `DOMContentReady` event. If you
165 need to defer routing, you can call the application's `deferReadiness()`
166 method. Once routing can begin, call the `advanceReadiness()` method.
167
168 If there is any setup required before routing begins, you can implement a
169 `ready()` method on your app that will be invoked immediately before routing
170 begins.
171
172 @class Application
173 @extends Engine
174 @public
175 */
176 class Application extends Engine {
177 /**
178 This creates a registry with the default Ember naming conventions.
179
180 It also configures the registry:
181
182 * registered views are created every time they are looked up (they are
183 not singletons)
184 * registered templates are not factories; the registered value is
185 returned directly.
186 * the router receives the application as its `namespace` property
187 * all controllers receive the router as their `target` and `controllers`
188 properties
189 * all controllers receive the application as their `namespace` property
190 * the application view receives the application controller as its
191 `controller` property
192 * the application view receives the application template as its
193 `defaultTemplate` property
194
195 @method buildRegistry
196 @static
197 @param {Application} namespace the application for which to
198 build the registry
199 @return {Ember.Registry} the built registry
200 @private
201 */
202 static buildRegistry(namespace: Application): Registry;
203 static initializer: (
204 this: typeof Engine,
205 initializer: import('@ember/engine').Initializer<Application>
206 ) => void;
207 static instanceInitializer: (
208 this: typeof Engine,
209 initializer: import('@ember/engine').Initializer<ApplicationInstance>
210 ) => void;
211 /**
212 The root DOM element of the Application. This can be specified as an
213 element or a [selector string](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors#reference_table_of_selectors).
214
215 This is the element that will be passed to the Application's,
216 `eventDispatcher`, which sets up the listeners for event delegation. Every
217 view in your application should be a child of the element you specify here.
218
219 @property rootElement
220 @type DOMElement
221 @default 'body'
222 @public
223 */
224 rootElement: SimpleElement | Element | string;
225 /**
226
227 @property _document
228 @type Document | null
229 @default 'window.document'
230 @private
231 */
232 _document: SimpleDocument | Document | null;
233 /**
234 The `Ember.EventDispatcher` responsible for delegating events to this
235 application's views.
236
237 The event dispatcher is created by the application at initialization time
238 and sets up event listeners on the DOM element described by the
239 application's `rootElement` property.
240
241 See the documentation for `Ember.EventDispatcher` for more information.
242
243 @property eventDispatcher
244 @type Ember.EventDispatcher
245 @default null
246 @public
247 */
248 eventDispatcher: EventDispatcher | null;
249 /**
250 The DOM events for which the event dispatcher should listen.
251
252 By default, the application's `Ember.EventDispatcher` listens
253 for a set of standard DOM events, such as `mousedown` and
254 `keyup`, and delegates them to your application's `Ember.View`
255 instances.
256
257 If you would like additional bubbling events to be delegated to your
258 views, set your `Application`'s `customEvents` property
259 to a hash containing the DOM event name as the key and the
260 corresponding view method name as the value. Setting an event to
261 a value of `null` will prevent a default event listener from being
262 added for that event.
263
264 To add new events to be listened to:
265
266 ```app/app.js
267 import Application from '@ember/application';
268
269 let App = Application.extend({
270 customEvents: {
271 // add support for the paste event
272 paste: 'paste'
273 }
274 });
275 ```
276
277 To prevent default events from being listened to:
278
279 ```app/app.js
280 import Application from '@ember/application';
281
282 let App = Application.extend({
283 customEvents: {
284 // remove support for mouseenter / mouseleave events
285 mouseenter: null,
286 mouseleave: null
287 }
288 });
289 ```
290 @property customEvents
291 @type Object
292 @default null
293 @public
294 */
295 customEvents: Record<string, string | null> | null;
296 /**
297 Whether the application should automatically start routing and render
298 templates to the `rootElement` on DOM ready. While default by true,
299 other environments such as FastBoot or a testing harness can set this
300 property to `false` and control the precise timing and behavior of the boot
301 process.
302
303 @property autoboot
304 @type Boolean
305 @default true
306 @private
307 */
308 autoboot: boolean;
309 /**
310 Whether the application should be configured for the legacy "globals mode".
311 Under this mode, the Application object serves as a global namespace for all
312 classes.
313
314 ```javascript
315 import Application from '@ember/application';
316 import Component from '@ember/component';
317
318 let App = Application.create({
319 ...
320 });
321
322 App.Router.reopen({
323 location: 'none'
324 });
325
326 App.Router.map({
327 ...
328 });
329
330 App.MyComponent = Component.extend({
331 ...
332 });
333 ```
334
335 This flag also exposes other internal APIs that assumes the existence of
336 a special "default instance", like `App.__container__.lookup(...)`.
337
338 This option is currently not configurable, its value is derived from
339 the `autoboot` flag – disabling `autoboot` also implies opting-out of
340 globals mode support, although they are ultimately orthogonal concerns.
341
342 Some of the global modes features are already deprecated in 1.x. The
343 existence of this flag is to untangle the globals mode code paths from
344 the autoboot code paths, so that these legacy features can be reviewed
345 for deprecation/removal separately.
346
347 Forcing the (autoboot=true, _globalsMode=false) here and running the tests
348 would reveal all the places where we are still relying on these legacy
349 behavior internally (mostly just tests).
350
351 @property _globalsMode
352 @type Boolean
353 @default true
354 @private
355 */
356 _globalsMode: boolean;
357 /**
358 An array of application instances created by `buildInstance()`. Used
359 internally to ensure that all instances get destroyed.
360
361 @property _applicationInstances
362 @type Array
363 @private
364 */
365 _applicationInstances: Set<ApplicationInstance>;
366 _readinessDeferrals: number;
367 _booted: boolean;
368 init(properties: object | undefined): void;
369 /**
370 Create an ApplicationInstance for this application.
371
372 @public
373 @method buildInstance
374 @return {ApplicationInstance} the application instance
375 */
376 buildInstance(options?: EngineInstanceOptions): ApplicationInstance;
377 /**
378 Start tracking an ApplicationInstance for this application.
379 Used when the ApplicationInstance is created.
380
381 @private
382 @method _watchInstance
383 */
384 _watchInstance(instance: ApplicationInstance): void;
385 /**
386 Stop tracking an ApplicationInstance for this application.
387 Used when the ApplicationInstance is about to be destroyed.
388
389 @private
390 @method _unwatchInstance
391 */
392 _unwatchInstance(instance: ApplicationInstance): boolean;
393 Router?: typeof Router;
394 /**
395 Enable the legacy globals mode by allowing this application to act
396 as a global namespace. See the docs on the `_globalsMode` property
397 for details.
398
399 Most of these features are already deprecated in 1.x, so we can
400 stop using them internally and try to remove them.
401
402 @private
403 @method _prepareForGlobalsMode
404 */
405 _prepareForGlobalsMode(): void;
406 __deprecatedInstance__?: ApplicationInstance;
407 __container__?: Container;
408 _buildDeprecatedInstance(): void;
409 /**
410 Automatically kick-off the boot process for the application once the
411 DOM has become ready.
412
413 The initialization itself is scheduled on the actions queue which
414 ensures that code-loading finishes before booting.
415
416 If you are asynchronously loading code, you should call `deferReadiness()`
417 to defer booting, and then call `advanceReadiness()` once all of your code
418 has finished loading.
419
420 @private
421 @method waitForDOMReady
422 */
423 waitForDOMReady(): void;
424 /**
425 This is the autoboot flow:
426
427 1. Boot the app by calling `this.boot()`
428 2. Create an instance (or use the `__deprecatedInstance__` in globals mode)
429 3. Boot the instance by calling `instance.boot()`
430 4. Invoke the `App.ready()` callback
431 5. Kick-off routing on the instance
432
433 Ideally, this is all we would need to do:
434
435 ```javascript
436 _autoBoot() {
437 this.boot().then(() => {
438 let instance = (this._globalsMode) ? this.__deprecatedInstance__ : this.buildInstance();
439 return instance.boot();
440 }).then((instance) => {
441 App.ready();
442 instance.startRouting();
443 });
444 }
445 ```
446
447 Unfortunately, we cannot actually write this because we need to participate
448 in the "synchronous" boot process. While the code above would work fine on
449 the initial boot (i.e. DOM ready), when `App.reset()` is called, we need to
450 boot a new instance synchronously (see the documentation on `_bootSync()`
451 for details).
452
453 Because of this restriction, the actual logic of this method is located
454 inside `didBecomeReady()`.
455
456 @private
457 @method domReady
458 */
459 domReady(): void;
460 /**
461 Use this to defer readiness until some condition is true.
462
463 Example:
464
465 ```javascript
466 import Application from '@ember/application';
467
468 let App = Application.create();
469
470 App.deferReadiness();
471
472 fetch('/auth-token')
473 .then(response => response.json())
474 .then(data => {
475 App.token = data.token;
476 App.advanceReadiness();
477 });
478 ```
479
480 This allows you to perform asynchronous setup logic and defer
481 booting your application until the setup has finished.
482
483 However, if the setup requires a loading UI, it might be better
484 to use the router for this purpose.
485
486 @method deferReadiness
487 @public
488 */
489 deferReadiness(): void;
490 /**
491 Call `advanceReadiness` after any asynchronous setup logic has completed.
492 Each call to `deferReadiness` must be matched by a call to `advanceReadiness`
493 or the application will never become ready and routing will not begin.
494
495 @method advanceReadiness
496 @see {Application#deferReadiness}
497 @public
498 */
499 advanceReadiness(): void;
500 _bootPromise: Promise<this> | null;
501 /**
502 Initialize the application and return a promise that resolves with the `Application`
503 object when the boot process is complete.
504
505 Run any application initializers and run the application load hook. These hooks may
506 choose to defer readiness. For example, an authentication hook might want to defer
507 readiness until the auth token has been retrieved.
508
509 By default, this method is called automatically on "DOM ready"; however, if autoboot
510 is disabled, this is automatically called when the first application instance is
511 created via `visit`.
512
513 @public
514 @method boot
515 @return {Promise<Application,Error>}
516 */
517 boot(): Promise<this>;
518 _bootResolver: ReturnType<(typeof RSVP)['defer']> | null;
519 /**
520 Unfortunately, a lot of existing code assumes the booting process is
521 "synchronous". Specifically, a lot of tests assumes the last call to
522 `app.advanceReadiness()` or `app.reset()` will result in the app being
523 fully-booted when the current runloop completes.
524
525 We would like new code (like the `visit` API) to stop making this assumption,
526 so we created the asynchronous version above that returns a promise. But until
527 we have migrated all the code, we would have to expose this method for use
528 *internally* in places where we need to boot an app "synchronously".
529
530 @private
531 */
532 _bootSync(): void;
533 /**
534 Reset the application. This is typically used only in tests. It cleans up
535 the application in the following order:
536
537 1. Deactivate existing routes
538 2. Destroy all objects in the container
539 3. Create a new application container
540 4. Re-route to the existing url
541
542 Typical Example:
543
544 ```javascript
545 import Application from '@ember/application';
546 let App;
547
548 run(function() {
549 App = Application.create();
550 });
551
552 module('acceptance test', {
553 setup: function() {
554 App.reset();
555 }
556 });
557
558 test('first test', function() {
559 // App is freshly reset
560 });
561
562 test('second test', function() {
563 // App is again freshly reset
564 });
565 ```
566
567 Advanced Example:
568
569 Occasionally you may want to prevent the app from initializing during
570 setup. This could enable extra configuration, or enable asserting prior
571 to the app becoming ready.
572
573 ```javascript
574 import Application from '@ember/application';
575 let App;
576
577 run(function() {
578 App = Application.create();
579 });
580
581 module('acceptance test', {
582 setup: function() {
583 run(function() {
584 App.reset();
585 App.deferReadiness();
586 });
587 }
588 });
589
590 test('first test', function() {
591 ok(true, 'something before app is initialized');
592
593 run(function() {
594 App.advanceReadiness();
595 });
596
597 ok(true, 'something after app is initialized');
598 });
599 ```
600
601 @method reset
602 @public
603 */
604 reset(): void;
605 /**
606 @private
607 @method didBecomeReady
608 */
609 didBecomeReady(): void;
610 /**
611 Called when the Application has become ready, immediately before routing
612 begins. The call will be delayed until the DOM has become ready.
613
614 @event ready
615 @public
616 */
617 ready(): this;
618 willDestroy(): void;
619 /**
620 Boot a new instance of `ApplicationInstance` for the current
621 application and navigate it to the given `url`. Returns a `Promise` that
622 resolves with the instance when the initial routing and rendering is
623 complete, or rejects with any error that occurred during the boot process.
624
625 When `autoboot` is disabled, calling `visit` would first cause the
626 application to boot, which runs the application initializers.
627
628 This method also takes a hash of boot-time configuration options for
629 customizing the instance's behavior. See the documentation on
630 `ApplicationInstance.BootOptions` for details.
631
632 `ApplicationInstance.BootOptions` is an interface class that exists
633 purely to document the available options; you do not need to construct it
634 manually. Simply pass a regular JavaScript object containing of the
635 desired options:
636
637 ```javascript
638 MyApp.visit("/", { location: "none", rootElement: "#container" });
639 ```
640
641 ### Supported Scenarios
642
643 While the `BootOptions` class exposes a large number of knobs, not all
644 combinations of them are valid; certain incompatible combinations might
645 result in unexpected behavior.
646
647 For example, booting the instance in the full browser environment
648 while specifying a foreign `document` object (e.g. `{ isBrowser: true,
649 document: iframe.contentDocument }`) does not work correctly today,
650 largely due to Ember's jQuery dependency.
651
652 Currently, there are three officially supported scenarios/configurations.
653 Usages outside of these scenarios are not guaranteed to work, but please
654 feel free to file bug reports documenting your experience and any issues
655 you encountered to help expand support.
656
657 #### Browser Applications (Manual Boot)
658
659 The setup is largely similar to how Ember works out-of-the-box. Normally,
660 Ember will boot a default instance for your Application on "DOM ready".
661 However, you can customize this behavior by disabling `autoboot`.
662
663 For example, this allows you to render a miniture demo of your application
664 into a specific area on your marketing website:
665
666 ```javascript
667 import MyApp from 'my-app';
668
669 $(function() {
670 let App = MyApp.create({ autoboot: false });
671
672 let options = {
673 // Override the router's location adapter to prevent it from updating
674 // the URL in the address bar
675 location: 'none',
676
677 // Override the default `rootElement` on the app to render into a
678 // specific `div` on the page
679 rootElement: '#demo'
680 };
681
682 // Start the app at the special demo URL
683 App.visit('/demo', options);
684 });
685 ```
686
687 Or perhaps you might want to boot two instances of your app on the same
688 page for a split-screen multiplayer experience:
689
690 ```javascript
691 import MyApp from 'my-app';
692
693 $(function() {
694 let App = MyApp.create({ autoboot: false });
695
696 let sessionId = MyApp.generateSessionID();
697
698 let player1 = App.visit(`/matches/join?name=Player+1&session=${sessionId}`, { rootElement: '#left', location: 'none' });
699 let player2 = App.visit(`/matches/join?name=Player+2&session=${sessionId}`, { rootElement: '#right', location: 'none' });
700
701 Promise.all([player1, player2]).then(() => {
702 // Both apps have completed the initial render
703 $('#loading').fadeOut();
704 });
705 });
706 ```
707
708 Do note that each app instance maintains their own registry/container, so
709 they will run in complete isolation by default.
710
711 #### Server-Side Rendering (also known as FastBoot)
712
713 This setup allows you to run your Ember app in a server environment using
714 Node.js and render its content into static HTML for SEO purposes.
715
716 ```javascript
717 const HTMLSerializer = new SimpleDOM.HTMLSerializer(SimpleDOM.voidMap);
718
719 function renderURL(url) {
720 let dom = new SimpleDOM.Document();
721 let rootElement = dom.body;
722 let options = { isBrowser: false, document: dom, rootElement: rootElement };
723
724 return MyApp.visit(options).then(instance => {
725 try {
726 return HTMLSerializer.serialize(rootElement.firstChild);
727 } finally {
728 instance.destroy();
729 }
730 });
731 }
732 ```
733
734 In this scenario, because Ember does not have access to a global `document`
735 object in the Node.js environment, you must provide one explicitly. In practice,
736 in the non-browser environment, the stand-in `document` object only needs to
737 implement a limited subset of the full DOM API. The `SimpleDOM` library is known
738 to work.
739
740 Since there is no DOM access in the non-browser environment, you must also
741 specify a DOM `Element` object in the same `document` for the `rootElement` option
742 (as opposed to a selector string like `"body"`).
743
744 See the documentation on the `isBrowser`, `document` and `rootElement` properties
745 on `ApplicationInstance.BootOptions` for details.
746
747 #### Server-Side Resource Discovery
748
749 This setup allows you to run the routing layer of your Ember app in a server
750 environment using Node.js and completely disable rendering. This allows you
751 to simulate and discover the resources (i.e. AJAX requests) needed to fulfill
752 a given request and eagerly "push" these resources to the client.
753
754 ```app/initializers/network-service.js
755 import BrowserNetworkService from 'app/services/network/browser';
756 import NodeNetworkService from 'app/services/network/node';
757
758 // Inject a (hypothetical) service for abstracting all AJAX calls and use
759 // the appropriate implementation on the client/server. This also allows the
760 // server to log all the AJAX calls made during a particular request and use
761 // that for resource-discovery purpose.
762
763 export function initialize(application) {
764 if (window) { // browser
765 application.register('service:network', BrowserNetworkService);
766 } else { // node
767 application.register('service:network', NodeNetworkService);
768 }
769 };
770
771 export default {
772 name: 'network-service',
773 initialize: initialize
774 };
775 ```
776
777 ```app/routes/post.js
778 import Route from '@ember/routing/route';
779 import { service } from '@ember/service';
780
781 // An example of how the (hypothetical) service is used in routes.
782
783 export default class IndexRoute extends Route {
784 @service network;
785
786 model(params) {
787 return this.network.fetch(`/api/posts/${params.post_id}.json`);
788 }
789
790 afterModel(post) {
791 if (post.isExternalContent) {
792 return this.network.fetch(`/api/external/?url=${post.externalURL}`);
793 } else {
794 return post;
795 }
796 }
797 }
798 ```
799
800 ```javascript
801 // Finally, put all the pieces together
802
803 function discoverResourcesFor(url) {
804 return MyApp.visit(url, { isBrowser: false, shouldRender: false }).then(instance => {
805 let networkService = instance.lookup('service:network');
806 return networkService.requests; // => { "/api/posts/123.json": "..." }
807 });
808 }
809 ```
810
811 @public
812 @method visit
813 @param url {String} The initial URL to navigate to
814 @param options {ApplicationInstance.BootOptions}
815 @return {Promise<ApplicationInstance, Error>}
816 */
817 visit(url: string, options: BootOptions): Promise<unknown>;
818 }
819 export { Application as default, _loaded, onLoad, runLoadHooks };
820}