API Docs for: v2.11.1
Show:

File: packages/ember-glimmer/lib/syntax/render.js

/**
@module ember
@submodule ember-glimmer
*/
import {
  ArgsSyntax,
  StatementSyntax,
  ComponentDefinition
} from 'glimmer-runtime';
import { ConstReference, isConst } from 'glimmer-reference';
import { assert, runInDebug } from 'ember-metal';
import { RootReference } from '../utils/references';
import { generateController, generateControllerFactory } from 'ember-routing';
import { OutletLayoutCompiler } from './outlet';
import AbstractManager from './abstract-manager';

function makeComponentDefinition(vm) {
  let env     = vm.env;
  let args    = vm.getArgs();
  let nameRef = args.positional.at(0);

  assert(`The first argument of {{render}} must be quoted, e.g. {{render "sidebar"}}.`, isConst(nameRef));
  assert(`The second argument of {{render}} must be a path, e.g. {{render "post" post}}.`, args.positional.length === 1 || !isConst(args.positional.at(1)));

  let templateName = nameRef.value();

  assert(`You used \`{{render '${templateName}'}}\`, but '${templateName}' can not be found as a template.`, env.owner.hasRegistration(`template:${templateName}`));

  let template = env.owner.lookup(`template:${templateName}`);

  let controllerName;

  if (args.named.has('controller')) {
    let controllerNameRef = args.named.get('controller');

    assert(`The controller argument for {{render}} must be quoted, e.g. {{render "sidebar" controller="foo"}}.`, isConst(controllerNameRef));

    controllerName = controllerNameRef.value();

    assert(`The controller name you supplied '${controllerName}' did not resolve to a controller.`, env.owner.hasRegistration(`controller:${controllerName}`));
  } else {
    controllerName = templateName;
  }

  if (args.positional.length === 1) {
    return new ConstReference(new RenderDefinition(controllerName, template, env, SINGLETON_RENDER_MANAGER));
  } else {
    return new ConstReference(new RenderDefinition(controllerName, template, env, NON_SINGLETON_RENDER_MANAGER));
  }
}

/**
  Calling ``{{render}}`` from within a template will insert another
  template that matches the provided name. The inserted template will
  access its properties on its own controller (rather than the controller
  of the parent template).

  If a view class with the same name exists, the view class also will be used.
  Note: A given controller may only be used *once* in your app in this manner.
  A singleton instance of the controller will be created for you.

  Example:

  ```javascript
  App.NavigationController = Ember.Controller.extend({
    who: "world"
  });
  ```

  ```handlebars
  <!-- navigation.hbs -->
  Hello, {{who}}.
  ```

  ```handlebars
  <!-- application.hbs -->
  <h1>My great app</h1>
  {{render "navigation"}}
  ```

  ```html
  <h1>My great app</h1>
  <div class='ember-view'>
    Hello, world.
  </div>
  ```

  Optionally you may provide a second argument: a property path
  that will be bound to the `model` property of the controller.
  If a `model` property path is specified, then a new instance of the
  controller will be created and `{{render}}` can be used multiple times
  with the same name.

  For example if you had this `author` template.

  ```handlebars
  <div class="author">
    Written by {{firstName}} {{lastName}}.
    Total Posts: {{postCount}}
  </div>
  ```

  You could render it inside the `post` template using the `render` helper.

  ```handlebars
  <div class="post">
    <h1>{{title}}</h1>
    <div>{{body}}</div>
    {{render "author" author}}
  </div>
  ```

  @method render
  @for Ember.Templates.helpers
  @param {String} name
  @param {Object?} context
  @param {Hash} options
  @return {String} HTML string
  @public
*/
export class RenderSyntax extends StatementSyntax {
  static create(environment, args, symbolTable) {
    return new this(environment, args, symbolTable);
  }

  constructor(environment, args, symbolTable) {
    super();
    this.definitionArgs = args;
    this.definition = makeComponentDefinition;
    this.args = ArgsSyntax.fromPositionalArgs(args.positional.slice(1, 2));
    this.symbolTable = symbolTable;
    this.shadow = null;
  }

  compile(builder) {
    builder.component.dynamic(this.definitionArgs, this.definition, this.args, this.symbolTable, this.shadow);
  }
}

class AbstractRenderManager extends AbstractManager {
  prepareArgs(definition, args) {
    return args;
  }

  /* abstract create(environment, definition, args, dynamicScope); */

  layoutFor(definition, bucket, env) {
    return env.getCompiledBlock(OutletLayoutCompiler, definition.template);
  }

  getSelf({ controller }) {
    return new RootReference(controller);
  }

  getTag() {
    return null;
  }

  getDestructor() {
    return null;
  }

  didCreateElement() {}
  didRenderLayout() {}
  didCreate() {}
  update() {}
  didUpdateLayout() {}
  didUpdate() {}
}

runInDebug(() => {
  AbstractRenderManager.prototype.didRenderLayout = function() {
    this.debugStack.pop();
  };
});

class SingletonRenderManager extends AbstractRenderManager {
  create(environment, definition, args, dynamicScope) {
    let { name, env } = definition;
    let controller = env.owner.lookup(`controller:${name}`) || generateController(env.owner, name);

    runInDebug(() => this._pushToDebugStack(`controller:${name} (with the render helper)`, environment));

    if (dynamicScope.rootOutletState) {
      dynamicScope.outletState = dynamicScope.rootOutletState.getOrphan(name);
    }

    return { controller };
  }
}

const SINGLETON_RENDER_MANAGER = new SingletonRenderManager();

class NonSingletonRenderManager extends AbstractRenderManager {
  create(environment, definition, args, dynamicScope) {
    let { name, env } = definition;
    let modelRef = args.positional.at(0);

    let factory = env.owner._lookupFactory(`controller:${name}`) || generateControllerFactory(env.owner, name);
    let controller = factory.create({ model: modelRef.value() });

    runInDebug(() => this._pushToDebugStack(`controller:${name} (with the render helper)`, environment));

    if (dynamicScope.rootOutletState) {
      dynamicScope.outletState = dynamicScope.rootOutletState.getOrphan(name);
    }

    return { controller };
  }

  update({ controller }, args, dynamicScope) {
    controller.set('model', args.positional.at(0).value());
  }

  getDestructor({ controller }) {
    return controller;
  }
}

const NON_SINGLETON_RENDER_MANAGER = new NonSingletonRenderManager();

class RenderDefinition extends ComponentDefinition {
  constructor(name, template, env, manager) {
    super('render', manager, null);

    this.name = name;
    this.template = template;
    this.env = env;
  }
}