From eacc8e3803f6cb70a5d4ab2d0de22aa7d1c6a1f5 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Sat, 10 Oct 2015 14:28:05 -0700 Subject: [PATCH] fix(compiler): merge `class` and `style` attributes from the element with the host attributes Closes #4583 Closes #4680 --- .../src/core/compiler/command_compiler.ts | 42 ++++++++++++++++--- .../core/compiler/command_compiler_spec.ts | 42 +++++++++++++++++++ 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/modules/angular2/src/core/compiler/command_compiler.ts b/modules/angular2/src/core/compiler/command_compiler.ts index e79e4d6ca6..c5c0d6bbac 100644 --- a/modules/angular2/src/core/compiler/command_compiler.ts +++ b/modules/angular2/src/core/compiler/command_compiler.ts @@ -1,4 +1,4 @@ -import {isPresent, isBlank, Type, isString} from 'angular2/src/core/facade/lang'; +import {isPresent, isBlank, Type, isString, StringWrapper} from 'angular2/src/core/facade/lang'; import {SetWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection'; import { TemplateCmd, @@ -44,6 +44,8 @@ export var TEMPLATE_COMMANDS_MODULE_REF = moduleRef(`package:angular2/src/core/linker/template_commands${MODULE_SUFFIX}`); const IMPLICIT_TEMPLATE_VAR = '\$implicit'; +const CLASS_ATTR = 'class'; +const STYLE_ATTR = 'style'; @Injectable() export class CommandCompiler { @@ -211,14 +213,14 @@ class CommandBuilderVisitor implements TemplateAstVisitor { private _readAttrNameAndValues(directives: CompileDirectiveMetadata[], attrAsts: TemplateAst[]): string[] { - var attrNameAndValues: string[] = visitAndReturnContext(this, attrAsts, []); + var attrs = keyValueArrayToMap(visitAndReturnContext(this, attrAsts, [])); directives.forEach(directiveMeta => { StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => { - attrNameAndValues.push(name); - attrNameAndValues.push(value); + var prevValue = attrs[name]; + attrs[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value; }); }); - return removeKeyValueArrayDuplicates(attrNameAndValues); + return mapToKeyValueArray(attrs); } visitNgContent(ast: NgContentAst, context: any): any { @@ -328,6 +330,36 @@ function removeKeyValueArrayDuplicates(keyValueArray: string[]): string[] { return resultKeyValueArray; } +function keyValueArrayToMap(keyValueArr: string[]): {[key: string]: string} { + var data: {[key: string]: string} = {}; + for (var i = 0; i < keyValueArr.length; i += 2) { + data[keyValueArr[i]] = keyValueArr[i + 1]; + } + return data; +} + +function mapToKeyValueArray(data: {[key: string]: string}): string[] { + var entryArray = []; + StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); }); + // We need to sort to get a defined output order + // for tests and for caching generated artifacts... + ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0])); + var keyValueArray = []; + entryArray.forEach((entry) => { + keyValueArray.push(entry[0]); + keyValueArray.push(entry[1]); + }); + return keyValueArray; +} + +function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string { + if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) { + return `${attrValue1} ${attrValue2}`; + } else { + return attrValue2; + } +} + class DirectiveContext { constructor(public index: number, public eventTargetAndNames: string[], public targetVariableNameAndValues: any[], diff --git a/modules/angular2/test/core/compiler/command_compiler_spec.ts b/modules/angular2/test/core/compiler/command_compiler_spec.ts index fb27aeba6f..7c04b64934 100644 --- a/modules/angular2/test/core/compiler/command_compiler_spec.ts +++ b/modules/angular2/test/core/compiler/command_compiler_spec.ts @@ -218,6 +218,48 @@ export function main() { }); })); + it('should merge element attributes with host attributes', + inject([AsyncTestCompleter], (async) => { + var rootComp = createComp({ + type: RootCompTypeMeta, + template: '
' + }); + var dir = CompileDirectiveMetadata.create({ + selector: 'div', + isComponent: false, + type: SomeDirTypeMeta, + host: {'class': 'newclass', 'style': 'newstyle', 'role': 'newrole', 'attr2': ''} + }); + run(rootComp, [dir]) + .then((data) => { + expect(data).toEqual([ + [ + BEGIN_ELEMENT, + 'div', + [ + 'attr1', + '', + 'attr2', + '', + 'class', + 'origclass newclass', + 'role', + 'newrole', + 'style', + 'origstyle newstyle' + ], + [], + [], + ['SomeDirType'], + true, + null + ], + [END_ELEMENT] + ]); + async.done(); + }); + })); + it('should emulate style encapsulation', inject([AsyncTestCompleter], (async) => { var rootComp = createComp({ type: RootCompTypeMeta,