Skip to content

Commit

Permalink
feat(template): auto infer binding expression when empty
Browse files Browse the repository at this point in the history
  • Loading branch information
bigopon committed May 7, 2024
1 parent c47df91 commit 98b3d43
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ describe('3-runtime-html/custom-elements.harmony.spec.ts', function () {
});

it('gives priority to custom element bindable over custom attribute with the same name', function () {
const { assertStyles, printHtml } = createFixture(
const { assertStyles } = createFixture(
`<square size.bind="width"></square>`,
{ width: 10 },
[
Expand All @@ -376,7 +376,6 @@ describe('3-runtime-html/custom-elements.harmony.spec.ts', function () {
]
);

printHtml();
assertStyles('square', { width: '10px' });
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { resolve } from '@aurelia/kernel';
import { CustomAttribute, INode } from '@aurelia/runtime-html';
import { assert, createFixture } from '@aurelia/testing';

describe('3-runtime-html/custom-elements.infer-expression.spec.ts', function () {
it('auto infers binding expression with .bind', function () {
const { assertHtml, component, flush } = createFixture('<div textcontent.bind>', {
textcontent: 'hey'
});
assertHtml('<div>hey</div>');
component.textcontent = 'ahh';
flush();
assertHtml('<div>ahh</div>');
});

it('auto infers binding expression with .one-time', function () {
const { assertHtml, component, flush } = createFixture('<div textcontent.one-time>', {
textcontent: 'hey'
});
assertHtml('<div>hey</div>');
component.textcontent = 'ahh';
flush();
assertHtml('<div>hey</div>');
});

it('auto infers binding expression with .to-view', function () {
const { assertHtml, component, flush } = createFixture('<div textcontent.to-view>', {
textcontent: 'hey'
});
assertHtml('<div>hey</div>');
component.textcontent = 'ahh';
flush();
assertHtml('<div>ahh</div>');
});

it('auto infers binding expression with .two-way', function () {
const { assertValue, type, component } = createFixture('<input value.two-way>', {
value: 'hey'
});
assertValue('input', 'hey');
type('input', 'you');
assert.strictEqual(component.value, 'you');
});

it('auto infers binding expression with .from-view', function () {
const { assertValue, type, component, flush } = createFixture('<input value.from-view>', {
value: 'hey'
});
assertValue('input', '');
type('input', 'you');
assert.strictEqual(component.value, 'you');
component.value = 'ahh';
flush();
assertValue('input', 'you');
});

it('auto infers binding expression with .attr', function () {
const { assertHtml } = createFixture('<div hey-there.attr>', {
'hey-there': 1,
'heyThere': 2,
});
assertHtml('<div hey-there="2"></div>');
});

it('does not use mapped attribute name when inferring binding expression', function () {
const { assertHtml } = createFixture('<input minlength.bind>', {
minLength: 0,
minlength: 1,
});
assertHtml('<input minlength="1">');
});

it('infers expression with custom attribute', function () {
const { assertHtml } = createFixture('<div square.bind foo-bar.bind>', {
square: 2,
fooBar: 3,
}, [
CustomAttribute.define('square', class {
host = resolve(INode) as HTMLElement;
value: any;
binding() {
this.host.setAttribute('square', String(Number(this.value) * Number(this.value)));
}
}),
CustomAttribute.define('foo-bar', class {
host = resolve(INode) as HTMLElement;
value: any;
binding() {
this.host.setAttribute('random', String(this.value * 10));
}
}),
]);
assertHtml('<div square="4" random="30"></div>');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -1635,7 +1635,7 @@ describe('3-runtime-html/template-compiler.spec.ts', function () {

assert.deepStrictEqual(
result.instructions[0],
[new PropertyBindingInstruction(new PrimitiveLiteralExpression(''), 'id', BindingMode.toView)]
[new PropertyBindingInstruction(new AccessScopeExpression('id'), 'id', BindingMode.toView)]
);
});

Expand Down
42 changes: 13 additions & 29 deletions packages/template-compiler/src/binding-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
RefBindingInstruction,
SpreadBindingInstruction,
} from './instructions';
import { aliasRegistration, definitionTypeElement, etIsFunction, etIsProperty, isString, objectFreeze, singletonRegistration } from './utilities';
import { aliasRegistration, etIsFunction, etIsProperty, isString, objectFreeze, singletonRegistration } from './utilities';

import type {
Constructable,
Expand Down Expand Up @@ -214,17 +214,13 @@ export class OneTimeBindingCommand implements BindingCommandInstance {
const attr = info.attr;
let target = attr.target;
let value = info.attr.rawValue;
value = value === '' ? camelCase(target) : value;
if (info.bindable == null) {
target = attrMapper.map(info.node, target)
// if the mapper doesn't know how to map it
// use the default behavior, which is camel-casing
?? camelCase(target);
} else {
// if it looks like: <my-el value.bind>
// it means : <my-el value.bind="value">
if (value === '' && info.def.type === definitionTypeElement) {
value = camelCase(target);
}
target = info.bindable.name;
}
return new PropertyBindingInstruction(exprParser.parse(value, etIsProperty), target, InternalBindingMode.oneTime);
Expand All @@ -242,17 +238,13 @@ export class ToViewBindingCommand implements BindingCommandInstance {
const attr = info.attr;
let target = attr.target;
let value = info.attr.rawValue;
value = value === '' ? camelCase(target) : value;
if (info.bindable == null) {
target = attrMapper.map(info.node, target)
// if the mapper doesn't know how to map it
// use the default behavior, which is camel-casing
?? camelCase(target);
} else {
// if it looks like: <my-el value.bind>
// it means : <my-el value.bind="value">
if (value === '' && info.def.type === definitionTypeElement) {
value = camelCase(target);
}
target = info.bindable.name;
}
return new PropertyBindingInstruction(exprParser.parse(value, etIsProperty), target, InternalBindingMode.toView);
Expand All @@ -270,17 +262,13 @@ export class FromViewBindingCommand implements BindingCommandInstance {
const attr = info.attr;
let target = attr.target;
let value = attr.rawValue;
value = value === '' ? camelCase(target) : value;
if (info.bindable == null) {
target = attrMapper.map(info.node, target)
// if the mapper doesn't know how to map it
// use the default behavior, which is camel-casing
?? camelCase(target);
} else {
// if it looks like: <my-el value.bind>
// it means : <my-el value.bind="value">
if (value === '' && info.def.type === definitionTypeElement) {
value = camelCase(target);
}
target = info.bindable.name;
}
return new PropertyBindingInstruction(exprParser.parse(value, etIsProperty), target, InternalBindingMode.fromView);
Expand All @@ -298,17 +286,13 @@ export class TwoWayBindingCommand implements BindingCommandInstance {
const attr = info.attr;
let target = attr.target;
let value = attr.rawValue;
value = value === '' ? camelCase(target) : value;
if (info.bindable == null) {
target = attrMapper.map(info.node, target)
// if the mapper doesn't know how to map it
// use the default behavior, which is camel-casing
?? camelCase(target);
} else {
// if it looks like: <my-el value.bind>
// it means : <my-el value.bind="value">
if (value === '' && info.def.type === definitionTypeElement) {
value = camelCase(target);
}
target = info.bindable.name;
}
return new PropertyBindingInstruction(exprParser.parse(value, etIsProperty), target, InternalBindingMode.twoWay);
Expand All @@ -325,22 +309,18 @@ export class DefaultBindingCommand implements BindingCommandInstance {
public build(info: ICommandBuildInfo, exprParser: IExpressionParser, attrMapper: IAttrMapper): PropertyBindingInstruction {
const attr = info.attr;
const bindable = info.bindable;
let value = attr.rawValue;
let target = attr.target;
let defDefaultMode: string | number;
let mode: string | number;
let target = attr.target;
let value = attr.rawValue;
value = value === '' ? camelCase(target) : value;
if (bindable == null) {
mode = attrMapper.isTwoWay(info.node, target) ? InternalBindingMode.twoWay : InternalBindingMode.toView;
target = attrMapper.map(info.node, target)
// if the mapper doesn't know how to map it
// use the default behavior, which is camel-casing
?? camelCase(target);
} else {
// if it looks like: <my-el value.bind>
// it means : <my-el value.bind="value">
if (value === '' && info.def.type === definitionTypeElement) {
value = camelCase(target);
}
defDefaultMode = (info.def as IAttributeComponentDefinition).defaultBindingMode ?? 0;
mode = bindable.mode === 0 || bindable.mode == null
? defDefaultMode == null || defDefaultMode === 0
Expand Down Expand Up @@ -435,7 +415,11 @@ export class AttrBindingCommand implements BindingCommandInstance {
public get ignoreAttr() { return true; }

public build(info: ICommandBuildInfo, exprParser: IExpressionParser): IInstruction {
return new AttributeBindingInstruction(info.attr.target, exprParser.parse(info.attr.rawValue, etIsProperty), info.attr.target);
const attr = info.attr;
const target = attr.target;
let value = attr.rawValue;
value = value === '' ? camelCase(target) : value;
return new AttributeBindingInstruction(target, exprParser.parse(value, etIsProperty), target);
}
}

Expand Down

0 comments on commit 98b3d43

Please sign in to comment.