import { ConstReference } from '@glimmer/reference';
import { curry, UNDEFINED_REFERENCE, } from '@glimmer/runtime';
import * as WireFormat from '@glimmer/wire-format';
import { OutletComponentDefinition } from '../component-managers/outlet';
import { OutletReference } from '../utils/outlet';
/**
The `{{outlet}}` helper lets you specify where a child route will render in
your template. An important use of the `{{outlet}}` helper is in your
application's `application.hbs` file:
```handlebars
{{! app/templates/application.hbs }}
{{my-header}}
{{outlet}}
{{my-footer}}
```
You may also specify a name for the `{{outlet}}`, which is useful when using more than one
`{{outlet}}` in a template:
```handlebars
{{outlet "menu"}}
{{outlet "sidebar"}}
{{outlet "main"}}
```
Your routes can then render into a specific one of these `outlet`s by specifying the `outlet`
attribute in your `renderTemplate` function:
```app/routes/menu.js
import Route from '@ember/routing/route';
export default Route.extend({
renderTemplate() {
this.render({ outlet: 'menu' });
}
});
```
See the [routing guide](https://guides.emberjs.com/release/routing/rendering-a-template/) for more
information on how your `route` interacts with the `{{outlet}}` helper.
Note: Your content __will not render__ if there isn't an `{{outlet}}` for it.
@method outlet
@param {String} [name]
@for Ember.Templates.helpers
@public
*/
export function outletHelper(vm, args) {
let scope = vm.dynamicScope();
let nameRef;
if (args.positional.length === 0) {
nameRef = new ConstReference('main');
}
else {
nameRef = args.positional.at(0);
}
return new OutletComponentReference(new OutletReference(scope.outletState, nameRef));
}
export function outletMacro(_name, params, hash, builder) {
let expr = [WireFormat.Ops.Helper, '-outlet', params || [], hash];
builder.dynamicComponent(expr, null, [], null, false, null, null);
return true;
}
class OutletComponentReference {
constructor(outletRef) {
this.outletRef = outletRef;
this.definition = null;
this.lastState = null;
// The router always dirties the root state.
this.tag = outletRef.tag;
}
value() {
let state = stateFor(this.outletRef);
if (validate(state, this.lastState)) {
return this.definition;
}
this.lastState = state;
let definition = null;
if (state !== null) {
definition = curry(new OutletComponentDefinition(state));
}
return (this.definition = definition);
}
get(_key) {
return UNDEFINED_REFERENCE;
}
}
function stateFor(ref) {
let outlet = ref.value();
if (outlet === undefined)
return null;
let render = outlet.render;
if (render === undefined)
return null;
let template = render.template;
if (template === undefined)
return null;
return {
ref,
name: render.name,
outlet: render.outlet,
template,
controller: render.controller,
};
}
function validate(state, lastState) {
if (state === null) {
return lastState === null;
}
if (lastState === null) {
return false;
}
return state.template === lastState.template && state.controller === lastState.controller;
}