Skip to content

Commit

Permalink
Merge pull request #17735 from emberjs/angle-built-ins
Browse files Browse the repository at this point in the history
[FEATURE ember-glimmer-angle-bracket-built-ins]
  • Loading branch information
rwjblue authored Mar 18, 2019
2 parents 873afa6 + 04ff55b commit c8987af
Show file tree
Hide file tree
Showing 27 changed files with 2,140 additions and 303 deletions.
7 changes: 7 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,10 @@ for a detailed explanation.
syntax.

See [RFC #457](https://github.com/emberjs/rfcs/pull/457).

* `ember-glimmer-angle-bracket-built-ins`

Allow the built-in `LinkTo`, `Input`, and `Textarea` components to be invoked
with the angle bracket invocation sytnax.

See [RFC #459](https://github.com/emberjs/rfcs/pull/459).
4 changes: 2 additions & 2 deletions packages/@ember/-internals/glimmer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,8 @@
export { default as RootTemplate } from './lib/templates/root';
export { default as template } from './lib/template';
export { default as Checkbox } from './lib/components/checkbox';
export { default as TextField } from './lib/components/text_field';
export { default as TextArea } from './lib/components/text_area';
export { default as TextField } from './lib/components/text-field';
export { default as TextArea } from './lib/components/textarea';
export { default as LinkComponent } from './lib/components/link-to';
export { default as Component, ROOT_REF } from './lib/component';
export { default as Helper, helper } from './lib/helper';
Expand Down
177 changes: 177 additions & 0 deletions packages/@ember/-internals/glimmer/lib/components/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/**
@module @ember/component
*/
import { computed } from '@ember/-internals/metal';
import { EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS } from '@ember/canary-features';
import { assert } from '@ember/debug';
import { DEBUG } from '@glimmer/env';
import Component from '../component';

let Input: any;

if (EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS) {
/**
The `{{input}}` helper lets you create an HTML `<input />` component.
It causes a `TextField` component to be rendered. For more info,
see the [TextField](/api/ember/release/classes/TextField) docs and
the [templates guide](https://guides.emberjs.com/release/templates/input-helpers/).
```handlebars
{{input value="987"}}
```
renders as:
```HTML
<input type="text" value="987" />
```
### Text field
If no `type` option is specified, a default of type 'text' is used.
Many of the standard HTML attributes may be passed to this helper.
<table>
<tr><td>`readonly`</td><td>`required`</td><td>`autofocus`</td></tr>
<tr><td>`value`</td><td>`placeholder`</td><td>`disabled`</td></tr>
<tr><td>`size`</td><td>`tabindex`</td><td>`maxlength`</td></tr>
<tr><td>`name`</td><td>`min`</td><td>`max`</td></tr>
<tr><td>`pattern`</td><td>`accept`</td><td>`autocomplete`</td></tr>
<tr><td>`autosave`</td><td>`formaction`</td><td>`formenctype`</td></tr>
<tr><td>`formmethod`</td><td>`formnovalidate`</td><td>`formtarget`</td></tr>
<tr><td>`height`</td><td>`inputmode`</td><td>`multiple`</td></tr>
<tr><td>`step`</td><td>`width`</td><td>`form`</td></tr>
<tr><td>`selectionDirection`</td><td>`spellcheck`</td><td>&nbsp;</td></tr>
</table>
When set to a quoted string, these values will be directly applied to the HTML
element. When left unquoted, these values will be bound to a property on the
template's current rendering context (most typically a controller instance).
A very common use of this helper is to bind the `value` of an input to an Object's attribute:
```handlebars
Search:
{{input value=searchWord}}
```
In this example, the initial value in the `<input />` will be set to the value of `searchWord`.
If the user changes the text, the value of `searchWord` will also be updated.
### Actions
The helper can send multiple actions based on user events.
The action property defines the action which is sent when
the user presses the return key.
```handlebars
{{input action="submit"}}
```
The helper allows some user events to send actions.
* `enter`
* `insert-newline`
* `escape-press`
* `focus-in`
* `focus-out`
* `key-press`
* `key-up`
For example, if you desire an action to be sent when the input is blurred,
you only need to setup the action name to the event name property.
```handlebars
{{input focus-out="alertMessage"}}
```
See more about [Text Support Actions](/api/ember/release/classes/TextField)
### Extending `TextField`
Internally, `{{input type="text"}}` creates an instance of `TextField`, passing
arguments from the helper to `TextField`'s `create` method. You can extend the
capabilities of text inputs in your applications by reopening this class. For example,
if you are building a Bootstrap project where `data-*` attributes are used, you
can add one to the `TextField`'s `attributeBindings` property:
```javascript
import TextField from '@ember/component/text-field';
TextField.reopen({
attributeBindings: ['data-error']
});
```
Keep in mind when writing `TextField` subclasses that `TextField`
itself extends `Component`. Expect isolated component semantics, not
legacy 1.x view semantics (like `controller` being present).
See more about [Ember components](/api/ember/release/classes/Component)
### Checkbox
Checkboxes are special forms of the `{{input}}` helper. To create a `<checkbox />`:
```handlebars
Emberize Everything:
{{input type="checkbox" name="isEmberized" checked=isEmberized}}
```
This will bind checked state of this checkbox to the value of `isEmberized` -- if either one changes,
it will be reflected in the other.
The following HTML attributes can be set via the helper:
* `checked`
* `disabled`
* `tabindex`
* `indeterminate`
* `name`
* `autofocus`
* `form`
### Extending `Checkbox`
Internally, `{{input type="checkbox"}}` creates an instance of `Checkbox`, passing
arguments from the helper to `Checkbox`'s `create` method. You can extend the
capablilties of checkbox inputs in your applications by reopening this class. For example,
if you wanted to add a css class to all checkboxes in your application:
```javascript
import Checkbox from '@ember/component/checkbox';
Checkbox.reopen({
classNames: ['my-app-checkbox']
});
```
@method input
@for Ember.Templates.helpers
@param {Hash} options
@public
*/
Input = Component.extend({
tagName: '',

isCheckbox: computed('type', function(this: { type?: unknown }) {
return this.type === 'checkbox';
}),
});

Input.toString = () => '@ember/component/input';

if (DEBUG) {
const UNSET = {};

Input.reopen({
value: UNSET,

didReceiveAttrs() {
this._super();

assert(
"`<Input @type='checkbox' @value={{...}} />` is not supported; " +
"please use `<Input @type='checkbox' @checked={{...}} />` instead.",
!(this.type === 'checkbox' && this.value !== UNSET)
);
},
});
}
}

export default Input;
22 changes: 19 additions & 3 deletions packages/@ember/-internals/glimmer/lib/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { privatize as P } from '@ember/-internals/container';
import { ENV } from '@ember/-internals/environment';
import { LookupOptions, Owner, setOwner } from '@ember/-internals/owner';
import { lookupComponent, lookupPartial, OwnedTemplateMeta } from '@ember/-internals/views';
import { EMBER_MODULE_UNIFICATION } from '@ember/canary-features';
import {
EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS,
EMBER_MODULE_UNIFICATION,
} from '@ember/canary-features';
import { assert } from '@ember/debug';
import { _instrumentStart } from '@ember/instrumentation';
import {
Expand Down Expand Up @@ -136,6 +139,12 @@ export default class RuntimeResolver implements IRuntimeResolver<OwnedTemplateMe
lookupComponentHandle(name: string, meta: OwnedTemplateMeta) {
let nextHandle = this.handles.length;
let handle = this.handle(this._lookupComponentDefinition(name, meta));

assert(
'Could not find component `<TextArea />` (did you mean `<Textarea />`?)',
!(EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS && name === 'text-area' && handle === null)
);

if (nextHandle === handle) {
this.componentDefinitionCount++;
}
Expand Down Expand Up @@ -303,8 +312,15 @@ export default class RuntimeResolver implements IRuntimeResolver<OwnedTemplateMe
_name: string,
meta: OwnedTemplateMeta
): Option<ComponentDefinition> {
assert('You cannot use `textarea` as a component name.', _name !== 'textarea');
assert('You cannot use `input` as a component name.', _name !== 'input');
assert(
'Invoking `{{textarea}}` using angle bracket syntax or `component` helper is not yet supported.',
EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS || _name !== 'textarea'
);

assert(
'Invoking `{{input}}` using angle bracket syntax or `component` helper is not yet supported.',
EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS || _name !== 'input'
);

let name = _name;
let namespace = undefined;
Expand Down
27 changes: 24 additions & 3 deletions packages/@ember/-internals/glimmer/lib/setup-registry.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { hasDOM } from '@ember/-internals/browser-environment';
import { privatize as P, Registry } from '@ember/-internals/container';
import { ENV } from '@ember/-internals/environment';
import { EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS } from '@ember/canary-features';
import { Simple } from '@glimmer/interfaces';
import Component from './component';
import Checkbox from './components/checkbox';
import Input from './components/input';
import LinkToComponent from './components/link-to';
import TextArea from './components/text_area';
import TextField from './components/text_field';
import TextField from './components/text-field';
import TextArea from './components/textarea';
import {
clientBuilder,
DOMChanges,
Expand All @@ -20,6 +22,7 @@ import loc from './helpers/loc';
import { InertRenderer, InteractiveRenderer } from './renderer';
import TemplateCompiler from './template-compiler';
import ComponentTemplate from './templates/component';
import InputTemplate from './templates/input';
import OutletTemplate from './templates/outlet';
import RootTemplate from './templates/root';
import OutletView from './views/outlet';
Expand Down Expand Up @@ -97,10 +100,28 @@ export function setupEngineRegistry(registry: Registry) {
registry.register('helper:loc', loc);

registry.register('component:-text-field', TextField);
registry.register('component:-text-area', TextArea);
registry.register('component:-checkbox', Checkbox);
registry.register('component:link-to', LinkToComponent);

if (EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS) {
// Internal

// These are registered as CapCase because our internal tempaltes do not
// go through the dashify transform. As a nice bonus, it also makes it
// more difficult for users to invoke them by accident.
registry.register('component:TextField', TextField);
registry.register('component:Checkbox', Checkbox);

// Public

registry.register('component:input', Input);
registry.register('template:components/input', InputTemplate);

registry.register('component:textarea', TextArea);
} else {
registry.register('component:-text-area', TextArea);
}

if (!ENV._TEMPLATE_ONLY_GLIMMER_COMPONENTS) {
registry.register(P`component:-default`, Component);
}
Expand Down
17 changes: 12 additions & 5 deletions packages/@ember/-internals/glimmer/lib/syntax.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { OwnedTemplateMeta } from '@ember/-internals/views';
import { EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS } from '@ember/canary-features';
import { assert } from '@ember/debug';
import { CompilableBlock } from '@glimmer/interfaces';
import { Macros, OpcodeBuilder } from '@glimmer/opcode-compiler';
import { Option } from '@glimmer/util';
import { Core } from '@glimmer/wire-format';
import CompileTimeLookup from './compile-time-lookup';
import { textAreaMacro } from './syntax/-text-area';
import { inputMacro } from './syntax/input';
import { blockLetMacro } from './syntax/let';
import { mountMacro } from './syntax/mount';
import { outletMacro } from './syntax/outlet';
import { textAreaMacro } from './syntax/textarea';
import { hashToArgs } from './syntax/utils';
import { wrapComponentClassAttribute } from './utils/bindings';

Expand All @@ -26,7 +27,8 @@ function refineInlineSyntax(
builder.referrer.owner.hasRegistration(`helper:${name}`)
)
);
if (name.indexOf('-') === -1) {

if (!EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS && name.indexOf('-') === -1) {
return false;
}

Expand All @@ -48,7 +50,7 @@ function refineBlockSyntax(
inverse: Option<CompilableBlock>,
builder: OpcodeBuilder<OwnedTemplateMeta>
) {
if (name.indexOf('-') === -1) {
if (!EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS && name.indexOf('-') === -1) {
return false;
}

Expand Down Expand Up @@ -96,9 +98,14 @@ export function populateMacros(macros: Macros) {
let { inlines, blocks } = macros;
inlines.add('outlet', outletMacro);
inlines.add('mount', mountMacro);
inlines.add('input', inputMacro);
inlines.add('textarea', textAreaMacro);

if (!EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS) {
inlines.add('input', inputMacro);
inlines.add('textarea', textAreaMacro);
}

inlines.addMissing(refineInlineSyntax);

blocks.add('let', blockLetMacro);
blocks.addMissing(refineBlockSyntax);

Expand Down
21 changes: 0 additions & 21 deletions packages/@ember/-internals/glimmer/lib/syntax/-text-area.ts

This file was deleted.

Loading

0 comments on commit c8987af

Please sign in to comment.