Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement angle-bracket components #11141

Merged
merged 2 commits into from
May 14, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/ember-htmlbars/lib/hooks/component.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ComponentNodeManager from "ember-htmlbars/node-managers/component-node-manager";

export default function componentHook(renderNode, env, scope, tagName, params, attrs, templates, visitor) {
export default function componentHook(renderNode, env, scope, _tagName, params, attrs, templates, visitor) {
var state = renderNode.state;

// Determine if this is an initial render or a re-render
Expand All @@ -9,6 +9,14 @@ export default function componentHook(renderNode, env, scope, tagName, params, a
return;
}

let tagName = _tagName;
let isAngleBracket = false;

if (tagName.charAt(0) === '<') {
tagName = tagName.slice(1, -1);
isAngleBracket = true;
}

var read = env.hooks.getValue;
var parentView = read(env.view);

Expand All @@ -18,6 +26,7 @@ export default function componentHook(renderNode, env, scope, tagName, params, a
attrs,
parentView,
templates,
isAngleBracket,
parentScope: scope
});

Expand Down
156 changes: 85 additions & 71 deletions packages/ember-htmlbars/lib/node-managers/component-node-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ import setProperties from "ember-metal/set_properties";
import { MUTABLE_CELL } from "ember-views/compat/attrs-proxy";
import SafeString from "htmlbars-util/safe-string";
import { instrument } from "ember-htmlbars/system/instrumentation-support";
import EmberComponent from "ember-views/views/component";

// In theory this should come through the env, but it should
// be safe to import this until we make the hook system public
// and it gets actively used in addons or other downstream
// libraries.
import getValue from "ember-htmlbars/hooks/get-value";

function ComponentNodeManager(component, scope, renderNode, attrs, block, expectElement) {
function ComponentNodeManager(component, isAngleBracket, scope, renderNode, attrs, block, expectElement) {
this.component = component;
this.isAngleBracket = isAngleBracket;
this.scope = scope;
this.renderNode = renderNode;
this.attrs = attrs;
Expand All @@ -33,6 +35,7 @@ ComponentNodeManager.create = function(renderNode, env, options) {
attrs,
parentView,
parentScope,
isAngleBracket,
templates } = options;

attrs = attrs || {};
Expand All @@ -45,39 +48,40 @@ ComponentNodeManager.create = function(renderNode, env, options) {
return component || layout;
});

if (component) {
let createOptions = { parentView };
component = component || EmberComponent;

// Map passed attributes (e.g. <my-component id="foo">) to component
// properties ({ id: "foo" }).
configureCreateOptions(attrs, createOptions);
let createOptions = { parentView };

// If there is a controller on the scope, pluck it off and save it on the
// component. This allows the component to target actions sent via
// `sendAction` correctly.
if (parentScope.locals.controller) {
createOptions._controller = getValue(parentScope.locals.controller);
}

// Instantiate the component
component = createComponent(component, createOptions, renderNode, env, attrs);
configureTagName(attrs, tagName, component, isAngleBracket, createOptions);

// If the component specifies its template via the `layout` or `template`
// properties instead of using the template looked up in the container, get
// them now that we have the component instance.
let result = extractComponentTemplates(component, templates);
layout = result.layout || layout;
templates = result.templates || templates;
// Map passed attributes (e.g. <my-component id="foo">) to component
// properties ({ id: "foo" }).
configureCreateOptions(attrs, createOptions);

extractPositionalParams(renderNode, component, params, attrs);
// If there is a controller on the scope, pluck it off and save it on the
// component. This allows the component to target actions sent via
// `sendAction` correctly.
if (parentScope.locals.controller) {
createOptions._controller = getValue(parentScope.locals.controller);
}

var results = buildComponentTemplate({ layout: layout, component: component }, attrs, {
templates,
scope: parentScope
});
// Instantiate the component
component = createComponent(component, isAngleBracket, createOptions, renderNode, env, attrs);

// If the component specifies its template via the `layout` or `template`
// properties instead of using the template looked up in the container, get
// them now that we have the component instance.
let result = extractComponentTemplates(component, templates);
layout = result.layout || layout;
templates = result.templates || templates;

extractPositionalParams(renderNode, component, params, attrs);

var results = buildComponentTemplate(
{ layout, component, isAngleBracket }, attrs, { templates, scope: parentScope }
);

return new ComponentNodeManager(component, parentScope, renderNode, attrs, results.block, results.createdElement);
return new ComponentNodeManager(component, isAngleBracket, parentScope, renderNode, attrs, results.block, results.createdElement);
};

function extractPositionalParams(renderNode, component, params, attrs) {
Expand Down Expand Up @@ -132,12 +136,19 @@ function extractLegacyTemplate(_templates, componentTemplate) {
return templates;
}

function configureTagName(attrs, tagName, component, isAngleBracket, createOptions) {
if (isAngleBracket) {
createOptions.tagName = tagName;
} else if (attrs.tagName) {
createOptions.tagName = getValue(attrs.tagName);
}
}

function configureCreateOptions(attrs, createOptions) {
// Some attrs are special and need to be set as properties on the component
// instance. Make sure we use getValue() to get them from `attrs` since
// they are still streams.
if (attrs.id) { createOptions.elementId = getValue(attrs.id); }
if (attrs.tagName) { createOptions.tagName = getValue(attrs.tagName); }
if (attrs._defaultTagName) { createOptions._defaultTagName = getValue(attrs._defaultTagName); }
if (attrs.viewName) { createOptions.viewName = getValue(attrs.viewName); }
}
Expand All @@ -148,32 +159,32 @@ ComponentNodeManager.prototype.render = function(_env, visitor) {
return instrument(component, function() {
let env = _env;

if (component) {
env = assign({ view: component }, env);
env = assign({ view: component }, env);

var snapshot = takeSnapshot(attrs);
env.renderer.componentInitAttrs(this.component, snapshot);
env.renderer.componentWillRender(component);
env.renderedViews.push(component.elementId);
}
var snapshot = takeSnapshot(attrs);
env.renderer.componentInitAttrs(this.component, snapshot);
env.renderer.componentWillRender(component);
env.renderedViews.push(component.elementId);

if (this.block) {
this.block(env, [], undefined, this.renderNode, this.scope, visitor);
}

if (component) {
var element = this.expectElement && this.renderNode.firstNode;
handleLegacyRender(component, element);
env.renderer.didCreateElement(component, element);
env.renderer.willInsertElement(component, element); // 2.0TODO remove legacy hook
env.lifecycleHooks.push({ type: 'didInsertElement', view: component });
}
var element = this.expectElement && this.renderNode.firstNode;

handleLegacyRender(component, element);
env.renderer.didCreateElement(component, element);
env.renderer.willInsertElement(component, element); // 2.0TODO remove legacy hook

env.lifecycleHooks.push({ type: 'didInsertElement', view: component });
}, this);
};

export function handleLegacyRender(component, element) {
if (!component.render) { return; }

Ember.assert("Legacy render functions are not supported with angle-bracket components", !component._isAngleBracket);

var content, node, lastChildIndex;
var buffer = [];
var renderNode = component._renderNode;
Expand All @@ -194,60 +205,63 @@ ComponentNodeManager.prototype.rerender = function(_env, attrs, visitor) {
return instrument(component, function() {
let env = _env;

if (component) {
env = assign({ view: component }, env);
env = assign({ view: component }, env);

var snapshot = takeSnapshot(attrs);
var snapshot = takeSnapshot(attrs);

if (component._renderNode.shouldReceiveAttrs) {
env.renderer.componentUpdateAttrs(component, component.attrs, snapshot);
if (component._renderNode.shouldReceiveAttrs) {
env.renderer.componentUpdateAttrs(component, component.attrs, snapshot);

// 2.0TODO: remove legacy semantics for angle-bracket semantics
if (!component._isAngleBracket) {
setProperties(component, mergeBindings({}, shadowedAttrs(component, snapshot)));

component._renderNode.shouldReceiveAttrs = false;
}

// Notify component that it has become dirty and is about to change.
env.renderer.componentWillUpdate(component, snapshot);
env.renderer.componentWillRender(component);

env.renderedViews.push(component.elementId);
component._renderNode.shouldReceiveAttrs = false;
}

// Notify component that it has become dirty and is about to change.
env.renderer.componentWillUpdate(component, snapshot);
env.renderer.componentWillRender(component);

env.renderedViews.push(component.elementId);

if (this.block) {
this.block(env, [], undefined, this.renderNode, this.scope, visitor);
}

if (component) {
env.lifecycleHooks.push({ type: 'didUpdate', view: component });
}
env.lifecycleHooks.push({ type: 'didUpdate', view: component });

return env;
}, this);
};


export function createComponent(_component, options, renderNode, env, attrs = {}) {
let snapshot = takeSnapshot(attrs);
let props = assign({}, options);
let hasSuppliedController = 'controller' in attrs; // 2.0TODO remove
export function createComponent(_component, isAngleBracket, _props, renderNode, env, attrs = {}) {
let props = assign({}, _props);

Ember.deprecate("controller= is deprecated", !hasSuppliedController);
if (!isAngleBracket) {
let hasSuppliedController = 'controller' in attrs; // 2.0TODO remove
Ember.deprecate("controller= is deprecated", !hasSuppliedController);

props.attrs = snapshot;
let snapshot = takeSnapshot(attrs);
props.attrs = snapshot;

// 2.0TODO deprecate and remove from angle components
let proto = _component.proto();
mergeBindings(props, shadowedAttrs(proto, snapshot));
let proto = _component.proto();
mergeBindings(props, shadowedAttrs(proto, snapshot));
} else {
props._isAngleBracket = true;
}

let component = _component.create(props);

if (options.parentView) {
options.parentView.appendChild(component);
// for the fallback case
component.container = component.container || env.container;

if (props.parentView) {
props.parentView.appendChild(component);

if (options.viewName) {
set(options.parentView, options.viewName, component);
if (props.viewName) {
set(props.parentView, props.viewName, component);
}
}

Expand Down
Loading