fix(compiler): Allow components to use any style of selector. Fixes #1602
This commit is contained in:
parent
4422819754
commit
c20a5d65d8
|
@ -20,6 +20,7 @@ import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
|
||||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||||
import {Parser} from 'angular2/src/change_detection/change_detection';
|
import {Parser} from 'angular2/src/change_detection/change_detection';
|
||||||
import * as pvm from '../view/proto_view_merger';
|
import * as pvm from '../view/proto_view_merger';
|
||||||
|
import {CssSelector} from './selector';
|
||||||
import {DOCUMENT_TOKEN, APP_ID_TOKEN} from '../dom_tokens';
|
import {DOCUMENT_TOKEN, APP_ID_TOKEN} from '../dom_tokens';
|
||||||
import {Inject} from 'angular2/di';
|
import {Inject} from 'angular2/di';
|
||||||
import {SharedStylesHost} from '../view/shared_styles_host';
|
import {SharedStylesHost} from '../view/shared_styles_host';
|
||||||
|
@ -50,7 +51,7 @@ export class DomCompiler extends RenderCompiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileHost(directiveMetadata: DirectiveMetadata): Promise<ProtoViewDto> {
|
compileHost(directiveMetadata: DirectiveMetadata): Promise<ProtoViewDto> {
|
||||||
var hostViewDef = new ViewDefinition({
|
let hostViewDef = new ViewDefinition({
|
||||||
componentId: directiveMetadata.id,
|
componentId: directiveMetadata.id,
|
||||||
templateAbsUrl: null, template: null,
|
templateAbsUrl: null, template: null,
|
||||||
styles: null,
|
styles: null,
|
||||||
|
@ -58,10 +59,12 @@ export class DomCompiler extends RenderCompiler {
|
||||||
directives: [directiveMetadata],
|
directives: [directiveMetadata],
|
||||||
encapsulation: ViewEncapsulation.NONE
|
encapsulation: ViewEncapsulation.NONE
|
||||||
});
|
});
|
||||||
return this._compileView(
|
|
||||||
hostViewDef, new TemplateAndStyles(
|
let selector = CssSelector.parse(directiveMetadata.selector)[0];
|
||||||
`<${directiveMetadata.selector}></${directiveMetadata.selector}>`, []),
|
let hostTemplate = selector.getMatchingElementTemplate();
|
||||||
ViewType.HOST);
|
let templateAndStyles = new TemplateAndStyles(hostTemplate, []);
|
||||||
|
|
||||||
|
return this._compileView(hostViewDef, templateAndStyles, ViewType.HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeProtoViewsRecursively(
|
mergeProtoViewsRecursively(
|
||||||
|
|
|
@ -24,21 +24,12 @@ export class DirectiveParser implements CompileStep {
|
||||||
for (var i = 0; i < _directives.length; i++) {
|
for (var i = 0; i < _directives.length; i++) {
|
||||||
var directive = _directives[i];
|
var directive = _directives[i];
|
||||||
var selector = CssSelector.parse(directive.selector);
|
var selector = CssSelector.parse(directive.selector);
|
||||||
this._ensureComponentOnlyHasElementSelector(selector, directive);
|
|
||||||
this._selectorMatcher.addSelectables(selector, i);
|
this._selectorMatcher.addSelectables(selector, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processStyle(style: string): string { return style; }
|
processStyle(style: string): string { return style; }
|
||||||
|
|
||||||
_ensureComponentOnlyHasElementSelector(selector, directive) {
|
|
||||||
var isElementSelector = selector.length === 1 && selector[0].isElementSelector();
|
|
||||||
if (!isElementSelector && directive.type === DirectiveMetadata.COMPONENT_TYPE) {
|
|
||||||
throw new BaseException(
|
|
||||||
`Component '${directive.id}' can only have an element selector, but had '${directive.selector}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
|
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
|
||||||
var attrs = current.attrs();
|
var attrs = current.attrs();
|
||||||
var classList = current.classList();
|
var classList = current.classList();
|
||||||
|
|
|
@ -91,6 +91,21 @@ export class CssSelector {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Gets a template string for an element that matches the selector. */
|
||||||
|
getMatchingElementTemplate(): string {
|
||||||
|
let tagName = isPresent(this.element) ? this.element : 'div';
|
||||||
|
let classAttr = this.classNames.length > 0 ? ` class="${this.classNames.join(' ')}"` : '';
|
||||||
|
|
||||||
|
let attrs = '';
|
||||||
|
for (let i = 0; i < this.attrs.length; i += 2) {
|
||||||
|
let attrName = this.attrs[i];
|
||||||
|
let attrValue = this.attrs[i + 1] !== '' ? `="${this.attrs[i + 1]}"` : '';
|
||||||
|
attrs += ` ${attrName}${attrValue}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<${tagName}${classAttr}${attrs}></${tagName}>`;
|
||||||
|
}
|
||||||
|
|
||||||
addAttribute(name: string, value: string = _EMPTY_ATTR_VALUE) {
|
addAttribute(name: string, value: string = _EMPTY_ATTR_VALUE) {
|
||||||
this.attrs.push(name.toLowerCase());
|
this.attrs.push(name.toLowerCase());
|
||||||
if (isPresent(value)) {
|
if (isPresent(value)) {
|
||||||
|
|
|
@ -89,6 +89,27 @@ export function runCompilerCommonTests() {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should create element from component selector', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var compiler = createCompiler((parent, current, control) => {
|
||||||
|
current.inheritedProtoView.bindVariable('b', 'a');
|
||||||
|
});
|
||||||
|
|
||||||
|
var dirMetadata = DirectiveMetadata.create({
|
||||||
|
id: 'id',
|
||||||
|
selector: 'marquee.jazzy[size=huge]',
|
||||||
|
type: DirectiveMetadata.COMPONENT_TYPE
|
||||||
|
});
|
||||||
|
|
||||||
|
compiler.compileHost(dirMetadata)
|
||||||
|
.then((protoView) => {
|
||||||
|
let element = DOM.firstChild(DOM.content(templateRoot(protoView)));
|
||||||
|
expect(DOM.tagName(element).toLowerCase()).toEqual('marquee');
|
||||||
|
expect(DOM.hasClass(element, 'jazzy')).toBe(true);
|
||||||
|
expect(DOM.getAttribute(element, 'size')).toEqual('huge');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should use the inline template and compile in sync',
|
it('should use the inline template and compile in sync',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
var compiler = createCompiler(EMPTY_STEP);
|
var compiler = createCompiler(EMPTY_STEP);
|
||||||
|
|
|
@ -174,12 +174,6 @@ export function main() {
|
||||||
expect(results[0].componentId).toEqual('someComponent');
|
expect(results[0].componentId).toEqual('someComponent');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when the provided selector is not an element selector', () => {
|
|
||||||
expect(() => { createPipeline(null, [componentWithNonElementSelector]); })
|
|
||||||
.toThrowError(
|
|
||||||
`Component 'componentWithNonElementSelector' can only have an element selector, but had '[attr]'`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not allow multiple component directives on the same element', () => {
|
it('should not allow multiple component directives on the same element', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
process(el('<some-comp></some-comp>'), null, [someComponent, someComponentDup]);
|
process(el('<some-comp></some-comp>'), null, [someComponent, someComponentDup]);
|
||||||
|
|
|
@ -335,4 +335,27 @@ export function main() {
|
||||||
expect(cssSelectors[2].notSelectors[0].classNames).toEqual(['special']);
|
expect(cssSelectors[2].notSelectors[0].classNames).toEqual(['special']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('CssSelector.getMatchingElementTemplate', () => {
|
||||||
|
it('should create an element with a tagName, classes, and attributes', () => {
|
||||||
|
let selector = CssSelector.parse('blink.neon.hotpink[sweet][dismissable=false]')[0];
|
||||||
|
let template = selector.getMatchingElementTemplate();
|
||||||
|
|
||||||
|
expect(template).toEqual('<blink class="neon hotpink" sweet dismissable="false"></blink>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an element without a tag name', () => {
|
||||||
|
let selector = CssSelector.parse('[fancy]')[0];
|
||||||
|
let template = selector.getMatchingElementTemplate();
|
||||||
|
|
||||||
|
expect(template).toEqual('<div fancy></div>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore :not selectors', () => {
|
||||||
|
let selector = CssSelector.parse('grape:not(.red)')[0];
|
||||||
|
let template = selector.getMatchingElementTemplate();
|
||||||
|
|
||||||
|
expect(template).toEqual('<grape></grape>');
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue