diff --git a/modules/angular2/change_detection.js b/modules/angular2/change_detection.js index 4501f2a65e..9e62690601 100644 --- a/modules/angular2/change_detection.js +++ b/modules/angular2/change_detection.js @@ -18,7 +18,7 @@ export {ProtoChangeDetector, ChangeDispatcher, ChangeDetector, ChangeDetection} export {CHECK_ONCE, CHECK_ALWAYS, DETACHED, CHECKED, ON_PUSH, DEFAULT} from './src/change_detection/constants'; export {DynamicProtoChangeDetector, JitProtoChangeDetector} from './src/change_detection/proto_change_detector'; export {BindingRecord} from './src/change_detection/binding_record'; -export {DirectiveRecord} from './src/change_detection/directive_record'; +export {DirectiveIndex, DirectiveRecord} from './src/change_detection/directive_record'; export {DynamicChangeDetector} from './src/change_detection/dynamic_change_detector'; export {ChangeDetectorRef} from './src/change_detection/change_detector_ref'; export {PipeRegistry} from './src/change_detection/pipes/pipe_registry'; diff --git a/modules/angular2/src/change_detection/binding_record.js b/modules/angular2/src/change_detection/binding_record.js index ddcb8c67dd..dbfd815ab9 100644 --- a/modules/angular2/src/change_detection/binding_record.js +++ b/modules/angular2/src/change_detection/binding_record.js @@ -1,7 +1,7 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {SetterFn} from 'angular2/src/reflection/types'; import {AST} from './parser/ast'; -import {DirectiveRecord} from './directive_record'; +import {DirectiveIndex, DirectiveRecord} from './directive_record'; const DIRECTIVE="directive"; const ELEMENT="element"; @@ -11,14 +11,16 @@ export class BindingRecord { mode:string; ast:AST; + implicitReceiver:any; //number | DirectiveIndex elementIndex:number; propertyName:string; setter:SetterFn; directiveRecord:DirectiveRecord; - constructor(mode:string, ast:AST, elementIndex:number, propertyName:string, setter:SetterFn, directiveRecord:DirectiveRecord) { + constructor(mode:string, implicitReceiver:any, ast:AST, elementIndex:number, propertyName:string, setter:SetterFn, directiveRecord:DirectiveRecord) { this.mode = mode; + this.implicitReceiver = implicitReceiver; this.ast = ast; this.elementIndex = elementIndex; @@ -49,14 +51,18 @@ export class BindingRecord { } static createForDirective(ast:AST, propertyName:string, setter:SetterFn, directiveRecord:DirectiveRecord) { - return new BindingRecord(DIRECTIVE, ast, 0, propertyName, setter, directiveRecord); + return new BindingRecord(DIRECTIVE, 0, ast, 0, propertyName, setter, directiveRecord); } static createForElement(ast:AST, elementIndex:number, propertyName:string) { - return new BindingRecord(ELEMENT, ast, elementIndex, propertyName, null, null); + return new BindingRecord(ELEMENT, 0, ast, elementIndex, propertyName, null, null); + } + + static createForHostProperty(directiveIndex:DirectiveIndex, ast:AST, propertyName:string) { + return new BindingRecord(ELEMENT, directiveIndex, ast, directiveIndex.elementIndex, propertyName, null, null); } static createForTextNode(ast:AST, elementIndex:number) { - return new BindingRecord(TEXT_NODE, ast, elementIndex, null, null, null); + return new BindingRecord(TEXT_NODE, 0, ast, elementIndex, null, null, null); } } \ No newline at end of file diff --git a/modules/angular2/src/change_detection/change_detection_jit_generator.es6 b/modules/angular2/src/change_detection/change_detection_jit_generator.es6 index d53b0b43ee..07bdd8f570 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.es6 +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.es6 @@ -3,7 +3,7 @@ import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/faca import {AbstractChangeDetector} from './abstract_change_detector'; import {ChangeDetectionUtil} from './change_detection_util'; -import {DirectiveRecord} from './directive_record'; +import {DirectiveIndex, DirectiveRecord} from './directive_record'; import { ProtoRecord, @@ -81,12 +81,12 @@ function hydrateTemplate(type:string, mode:string, fieldDefinitions:string, pipe directiveFieldNames:List, detectorFieldNames:List):string { var directiveInit = ""; for(var i = 0; i < directiveFieldNames.length; ++i) { - directiveInit += `${directiveFieldNames[i]} = directives.getDirectiveFor(this.directiveRecords[${i}]);\n`; + directiveInit += `${directiveFieldNames[i]} = directives.getDirectiveFor(this.directiveRecords[${i}].directiveIndex);\n`; } var detectorInit = ""; for(var i = 0; i < detectorFieldNames.length; ++i) { - detectorInit += `${detectorFieldNames[i]} = directives.getDetectorFor(this.directiveRecords[${i}]);\n`; + detectorInit += `${detectorFieldNames[i]} = directives.getDetectorFor(this.directiveRecords[${i}].directiveIndex);\n`; } return ` @@ -313,18 +313,18 @@ export class ChangeDetectorJITGenerator { } getDirectiveFieldNames():List { - return this.directiveRecords.map((d) => this.getDirective(d)); + return this.directiveRecords.map((d) => this.getDirective(d.directiveIndex)); } getDetectorFieldNames():List { - return this.directiveRecords.filter(r => r.isOnPushChangeDetection()).map((d) => this.getDetector(d)); + return this.directiveRecords.filter(r => r.isOnPushChangeDetection()).map((d) => this.getDetector(d.directiveIndex)); } - getDirective(d:DirectiveRecord) { + getDirective(d:DirectiveIndex) { return `this.directive_${d.name}`; } - getDetector(d:DirectiveRecord) { + getDetector(d:DirectiveIndex) { return `this.detector_${d.name}`; } @@ -359,7 +359,7 @@ export class ChangeDetectorJITGenerator { for (var i = dirs.length - 1; i >= 0; --i) { var dir = dirs[i]; if (dir.callOnAllChangesDone) { - var directive = `this.directive_${dir.name}`; + var directive = `this.directive_${dir.directiveIndex.name}`; notifications.push(onAllChangesDoneTemplate(directive)); } } @@ -425,7 +425,7 @@ export class ChangeDetectorJITGenerator { } genUpdateCurrentValue(r:ProtoRecord):string { - var context = this.localNames[r.contextIndex]; + var context = this.getContext(r); var newValue = this.localNames[r.selfIndex]; var args = this.genArgs(r); @@ -463,6 +463,14 @@ export class ChangeDetectorJITGenerator { } } + getContext(r:ProtoRecord):string { + if (r.contextIndex == -1) { + return this.getDirective(r.directiveIndex); + } else { + return this.localNames[r.contextIndex]; + } + } + ifChangedGuard(r:ProtoRecord, body:string):string { return ifChangedGuardTemplate(r.args.map((a) => this.changeNames[a]), body); } @@ -491,7 +499,7 @@ export class ChangeDetectorJITGenerator { var br = r.bindingRecord; if (br.isDirective()) { - var directiveProperty = `${this.getDirective(br.directiveRecord)}.${br.propertyName}`; + var directiveProperty = `${this.getDirective(br.directiveRecord.directiveIndex)}.${br.propertyName}`; return updateDirectiveTemplate(oldValue, newValue, directiveProperty); } else { return updateElementTemplate(oldValue, newValue); @@ -513,7 +521,7 @@ export class ChangeDetectorJITGenerator { genNotifyOnChanges(r:ProtoRecord):string{ var br = r.bindingRecord; if (r.lastInDirective && br.callOnChange()) { - return notifyOnChangesTemplate(this.getDirective(br.directiveRecord)); + return notifyOnChangesTemplate(this.getDirective(br.directiveRecord.directiveIndex)); } else { return ""; } @@ -522,7 +530,7 @@ export class ChangeDetectorJITGenerator { genNotifyOnPushDetectors(r:ProtoRecord):string{ var br = r.bindingRecord; if (r.lastInDirective && br.isOnPushChangeDetection()) { - return notifyOnPushDetectorsTemplate(this.getDetector(br.directiveRecord)); + return notifyOnPushDetectorsTemplate(this.getDetector(br.directiveRecord.directiveIndex)); } else { return ""; } diff --git a/modules/angular2/src/change_detection/coalesce.js b/modules/angular2/src/change_detection/coalesce.js index 8c888e6f2a..2b2e860fcb 100644 --- a/modules/angular2/src/change_detection/coalesce.js +++ b/modules/angular2/src/change_detection/coalesce.js @@ -45,6 +45,7 @@ function _selfRecord(r:ProtoRecord, contextIndex:number, selfIndex:number):Proto [], r.fixedArgs, contextIndex, + r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString, @@ -72,6 +73,7 @@ function _replaceIndices(r:ProtoRecord, selfIndex:number, indexMap:Map) { args, r.fixedArgs, contextIndex, + r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString, diff --git a/modules/angular2/src/change_detection/directive_record.js b/modules/angular2/src/change_detection/directive_record.js index ef6a6fc714..9b7ec3c214 100644 --- a/modules/angular2/src/change_detection/directive_record.js +++ b/modules/angular2/src/change_detection/directive_record.js @@ -1,16 +1,27 @@ import {ON_PUSH} from './constants'; import {StringWrapper} from 'angular2/src/facade/lang'; -export class DirectiveRecord { +export class DirectiveIndex { elementIndex:number; directiveIndex:number; + + constructor(elementIndex:number, directiveIndex:number) { + this.elementIndex = elementIndex; + this.directiveIndex = directiveIndex; + } + + get name() { + return `${this.elementIndex}_${this.directiveIndex}`; + } +} + +export class DirectiveRecord { + directiveIndex:DirectiveIndex; callOnAllChangesDone:boolean; callOnChange:boolean; changeDetection:string; - constructor(elementIndex:number, directiveIndex:number, - callOnAllChangesDone:boolean, callOnChange:boolean, changeDetection:string) { - this.elementIndex = elementIndex; + constructor(directiveIndex:DirectiveIndex, callOnAllChangesDone:boolean, callOnChange:boolean, changeDetection:string) { this.directiveIndex = directiveIndex; this.callOnAllChangesDone = callOnAllChangesDone; this.callOnChange = callOnChange; @@ -20,8 +31,4 @@ export class DirectiveRecord { isOnPushChangeDetection():boolean { return StringWrapper.equals(this.changeDetection, ON_PUSH); } - - get name() { - return `${this.elementIndex}_${this.directiveIndex}`; - } } \ No newline at end of file diff --git a/modules/angular2/src/change_detection/dynamic_change_detector.js b/modules/angular2/src/change_detection/dynamic_change_detector.js index a4cfae3263..301fe10605 100644 --- a/modules/angular2/src/change_detection/dynamic_change_detector.js +++ b/modules/angular2/src/change_detection/dynamic_change_detector.js @@ -3,7 +3,6 @@ import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/faca import {AbstractChangeDetector} from './abstract_change_detector'; import {BindingRecord} from './binding_record'; -import {DirectiveRecord} from './directive_record'; import {PipeRegistry} from './pipes/pipe_registry'; import {ChangeDetectionUtil, uninitialized} from './change_detection_util'; @@ -111,12 +110,12 @@ export class DynamicChangeDetector extends AbstractChangeDetector { if (proto.lastInDirective) { if (isPresent(changes)) { - this._getDirectiveFor(directiveRecord).onChange(changes); + this._getDirectiveFor(directiveRecord.directiveIndex).onChange(changes); changes = null; } if (isChanged && bindingRecord.isOnPushChangeDetection()) { - this._getDetectorFor(directiveRecord).markAsCheckOnce(); + this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce(); } isChanged = false; @@ -129,7 +128,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { for (var i = dirs.length - 1; i >= 0; --i) { var dir = dirs[i]; if (dir.callOnAllChangesDone) { - this._getDirectiveFor(dir).onAllChangesDone(); + this._getDirectiveFor(dir.directiveIndex).onAllChangesDone(); } } } @@ -138,7 +137,8 @@ export class DynamicChangeDetector extends AbstractChangeDetector { if (isBlank(bindingRecord.directiveRecord)) { this.dispatcher.notifyOnBinding(bindingRecord, change.currentValue); } else { - bindingRecord.setter(this._getDirectiveFor(bindingRecord.directiveRecord), change.currentValue); + var directiveIndex = bindingRecord.directiveRecord.directiveIndex; + bindingRecord.setter(this._getDirectiveFor(directiveIndex), change.currentValue); } } @@ -150,12 +150,12 @@ export class DynamicChangeDetector extends AbstractChangeDetector { } } - _getDirectiveFor(directive:DirectiveRecord) { - return this.directives.getDirectiveFor(directive); + _getDirectiveFor(directiveIndex) { + return this.directives.getDirectiveFor(directiveIndex); } - _getDetectorFor(directive:DirectiveRecord) { - return this.directives.getDetectorFor(directive); + _getDetectorFor(directiveIndex) { + return this.directives.getDetectorFor(directiveIndex); } _check(proto:ProtoRecord) { @@ -235,6 +235,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { var pipe = this._pipeFor(proto, context); var newValue = pipe.transform(context); + if (! ChangeDetectionUtil.noChangeMarker(newValue)) { var prevValue = this._readSelf(proto); this._writeSelf(proto, newValue); @@ -272,6 +273,12 @@ export class DynamicChangeDetector extends AbstractChangeDetector { } _readContext(proto:ProtoRecord) { + if (proto.contextIndex == -1) { + return this._getDirectiveFor(proto.directiveIndex); + } else { + return this.values[proto.contextIndex]; + } + return this.values[proto.contextIndex]; } diff --git a/modules/angular2/src/change_detection/parser/ast.js b/modules/angular2/src/change_detection/parser/ast.js index 3a7cbe1099..7744ef2c3c 100644 --- a/modules/angular2/src/change_detection/parser/ast.js +++ b/modules/angular2/src/change_detection/parser/ast.js @@ -450,7 +450,7 @@ export class AstVisitor { export class AstTransformer { visitImplicitReceiver(ast:ImplicitReceiver) { - return new ImplicitReceiver(); + return ast; } visitInterpolation(ast:Interpolation) { diff --git a/modules/angular2/src/change_detection/proto_change_detector.js b/modules/angular2/src/change_detection/proto_change_detector.js index a09cb9369f..0028f5d278 100644 --- a/modules/angular2/src/change_detection/proto_change_detector.js +++ b/modules/angular2/src/change_detection/proto_change_detector.js @@ -28,6 +28,7 @@ import {DynamicChangeDetector} from './dynamic_change_detector'; import {ChangeDetectorJITGenerator} from './change_detection_jit_generator'; import {PipeRegistry} from './pipes/pipe_registry'; import {BindingRecord} from './binding_record'; +import {DirectiveIndex} from './directive_record'; import {coalesce} from './coalesce'; @@ -153,7 +154,7 @@ class _ConvertAstIntoProtoRecords { } visitImplicitReceiver(ast:ImplicitReceiver) { - return 0; + return this.bindingRecord.implicitReceiver; } visitInterpolation(ast:Interpolation) { @@ -247,9 +248,15 @@ class _ConvertAstIntoProtoRecords { _addRecord(type, name, funcOrValue, args, fixedArgs, context) { var selfIndex = ++ this.contextIndex; - ListWrapper.push(this.protoRecords, - new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, selfIndex, - this.bindingRecord, this.expressionAsString, false, false)); + if (context instanceof DirectiveIndex) { + ListWrapper.push(this.protoRecords, + new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context, selfIndex, + this.bindingRecord, this.expressionAsString, false, false)); + } else { + ListWrapper.push(this.protoRecords, + new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null, selfIndex, + this.bindingRecord, this.expressionAsString, false, false)); + } return selfIndex; } } diff --git a/modules/angular2/src/change_detection/proto_record.js b/modules/angular2/src/change_detection/proto_record.js index 6f5eb4079f..1731ce6f24 100644 --- a/modules/angular2/src/change_detection/proto_record.js +++ b/modules/angular2/src/change_detection/proto_record.js @@ -1,5 +1,6 @@ import {List} from 'angular2/src/facade/collection'; import {BindingRecord} from './binding_record'; +import {DirectiveIndex} from './directive_record'; export const RECORD_TYPE_SELF = 0; export const RECORD_TYPE_CONST = 1; @@ -19,7 +20,10 @@ export class ProtoRecord { funcOrValue:any; args:List; fixedArgs:List; + contextIndex:number; + directiveIndex:DirectiveIndex; + selfIndex:number; bindingRecord:BindingRecord; lastInBinding:boolean; @@ -32,6 +36,7 @@ export class ProtoRecord { args:List, fixedArgs:List, contextIndex:number, + directiveIndex:DirectiveIndex, selfIndex:number, bindingRecord:BindingRecord, expressionAsString:string, @@ -43,7 +48,10 @@ export class ProtoRecord { this.funcOrValue = funcOrValue; this.args = args; this.fixedArgs = fixedArgs; + this.contextIndex = contextIndex; + this.directiveIndex = directiveIndex; + this.selfIndex = selfIndex; this.bindingRecord = bindingRecord; this.lastInBinding = lastInBinding; diff --git a/modules/angular2/src/core/annotations/annotations.js b/modules/angular2/src/core/annotations/annotations.js index 6837822447..6a8e4af534 100644 --- a/modules/angular2/src/core/annotations/annotations.js +++ b/modules/angular2/src/core/annotations/annotations.js @@ -187,8 +187,8 @@ import {DEFAULT} from 'angular2/change_detection'; * * * A directive can also query for other child directives. Since parent directives are instantiated before child - * directives, a directive can't simply inject the list of child directives. Instead, the directive - * injects a {@link QueryList}, which updates its contents as children are added, removed, or moved by any + * directives, a directive can't simply inject the list of child directives. Instead, the directive + * injects a {@link QueryList}, which updates its contents as children are added, removed, or moved by any * {@link Viewport} directive such as a `for`, an `if`, or a `switch`. * * ``` @@ -199,7 +199,7 @@ import {DEFAULT} from 'angular2/change_detection'; * } * ``` * - * This directive would be instantiated with a {@link QueryList} which contains `Dependency` 4 and 6. Here, `Dependency` + * This directive would be instantiated with a {@link QueryList} which contains `Dependency` 4 and 6. Here, `Dependency` * 5 would not be included, because it is not a direct child. * * ### Injecting a live collection of descendant directives @@ -444,6 +444,29 @@ export class Directive extends Injectable { */ hostListeners:any; // StringMap + + /** + * Specifies which DOM properties a directives updates. + * + * ## Syntax + * + * ``` + * @Decorator({ + * selector: 'input', + * hostProperties: { + * 'value': 'value' + * } + * }) + * class InputDecorator { + * value:string; + * } + * + * In this example every time the value property of the decorator changes, Angular will update the value property of + * the host element. + * ``` + */ + hostProperties:any; // String map + /** * Specifies a set of lifecycle hostListeners in which the directive participates. * @@ -457,12 +480,14 @@ export class Directive extends Injectable { properties, events, hostListeners, + hostProperties, lifecycle }:{ selector:string, properties:any, events:List, hostListeners: any, + hostProperties: any, lifecycle:List }={}) { @@ -471,6 +496,7 @@ export class Directive extends Injectable { this.properties = properties; this.events = events; this.hostListeners = hostListeners; + this.hostProperties = hostProperties; this.lifecycle = lifecycle; } @@ -579,20 +605,22 @@ export class Component extends Directive { */ injectables:List; -@CONST() + @CONST() constructor({ - selector, - properties, - events, - hostListeners, - injectables, - lifecycle, - changeDetection = DEFAULT + selector, + properties, + events, + hostListeners, + hostProperties, + injectables, + lifecycle, + changeDetection = DEFAULT }:{ selector:string, properties:Object, events:List, - hostListeners:Object, + hostListeners:any, + hostProperties:any, injectables:List, lifecycle:List, changeDetection:string @@ -603,6 +631,7 @@ export class Component extends Directive { properties: properties, events: events, hostListeners: hostListeners, + hostProperties: hostProperties, lifecycle: lifecycle }); @@ -667,17 +696,19 @@ export class DynamicComponent extends Directive { @CONST() constructor({ - selector, - properties, - events, - hostListeners, - injectables, - lifecycle + selector, + properties, + events, + hostListeners, + hostProperties, + injectables, + lifecycle }:{ selector:string, - properties:Object, + properties:any, events:List, - hostListeners:Object, + hostListeners:any, + hostProperties:any, injectables:List, lifecycle:List }={}) { @@ -686,6 +717,7 @@ export class DynamicComponent extends Directive { properties: properties, events: events, hostListeners: hostListeners, + hostProperties: hostProperties, lifecycle: lifecycle }); @@ -767,6 +799,7 @@ export class Decorator extends Directive { properties, events, hostListeners, + hostProperties, lifecycle, compileChildren = true, }:{ @@ -774,16 +807,18 @@ export class Decorator extends Directive { properties:any, events:List, hostListeners:any, + hostProperties:any, lifecycle:List, compileChildren:boolean }={}) { super({ - selector: selector, - properties: properties, - events: events, - hostListeners: hostListeners, - lifecycle: lifecycle + selector: selector, + properties: properties, + events: events, + hostListeners: hostListeners, + hostProperties: hostProperties, + lifecycle: lifecycle }); this.compileChildren = compileChildren; } @@ -889,20 +924,24 @@ export class Viewport extends Directive { properties, events, hostListeners, + hostProperties, lifecycle }:{ - selector:string, - properties:any, - events:List, - lifecycle:List + selector:string, + properties:any, + hostListeners:any, + hostProperties:any, + events:List, + lifecycle:List }={}) { super({ - selector: selector, - properties: properties, - events: events, - hostListeners: hostListeners, - lifecycle: lifecycle + selector: selector, + properties: properties, + events: events, + hostListeners: hostListeners, + hostProperties: hostProperties, + lifecycle: lifecycle }); } } diff --git a/modules/angular2/src/core/annotations/di.js b/modules/angular2/src/core/annotations/di.js index 409ba72703..36a5485acd 100644 --- a/modules/angular2/src/core/annotations/di.js +++ b/modules/angular2/src/core/annotations/di.js @@ -1,28 +1,6 @@ import {CONST} from 'angular2/src/facade/lang'; import {DependencyAnnotation} from 'angular2/di'; -/** - * Specifies that a function for setting host properties should be injected. - * - * NOTE: This is changing pre 1.0. - * - * The directive can inject a property setter that would allow setting this property on the host element. - * - * @exportedAs angular2/annotations - */ -export class PropertySetter extends DependencyAnnotation { - propName: string; - @CONST() - constructor(propName) { - super(); - this.propName = propName; - } - - get token() { - return Function; - } -} - /** * Specifies that a constant attribute value should be injected. * diff --git a/modules/angular2/src/core/compiler/compiler.js b/modules/angular2/src/core/compiler/compiler.js index 099e074085..74c9ff3447 100644 --- a/modules/angular2/src/core/compiler/compiler.js +++ b/modules/angular2/src/core/compiler/compiler.js @@ -223,12 +223,8 @@ export class Compiler { renderType = renderApi.DirectiveMetadata.DECORATOR_TYPE; compileChildren = ann.compileChildren; } - var setters = []; var readAttributes = []; ListWrapper.forEach(directiveBinding.dependencies, (dep) => { - if (isPresent(dep.propSetterName)) { - ListWrapper.push(setters, dep.propSetterName); - } if (isPresent(dep.attributeName)) { ListWrapper.push(readAttributes, dep.attributeName); } @@ -239,8 +235,8 @@ export class Compiler { selector: ann.selector, compileChildren: compileChildren, hostListeners: isPresent(ann.hostListeners) ? MapWrapper.createFromStringMap(ann.hostListeners) : null, + hostProperties: isPresent(ann.hostProperties) ? MapWrapper.createFromStringMap(ann.hostProperties) : null, properties: isPresent(ann.properties) ? MapWrapper.createFromStringMap(ann.properties) : null, - setters: setters, readAttributes: readAttributes }); } diff --git a/modules/angular2/src/core/compiler/element_injector.js b/modules/angular2/src/core/compiler/element_injector.js index cb727c74bc..0f86e70684 100644 --- a/modules/angular2/src/core/compiler/element_injector.js +++ b/modules/angular2/src/core/compiler/element_injector.js @@ -5,7 +5,7 @@ import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoBindingError, AbstractBindingError, CyclicDependencyError} from 'angular2/di'; import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; -import {PropertySetter, Attribute, Query} from 'angular2/src/core/annotations/di'; +import {Attribute, Query} from 'angular2/src/core/annotations/di'; import * as viewModule from 'angular2/src/core/compiler/view'; import {ViewContainer} from 'angular2/src/core/compiler/view_container'; import {NgElement} from 'angular2/src/core/compiler/ng_element'; @@ -195,15 +195,13 @@ export class TreeNode { export class DirectiveDependency extends Dependency { depth:int; - propSetterName:string; attributeName:string; queryDirective; constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean, properties:List, - depth:int, propSetterName: string, attributeName:string, queryDirective) { + depth:int, attributeName:string, queryDirective) { super(key, asPromise, lazy, optional, properties); this.depth = depth; - this.propSetterName = propSetterName; this.attributeName = attributeName; this.queryDirective = queryDirective; this._verify(); @@ -211,17 +209,15 @@ export class DirectiveDependency extends Dependency { _verify():void { var count = 0; - if (isPresent(this.propSetterName)) count++; if (isPresent(this.queryDirective)) count++; if (isPresent(this.attributeName)) count++; if (count > 1) throw new BaseException( - 'A directive injectable can contain only one of the following @PropertySetter, @Attribute or @Query.'); + 'A directive injectable can contain only one of the following @Attribute or @Query.'); } static createFrom(d:Dependency):Dependency { return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional, d.properties, DirectiveDependency._depth(d.properties), - DirectiveDependency._propSetterName(d.properties), DirectiveDependency._attributeName(d.properties), DirectiveDependency._query(d.properties) ); @@ -234,11 +230,6 @@ export class DirectiveDependency extends Dependency { return 0; } - static _propSetterName(properties):string { - var p = ListWrapper.find(properties, (p) => p instanceof PropertySetter); - return isPresent(p) ? p.propName : null; - } - static _attributeName(properties):string { var p = ListWrapper.find(properties, (p) => p instanceof Attribute); return isPresent(p) ? p.attributeName : null; @@ -268,6 +259,10 @@ export class DirectiveBinding extends ResolvedBinding { } } + get displayName() { + return this.key.displayName; + } + get eventEmitters():List { return isPresent(this.annotation) && isPresent(this.annotation.events) ? this.annotation.events : []; } @@ -735,7 +730,6 @@ export class ElementInjector extends TreeNode { } _getByDependency(dep:DirectiveDependency, requestor:Key) { - if (isPresent(dep.propSetterName)) return this._buildPropSetter(dep); if (isPresent(dep.attributeName)) return this._buildAttribute(dep); if (isPresent(dep.queryDirective)) return this._findQuery(dep.queryDirective).list; if (dep.key.id === StaticKeys.instance().elementRefId) { @@ -744,15 +738,6 @@ export class ElementInjector extends TreeNode { return this._getByKey(dep.key, dep.depth, dep.optional, requestor); } - _buildPropSetter(dep) { - var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId); - var renderer = view.renderer; - var index = this._proto.index; - return function(v) { - renderer.setElementProperty(view.render, index, dep.propSetterName, v); - }; - } - _buildAttribute(dep): string { var attributes = this._proto.attributes; if (isPresent(attributes) && MapWrapper.contains(attributes, dep.attributeName)) { diff --git a/modules/angular2/src/core/compiler/proto_view_factory.js b/modules/angular2/src/core/compiler/proto_view_factory.js index d13013d8a8..21ad7274b8 100644 --- a/modules/angular2/src/core/compiler/proto_view_factory.js +++ b/modules/angular2/src/core/compiler/proto_view_factory.js @@ -3,7 +3,7 @@ import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {reflector} from 'angular2/src/reflection/reflection'; -import {ChangeDetection} from 'angular2/change_detection'; +import {ChangeDetection, DirectiveIndex} from 'angular2/change_detection'; import {Component, Viewport, DynamicComponent} from '../annotations/annotations'; import * as renderApi from 'angular2/src/render/api'; @@ -18,7 +18,8 @@ export class ProtoViewFactory { this._changeDetection = changeDetection; } - createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto, directives:List):AppProtoView { + createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto, + directives:List):AppProtoView { var protoChangeDetector; if (isBlank(componentBinding)) { protoChangeDetector = this._changeDetection.createProtoChangeDetector('root', null); @@ -43,7 +44,7 @@ export class ProtoViewFactory { this._createElementBinder( protoView, renderElementBinder, protoElementInjector, sortedDirectives ); - this._createDirectiveBinders(protoView, sortedDirectives); + this._createDirectiveBinders(protoView, i, sortedDirectives); } MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => { protoView.bindVariable(varName, mappedName); @@ -128,27 +129,34 @@ export class ProtoViewFactory { return elBinder; } - _createDirectiveBinders(protoView, sortedDirectives) { - for (var i=0; i { + MapWrapper.forEach(directiveBinder.propertyBindings, (astWithSource, propertyName) => { // TODO: these setters should eventually be created by change detection, to make // it monomorphic! var setter = reflector.setter(propertyName); protoView.bindDirectiveProperty(i, astWithSource, propertyName, setter); }); + + // host properties + MapWrapper.forEach(directiveBinder.hostPropertyBindings, (astWithSource, propertyName) => { + var directiveIndex = new DirectiveIndex(boundElementIndex, i); + protoView.bindHostElementProperty(astWithSource, propertyName, directiveIndex); + }); + // directive events - protoView.bindEvent(renderDirectiveMetadata.eventBindings, i); + protoView.bindEvent(directiveBinder.eventBindings, i); } } - } class SortedDirectives { componentDirective: DirectiveBinding; viewportDirective: DirectiveBinding; - renderDirectives: List; + renderDirectives: List; directives: List; constructor(renderDirectives, allDirectives) { @@ -156,18 +164,18 @@ class SortedDirectives { this.directives = []; this.viewportDirective = null; this.componentDirective = null; - ListWrapper.forEach(renderDirectives, (renderDirectiveMetadata) => { - var directiveBinding = allDirectives[renderDirectiveMetadata.directiveIndex]; + ListWrapper.forEach(renderDirectives, (renderDirectiveBinder) => { + var directiveBinding = allDirectives[renderDirectiveBinder.directiveIndex]; if ((directiveBinding.annotation instanceof Component) || (directiveBinding.annotation instanceof DynamicComponent)) { // component directives need to be the first binding in ElementInjectors! this.componentDirective = directiveBinding; - ListWrapper.insert(this.renderDirectives, 0, renderDirectiveMetadata); + ListWrapper.insert(this.renderDirectives, 0, renderDirectiveBinder); ListWrapper.insert(this.directives, 0, directiveBinding); } else { if (directiveBinding.annotation instanceof Viewport) { this.viewportDirective = directiveBinding; } - ListWrapper.push(this.renderDirectives, renderDirectiveMetadata); + ListWrapper.push(this.renderDirectives, renderDirectiveBinder); ListWrapper.push(this.directives, directiveBinding); } }); diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js index e51d40ea8a..e0bf399292 100644 --- a/modules/angular2/src/core/compiler/view.js +++ b/modules/angular2/src/core/compiler/view.js @@ -1,6 +1,6 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; import {AST, Locals, ChangeDispatcher, ProtoChangeDetector, ChangeDetector, - ChangeRecord, BindingRecord, DirectiveRecord, ChangeDetectorRef} from 'angular2/change_detection'; + ChangeRecord, BindingRecord, DirectiveRecord, DirectiveIndex, ChangeDetectorRef} from 'angular2/change_detection'; import {ProtoElementInjector, ElementInjector, PreBuiltObjects, DirectiveBinding} from './element_injector'; import {ElementBinder} from './element_binder'; @@ -124,12 +124,12 @@ export class AppView { } } - getDirectiveFor(directive:DirectiveRecord) { + getDirectiveFor(directive:DirectiveIndex) { var elementInjector = this.elementInjectors[directive.elementIndex]; return elementInjector.getDirectiveAtIndex(directive.directiveIndex); } - getDetectorFor(directive:DirectiveRecord) { + getDetectorFor(directive:DirectiveIndex) { var elementInjector = this.elementInjectors[directive.elementIndex]; return elementInjector.getChangeDetector(); } @@ -267,6 +267,14 @@ export class AppProtoView { ListWrapper.push(this.bindings, b); } + /** + * Adds an host property binding for the last created ElementBinder via bindElement + */ + bindHostElementProperty(expression:AST, setterName:string, directiveIndex:DirectiveIndex):void { + var b = BindingRecord.createForHostProperty(directiveIndex, expression, setterName); + ListWrapper.push(this.bindings, b); + } + /** * Adds an event binding for the last created ElementBinder via bindElement. * @@ -323,7 +331,7 @@ export class AppProtoView { var changeDetection = binding.changeDetection; MapWrapper.set(this._directiveRecordsMap, id, - new DirectiveRecord(elementInjectorIndex, directiveIndex, + new DirectiveRecord(new DirectiveIndex(elementInjectorIndex, directiveIndex), binding.callOnAllChangesDone, binding.callOnChange, changeDetection)); } diff --git a/modules/angular2/src/di/key.js b/modules/angular2/src/di/key.js index 93038f8480..e28ef55429 100644 --- a/modules/angular2/src/di/key.js +++ b/modules/angular2/src/di/key.js @@ -1,5 +1,5 @@ import {MapWrapper} from 'angular2/src/facade/collection'; -//import {int} from 'angular2/src/facade/lang'; +import {stringify} from 'angular2/src/facade/lang'; // TODO: uncoment `int` once https://github.com/angular/angular/issues/1414 is fixed @@ -23,6 +23,10 @@ export class Key { this.id = id; } + get displayName() { + return stringify(this.token); + } + /** * Retrieves a `Key` for a token. */ diff --git a/modules/angular2/src/forms/directives.js b/modules/angular2/src/forms/directives.js index 15580da617..08fc01a666 100644 --- a/modules/angular2/src/forms/directives.js +++ b/modules/angular2/src/forms/directives.js @@ -1,5 +1,6 @@ -import {View, Component, Decorator, Ancestor, onChange, PropertySetter} from 'angular2/angular2'; +import {View, Component, Decorator, Ancestor, onChange, ElementRef} from 'angular2/angular2'; import {Optional} from 'angular2/di'; +import {Renderer} from 'angular2/src/render/api'; import {isBlank, isPresent, isString, CONST} from 'angular2/src/facade/lang'; import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {ControlGroup, Control} from './model'; @@ -27,19 +28,21 @@ import {Validators} from './validators'; hostListeners: { 'change' : 'onChange($event.target.value)', 'input' : 'onChange($event.target.value)' + }, + hostProperties: { + 'value' : 'value' } }) export class DefaultValueAccessor { - _setValueProperty:Function; + value; onChange:Function; - constructor(@PropertySetter('value') setValueProperty:Function) { - this._setValueProperty = setValueProperty; + constructor() { this.onChange = (_) => {}; } writeValue(value) { - this._setValueProperty(value); + this.value = value } } @@ -58,20 +61,28 @@ export class DefaultValueAccessor { selector: 'input[type=checkbox][control]', hostListeners: { 'change' : 'onChange($event.target.checked)' + }, + hostProperties: { + 'checked' : 'checked' } }) export class CheckboxControlValueAccessor { - _setCheckedProperty:Function; + _elementRef:ElementRef; + _renderer:Renderer; + + checked:boolean; onChange:Function; - constructor(cd:ControlDirective, @PropertySetter('checked') setCheckedProperty:Function) { - this._setCheckedProperty = setCheckedProperty; + constructor(cd:ControlDirective, elementRef:ElementRef, renderer:Renderer) { this.onChange = (_) => {}; + this._elementRef = elementRef; + this._renderer = renderer; cd.valueAccessor = this; //ControlDirective should inject CheckboxControlDirective } writeValue(value) { - this._setCheckedProperty(value); + this._renderer.setElementProperty(this._elementRef.hostView.render, this._elementRef.boundElementIndex, + 'checked', value) } } diff --git a/modules/angular2/src/render/api.js b/modules/angular2/src/render/api.js index efec2f0c49..bd42f80f22 100644 --- a/modules/angular2/src/render/api.js +++ b/modules/angular2/src/render/api.js @@ -72,12 +72,14 @@ export class DirectiveBinder { // that replaced the values that should be extracted from the element // with a local name eventBindings: List; + hostPropertyBindings: Map; constructor({ - directiveIndex, propertyBindings, eventBindings + directiveIndex, propertyBindings, eventBindings, hostPropertyBindings }) { this.directiveIndex = directiveIndex; this.propertyBindings = propertyBindings; this.eventBindings = eventBindings; + this.hostPropertyBindings = hostPropertyBindings; } } @@ -114,17 +116,17 @@ export class DirectiveMetadata { selector:string; compileChildren:boolean; hostListeners:Map; + hostProperties:Map; properties:Map; - setters:List; readAttributes:List; type:number; - constructor({id, selector, compileChildren, hostListeners, properties, setters, readAttributes, type}) { + constructor({id, selector, compileChildren, hostListeners, hostProperties, properties, readAttributes, type}) { this.id = id; this.selector = selector; this.compileChildren = isPresent(compileChildren) ? compileChildren : true; this.hostListeners = hostListeners; + this.hostProperties = hostProperties; this.properties = properties; - this.setters = setters; this.readAttributes = readAttributes; this.type = type; } @@ -248,7 +250,7 @@ export class Renderer { /** * Sets a property on an element. - * Note: This will fail if the property was not mentioned previously as a propertySetter + * Note: This will fail if the property was not mentioned previously as a host property * in the View. */ setElementProperty(view:ViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {} diff --git a/modules/angular2/src/render/dom/compiler/directive_parser.js b/modules/angular2/src/render/dom/compiler/directive_parser.js index d4d28157eb..9c57fbb606 100644 --- a/modules/angular2/src/render/dom/compiler/directive_parser.js +++ b/modules/angular2/src/render/dom/compiler/directive_parser.js @@ -56,21 +56,21 @@ export class DirectiveParser extends CompileStep { this._selectorMatcher.match(cssSelector, (selector, directiveIndex) => { var elementBinder = current.bindElement(); var directive = this._directives[directiveIndex]; - var directiveBinder = elementBinder.bindDirective(directiveIndex); + var directiveBinderBuilder = elementBinder.bindDirective(directiveIndex); current.compileChildren = current.compileChildren && directive.compileChildren; if (isPresent(directive.properties)) { MapWrapper.forEach(directive.properties, (bindConfig, dirProperty) => { - this._bindDirectiveProperty(dirProperty, bindConfig, current, directiveBinder); + this._bindDirectiveProperty(dirProperty, bindConfig, current, directiveBinderBuilder); }); } if (isPresent(directive.hostListeners)) { MapWrapper.forEach(directive.hostListeners, (action, eventName) => { - this._bindDirectiveEvent(eventName, action, current, directiveBinder); + this._bindDirectiveEvent(eventName, action, current, directiveBinderBuilder); }); } - if (isPresent(directive.setters)) { - ListWrapper.forEach(directive.setters, (propertyName) => { - elementBinder.bindPropertySetter(propertyName); + if (isPresent(directive.hostProperties)) { + MapWrapper.forEach(directive.hostProperties, (hostPropertyName, directivePropertyName) => { + this._bindHostProperty(hostPropertyName, directivePropertyName, current, directiveBinderBuilder); }); } if (isPresent(directive.readAttributes)) { @@ -102,7 +102,7 @@ export class DirectiveParser extends CompileStep { }); } - _bindDirectiveProperty(dirProperty, bindConfig, compileElement, directiveBinder) { + _bindDirectiveProperty(dirProperty, bindConfig, compileElement, directiveBinderBuilder) { var pipes = this._splitBindConfig(bindConfig); var elProp = ListWrapper.removeAt(pipes, 0); @@ -124,21 +124,27 @@ export class DirectiveParser extends CompileStep { // Bindings are optional, so this binding only needs to be set up if an expression is given. if (isPresent(bindingAst)) { var fullExpAstWithBindPipes = this._parser.addPipes(bindingAst, pipes); - directiveBinder.bindProperty( + directiveBinderBuilder.bindProperty( dirProperty, fullExpAstWithBindPipes ); } } - _bindDirectiveEvent(eventName, action, compileElement, directiveBinder) { + _bindDirectiveEvent(eventName, action, compileElement, directiveBinderBuilder) { var ast = this._parser.parseAction(action, compileElement.elementDescription); if (StringWrapper.contains(eventName, EVENT_TARGET_SEPARATOR)) { var parts = eventName.split(EVENT_TARGET_SEPARATOR); - directiveBinder.bindEvent(parts[1], ast, parts[0]); + directiveBinderBuilder.bindEvent(parts[1], ast, parts[0]); } else { - directiveBinder.bindEvent(eventName, ast); + directiveBinderBuilder.bindEvent(eventName, ast); } - + + } + + _bindHostProperty(hostPropertyName, directivePropertyName, compileElement, directiveBinderBuilder) { + var ast = this._parser.parseBinding(directivePropertyName, + `hostProperties of ${compileElement.elementDescription}`); + directiveBinderBuilder.bindHostProperty(hostPropertyName, ast); } _splitBindConfig(bindConfig:string) { diff --git a/modules/angular2/src/render/dom/view/proto_view_builder.js b/modules/angular2/src/render/dom/view/proto_view_builder.js index c058df1528..9890f69cb5 100644 --- a/modules/angular2/src/render/dom/view/proto_view_builder.js +++ b/modules/angular2/src/render/dom/view/proto_view_builder.js @@ -5,7 +5,6 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; import { ASTWithSource, AST, AstTransformer, AccessMember, LiteralArray, ImplicitReceiver } from 'angular2/change_detection'; -import {SetterFn} from 'angular2/src/reflection/types'; import {RenderProtoView} from './proto_view'; import {ElementBinder, Event} from './element_binder'; @@ -57,17 +56,26 @@ export class ProtoViewBuilder { var apiElementBinders = []; ListWrapper.forEach(this.elements, (ebb) => { var propertySetters = MapWrapper.create(); - var apiDirectiveBinders = ListWrapper.map(ebb.directives, (db) => { - ebb.eventBuilder.merge(db.eventBuilder); + + var apiDirectiveBinders = ListWrapper.map(ebb.directives, (dbb) => { + ebb.eventBuilder.merge(dbb.eventBuilder); + + MapWrapper.forEach(dbb.hostPropertyBindings, (_, hostPropertyName) => { + MapWrapper.set(propertySetters, hostPropertyName, setterFactory(hostPropertyName)); + }); + return new api.DirectiveBinder({ - directiveIndex: db.directiveIndex, - propertyBindings: db.propertyBindings, - eventBindings: db.eventBindings + directiveIndex: dbb.directiveIndex, + propertyBindings: dbb.propertyBindings, + eventBindings: dbb.eventBindings, + hostPropertyBindings: dbb.hostPropertyBindings }); }); - MapWrapper.forEach(ebb.propertySetters, (setter, propertyName) => { - MapWrapper.set(propertySetters, propertyName, setter); + + MapWrapper.forEach(ebb.propertyBindings, (_, propertyName) => { + MapWrapper.set(propertySetters, propertyName, setterFactory(propertyName)); }); + var nestedProtoView = isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build() : null; var parentIndex = isPresent(ebb.parent) ? ebb.parent.index : -1; @@ -119,7 +127,6 @@ export class ElementBinderBuilder { textBindingIndices: List; textBindings: List; contentTagSelector:string; - propertySetters: Map; readAttributes: Map; componentId: string; @@ -137,7 +144,6 @@ export class ElementBinderBuilder { this.textBindings = []; this.textBindingIndices = []; this.contentTagSelector = null; - this.propertySetters = MapWrapper.create(); this.componentId = null; this.readAttributes = MapWrapper.create(); } @@ -172,11 +178,10 @@ export class ElementBinderBuilder { bindProperty(name, expression) { MapWrapper.set(this.propertyBindings, name, expression); - this.bindPropertySetter(name); - } - bindPropertySetter(name) { - MapWrapper.set(this.propertySetters, name, setterFactory(name)); + //TODO: required for Dart transformers. Remove when Dart transformers + //run all the steps of the render compiler + setterFactory(name); } bindVariable(name, value) { @@ -216,12 +221,14 @@ export class ElementBinderBuilder { export class DirectiveBuilder { directiveIndex:number; propertyBindings: Map; + hostPropertyBindings: Map; eventBindings: List; eventBuilder: EventBuilder; constructor(directiveIndex) { this.directiveIndex = directiveIndex; this.propertyBindings = MapWrapper.create(); + this.hostPropertyBindings = MapWrapper.create(); this.eventBindings = ListWrapper.create(); this.eventBuilder = new EventBuilder(); } @@ -230,6 +237,10 @@ export class DirectiveBuilder { MapWrapper.set(this.propertyBindings, name, expression); } + bindHostProperty(name, expression) { + MapWrapper.set(this.hostPropertyBindings, name, expression); + } + bindEvent(name, expression, target = null) { ListWrapper.push(this.eventBindings, this.eventBuilder.add(name, expression, target)); } diff --git a/modules/angular2/src/transform/template_compiler/directive_metadata_reader.dart b/modules/angular2/src/transform/template_compiler/directive_metadata_reader.dart index a7316e9b0d..b53135949f 100644 --- a/modules/angular2/src/transform/template_compiler/directive_metadata_reader.dart +++ b/modules/angular2/src/transform/template_compiler/directive_metadata_reader.dart @@ -45,7 +45,7 @@ class _DirectiveMetadataVisitor extends Object compileChildren: false, properties: {}, hostListeners: {}, - setters: [], + hostProperties: {}, readAttributes: []); super.visitInstanceCreationExpression(node); } @@ -76,6 +76,9 @@ class _DirectiveMetadataVisitor extends Object case 'properties': _populateProperties(node.expression); break; + case 'hostProperties': + _populateHostProperties(node.expression); + break; case 'hostListeners': _populateHostListeners(node.expression); } @@ -135,4 +138,19 @@ class _DirectiveMetadataVisitor extends Object meta.hostListeners[sKey] = sVal; } } + + void _populateHostProperties(Expression hostPropertyValue) { + if (hostPropertyValue is! MapLiteral) { + logger.error('Angular 2 currently only supports map literal values for ' + 'Directive#hostProperties.' + ' Source: ${hostPropertyValue}'); + return; + } + for (MapLiteralEntry entry in (hostPropertyValue as MapLiteral).entries) { + var sKey = _expressionToString(entry.key, 'Directive#hostProperties keys'); + var sVal = + _expressionToString(entry.value, 'Directive#hostProperties values'); + meta.hostProperties[sKey] = sVal; + } + } } diff --git a/modules/angular2/test/change_detection/change_detection_spec.js b/modules/angular2/test/change_detection/change_detection_spec.js index 5e8d78dddf..823a568088 100644 --- a/modules/angular2/test/change_detection/change_detection_spec.js +++ b/modules/angular2/test/change_detection/change_detection_spec.js @@ -7,7 +7,7 @@ import {Parser} from 'angular2/src/change_detection/parser/parser'; import {Lexer} from 'angular2/src/change_detection/parser/lexer'; import {Locals} from 'angular2/src/change_detection/parser/locals'; -import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, BindingRecord, DirectiveRecord, +import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, BindingRecord, DirectiveRecord, DirectiveIndex, PipeRegistry, Pipe, NO_CHANGE, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH, DEFAULT} from 'angular2/change_detection'; import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'angular2/src/change_detection/proto_change_detector'; @@ -22,8 +22,9 @@ export function main() { if (name == "JIT" && IS_DARTIUM) return; + var parser = new Parser(new Lexer()); + function ast(exp:string, location:string = 'location') { - var parser = new Parser(new Lexer()); return parser.parseBinding(exp, location); } @@ -204,7 +205,6 @@ export function main() { }); it("should support interpolation", () => { - var parser = new Parser(new Lexer()); var pcd = createProtoChangeDetector(); var ast = parser.parseInterpolation("B{{a}}A", "location"); @@ -246,9 +246,9 @@ export function main() { }); describe("updating directives", () => { - var dirRecord1 = new DirectiveRecord(0, 0, true, true, DEFAULT); - var dirRecord2 = new DirectiveRecord(0, 1, true, true, DEFAULT); - var dirRecordNoCallbacks = new DirectiveRecord(0, 0, false, false, DEFAULT); + var dirRecord1 = new DirectiveRecord(new DirectiveIndex(0, 0), true, true, DEFAULT); + var dirRecord2 = new DirectiveRecord(new DirectiveIndex(0, 1), true, true, DEFAULT); + var dirRecordNoCallbacks = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, DEFAULT); function updateA(exp:string, dirRecord) { return BindingRecord.createForDirective(ast(exp), "a", (o,v) => o.a = v, dirRecord); @@ -381,6 +381,24 @@ export function main() { }); }); + describe("reading directives", () => { + var index = new DirectiveIndex(0, 0); + var dirRecord = new DirectiveRecord(index, false, false, DEFAULT); + + it("should read directive properties", () => { + var directive = new TestDirective(); + directive.a = "aaa"; + + var pcd = createProtoChangeDetector(); + var cd = instantiate(pcd, dispatcher, [BindingRecord.createForHostProperty(index, ast("a"), "prop")], [dirRecord]); + cd.hydrate(null, null, dirs([directive])); + + cd.detectChanges(); + + expect(dispatcher.loggedValues).toEqual(['aaa']); + }); + }); + describe("enforce no new changes", () => { it("should throw when a record gets changed after it has been checked", () => { var pcd = createProtoChangeDetector(); @@ -567,7 +585,7 @@ export function main() { checkedDetector.mode = CHECKED; // this directive is a component with ON_PUSH change detection - dirRecordWithOnPush = new DirectiveRecord(0, 0, false, false, ON_PUSH); + dirRecordWithOnPush = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, ON_PUSH); // a record updating a component updateDirWithOnPushRecord = @@ -898,12 +916,12 @@ class FakeDirectives { this.detectors = detectors; } - getDirectiveFor(directiveRecord:DirectiveRecord) { - return this.directives[directiveRecord.directiveIndex]; + getDirectiveFor(di:DirectiveIndex) { + return this.directives[di.directiveIndex]; } - getDetectorFor(directiveRecord:DirectiveRecord) { - return this.detectors[directiveRecord.directiveIndex]; + getDetectorFor(di:DirectiveIndex) { + return this.detectors[di.directiveIndex]; } } diff --git a/modules/angular2/test/change_detection/coalesce_spec.js b/modules/angular2/test/change_detection/coalesce_spec.js index e67617d3a6..64f6aec55d 100644 --- a/modules/angular2/test/change_detection/coalesce_spec.js +++ b/modules/angular2/test/change_detection/coalesce_spec.js @@ -5,7 +5,7 @@ import {RECORD_TYPE_SELF, ProtoRecord} from 'angular2/src/change_detection/proto export function main() { function r(funcOrValue, args, contextIndex, selfIndex, lastInBinding = false) { - return new ProtoRecord(99, "name", funcOrValue, args, null, contextIndex, selfIndex, + return new ProtoRecord(99, "name", funcOrValue, args, null, contextIndex, null, selfIndex, null, null, lastInBinding, false); } @@ -73,7 +73,7 @@ export function main() { expect(rs[1]).toEqual(new ProtoRecord( RECORD_TYPE_SELF, "self", null, - [], null, 1, 2, + [], null, 1, null, 2, null, null, true, false) ); diff --git a/modules/angular2/test/core/compiler/compiler_spec.js b/modules/angular2/test/core/compiler/compiler_spec.js index a08af50f29..eda7686181 100644 --- a/modules/angular2/test/core/compiler/compiler_spec.js +++ b/modules/angular2/test/core/compiler/compiler_spec.js @@ -22,7 +22,7 @@ import {AppProtoView} from 'angular2/src/core/compiler/view'; import {ElementBinder} from 'angular2/src/core/compiler/element_binder'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {Component, DynamicComponent, Viewport, Decorator} from 'angular2/src/core/annotations/annotations'; -import {PropertySetter, Attribute} from 'angular2/src/core/annotations/di'; +import {Attribute} from 'angular2/src/core/annotations/di'; import {View} from 'angular2/src/core/annotations/view'; import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; @@ -185,18 +185,20 @@ export function main() { }); })); - it('should set directive.bind', inject([AsyncTestCompleter], (async) => { - captureDirective(DirectiveWithBind).then( (renderDir) => { - expect(renderDir.properties).toEqual(MapWrapper.createFromStringMap({ - 'a': 'b' + it('should set directive.hostProperties', inject([AsyncTestCompleter], (async) => { + captureDirective(DirectiveWithProperties).then( (renderDir) => { + expect(renderDir.hostProperties).toEqual(MapWrapper.createFromStringMap({ + 'someField': 'someProp' })); async.done(); }); })); - it('should read @PropertySetter', inject([AsyncTestCompleter], (async) => { - captureDirective(DirectiveWithPropertySetters).then( (renderDir) => { - expect(renderDir.setters).toEqual(['someProp']); + it('should set directive.bind', inject([AsyncTestCompleter], (async) => { + captureDirective(DirectiveWithBind).then( (renderDir) => { + expect(renderDir.properties).toEqual(MapWrapper.createFromStringMap({ + 'a': 'b' + })); async.done(); }); })); @@ -500,16 +502,16 @@ class IgnoreChildrenDecoratorDirective {} }) class DirectiveWithEvents {} +@Decorator({ + hostProperties: {'someField': 'someProp'} +}) +class DirectiveWithProperties {} + @Decorator({ properties: {'a': 'b'} }) class DirectiveWithBind {} -@Decorator() -class DirectiveWithPropertySetters { - constructor(@PropertySetter('someProp') someProp) {} -} - @Decorator() class DirectiveWithAttributes { constructor(@Attribute('someAttr') someAttr:string) {} @@ -568,7 +570,8 @@ class FakeProtoViewFactory extends ProtoViewFactory { this._results = results; } - createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto, directives:List):AppProtoView { + createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto, + directives:List):AppProtoView { ListWrapper.push(this.requests, [componentBinding, renderProtoView, directives]); return ListWrapper.removeAt(this._results, 0); } diff --git a/modules/angular2/test/core/compiler/element_injector_spec.js b/modules/angular2/test/core/compiler/element_injector_spec.js index b4e2624d95..acc3f66444 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.js +++ b/modules/angular2/test/core/compiler/element_injector_spec.js @@ -4,7 +4,7 @@ import {ListWrapper, MapWrapper, List, StringMapWrapper, iterateListLike} from ' import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding, TreeNode, ElementRef} from 'angular2/src/core/compiler/element_injector'; import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; -import {PropertySetter, Attribute, Query} from 'angular2/src/core/annotations/di'; +import {Attribute, Query} from 'angular2/src/core/annotations/di'; import {onDestroy} from 'angular2/src/core/annotations/annotations'; import {Optional, Injector, Inject, bind} from 'angular2/di'; import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; @@ -87,51 +87,6 @@ class HasEventEmitter { } } -class NeedsPropertySetter { - propSetter; - roleSetter; - classSetter; - classWithDashSetter; - styleSetter; - unitSetter; - constructor(@PropertySetter('title') propSetter: Function, @PropertySetter('attr.role') roleSetter: Function, - @PropertySetter('class.active') classSetter: Function, @PropertySetter('class.foo-bar') classWithDashSetter: Function, - @PropertySetter('style.width') styleSetter: Function, @PropertySetter('style.height.px') unitSetter: Function) { - this.propSetter = propSetter; - this.roleSetter = roleSetter; - this.classSetter = classSetter; - this.classWithDashSetter = classWithDashSetter; - this.styleSetter = styleSetter; - this.unitSetter = unitSetter; - } - setProp(value) { - this.propSetter(value); - } - setRole(value) { - this.roleSetter(value); - } - setClass(value) { - this.classSetter(value); - } - setStyle(value) { - this.styleSetter(value); - } - setStyleWithUnit(value) { - this.unitSetter(value); - } -} - -class NeedsPropertySetterNoType { - propSetter; - constructor(@PropertySetter('title') propSetter) { - this.propSetter = propSetter; - } - - setProp(value) { - this.propSetter(value); - } -} - class NeedsAttribute { typeAttribute; titleAttribute; @@ -708,45 +663,6 @@ export function main() { }); }); - describe('property setter', () => { - var renderer, view; - - beforeEach( () => { - renderer = new FakeRenderer(); - var protoView = new AppProtoView(null, null); - view = new AppView(renderer, null, protoView, MapWrapper.create()); - view.render = new ViewRef(); - }); - - it('should be injectable and callable', () => { - var preBuildObject = new PreBuiltObjects(view, null, null); - var inj = injector([NeedsPropertySetter], null, null, preBuildObject); - var component = inj.get(NeedsPropertySetter); - component.setProp('foobar'); - component.setRole('button'); - component.setClass(true); - component.classWithDashSetter(true); - component.setStyle('40px'); - component.setStyleWithUnit(50); - - expect(renderer.log[0]).toEqual([view.render, 0, 'title', 'foobar']); - expect(renderer.log[1]).toEqual([view.render, 0, 'attr.role', 'button']); - expect(renderer.log[2]).toEqual([view.render, 0, 'class.active', true]); - expect(renderer.log[3]).toEqual([view.render, 0, 'class.foo-bar', true]); - expect(renderer.log[4]).toEqual([view.render, 0, 'style.width', '40px']); - expect(renderer.log[5]).toEqual([view.render, 0, 'style.height.px', 50]); - }); - - it('should be injectable and callable without specifying param type annotation', () => { - var preBuildObject = new PreBuiltObjects(view, null, null); - var inj = injector([NeedsPropertySetterNoType], null, null, preBuildObject); - var component = inj.get(NeedsPropertySetterNoType); - component.setProp('foobar'); - - expect(renderer.log[0]).toEqual([view.render, 0, 'title', 'foobar']); - }); - }); - describe('static attributes', () => { it('should be injectable', () => { var attributes = MapWrapper.create(); diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index 3bac335f5b..4956644516 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -190,7 +190,6 @@ export function main() { })); tb.createView(MyComp, {context: ctx}).then((view) => { - ctx.ctxProp = 'a'; view.detectChanges(); @@ -591,6 +590,26 @@ export function main() { }); })); + it('should support updating host element via hostProperties', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideView(MyComp, new View({ + template: '
', + directives: [DecoratorUpdatingHostProperties] + })); + + tb.createView(MyComp, {context: ctx}).then((view) => { + var injector = view.rawView.elementInjectors[0]; + var updateHost = injector.get(DecoratorUpdatingHostProperties); + + updateHost.id = "newId"; + + view.detectChanges(); + + expect(view.rootNodes[0].id).toEqual("newId"); + + async.done(); + }); + })); + if (DOM.supportsDOMEvents()) { it('should support preventing default on render events', inject([TestBed, AsyncTestCompleter], (tb, async) => { tb.overrideView(MyComp, new View({ @@ -1047,6 +1066,20 @@ class DecoratorEmitingEvent { } } +@Decorator({ + selector: '[update-host-properties]', + hostProperties: { + 'id' : 'id' + } +}) +class DecoratorUpdatingHostProperties { + id:string; + + constructor() { + this.id = "one"; + } +} + @Decorator({ selector: '[listener]', hostListeners: {'event': 'onEvent($event)'} diff --git a/modules/angular2/test/forms/integration_spec.js b/modules/angular2/test/forms/integration_spec.js index f84bfb112e..4a5019e15e 100644 --- a/modules/angular2/test/forms/integration_spec.js +++ b/modules/angular2/test/forms/integration_spec.js @@ -16,7 +16,7 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; import {Inject} from 'angular2/di'; -import {Component, Decorator, View, PropertySetter} from 'angular2/angular2'; +import {Component, Decorator, View} from 'angular2/angular2'; import {TestBed} from 'angular2/src/test_lib/test_bed'; @@ -374,8 +374,8 @@ export function main() { ControlDirective, WrappedValue, RequiredValidatorDirective, - CheckboxControlValueAccessor, - DefaultValueAccessor]}) + DefaultValueAccessor, + CheckboxControlValueAccessor]}) class MyComp { form:any; name:string; @@ -390,19 +390,21 @@ class MyComp { selector:'[wrapped-value]', hostListeners: { 'change' : 'handleOnChange($event.target.value)' + }, + hostProperties: { + 'value' : 'value' } }) class WrappedValue { - _setProperty:Function; + value; onChange:Function; - constructor(cd:ControlDirective, @PropertySetter('value') setProperty:Function) { - this._setProperty = setProperty; + constructor(cd:ControlDirective) { cd.valueAccessor = this; } writeValue(value) { - this._setProperty(`!${value}!`); + this.value = `!${value}!`; } handleOnChange(value) { diff --git a/modules/angular2/test/render/dom/compiler/directive_parser_spec.js b/modules/angular2/test/render/dom/compiler/directive_parser_spec.js index 98a1280278..05a8da3ce0 100644 --- a/modules/angular2/test/render/dom/compiler/directive_parser_spec.js +++ b/modules/angular2/test/render/dom/compiler/directive_parser_spec.js @@ -23,6 +23,7 @@ export function main() { someDecorator, someDecoratorIgnoringChildren, someDecoratorWithProps, + someDecoratorWithHostProperties, someDecoratorWithEvents, someDecoratorWithGlobalEvents ]; @@ -112,12 +113,14 @@ export function main() { expect(simpleProp.source).toEqual('someValue'); }); - it('should store working property setters', () => { - var element = el(''); + it('should bind host directive properties', () => { + var element = el(''); var results = process(element); - var setter = MapWrapper.get(results[0].propertySetters, 'value'); - setter(element, 'abc'); - expect(element.value).toEqual('abc'); + + var directiveBinding = results[0].directives[0]; + + var ast = MapWrapper.get(directiveBinding.hostPropertyBindings, 'hostProperty'); + expect(ast.source).toEqual('dirProp'); }); it('should read attribute values', () => { @@ -256,10 +259,16 @@ var someDecoratorWithProps = new DirectiveMetadata({ 'dirProp': 'elProp', 'doubleProp': 'elProp | double' }), - setters: ['value'], readAttributes: ['some-attr'] }); +var someDecoratorWithHostProperties = new DirectiveMetadata({ + selector: '[some-decor-with-host-props]', + hostProperties: MapWrapper.createFromStringMap({ + 'dirProp': 'hostProperty' + }) +}); + var someDecoratorWithEvents = new DirectiveMetadata({ selector: '[some-decor-events]', hostListeners: MapWrapper.createFromStringMap({ diff --git a/modules/angular2/test/render/dom/compiler/property_binding_parser_spec.js b/modules/angular2/test/render/dom/compiler/property_binding_parser_spec.js index c2d5fbf144..3484091bbb 100644 --- a/modules/angular2/test/render/dom/compiler/property_binding_parser_spec.js +++ b/modules/angular2/test/render/dom/compiler/property_binding_parser_spec.js @@ -52,6 +52,12 @@ export function main() { expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('{{b}}'); }); + it('should store property setters as camel case', () => { + var element = el('
'); + var results = process(element); + expect(MapWrapper.get(results[0].propertyBindings, 'someProp')).toBeTruthy(); + }); + it('should detect var- syntax', () => { var results = process(el('')); expect(MapWrapper.get(results[0].variableBindings, 'b')).toEqual('a'); @@ -143,20 +149,6 @@ export function main() { expect(MapWrapper.get(results[0].attrs(), 'a')).toEqual('b'); expect(MapWrapper.get(results[0].attrs(), 'c')).toEqual('d'); }); - - it('should store working property setters', () => { - var element = el(''); - var results = process(element); - var setter = MapWrapper.get(results[0].propertySetters, 'value'); - setter(element, 'abc'); - expect(element.value).toEqual('abc'); - }); - - it('should store property setters as camel case', () => { - var element = el('
'); - var results = process(element); - expect(MapWrapper.get(results[0].propertySetters, 'someProp')).toBeTruthy(); - }); }); } diff --git a/modules/angular2_material/src/components/button/button.js b/modules/angular2_material/src/components/button/button.js index 232c70bd89..3f03bb7250 100644 --- a/modules/angular2_material/src/components/button/button.js +++ b/modules/angular2_material/src/components/button/button.js @@ -1,7 +1,6 @@ import {Component, View} from 'angular2/angular2'; -import {PropertySetter, EventEmitter} from 'angular2/src/core/annotations/di'; import {onChange} from 'angular2/src/core/annotations/annotations'; -import {isPresent, StringWrapper} from 'angular2/src/facade/lang'; +import {isPresent} from 'angular2/src/facade/lang'; @Component({selector: '[md-button]:not([href])'}) @@ -17,21 +16,18 @@ export class MdButton { 'disabled': 'disabled' }, hostListeners: {'click': 'onClick($event)'}, + hostProperties: {'tabIndex': 'tabIndex'}, lifecycle: [onChange] }) @View({ templateUrl: 'angular2_material/src/components/button/button.html' }) export class MdAnchor { - tabIndexSetter: Function; + tabIndex: number; /** Whether the component is disabled. */ disabled: boolean; - constructor(@PropertySetter('tabIndex') tabIndexSetter: Function) { - this.tabIndexSetter = tabIndexSetter; - } - onClick(event) { // A disabled anchor shouldn't navigate anywhere. if (isPresent(this.disabled) && this.disabled !== false) { @@ -42,6 +38,6 @@ export class MdAnchor { /** Invoked when a change is detected. */ onChange(_) { // A disabled anchor should not be in the tab flow. - this.tabIndexSetter(this.disabled ? -1 : 0); + this.tabIndex = this.disabled ? -1 : 0; } } diff --git a/modules/angular2_material/src/components/checkbox/checkbox.js b/modules/angular2_material/src/components/checkbox/checkbox.js index 2cecb7f31f..33f080d35c 100644 --- a/modules/angular2_material/src/components/checkbox/checkbox.js +++ b/modules/angular2_material/src/components/checkbox/checkbox.js @@ -1,4 +1,4 @@ -import {Component, View, Attribute, PropertySetter} from 'angular2/angular2'; +import {Component, View, Attribute} from 'angular2/angular2'; import {isPresent} from 'angular2/src/facade/lang'; import {KEY_SPACE} from 'angular2_material/src/core/constants' import {KeyboardEvent} from 'angular2/src/facade/browser'; @@ -11,6 +11,12 @@ import {KeyboardEvent} from 'angular2/src/facade/browser'; }, hostListeners: { 'keydown': 'onKeydown($event)' + }, + hostProperties: { + 'tabindex': 'tabindex', + 'role': 'attr.role', + 'checked': 'attr.aria-checked', + 'disabled_': 'attr.aria-disabled' } }) @View({ @@ -19,38 +25,21 @@ import {KeyboardEvent} from 'angular2/src/facade/browser'; }) export class MdCheckbox { /** Whether this checkbox is checked. */ - checked_: boolean; + checked: boolean; /** Whether this checkbox is disabled. */ disabled_: boolean; - /** Setter for `aria-checked` attribute. */ - ariaCheckedSetter: Function; + /** Setter for `role` attribute. */ + role: string; - /** Setter for `aria-disabled` attribute. */ - ariaDisabledSetter: Function; + /** Setter for tabindex */ + tabindex: any; - constructor( - @Attribute('tabindex') tabindex: string, - @PropertySetter('tabindex') tabindexSetter: Function, - @PropertySetter('attr.role') roleSetter: Function, - @PropertySetter('attr.aria-checked') ariaCheckedSetter: Function, - @PropertySetter('attr.aria-disabled') ariaDisabledSetter: Function) { - this.ariaCheckedSetter = ariaCheckedSetter; - this.ariaDisabledSetter = ariaDisabledSetter; - - roleSetter('checkbox'); + constructor(@Attribute('tabindex') tabindex: string) { + this.role = 'checkbox'; this.checked = false; - tabindexSetter(isPresent(tabindex) ? tabindex : '0'); - } - - get checked() { - return this.checked_; - } - - set checked(value) { - this.checked_ = value; - this.ariaCheckedSetter(value); + this.tabindex = isPresent(tabindex) ? tabindex : '0'; } get disabled() { @@ -59,7 +48,6 @@ export class MdCheckbox { set disabled(value) { this.disabled_ = isPresent(value) && value !== false; - this.ariaDisabledSetter(this.disabled_); } onKeydown(event: KeyboardEvent) { @@ -76,6 +64,5 @@ export class MdCheckbox { } this.checked = !this.checked; - this.ariaCheckedSetter(this.checked); } } diff --git a/modules/angular2_material/src/components/grid_list/grid_list.js b/modules/angular2_material/src/components/grid_list/grid_list.js index 71e7affab6..1a617de25c 100644 --- a/modules/angular2_material/src/components/grid_list/grid_list.js +++ b/modules/angular2_material/src/components/grid_list/grid_list.js @@ -2,7 +2,6 @@ import {Component, View, onAllChangesDone, Parent} from 'angular2/angular2'; import {onDestroy, onChange} from 'angular2/src/core/annotations/annotations'; import {ListWrapper} from 'angular2/src/facade/collection'; import {isPresent, isString, NumberWrapper, stringify} from 'angular2/src/facade/lang'; -import {PropertySetter} from 'angular2/src/core/annotations/di'; // TODO(jelbourn): Set appropriate aria attributes for grid list elements. @@ -172,6 +171,15 @@ export class MdGridList { 'rowspan': 'rowspan', 'colspan': 'colspan' }, + hostProperties: { + 'styleHeight': 'style.height', + 'styleWidth': 'style.width', + 'styleTop': 'style.top', + 'styleLeft': 'style.left', + 'styleMarginTop': 'style.marginTop', + 'stylePaddingTop': 'style.paddingTop', + 'role': 'role' + }, lifecycle: [onDestroy, onChange] }) @View({ @@ -181,40 +189,28 @@ export class MdGridTile { gridList: MdGridList; rowspan: number; colspan: number; - heightSetter; - widthSetter; - topSetter; - leftSetter; - marginTopSetter; - paddingTopSetter; + + styleHeight:any; + styleWidth:any; + styleTop:any; + styleLeft:any; + styleMarginTop:any; + stylePaddingTop:any; + role:any; isRegisteredWithGridList: boolean; - constructor( - @Parent() gridList: MdGridList, - @PropertySetter('style.height') heightSetter: Function, - @PropertySetter('style.width') widthSetter: Function, - @PropertySetter('style.top') topSetter: Function, - @PropertySetter('style.left') leftSetter: Function, - @PropertySetter('style.marginTop') marginTopSetter: Function, - @PropertySetter('style.paddingTop') paddingTopSetter: Function, - @PropertySetter('role') roleSetter: Function - ) { + constructor(@Parent() gridList: MdGridList) { this.gridList = gridList; - this.heightSetter = heightSetter; - this.widthSetter = widthSetter; - this.topSetter = topSetter; - this.leftSetter = leftSetter; - this.marginTopSetter = marginTopSetter; - this.paddingTopSetter = paddingTopSetter; - roleSetter('listitem'); + + this.role = 'listitem'; // Tiles default to 1x1, but rowspan and colspan can be changed via binding. this.rowspan = 1; this.colspan = 1; // DEBUG - heightSetter(`${gridList.tiles.length * 100}px`); + this.styleHeight = `${gridList.tiles.length * 100}px`; } /** diff --git a/modules/angular2_material/src/components/progress-linear/progress_linear.js b/modules/angular2_material/src/components/progress-linear/progress_linear.js index e7803807e6..5823fcd445 100644 --- a/modules/angular2_material/src/components/progress-linear/progress_linear.js +++ b/modules/angular2_material/src/components/progress-linear/progress_linear.js @@ -1,4 +1,4 @@ -import {Component, View, Attribute, PropertySetter, onChange} from 'angular2/angular2'; +import {Component, View, Attribute, onChange} from 'angular2/angular2'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {Math} from 'angular2/src/facade/math'; @@ -8,6 +8,12 @@ import {Math} from 'angular2/src/facade/math'; properties: { 'value': 'value', 'bufferValue': 'buffer-value' + }, + hostProperties: { + 'role': 'attr.role', + 'ariaValuemin': 'attr.aria-valuemin', + 'ariaValuemax': 'attr.aria-valuemax', + 'ariaValuenow': 'attr.aria-valuenow' } }) @View({ @@ -33,19 +39,20 @@ export class MdProgressLinear { /** CSS `transform` property applied to the secondary bar. */ secondaryBarTransform: string; - constructor( - @Attribute('md-mode') mode: string, - @PropertySetter('attr.role') roleSetter: Function, - @PropertySetter('attr.aria-valuemin') ariaValueMinSetter: Function, - @PropertySetter('attr.aria-valuemax') ariaValueMaxSetter: Function, - @PropertySetter('attr.aria-valuenow') ariaValueNowSetter: Function) { + + role:any; + ariaValuemin:any; + ariaValuemax:any; + ariaValuenow:any; + + constructor(@Attribute('md-mode') mode: string) { this.ariaValueNowSetter = ariaValueNowSetter; this.primaryBarTransform = ''; this.secondaryBarTransform = ''; - roleSetter('progressbar'); - ariaValueMinSetter('0'); - ariaValueMaxSetter('100'); + this.role = 'progressbar'; + this.ariaValuemin = '0'; + this.ariaValuemax = '100'; this.mode = isPresent(mode) ? mode : Mode.DETERMINATE; } diff --git a/modules/angular2_material/src/components/radio/radio_button.js b/modules/angular2_material/src/components/radio/radio_button.js index cf96e01589..ba526a06a7 100644 --- a/modules/angular2_material/src/components/radio/radio_button.js +++ b/modules/angular2_material/src/components/radio/radio_button.js @@ -1,4 +1,4 @@ -import {Component, View, Parent, Ancestor, Attribute, PropertySetter} from 'angular2/angular2'; +import {Component, View, Parent, Ancestor, Attribute} from 'angular2/angular2'; import {Optional} from 'angular2/src/di/annotations'; import {MdRadioDispatcher} from 'angular2_material/src/components/radio/radio_dispatcher' import {onChange} from 'angular2/src/core/annotations/annotations'; @@ -34,6 +34,13 @@ var _uniqueIdCounter:number = 0; }, hostListeners: { 'keydown': 'onKeydown($event)' + }, + hostProperties: { + 'id': 'id', + 'tabindex': 'tabindex', + 'role': 'attr.role', + 'checked': 'attr.aria-checked', + 'disabled': 'attr.aria-disabled' } }) @View({ @@ -42,7 +49,7 @@ var _uniqueIdCounter:number = 0; }) export class MdRadioButton { /** Whether this radio is checked. */ - checked_: boolean; + checked: boolean; /** Whether the radio is disabled. */ disabled_: boolean; @@ -62,36 +69,26 @@ export class MdRadioButton { /** Dispatcher for coordinating radio unique-selection by name. */ radioDispatcher: MdRadioDispatcher; - /** Setter for `aria-checked` attribute. */ - ariaCheckedSetter: Function; - - /** Setter for `aria-disabled` attribute. */ - ariaDisabledSetter: Function; + tabindex:any; + + role:any; constructor( @Optional() @Parent() radioGroup: MdRadioGroup, @Attribute('id') id: string, @Attribute('tabindex') tabindex: string, - @PropertySetter('id') idSetter: Function, - @PropertySetter('tabindex') tabindexSetter: Function, - @PropertySetter('attr.role') roleSetter: Function, - @PropertySetter('attr.aria-checked') ariaCheckedSetter: Function, - @PropertySetter('attr.aria-disabled') ariaDisabledSetter: Function, radioDispatcher: MdRadioDispatcher) { // Assertions. Ideally these should be stripped out by the compiler. // TODO(jelbourn): Assert that there's no name binding AND a parent radio group. this.radioGroup = radioGroup; this.radioDispatcher = radioDispatcher; - this.ariaCheckedSetter = ariaCheckedSetter; - this.ariaDisabledSetter = ariaDisabledSetter; this.value = null; - roleSetter('radio'); + this.role = 'radio'; this.checked = false; - this.id = isPresent(id) ? id : `md-radio-${_uniqueIdCounter++}`; - idSetter(this.id); + this.id = isPresent(id) ? id : `md-radio-${_uniqueIdCounter++}`;; // Whenever a radio button with the same name is checked, uncheck this radio button. radioDispatcher.listen((name) => { @@ -108,7 +105,7 @@ export class MdRadioButton { // If the user has not set a tabindex, default to zero (in the normal document flow). if (!isPresent(radioGroup)) { - tabindexSetter(isPresent(tabindex) ? tabindex : '0'); + this.tabindex = isPresent(tabindex) ? tabindex : '0'; } } @@ -129,22 +126,12 @@ export class MdRadioButton { (isPresent(this.radioGroup) && this.radioGroup.disabled); } - get checked() { - return this.checked_; - } - - set checked(value) { - this.checked_ = value; - this.ariaCheckedSetter(value); - } - get disabled() { return this.disabled_; } set disabled(value) { this.disabled_ = isPresent(value) && value !== false; - this.ariaDisabledSetter(this.disabled_); } /** Select this radio button. */ @@ -183,6 +170,13 @@ export class MdRadioButton { }, hostListeners: { 'keydown': 'onKeydown($event)' + }, + hostProperties: { + 'tabindex': 'tabindex', + 'role': 'attr.role', + 'checked': 'attr.aria-checked', + 'disabled': 'attr.aria-disabled', + 'activedescendant': 'attr.aria-activedescendant' } }) @View({ @@ -201,11 +195,7 @@ export class MdRadioGroup { /** List of child radio buttons. */ radios_: List; - changeEmitter: Function; - - ariaActiveDescendantSetter: Function; - - ariaDisabledSetter: Function; + activedescendant: any; disabled_: boolean; @@ -214,30 +204,28 @@ export class MdRadioGroup { change:EventEmitter; + tabindex:any; + + role:any; + constructor( @Attribute('tabindex') tabindex: string, @Attribute('disabled') disabled: string, - @PropertySetter('tabindex') tabindexSetter: Function, - @PropertySetter('attr.role') roleSetter: Function, - @PropertySetter('attr.aria-disabled') ariaDisabledSetter: Function, - @PropertySetter('attr.aria-activedescendant') ariaActiveDescendantSetter: Function, radioDispatcher: MdRadioDispatcher) { this.name_ = `md-radio-group-${_uniqueIdCounter++}`; this.radios_ = []; this.change = new EventEmitter(); - this.ariaActiveDescendantSetter = ariaActiveDescendantSetter; - this.ariaDisabledSetter = ariaDisabledSetter; this.radioDispatcher = radioDispatcher; this.selectedRadioId = ''; this.disabled_ = false; - roleSetter('radiogroup'); + this.role = 'radiogroup'; // The simple presence of the `disabled` attribute dictates disabled state. this.disabled = isPresent(disabled); // If the user has not set a tabindex, default to zero (in the normal document flow). - tabindexSetter(isPresent(tabindex) ? tabindex : '0'); + this.tabindex = isPresent(tabindex) ? tabindex : '0'; } /** Gets the name of this group, as to be applied in the HTML 'name' attribute. */ @@ -251,7 +239,6 @@ export class MdRadioGroup { set disabled(value) { this.disabled_ = isPresent(value) && value !== false; - this.ariaDisabledSetter(this.disabled_); } /** Change handler invoked when bindings are resolved or when bindings have changed. */ @@ -267,7 +254,7 @@ export class MdRadioGroup { if (radio.value == this.value) { radio.checked = true; this.selectedRadioId = radio.id; - this.ariaActiveDescendantSetter(radio.id); + this.activedescendant = radio.id; } }); } @@ -277,7 +264,7 @@ export class MdRadioGroup { updateValue(value: any, id: string) { this.value = value; this.selectedRadioId = id; - this.ariaActiveDescendantSetter(id); + this.activedescendant = id; ObservableWrapper.callNext(this.change, null); } @@ -335,6 +322,6 @@ export class MdRadioGroup { this.value = radio.value; this.selectedRadioId = radio.id; - this.ariaActiveDescendantSetter(radio.id); + this.activedescendant = radio.id; } } diff --git a/modules/angular2_material/src/components/switcher/switch.js b/modules/angular2_material/src/components/switcher/switch.js index c092b686df..986f857759 100644 --- a/modules/angular2_material/src/components/switcher/switch.js +++ b/modules/angular2_material/src/components/switcher/switch.js @@ -1,4 +1,4 @@ -import {Component, View, Attribute, PropertySetter} from 'angular2/angular2'; +import {Component, View, Attribute} from 'angular2/angular2'; import {isPresent} from 'angular2/src/facade/lang'; import {KEY_SPACE} from 'angular2_material/src/core/constants' import {KeyboardEvent} from 'angular2/src/facade/browser'; @@ -13,6 +13,11 @@ import {KeyboardEvent} from 'angular2/src/facade/browser'; }, hostListeners: { 'keydown': 'onKeydown($event)' + }, + hostProperties: { + 'checked': 'attr.aria-checked', + 'disabled_': 'attr.aria-disabled', + 'role': 'attr.role' } }) @View({ @@ -21,38 +26,18 @@ import {KeyboardEvent} from 'angular2/src/facade/browser'; }) export class MdSwitch { /** Whether this switch is checked. */ - checked_: boolean; + checked: boolean; /** Whether this switch is disabled. */ disabled_: boolean; - /** Setter for `aria-checked` attribute. */ - ariaCheckedSetter: Function; + tabindex:any; + role:any; - /** Setter for `aria-disabled` attribute. */ - ariaDisabledSetter: Function; - - constructor( - @Attribute('tabindex') tabindex: string, - @PropertySetter('tabindex') tabindexSetter: Function, - @PropertySetter('attr.role') roleSetter: Function, - @PropertySetter('attr.aria-checked') ariaCheckedSetter: Function, - @PropertySetter('attr.aria-disabled') ariaDisabledSetter: Function) { - this.ariaCheckedSetter = ariaCheckedSetter; - this.ariaDisabledSetter = ariaDisabledSetter; - - roleSetter('checkbox'); + constructor(@Attribute('tabindex') tabindex: string) { + this.role = 'checkbox'; this.checked = false; - tabindexSetter(isPresent(tabindex) ? tabindex : '0'); - } - - get checked() { - return this.checked_; - } - - set checked(value) { - this.checked_ = value; - this.ariaCheckedSetter(value); + this.tabindex = isPresent(tabindex) ? tabindex : '0'; } get disabled() { @@ -61,7 +46,6 @@ export class MdSwitch { set disabled(value) { this.disabled_ = isPresent(value) && value !== false; - this.ariaDisabledSetter(this.disabled_); } onKeydown(event: KeyboardEvent) { @@ -78,7 +62,6 @@ export class MdSwitch { } this.checked = !this.checked; - this.ariaCheckedSetter(this.checked); } } diff --git a/modules/benchmarks/src/change_detection/change_detection_benchmark.js b/modules/benchmarks/src/change_detection/change_detection_benchmark.js index 5352c4866f..7800bc45b3 100644 --- a/modules/benchmarks/src/change_detection/change_detection_benchmark.js +++ b/modules/benchmarks/src/change_detection/change_detection_benchmark.js @@ -12,6 +12,7 @@ import { JitChangeDetection, BindingRecord, DirectiveRecord, + DirectiveIndex, DEFAULT } from 'angular2/change_detection'; @@ -191,7 +192,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations, objec var proto = changeDetection.createProtoChangeDetector("proto"); - var directiveRecord = new DirectiveRecord(0, 0, false, false, DEFAULT); + var directiveRecord = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, DEFAULT); var bindings = [ BindingRecord.createForDirective(parser.parseBinding('field0', null), "field0", reflector.setter("field0"), directiveRecord), BindingRecord.createForDirective(parser.parseBinding('field1', null), "field1", reflector.setter("field1"), directiveRecord),