refactor(ivy): ensure directive host bindings use the styling algorithm (#27134)

PR Close #27134
This commit is contained in:
Matias Niemelä
2018-11-16 15:22:12 +01:00
committed by Misko Hevery
parent 92e80af875
commit b5dbf5154e
6 changed files with 391 additions and 78 deletions

View File

@ -12,7 +12,7 @@ import {CompileReflector} from '../../compile_reflector';
import {BindingForm, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
import {ConstantPool, DefinitionKind} from '../../constant_pool';
import * as core from '../../core';
import {ParsedEvent} from '../../expression_parser/ast';
import {AST, ParsedEvent} from '../../expression_parser/ast';
import {LifecycleHooks} from '../../lifecycle_reflector';
import * as o from '../../output/output_ast';
import {typeSourceSpan} from '../../parse_util';
@ -27,6 +27,7 @@ import {Render3ParseResult} from '../r3_template_transform';
import {typeWithParameters} from '../util';
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
import {StylingBuilder, StylingInstruction} from './styling';
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, renderFlagCheckIfStmt} from './template';
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, mapToExpression, temporaryAllocator} from './util';
@ -65,23 +66,48 @@ function baseDirectiveFields(
// Initialize hostVars to number of bound host properties (interpolations illegal)
let hostVars = Object.keys(meta.host.properties).length;
const elVarExp = o.variable('elIndex');
const dirVarExp = o.variable('dirIndex');
const styleBuilder = new StylingBuilder(elVarExp, dirVarExp);
const allOtherAttributes: any = {};
const attrNames = Object.getOwnPropertyNames(meta.host.attributes);
for (let i = 0; i < attrNames.length; i++) {
const attr = attrNames[i];
const value = meta.host.attributes[attr];
switch (attr) {
// style attributes are handled in the styling context
case 'style':
styleBuilder.registerStyleAttr(value);
break;
// class attributes are handled in the styling context
case 'class':
styleBuilder.registerClassAttr(value);
break;
default:
allOtherAttributes[attr] = value;
break;
}
}
// e.g. `attributes: ['role', 'listbox']`
definitionMap.set('attributes', createHostAttributesArray(allOtherAttributes));
// e.g. `hostBindings: (dirIndex, elIndex) => { ... }
definitionMap.set(
'hostBindings',
createHostBindingsFunction(meta, bindingParser, constantPool, (slots: number) => {
const originalSlots = hostVars;
hostVars += slots;
return originalSlots;
}));
createHostBindingsFunction(
meta, elVarExp, dirVarExp, styleBuilder, bindingParser, constantPool, (slots: number) => {
const originalSlots = hostVars;
hostVars += slots;
return originalSlots;
}));
if (hostVars) {
// e.g. `hostVars: 2
definitionMap.set('hostVars', o.literal(hostVars));
}
// e.g. `attributes: ['role', 'listbox']`
definitionMap.set('attributes', createHostAttributesArray(meta));
// e.g 'inputs: {a: 'a'}`
definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs));
@ -466,9 +492,8 @@ function createDirectiveSelector(selector: string): o.Expression {
return asLiteral(core.parseSelectorToR3Selector(selector));
}
function createHostAttributesArray(meta: R3DirectiveMetadata): o.Expression|null {
function createHostAttributesArray(attributes: any): o.Expression|null {
const values: o.Expression[] = [];
const attributes = meta.host.attributes;
for (let key of Object.getOwnPropertyNames(attributes)) {
const value = attributes[key];
values.push(o.literal(key), o.literal(value));
@ -611,7 +636,8 @@ function createViewQueriesFunction(
// Return a host binding function or null if one is not necessary.
function createHostBindingsFunction(
meta: R3DirectiveMetadata, bindingParser: BindingParser, constantPool: ConstantPool,
meta: R3DirectiveMetadata, elVarExp: o.ReadVarExpr, dirVarExp: o.ReadVarExpr,
styleBuilder: StylingBuilder, bindingParser: BindingParser, constantPool: ConstantPool,
allocatePureFunctionSlots: (slots: number) => number): o.Expression|null {
const statements: o.Statement[] = [];
@ -621,7 +647,13 @@ function createHostBindingsFunction(
// Calculate the host property bindings
const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan);
const bindingContext = o.importExpr(R3.load).callFn([o.variable('dirIndex')]);
const bindingContext = o.importExpr(R3.load).callFn([dirVarExp]);
const bindingFn = (implicit: any, value: AST) => {
return convertPropertyBinding(
null, implicit, value, 'b', BindingForm.TrySimple, () => error('Unexpected interpolation'));
};
if (bindings) {
const valueConverter = new ValueConverter(
constantPool,
@ -629,22 +661,43 @@ function createHostBindingsFunction(
/* pipes are illegal here */ () => error('Unexpected pipe'));
for (const binding of bindings) {
// resolve literal arrays and literal objects
const value = binding.expression.visit(valueConverter);
const bindingExpr = convertPropertyBinding(
null, bindingContext, value, 'b', BindingForm.TrySimple,
() => error('Unexpected interpolation'));
const name = binding.name;
const stylePrefix = name.substring(0, 5).toLowerCase();
if (stylePrefix === 'style') {
const {propertyName, unit} = parseNamedProperty(name);
styleBuilder.registerStyleInput(propertyName, binding.expression, unit, binding.sourceSpan);
} else if (stylePrefix === 'class') {
styleBuilder.registerClassInput(
parseNamedProperty(name).propertyName, binding.expression, binding.sourceSpan);
} else {
// resolve literal arrays and literal objects
const value = binding.expression.visit(valueConverter);
const bindingExpr = bindingFn(bindingContext, value);
const {bindingName, instruction} = getBindingNameAndInstruction(binding.name);
const {bindingName, instruction} = getBindingNameAndInstruction(name);
statements.push(...bindingExpr.stmts);
statements.push(o.importExpr(instruction)
.callFn([
o.variable('elIndex'),
o.literal(bindingName),
o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]),
])
.toStmt());
statements.push(...bindingExpr.stmts);
statements.push(o.importExpr(instruction)
.callFn([
elVarExp,
o.literal(bindingName),
o.importExpr(R3.bind).callFn([bindingExpr.currValExpr]),
])
.toStmt());
}
}
if (styleBuilder.hasBindingsOrInitialValues) {
const createInstruction = styleBuilder.buildCreateLevelInstruction(null, constantPool);
if (createInstruction) {
const createStmt = createStylingStmt(createInstruction, bindingContext, bindingFn);
statements.push(createStmt);
}
styleBuilder.buildUpdateLevelInstructions(valueConverter).forEach(instruction => {
const updateStmt = createStylingStmt(instruction, bindingContext, bindingFn);
statements.push(updateStmt);
});
}
}
@ -652,8 +705,8 @@ function createHostBindingsFunction(
const typeName = meta.name;
return o.fn(
[
new o.FnParam('dirIndex', o.NUMBER_TYPE),
new o.FnParam('elIndex', o.NUMBER_TYPE),
new o.FnParam(dirVarExp.name !, o.NUMBER_TYPE),
new o.FnParam(elVarExp.name !, o.NUMBER_TYPE),
],
statements, o.INFERRED_TYPE, null, typeName ? `${typeName}_HostBindings` : null);
}
@ -661,6 +714,14 @@ function createHostBindingsFunction(
return null;
}
function createStylingStmt(
instruction: StylingInstruction, bindingContext: any, bindingFn: Function): o.Statement {
const params = instruction.buildParams(value => bindingFn(bindingContext, value).currValExpr);
return o.importExpr(instruction.reference, null, instruction.sourceSpan)
.callFn(params, instruction.sourceSpan)
.toStmt();
}
function getBindingNameAndInstruction(bindingName: string):
{bindingName: string, instruction: o.ExternalReference} {
let instruction !: o.ExternalReference;
@ -768,3 +829,19 @@ function compileStyles(styles: string[], selector: string, hostSelector: string)
const shadowCss = new ShadowCss();
return styles.map(style => { return shadowCss !.shimCssText(style, selector, hostSelector); });
}
function parseNamedProperty(name: string): {propertyName: string, unit: string} {
let unit = '';
let propertyName = '';
const index = name.indexOf('.');
if (index > 0) {
const unitIndex = name.lastIndexOf('.');
if (unitIndex !== index) {
unit = name.substring(unitIndex + 1, name.length);
propertyName = name.substring(index + 1, unitIndex);
} else {
propertyName = name.substring(index + 1, name.length);
}
}
return {propertyName, unit};
}