Skip to content
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
42 changes: 25 additions & 17 deletions modules/angular2/src/render/dom/compiler/selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var _SELECTOR_REGEXP = RegExpWrapper.create(
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
'(?:\\))|' + // ")"
'(\\))|' + // ")"
'(\\s*,\\s*)'); // ","

/**
Expand All @@ -29,11 +29,11 @@ export class CssSelector {
element: string;
classNames: List<string>;
attrs: List<string>;
notSelector: CssSelector;
notSelectors: List<CssSelector>;
static parse(selector: string): List<CssSelector> {
var results = ListWrapper.create();
var _addResult = (res, cssSel) => {
if (isPresent(cssSel.notSelector) && isBlank(cssSel.element) &&
if (cssSel.notSelectors.length > 0 && isBlank(cssSel.element) &&
ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
cssSel.element = "*";
}
Expand All @@ -43,13 +43,15 @@ export class CssSelector {
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
var match;
var current = cssSelector;
var inNot = false;
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
if (isPresent(match[1])) {
if (isPresent(cssSelector.notSelector)) {
if (inNot) {
throw new BaseException('Nesting :not is not allowed in a selector');
}
current.notSelector = new CssSelector();
current = current.notSelector;
inNot = true;
current = new CssSelector();
ListWrapper.push(cssSelector.notSelectors, current);
}
if (isPresent(match[2])) {
current.setElement(match[2]);
Expand All @@ -61,6 +63,13 @@ export class CssSelector {
current.addAttribute(match[4], match[5]);
}
if (isPresent(match[6])) {
inNot = false;
current = cssSelector;
}
if (isPresent(match[7])) {
if (inNot) {
throw new BaseException('Multiple selectors in :not are not supported');
}
_addResult(results, cssSelector);
cssSelector = current = new CssSelector();
}
Expand All @@ -73,12 +82,12 @@ export class CssSelector {
this.element = null;
this.classNames = ListWrapper.create();
this.attrs = ListWrapper.create();
this.notSelector = null;
this.notSelectors = ListWrapper.create();
}

isElementSelector(): boolean {
return isPresent(this.element) && ListWrapper.isEmpty(this.classNames) &&
ListWrapper.isEmpty(this.attrs) && isBlank(this.notSelector);
ListWrapper.isEmpty(this.attrs) && this.notSelectors.length === 0;
}

setElement(element: string = null) {
Expand Down Expand Up @@ -121,9 +130,8 @@ export class CssSelector {
res += ']';
}
}
if (isPresent(this.notSelector)) {
res += ":not(" + this.notSelector.toString() + ")";
}
ListWrapper.forEach(this.notSelectors,
(notSelector) => { res += ":not(" + notSelector.toString() + ")"; });
return res;
}
}
Expand All @@ -133,9 +141,9 @@ export class CssSelector {
* are contained in a given CssSelector.
*/
export class SelectorMatcher {
static createNotMatcher(notSelector: CssSelector) {
static createNotMatcher(notSelectors: List<CssSelector>) {
var notMatcher = new SelectorMatcher();
notMatcher._addSelectable(notSelector, null, null);
notMatcher.addSelectables(notSelectors, null);
return notMatcher;
}

Expand Down Expand Up @@ -357,22 +365,22 @@ class SelectorListContext {
// Store context to pass back selector and context when a selector is matched
class SelectorContext {
selector: CssSelector;
notSelector: CssSelector;
notSelectors: List<CssSelector>;
cbContext; // callback context
listContext: SelectorListContext;

constructor(selector: CssSelector, cbContext: any, listContext: SelectorListContext) {
this.selector = selector;
this.notSelector = selector.notSelector;
this.notSelectors = selector.notSelectors;
this.cbContext = cbContext;
this.listContext = listContext;
}

finalize(cssSelector: CssSelector, callback /*: (CssSelector, any) => void*/) {
var result = true;
if (isPresent(this.notSelector) &&
if (this.notSelectors.length > 0 &&
(isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
var notMatcher = SelectorMatcher.createNotMatcher(this.notSelector);
var notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors);
result = !notMatcher.match(cssSelector, null);
}
if (result && isPresent(callback) &&
Expand Down
22 changes: 17 additions & 5 deletions modules/angular2/test/render/dom/compiler/selector_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ export function main() {
expect(matched).toEqual([s1[0], 1, s2[0], 2, s3[0], 3, s4[0], 4]);
});

it('should match with multiple :not selectors', () => {
matcher.addSelectables(s1 = CssSelector.parse('div:not([a]):not([b])'), 1);
expect(matcher.match(CssSelector.parse('div[a]')[0], selectableCollector)).toBe(false);
expect(matcher.match(CssSelector.parse('div[b]')[0], selectableCollector)).toBe(false);
expect(matcher.match(CssSelector.parse('div[c]')[0], selectableCollector)).toBe(true);
});

it('should select with one match in a list', () => {
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);

Expand Down Expand Up @@ -256,7 +263,7 @@ export function main() {
expect(cssSelector.attrs.length).toEqual(0);
expect(cssSelector.classNames.length).toEqual(0);

var notSelector = cssSelector.notSelector;
var notSelector = cssSelector.notSelectors[0];
expect(notSelector.element).toEqual(null);
expect(notSelector.attrs).toEqual(['attrname', 'attrvalue']);
expect(notSelector.classNames).toEqual(['someclass']);
Expand All @@ -268,7 +275,7 @@ export function main() {
var cssSelector = CssSelector.parse(':not([attrname=attrvalue].someclass)')[0];
expect(cssSelector.element).toEqual("*");

var notSelector = cssSelector.notSelector;
var notSelector = cssSelector.notSelectors[0];
expect(notSelector.attrs).toEqual(['attrname', 'attrvalue']);
expect(notSelector.classNames).toEqual(['someclass']);

Expand All @@ -280,6 +287,11 @@ export function main() {
.toThrowError('Nesting :not is not allowed in a selector');
});

it('should throw when multiple selectors in :not', () => {
expect(() => { CssSelector.parse('sometag:not(a,b)'); })
.toThrowError('Multiple selectors in :not are not supported');
});

it('should detect lists of selectors', () => {
var cssSelectors = CssSelector.parse('.someclass,[attrname=attrvalue], sometag');
expect(cssSelectors.length).toEqual(3);
Expand All @@ -298,10 +310,10 @@ export function main() {
expect(cssSelectors[0].attrs).toEqual(['type', 'text']);

expect(cssSelectors[1].element).toEqual('*');
expect(cssSelectors[1].notSelector.element).toEqual('textarea');
expect(cssSelectors[1].notSelectors[0].element).toEqual('textarea');

expect(cssSelectors[2].element).toEqual('textbox');
expect(cssSelectors[2].notSelector.classNames).toEqual(['special']);
expect(cssSelectors[2].notSelectors[0].classNames).toEqual(['special']);
});
});
}
}