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

Dynamic tags in glimmer templates #389

Merged
merged 4 commits into from
Apr 12, 2019
Merged
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
118 changes: 118 additions & 0 deletions text/0389-dynamic-tag-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
- Start Date: 2018-10-14
- RFC PR: (leave this empty)
- Ember Issue: (leave this empty)

# Dynamic tag names in glimmer templates.

## Summary

With the transition from inner-html semantics to outer-html semantics in components, we lost one feature: Being
able dynamically define the tag name of components.

I think it was an useful feature and we should find a way to bring it back.

## Motivation

Although not something we use every day, there is a need for some components to have a dynamic tag name.

This is most often used for certain low-level _presentational_ components.

Take for instance a component named `<Panel>` that is used to encapsulate some presentation concerns.
The template of that component could be like this:

```hbs
<div class="pt-10 pb-10 ps-20 box-shadow" ...attributes>
{{yield}}
</div>
```

For accessibility and semantic reasons, sometimes a `<div>` may not be the best kind of tag.
We might want the panel to be a `<section>` element, or an `<aside>` when it's content is somewhat unrelated
with the rest of the page. Or maybe a `<legend>` if it's and the end of a form.

With the `element` helper proposed in this RFC, this can be accomplished with something like this:

```hbs
{{#let (element @tagName) as |Tag|}}
<Tag class="pt-10 pb-10 ps-20 box-shadow" ...attributes>
{{yield}}
</Tag>
{{/let}}
```

## Detailed design

We propose to add a new `element` helper that takes a tag name and generates a contextual component that, when invoked, renders the selected element.

Example:

```hbs
{{#let (element @htmlTag) as |Tag|}}
<Tag>...</Tag>
{{/let}}
```

Unlike ids, classes or other attributes, the tag name of DOM element cannot be changed in runtime.

To help the user understand that changing the tag of an element in runtime is an expensive operation,
the syntax is intentionally chosen to express that changes in the tag name will turn down the given block
and recreate it again.

Having dynamic tag names can also open the door to possible XSS vulnerabilities if developers allow user-input
to become tag names (e.g. `xlink:`) . To prevent that, this helper will throw an error for any tag name containing anyting
but lowercase letters and dashes.

A working proof of concept of this approach has been created in https://github.com/tildeio/ember-element-helper

Shall this RFC me accepted, that helper would be ported to Ember.js itself, perhaps making it more efficient
on the process.

## How we teach this

_It's vitally important that developers have awareness the negative impact that misuse of dynamic tags can have on the accessibility of an Ember application._

While dynamic tags enable a great deal of flexibility in components, it's essential to recognize that these can also negatively impact accessibility. Developers should be keenly aware of the appropriate role that should be applied per HTML element (as specified in the [WAI-ARIA specification](https://www.w3.org/WAI/PF/aria/roles)), and ensure that the role is also updated, if necessary, as the tag name is changed. In some cases, the use of native, semantic HTML elements may eliminate the need to apply a role at all, so developers should consult the WAI-ARIA specification until they are certain of the correct use for their specific use cases.

This new helpers will be added to the list of [built-in helpers](https://emberjs.com/api/ember/release/classes/Ember.Templates.helpers) along
with its cousins `component`, `array`, `each`, etc...

Some meaningful example for the docs could look like this (extracted from a real use case):

```hbs
{{!-- sidebar.hbs, a template-only component --}}
{{#let (element (or @htmlTag "aside")) as |Tag|}}
<Tag ...>...</Tag>
{{/let}}
```

```hbs
<Sidebar @htmlTag="nav">...</Sidebar>
```

Since this feature is not very commonly used, it should not be mentioned in the more beginner-friendly portion
of the guides. However, the _Components > Customizing a Component's Element_ section is a perfect fit for this.

## Drawbacks

Admittedly this syntax is somewhat convoluted, as it involves using the `let` helper, the new `element`
helper that yields a contextual component that is then invoked using angle-bracket syntax.

This syntax is intentional to make clear that any change in the tag name would yield a new contextual component,
effectively tearing down the previous one before rendering the new one, but I can see perceiving this
as unnecessarily complex.

## Alternatives

We can decide not to do anything and leave this problem to be solved in user space, as it can be
solved using only public APIs.

We can also propose other alternative syntaxes. For instance, we could have a special built-in component for this:

```hbs
<DynamicElement @tagName={{@tagName}} class="some-class" ...attributes>
</DynamicElement>
```

## Unresolved questions

---