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

[FEATURE ember-glimmer-angle-bracket-built-ins] #17735

Merged
merged 5 commits into from
Mar 18, 2019
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
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