feat(selector): support , for multiple targets

Fixes #867
Closes #1019
This commit is contained in:
Marc Laval
2015-03-19 17:01:42 +01:00
parent 0fb9f3bd6c
commit 41b53e71e1
6 changed files with 172 additions and 87 deletions

View File

@ -248,7 +248,7 @@ export class Directive extends Injectable {
* - `[attribute]`: select by attribute name.
* - `[attribute=value]`: select by attribute name and value.
* - `:not(sub_selector)`: select only if the element does not match the `sub_selector`.
* - `selector1, selector2`: select if either `selector1` or `selector2` matches. [TO BE IMPLMENTED]
* - `selector1, selector2`: select if either `selector1` or `selector2` matches.
*
*
* ## Example

View File

@ -36,8 +36,8 @@ export class DirectiveParser extends CompileStep {
this._selectorMatcher = new SelectorMatcher();
for (var i=0; i<directives.length; i++) {
var directiveMetadata = directives[i];
selector=CssSelector.parse(directiveMetadata.annotation.selector);
this._selectorMatcher.addSelectable(selector, directiveMetadata);
selector = CssSelector.parse(directiveMetadata.annotation.selector);
this._selectorMatcher.addSelectables(selector, directiveMetadata);
}
}

View File

@ -9,7 +9,9 @@ var _SELECTOR_REGEXP =
RegExpWrapper.create('(\\:not\\()|' + //":not("
'([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
'(?:\\))|' + // ")"
'(\\s*,\\s*)'); // ","
/**
* A css selector contains an element name,
@ -21,7 +23,15 @@ export class CssSelector {
classNames:List;
attrs:List;
notSelector: CssSelector;
static parse(selector:string): CssSelector {
static parse(selector:string): List<CssSelector> {
var results = ListWrapper.create();
var _addResult = (res, cssSel) => {
if (isPresent(cssSel.notSelector) && isBlank(cssSel.element)
&& ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
cssSel.element = "*";
}
ListWrapper.push(res, cssSel);
}
var cssSelector = new CssSelector();
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
var match;
@ -43,13 +53,13 @@ export class CssSelector {
if (isPresent(match[4])) {
current.addAttribute(match[4], match[5]);
}
if (isPresent(match[6])) {
_addResult(results, cssSelector);
cssSelector = current = new CssSelector();
}
}
if (isPresent(cssSelector.notSelector) && isBlank(cssSelector.element)
&& ListWrapper.isEmpty(cssSelector.classNames) && ListWrapper.isEmpty(cssSelector.attrs)) {
cssSelector.element = "*";
}
return cssSelector;
_addResult(results, cssSelector);
return results;
}
constructor() {
@ -119,6 +129,7 @@ export class SelectorMatcher {
_classPartialMap:Map;
_attrValueMap:Map;
_attrValuePartialMap:Map;
_listContexts:List;
constructor() {
this._elementMap = MapWrapper.create();
this._elementPartialMap = MapWrapper.create();
@ -128,6 +139,19 @@ export class SelectorMatcher {
this._attrValueMap = MapWrapper.create();
this._attrValuePartialMap = MapWrapper.create();
this._listContexts = ListWrapper.create();
}
addSelectables(cssSelectors:List<CssSelector>, callbackCtxt) {
var listContext = null;
if (cssSelectors.length > 1) {
listContext= new SelectorListContext(cssSelectors);
ListWrapper.push(this._listContexts, listContext);
}
for (var i = 0; i < cssSelectors.length; i++) {
this.addSelectable(cssSelectors[i], callbackCtxt, listContext);
}
}
/**
@ -135,12 +159,12 @@ export class SelectorMatcher {
* @param cssSelector A css selector
* @param callbackCtxt An opaque object that will be given to the callback of the `match` function
*/
addSelectable(cssSelector:CssSelector, callbackCtxt) {
addSelectable(cssSelector, callbackCtxt, listContext: SelectorListContext) {
var matcher = this;
var element = cssSelector.element;
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
var selectable = new SelectorContext(cssSelector, callbackCtxt);
var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
if (isPresent(element)) {
@ -215,6 +239,10 @@ export class SelectorMatcher {
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
for (var i = 0; i < this._listContexts.length; i++) {
this._listContexts[i].alreadyMatched = false;
}
result = this._matchTerminal(this._elementMap, element, cssSelector, matchedCallback) || result;
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) || result;
@ -282,26 +310,41 @@ export class SelectorMatcher {
}
class SelectorListContext {
selectors: List<CssSelector>;
alreadyMatched: boolean;
constructor(selectors:List<CssSelector>) {
this.selectors = selectors;
this.alreadyMatched = false;
}
}
// Store context to pass back selector and context when a selector is matched
class SelectorContext {
selector:CssSelector;
notSelector:CssSelector;
cbContext; // callback context
listContext: SelectorListContext;
constructor(selector:CssSelector, cbContext) {
constructor(selector:CssSelector, cbContext, listContext: SelectorListContext) {
this.selector = selector;
this.notSelector = selector.notSelector;
this.cbContext = cbContext;
this.listContext = listContext;
}
finalize(cssSelector: CssSelector, callback) {
var result = true;
if (isPresent(this.notSelector)) {
if (isPresent(this.notSelector) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
var notMatcher = new SelectorMatcher();
notMatcher.addSelectable(this.notSelector, null);
notMatcher.addSelectable(this.notSelector, null, null);
result = !notMatcher.match(cssSelector, null);
}
if (result && isPresent(callback)) {
if (result && isPresent(callback) && (isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
if (isPresent(this.listContext)) {
this.listContext.alreadyMatched = true;
}
callback(this.selector, this.cbContext);
}
return result;

View File

@ -52,7 +52,7 @@ export class Parse5DomAdapter extends DomAdapter {
}
};
var matcher = new SelectorMatcher();
matcher.addSelectable(CssSelector.parse(selector));
matcher.addSelectables(CssSelector.parse(selector));
_recursive(res, el, selector, matcher);
return res;
}
@ -64,7 +64,7 @@ export class Parse5DomAdapter extends DomAdapter {
var result = false;
if (matcher == null) {
matcher = new SelectorMatcher();
matcher.addSelectable(CssSelector.parse(selector));
matcher.addSelectables(CssSelector.parse(selector));
}
var cssSelector = new CssSelector();