1 | declare 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 | }
|