feat(query): added support for querying by var bindings

This commit is contained in:
vsavkin
2015-06-15 15:18:11 -07:00
parent cd21df3572
commit b0e2ebda70
12 changed files with 407 additions and 185 deletions

View File

@ -1,4 +1,4 @@
import {CONST, Type, stringify, isPresent} from 'angular2/src/facade/lang';
import {CONST, Type, stringify, isPresent, StringWrapper, isString} from 'angular2/src/facade/lang';
import {DependencyAnnotation} from 'angular2/src/di/annotations_impl';
import {resolveForwardRef} from 'angular2/di';
@ -55,12 +55,17 @@ export class Attribute extends DependencyAnnotation {
@CONST()
export class Query extends DependencyAnnotation {
descendants: boolean;
constructor(private _selector:Type, {descendants = false}: {descendants?: boolean} = {}) {
constructor(private _selector: Type | string,
{descendants = false}: {descendants?: boolean} = {}) {
super();
this.descendants = descendants;
}
get selector() { return resolveForwardRef(this._selector); }
get isVarBindingQuery(): boolean { return isString(this.selector); }
get varBindings(): List<string> { return StringWrapper.split(this.selector, new RegExp(",")); }
toString() { return `@Query(${stringify(this.selector)})`; }
}

View File

@ -13,7 +13,6 @@ export class ElementBinder {
constructor(public index: int, public parent: ElementBinder, public distanceToParent: int,
public protoElementInjector: eiModule.ProtoElementInjector,
public directiveVariableBindings: Map<string, number>,
public componentDirective: DirectiveBinding) {
if (isBlank(index)) {
throw new BaseException('null index not allowed.');

View File

@ -393,7 +393,8 @@ export class ProtoElementInjector {
_strategy: _ProtoElementInjectorStrategy;
static create(parent: ProtoElementInjector, index: number, bindings: List<ResolvedBinding>,
firstBindingIsComponent: boolean, distanceToParent: number) {
firstBindingIsComponent: boolean, distanceToParent: number,
directiveVariableBindings: Map<string, number>) {
var bd = [];
ProtoElementInjector._createDirectiveBindingData(bindings, bd, firstBindingIsComponent);
@ -401,7 +402,8 @@ export class ProtoElementInjector {
ProtoElementInjector._createViewInjectorBindingData(bindings, bd);
}
ProtoElementInjector._createHostInjectorBindingData(bindings, bd, firstBindingIsComponent);
return new ProtoElementInjector(parent, index, bd, distanceToParent, firstBindingIsComponent);
return new ProtoElementInjector(parent, index, bd, distanceToParent, firstBindingIsComponent,
directiveVariableBindings);
}
private static _createDirectiveBindingData(dirBindings: List<ResolvedBinding>,
@ -450,7 +452,8 @@ export class ProtoElementInjector {
}
constructor(public parent: ProtoElementInjector, public index: int, bd: List<BindingData>,
public distanceToParent: number, public _firstBindingIsComponent: boolean) {
public distanceToParent: number, public _firstBindingIsComponent: boolean,
public directiveVariableBindings: Map<string, number>) {
var length = bd.length;
this.eventEmitterAccessors = ListWrapper.createFixedSize(length);
this.hostActionAccessors = ListWrapper.createFixedSize(length);
@ -693,12 +696,15 @@ export class ElementInjector extends TreeNode<ElementInjector> {
}
onAllChangesDone(): void {
if (isPresent(this._query0) && this._query0.originator === this)
if (isPresent(this._query0) && this._query0.originator === this) {
this._query0.list.fireCallbacks();
if (isPresent(this._query1) && this._query1.originator === this)
}
if (isPresent(this._query1) && this._query1.originator === this) {
this._query1.list.fireCallbacks();
if (isPresent(this._query2) && this._query2.originator === this)
}
if (isPresent(this._query2) && this._query2.originator === this) {
this._query2.list.fireCallbacks();
}
}
hydrate(injector: Injector, host: ElementInjector, preBuiltObjects: PreBuiltObjects): void {
@ -716,9 +722,22 @@ export class ElementInjector extends TreeNode<ElementInjector> {
this._checkShadowDomAppInjector(this._shadowDomAppInjector);
this._strategy.hydrate();
this._addVarBindingsToQueries();
this.hydrated = true;
}
hasVariableBinding(name: string): boolean {
var vb = this._proto.directiveVariableBindings;
return isPresent(vb) && MapWrapper.contains(vb, name);
}
getVariableBinding(name: string): any {
var index = MapWrapper.get(this._proto.directiveVariableBindings, name);
return isPresent(index) ? this.getDirectiveAtIndex(<number>index) : this.getElementRef();
}
private _createShadowDomAppInjector(componentDirective: DirectiveBinding,
appInjector: Injector): Injector {
if (!ListWrapper.isEmpty(componentDirective.resolvedAppInjectables)) {
@ -752,6 +771,10 @@ export class ElementInjector extends TreeNode<ElementInjector> {
return this._proto.hostActionAccessors;
}
getDirectiveVariableBindings(): Map<string, number> {
return this._proto.directiveVariableBindings;
}
getComponent(): any { return this._strategy.getComponent(); }
getElementRef(): ElementRef {
@ -884,6 +907,23 @@ export class ElementInjector extends TreeNode<ElementInjector> {
}
}
private _addVarBindingsToQueries(): void {
this._addVarBindingsToQuery(this._query0);
this._addVarBindingsToQuery(this._query1);
this._addVarBindingsToQuery(this._query2);
}
private _addVarBindingsToQuery(queryRef: QueryRef): void {
if (isBlank(queryRef) || !queryRef.query.isVarBindingQuery) return;
var vb = queryRef.query.varBindings;
for (var i = 0; i < vb.length; ++i) {
if (this.hasVariableBinding(vb[i])) {
queryRef.list.add(this.getVariableBinding(vb[i]));
}
}
}
private _createQueryRef(query: Query): void {
var queryList = new QueryList<any>();
if (isBlank(this._query0)) {
@ -1454,13 +1494,32 @@ class QueryRef {
visit(inj: ElementInjector, aggregator: any[]): void {
if (isBlank(inj) || !inj._hasQuery(this)) return;
if (inj.hasDirective(this.query.selector)) {
aggregator.push(inj.get(this.query.selector));
if (this.query.isVarBindingQuery) {
this._aggregateVariableBindings(inj, aggregator);
} else {
this._aggregateDirective(inj, aggregator);
}
var child = inj._head;
while (isPresent(child)) {
this.visit(child, aggregator);
child = child._next;
}
}
private _aggregateVariableBindings(inj: ElementInjector, aggregator: List<any>): void {
var vb = this.query.varBindings;
for (var i = 0; i < vb.length; ++i) {
if (inj.hasVariableBinding(vb[i])) {
aggregator.push(inj.getVariableBinding(vb[i]));
}
}
}
private _aggregateDirective(inj: ElementInjector, aggregator: List<any>): void {
if (inj.hasDirective(this.query.selector)) {
aggregator.push(inj.get(this.query.selector));
}
}
}

View File

@ -336,9 +336,13 @@ function _createProtoElementInjector(binderIndex, parentPeiWithDistance, renderE
// so that, when hydrating, $implicit can be set to the element.
var hasVariables = MapWrapper.size(renderElementBinder.variableBindings) > 0;
if (directiveBindings.length > 0 || hasVariables) {
protoElementInjector = ProtoElementInjector.create(
parentPeiWithDistance.protoElementInjector, binderIndex, directiveBindings,
isPresent(componentDirectiveBinding), parentPeiWithDistance.distance);
var directiveVariableBindings =
createDirectiveVariableBindings(renderElementBinder, directiveBindings);
protoElementInjector =
ProtoElementInjector.create(parentPeiWithDistance.protoElementInjector, binderIndex,
directiveBindings, isPresent(componentDirectiveBinding),
parentPeiWithDistance.distance, directiveVariableBindings);
protoElementInjector.attributes = renderElementBinder.readAttributes;
}
return protoElementInjector;
@ -351,12 +355,8 @@ function _createElementBinder(protoView, boundElementIndex, renderElementBinder,
if (renderElementBinder.parentIndex !== -1) {
parent = protoView.elementBinders[renderElementBinder.parentIndex];
}
var directiveVariableBindings =
createDirectiveVariableBindings(renderElementBinder, directiveBindings);
var elBinder =
protoView.bindElement(parent, renderElementBinder.distanceToParent, protoElementInjector,
directiveVariableBindings, componentDirectiveBinding);
var elBinder = protoView.bindElement(parent, renderElementBinder.distanceToParent,
protoElementInjector, componentDirectiveBinding);
protoView.bindEvent(renderElementBinder.eventBindings, boundElementIndex, -1);
// variables
// The view's locals needs to have a full set of variable names at construction time
@ -371,7 +371,7 @@ function _createElementBinder(protoView, boundElementIndex, renderElementBinder,
export function createDirectiveVariableBindings(
renderElementBinder: renderApi.ElementBinder,
directiveBindings: List<DirectiveBinding>): Map<String, number> {
directiveBindings: List<DirectiveBinding>): Map<string, number> {
var directiveVariableBindings = MapWrapper.create();
MapWrapper.forEach(renderElementBinder.variableBindings, (templateName, exportAs) => {
var dirIndex = _findDirectiveIndexByExportAs(renderElementBinder, directiveBindings, exportAs);

View File

@ -177,11 +177,9 @@ export class AppProtoView {
bindElement(parent: ElementBinder, distanceToParent: int,
protoElementInjector: ProtoElementInjector,
directiveVariableBindings: Map<string, number>,
componentDirective: DirectiveBinding = null): ElementBinder {
var elBinder =
new ElementBinder(this.elementBinders.length, parent, distanceToParent,
protoElementInjector, directiveVariableBindings, componentDirective);
var elBinder = new ElementBinder(this.elementBinders.length, parent, distanceToParent,
protoElementInjector, componentDirective);
this.elementBinders.push(elBinder);
return elBinder;

View File

@ -149,28 +149,30 @@ export class AppViewManagerUtils {
var binders = view.proto.elementBinders;
for (var i = 0; i < binders.length; ++i) {
var binder = binders[i];
var elementInjector = view.elementInjectors[i];
if (isPresent(elementInjector)) {
elementInjector.hydrate(appInjector, hostElementInjector, view.preBuiltObjects[i]);
this._populateViewLocals(view, elementInjector);
this._setUpEventEmitters(view, elementInjector, i);
this._setUpHostActions(view, elementInjector, i);
if (isPresent(binder.directiveVariableBindings)) {
MapWrapper.forEach(binder.directiveVariableBindings, (directiveIndex, name) => {
if (isBlank(directiveIndex)) {
view.locals.set(name, elementInjector.getElementRef().domElement);
} else {
view.locals.set(name, elementInjector.getDirectiveAtIndex(directiveIndex));
}
});
}
}
}
view.changeDetector.hydrate(view.context, view.locals, view);
}
_populateViewLocals(view: viewModule.AppView, elementInjector: eli.ElementInjector): void {
if (isPresent(elementInjector.getDirectiveVariableBindings())) {
MapWrapper.forEach(elementInjector.getDirectiveVariableBindings(), (directiveIndex, name) => {
if (isBlank(directiveIndex)) {
view.locals.set(name, elementInjector.getElementRef().domElement);
} else {
view.locals.set(name, elementInjector.getDirectiveAtIndex(directiveIndex));
}
});
}
}
_getOrCreateViewContainer(parentView: viewModule.AppView, boundElementIndex: number) {
var viewContainer = parentView.viewContainers[boundElementIndex];
if (isBlank(viewContainer)) {