-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements RFC #60 (Component Unification)
emberjs/rfcs#60 This commit implements the proposed semantics for angle-bracket components. The TLDR is that a component’s template represents its “outerHTML” rather than its “innerHTML”, which makes it easier to configure the element itself using templating constructs. Some salient points: 1. If there is a single root element, the attributes from the invocation are copied onto the root element. 2. The invocation’s attributes “win out” over the attributes from the root element in the component’s layout. 3. Classes are merged. If the invocation says `class=“a”` and the root element says `class=“b”`, the result is `class=“a b”`. The rationale is that when you say `class=“a”`, you are really saying “add the `a` class to this element”. A few sticky issues: 1. If the root element for the `my-foo` component is `<my-foo>`, we insert an element with tag name of `my-foo`. While this is intuitive, note that in all other cases, `<my-foo>` represents an invocation of the component. In the root position, that makes no sense, since it would inevitably produce a stack overflow. a. This “identity element” case makes it idiomatic to reflect the name of the component onto the DOM. b. Unfortunately, when we compile a template, we do not yet know what component that template is used for, and, indeed, whether it is even a template for a component at all. c. To capture the semantic differences, we do a bit of analysis at compile time (to determine *candidates* for top-level elements), and use runtime information (component invocation style and the name of the component looked up in the container) to disambiguate between a component’s element and an invocation of another component. 2. If the root element for the `my-foo` component is a regular HTML element, we use the `attachAttributes` functionality of HTMLBars to attach the attributes that the component was invoked with onto the root element. 3. In general, it is important that changes to attributes do not result in a change to the identity of the element that they are contained in. To achieve this, we reused much of the infrastructure built in `buildComponentTemplate`. The consequence of (1) and (2) above is that the semantics are always “a component’s layout represents its outerHTML”, even though the implementation is quite different depending on whether the root element is the same-named component or not.
- Loading branch information
Showing
9 changed files
with
328 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { render, internal } from "htmlbars-runtime"; | ||
|
||
export default function attributes(morph, env, scope, template, parentNode, visitor) { | ||
let state = morph.state; | ||
let block = state.block; | ||
|
||
if (!block) { | ||
let element = findRootElement(parentNode); | ||
if (!element) { return; } | ||
|
||
normalizeClassStatement(template.statements, element); | ||
|
||
template.element = element; | ||
block = morph.state.block = internal.blockFor(render, template, { scope }); | ||
} | ||
|
||
block(env, [], undefined, morph, undefined, visitor); | ||
} | ||
|
||
function normalizeClassStatement(statements, element) { | ||
let className = element.getAttribute('class'); | ||
if (!className) { return; } | ||
|
||
for (let i=0, l=statements.length; i<l; i++) { | ||
let statement = statements[i]; | ||
|
||
if (statement[1] === 'class') { | ||
statement[2][2].unshift(className); | ||
} | ||
} | ||
} | ||
|
||
function findRootElement(parentNode) { | ||
let node = parentNode.firstChild; | ||
let found = null; | ||
|
||
while (node) { | ||
if (node.nodeType === 1) { | ||
// found more than one top-level element, so there is no "root element" | ||
if (found) { return null; } | ||
found = node; | ||
} | ||
node = node.nextSibling; | ||
} | ||
|
||
let className = found && found.getAttribute('class'); | ||
if (!className || className.split(' ').indexOf('ember-view') === -1) { | ||
return found; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.