feat(view): reimplemented property setters using change detection

This commit is contained in:
vsavkin 2015-04-21 11:47:53 -07:00
parent 8a92a1f13e
commit 8ccafb0524
36 changed files with 510 additions and 469 deletions

View File

@ -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 {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 {DynamicProtoChangeDetector, JitProtoChangeDetector} from './src/change_detection/proto_change_detector';
export {BindingRecord} from './src/change_detection/binding_record'; 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 {DynamicChangeDetector} from './src/change_detection/dynamic_change_detector';
export {ChangeDetectorRef} from './src/change_detection/change_detector_ref'; export {ChangeDetectorRef} from './src/change_detection/change_detector_ref';
export {PipeRegistry} from './src/change_detection/pipes/pipe_registry'; export {PipeRegistry} from './src/change_detection/pipes/pipe_registry';

View File

@ -1,7 +1,7 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {SetterFn} from 'angular2/src/reflection/types'; import {SetterFn} from 'angular2/src/reflection/types';
import {AST} from './parser/ast'; import {AST} from './parser/ast';
import {DirectiveRecord} from './directive_record'; import {DirectiveIndex, DirectiveRecord} from './directive_record';
const DIRECTIVE="directive"; const DIRECTIVE="directive";
const ELEMENT="element"; const ELEMENT="element";
@ -11,14 +11,16 @@ export class BindingRecord {
mode:string; mode:string;
ast:AST; ast:AST;
implicitReceiver:any; //number | DirectiveIndex
elementIndex:number; elementIndex:number;
propertyName:string; propertyName:string;
setter:SetterFn; setter:SetterFn;
directiveRecord:DirectiveRecord; 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.mode = mode;
this.implicitReceiver = implicitReceiver;
this.ast = ast; this.ast = ast;
this.elementIndex = elementIndex; this.elementIndex = elementIndex;
@ -49,14 +51,18 @@ export class BindingRecord {
} }
static createForDirective(ast:AST, propertyName:string, setter:SetterFn, directiveRecord:DirectiveRecord) { 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) { 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) { 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);
} }
} }

View File

@ -3,7 +3,7 @@ import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/faca
import {AbstractChangeDetector} from './abstract_change_detector'; import {AbstractChangeDetector} from './abstract_change_detector';
import {ChangeDetectionUtil} from './change_detection_util'; import {ChangeDetectionUtil} from './change_detection_util';
import {DirectiveRecord} from './directive_record'; import {DirectiveIndex, DirectiveRecord} from './directive_record';
import { import {
ProtoRecord, ProtoRecord,
@ -81,12 +81,12 @@ function hydrateTemplate(type:string, mode:string, fieldDefinitions:string, pipe
directiveFieldNames:List<String>, detectorFieldNames:List<String>):string { directiveFieldNames:List<String>, detectorFieldNames:List<String>):string {
var directiveInit = ""; var directiveInit = "";
for(var i = 0; i < directiveFieldNames.length; ++i) { 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 = ""; var detectorInit = "";
for(var i = 0; i < detectorFieldNames.length; ++i) { 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 ` return `
@ -313,18 +313,18 @@ export class ChangeDetectorJITGenerator {
} }
getDirectiveFieldNames():List<string> { getDirectiveFieldNames():List<string> {
return this.directiveRecords.map((d) => this.getDirective(d)); return this.directiveRecords.map((d) => this.getDirective(d.directiveIndex));
} }
getDetectorFieldNames():List<string> { getDetectorFieldNames():List<string> {
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}`; return `this.directive_${d.name}`;
} }
getDetector(d:DirectiveRecord) { getDetector(d:DirectiveIndex) {
return `this.detector_${d.name}`; return `this.detector_${d.name}`;
} }
@ -359,7 +359,7 @@ export class ChangeDetectorJITGenerator {
for (var i = dirs.length - 1; i >= 0; --i) { for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i]; var dir = dirs[i];
if (dir.callOnAllChangesDone) { if (dir.callOnAllChangesDone) {
var directive = `this.directive_${dir.name}`; var directive = `this.directive_${dir.directiveIndex.name}`;
notifications.push(onAllChangesDoneTemplate(directive)); notifications.push(onAllChangesDoneTemplate(directive));
} }
} }
@ -425,7 +425,7 @@ export class ChangeDetectorJITGenerator {
} }
genUpdateCurrentValue(r:ProtoRecord):string { genUpdateCurrentValue(r:ProtoRecord):string {
var context = this.localNames[r.contextIndex]; var context = this.getContext(r);
var newValue = this.localNames[r.selfIndex]; var newValue = this.localNames[r.selfIndex];
var args = this.genArgs(r); 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 { ifChangedGuard(r:ProtoRecord, body:string):string {
return ifChangedGuardTemplate(r.args.map((a) => this.changeNames[a]), body); return ifChangedGuardTemplate(r.args.map((a) => this.changeNames[a]), body);
} }
@ -491,7 +499,7 @@ export class ChangeDetectorJITGenerator {
var br = r.bindingRecord; var br = r.bindingRecord;
if (br.isDirective()) { 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); return updateDirectiveTemplate(oldValue, newValue, directiveProperty);
} else { } else {
return updateElementTemplate(oldValue, newValue); return updateElementTemplate(oldValue, newValue);
@ -513,7 +521,7 @@ export class ChangeDetectorJITGenerator {
genNotifyOnChanges(r:ProtoRecord):string{ genNotifyOnChanges(r:ProtoRecord):string{
var br = r.bindingRecord; var br = r.bindingRecord;
if (r.lastInDirective && br.callOnChange()) { if (r.lastInDirective && br.callOnChange()) {
return notifyOnChangesTemplate(this.getDirective(br.directiveRecord)); return notifyOnChangesTemplate(this.getDirective(br.directiveRecord.directiveIndex));
} else { } else {
return ""; return "";
} }
@ -522,7 +530,7 @@ export class ChangeDetectorJITGenerator {
genNotifyOnPushDetectors(r:ProtoRecord):string{ genNotifyOnPushDetectors(r:ProtoRecord):string{
var br = r.bindingRecord; var br = r.bindingRecord;
if (r.lastInDirective && br.isOnPushChangeDetection()) { if (r.lastInDirective && br.isOnPushChangeDetection()) {
return notifyOnPushDetectorsTemplate(this.getDetector(br.directiveRecord)); return notifyOnPushDetectorsTemplate(this.getDetector(br.directiveRecord.directiveIndex));
} else { } else {
return ""; return "";
} }

View File

@ -45,6 +45,7 @@ function _selfRecord(r:ProtoRecord, contextIndex:number, selfIndex:number):Proto
[], [],
r.fixedArgs, r.fixedArgs,
contextIndex, contextIndex,
r.directiveIndex,
selfIndex, selfIndex,
r.bindingRecord, r.bindingRecord,
r.expressionAsString, r.expressionAsString,
@ -72,6 +73,7 @@ function _replaceIndices(r:ProtoRecord, selfIndex:number, indexMap:Map) {
args, args,
r.fixedArgs, r.fixedArgs,
contextIndex, contextIndex,
r.directiveIndex,
selfIndex, selfIndex,
r.bindingRecord, r.bindingRecord,
r.expressionAsString, r.expressionAsString,

View File

@ -1,16 +1,27 @@
import {ON_PUSH} from './constants'; import {ON_PUSH} from './constants';
import {StringWrapper} from 'angular2/src/facade/lang'; import {StringWrapper} from 'angular2/src/facade/lang';
export class DirectiveRecord { export class DirectiveIndex {
elementIndex:number; elementIndex:number;
directiveIndex: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; callOnAllChangesDone:boolean;
callOnChange:boolean; callOnChange:boolean;
changeDetection:string; changeDetection:string;
constructor(elementIndex:number, directiveIndex:number, constructor(directiveIndex:DirectiveIndex, callOnAllChangesDone:boolean, callOnChange:boolean, changeDetection:string) {
callOnAllChangesDone:boolean, callOnChange:boolean, changeDetection:string) {
this.elementIndex = elementIndex;
this.directiveIndex = directiveIndex; this.directiveIndex = directiveIndex;
this.callOnAllChangesDone = callOnAllChangesDone; this.callOnAllChangesDone = callOnAllChangesDone;
this.callOnChange = callOnChange; this.callOnChange = callOnChange;
@ -20,8 +31,4 @@ export class DirectiveRecord {
isOnPushChangeDetection():boolean { isOnPushChangeDetection():boolean {
return StringWrapper.equals(this.changeDetection, ON_PUSH); return StringWrapper.equals(this.changeDetection, ON_PUSH);
} }
get name() {
return `${this.elementIndex}_${this.directiveIndex}`;
}
} }

View File

@ -3,7 +3,6 @@ import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/faca
import {AbstractChangeDetector} from './abstract_change_detector'; import {AbstractChangeDetector} from './abstract_change_detector';
import {BindingRecord} from './binding_record'; import {BindingRecord} from './binding_record';
import {DirectiveRecord} from './directive_record';
import {PipeRegistry} from './pipes/pipe_registry'; import {PipeRegistry} from './pipes/pipe_registry';
import {ChangeDetectionUtil, uninitialized} from './change_detection_util'; import {ChangeDetectionUtil, uninitialized} from './change_detection_util';
@ -111,12 +110,12 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
if (proto.lastInDirective) { if (proto.lastInDirective) {
if (isPresent(changes)) { if (isPresent(changes)) {
this._getDirectiveFor(directiveRecord).onChange(changes); this._getDirectiveFor(directiveRecord.directiveIndex).onChange(changes);
changes = null; changes = null;
} }
if (isChanged && bindingRecord.isOnPushChangeDetection()) { if (isChanged && bindingRecord.isOnPushChangeDetection()) {
this._getDetectorFor(directiveRecord).markAsCheckOnce(); this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
} }
isChanged = false; isChanged = false;
@ -129,7 +128,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
for (var i = dirs.length - 1; i >= 0; --i) { for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i]; var dir = dirs[i];
if (dir.callOnAllChangesDone) { 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)) { if (isBlank(bindingRecord.directiveRecord)) {
this.dispatcher.notifyOnBinding(bindingRecord, change.currentValue); this.dispatcher.notifyOnBinding(bindingRecord, change.currentValue);
} else { } 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) { _getDirectiveFor(directiveIndex) {
return this.directives.getDirectiveFor(directive); return this.directives.getDirectiveFor(directiveIndex);
} }
_getDetectorFor(directive:DirectiveRecord) { _getDetectorFor(directiveIndex) {
return this.directives.getDetectorFor(directive); return this.directives.getDetectorFor(directiveIndex);
} }
_check(proto:ProtoRecord) { _check(proto:ProtoRecord) {
@ -235,6 +235,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
var pipe = this._pipeFor(proto, context); var pipe = this._pipeFor(proto, context);
var newValue = pipe.transform(context); var newValue = pipe.transform(context);
if (! ChangeDetectionUtil.noChangeMarker(newValue)) { if (! ChangeDetectionUtil.noChangeMarker(newValue)) {
var prevValue = this._readSelf(proto); var prevValue = this._readSelf(proto);
this._writeSelf(proto, newValue); this._writeSelf(proto, newValue);
@ -272,6 +273,12 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
} }
_readContext(proto:ProtoRecord) { _readContext(proto:ProtoRecord) {
if (proto.contextIndex == -1) {
return this._getDirectiveFor(proto.directiveIndex);
} else {
return this.values[proto.contextIndex];
}
return this.values[proto.contextIndex]; return this.values[proto.contextIndex];
} }

View File

@ -450,7 +450,7 @@ export class AstVisitor {
export class AstTransformer { export class AstTransformer {
visitImplicitReceiver(ast:ImplicitReceiver) { visitImplicitReceiver(ast:ImplicitReceiver) {
return new ImplicitReceiver(); return ast;
} }
visitInterpolation(ast:Interpolation) { visitInterpolation(ast:Interpolation) {

View File

@ -28,6 +28,7 @@ import {DynamicChangeDetector} from './dynamic_change_detector';
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator'; import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
import {PipeRegistry} from './pipes/pipe_registry'; import {PipeRegistry} from './pipes/pipe_registry';
import {BindingRecord} from './binding_record'; import {BindingRecord} from './binding_record';
import {DirectiveIndex} from './directive_record';
import {coalesce} from './coalesce'; import {coalesce} from './coalesce';
@ -153,7 +154,7 @@ class _ConvertAstIntoProtoRecords {
} }
visitImplicitReceiver(ast:ImplicitReceiver) { visitImplicitReceiver(ast:ImplicitReceiver) {
return 0; return this.bindingRecord.implicitReceiver;
} }
visitInterpolation(ast:Interpolation) { visitInterpolation(ast:Interpolation) {
@ -247,9 +248,15 @@ class _ConvertAstIntoProtoRecords {
_addRecord(type, name, funcOrValue, args, fixedArgs, context) { _addRecord(type, name, funcOrValue, args, fixedArgs, context) {
var selfIndex = ++ this.contextIndex; var selfIndex = ++ this.contextIndex;
ListWrapper.push(this.protoRecords, if (context instanceof DirectiveIndex) {
new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, selfIndex, ListWrapper.push(this.protoRecords,
this.bindingRecord, this.expressionAsString, false, false)); 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; return selfIndex;
} }
} }

View File

@ -1,5 +1,6 @@
import {List} from 'angular2/src/facade/collection'; import {List} from 'angular2/src/facade/collection';
import {BindingRecord} from './binding_record'; import {BindingRecord} from './binding_record';
import {DirectiveIndex} from './directive_record';
export const RECORD_TYPE_SELF = 0; export const RECORD_TYPE_SELF = 0;
export const RECORD_TYPE_CONST = 1; export const RECORD_TYPE_CONST = 1;
@ -19,7 +20,10 @@ export class ProtoRecord {
funcOrValue:any; funcOrValue:any;
args:List; args:List;
fixedArgs:List; fixedArgs:List;
contextIndex:number; contextIndex:number;
directiveIndex:DirectiveIndex;
selfIndex:number; selfIndex:number;
bindingRecord:BindingRecord; bindingRecord:BindingRecord;
lastInBinding:boolean; lastInBinding:boolean;
@ -32,6 +36,7 @@ export class ProtoRecord {
args:List, args:List,
fixedArgs:List, fixedArgs:List,
contextIndex:number, contextIndex:number,
directiveIndex:DirectiveIndex,
selfIndex:number, selfIndex:number,
bindingRecord:BindingRecord, bindingRecord:BindingRecord,
expressionAsString:string, expressionAsString:string,
@ -43,7 +48,10 @@ export class ProtoRecord {
this.funcOrValue = funcOrValue; this.funcOrValue = funcOrValue;
this.args = args; this.args = args;
this.fixedArgs = fixedArgs; this.fixedArgs = fixedArgs;
this.contextIndex = contextIndex; this.contextIndex = contextIndex;
this.directiveIndex = directiveIndex;
this.selfIndex = selfIndex; this.selfIndex = selfIndex;
this.bindingRecord = bindingRecord; this.bindingRecord = bindingRecord;
this.lastInBinding = lastInBinding; this.lastInBinding = lastInBinding;

View File

@ -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 * 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 * 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 * 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`. * {@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. * 5 would not be included, because it is not a direct child.
* *
* ### Injecting a live collection of descendant directives * ### Injecting a live collection of descendant directives
@ -444,6 +444,29 @@ export class Directive extends Injectable {
*/ */
hostListeners:any; // StringMap 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. * Specifies a set of lifecycle hostListeners in which the directive participates.
* *
@ -457,12 +480,14 @@ export class Directive extends Injectable {
properties, properties,
events, events,
hostListeners, hostListeners,
hostProperties,
lifecycle lifecycle
}:{ }:{
selector:string, selector:string,
properties:any, properties:any,
events:List, events:List,
hostListeners: any, hostListeners: any,
hostProperties: any,
lifecycle:List lifecycle:List
}={}) }={})
{ {
@ -471,6 +496,7 @@ export class Directive extends Injectable {
this.properties = properties; this.properties = properties;
this.events = events; this.events = events;
this.hostListeners = hostListeners; this.hostListeners = hostListeners;
this.hostProperties = hostProperties;
this.lifecycle = lifecycle; this.lifecycle = lifecycle;
} }
@ -579,20 +605,22 @@ export class Component extends Directive {
*/ */
injectables:List; injectables:List;
@CONST() @CONST()
constructor({ constructor({
selector, selector,
properties, properties,
events, events,
hostListeners, hostListeners,
injectables, hostProperties,
lifecycle, injectables,
changeDetection = DEFAULT lifecycle,
changeDetection = DEFAULT
}:{ }:{
selector:string, selector:string,
properties:Object, properties:Object,
events:List, events:List,
hostListeners:Object, hostListeners:any,
hostProperties:any,
injectables:List, injectables:List,
lifecycle:List, lifecycle:List,
changeDetection:string changeDetection:string
@ -603,6 +631,7 @@ export class Component extends Directive {
properties: properties, properties: properties,
events: events, events: events,
hostListeners: hostListeners, hostListeners: hostListeners,
hostProperties: hostProperties,
lifecycle: lifecycle lifecycle: lifecycle
}); });
@ -667,17 +696,19 @@ export class DynamicComponent extends Directive {
@CONST() @CONST()
constructor({ constructor({
selector, selector,
properties, properties,
events, events,
hostListeners, hostListeners,
injectables, hostProperties,
lifecycle injectables,
lifecycle
}:{ }:{
selector:string, selector:string,
properties:Object, properties:any,
events:List, events:List,
hostListeners:Object, hostListeners:any,
hostProperties:any,
injectables:List, injectables:List,
lifecycle:List lifecycle:List
}={}) { }={}) {
@ -686,6 +717,7 @@ export class DynamicComponent extends Directive {
properties: properties, properties: properties,
events: events, events: events,
hostListeners: hostListeners, hostListeners: hostListeners,
hostProperties: hostProperties,
lifecycle: lifecycle lifecycle: lifecycle
}); });
@ -767,6 +799,7 @@ export class Decorator extends Directive {
properties, properties,
events, events,
hostListeners, hostListeners,
hostProperties,
lifecycle, lifecycle,
compileChildren = true, compileChildren = true,
}:{ }:{
@ -774,16 +807,18 @@ export class Decorator extends Directive {
properties:any, properties:any,
events:List, events:List,
hostListeners:any, hostListeners:any,
hostProperties:any,
lifecycle:List, lifecycle:List,
compileChildren:boolean compileChildren:boolean
}={}) }={})
{ {
super({ super({
selector: selector, selector: selector,
properties: properties, properties: properties,
events: events, events: events,
hostListeners: hostListeners, hostListeners: hostListeners,
lifecycle: lifecycle hostProperties: hostProperties,
lifecycle: lifecycle
}); });
this.compileChildren = compileChildren; this.compileChildren = compileChildren;
} }
@ -889,20 +924,24 @@ export class Viewport extends Directive {
properties, properties,
events, events,
hostListeners, hostListeners,
hostProperties,
lifecycle lifecycle
}:{ }:{
selector:string, selector:string,
properties:any, properties:any,
events:List, hostListeners:any,
lifecycle:List hostProperties:any,
events:List,
lifecycle:List
}={}) }={})
{ {
super({ super({
selector: selector, selector: selector,
properties: properties, properties: properties,
events: events, events: events,
hostListeners: hostListeners, hostListeners: hostListeners,
lifecycle: lifecycle hostProperties: hostProperties,
lifecycle: lifecycle
}); });
} }
} }

View File

@ -1,28 +1,6 @@
import {CONST} from 'angular2/src/facade/lang'; import {CONST} from 'angular2/src/facade/lang';
import {DependencyAnnotation} from 'angular2/di'; 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. * Specifies that a constant attribute value should be injected.
* *

View File

@ -223,12 +223,8 @@ export class Compiler {
renderType = renderApi.DirectiveMetadata.DECORATOR_TYPE; renderType = renderApi.DirectiveMetadata.DECORATOR_TYPE;
compileChildren = ann.compileChildren; compileChildren = ann.compileChildren;
} }
var setters = [];
var readAttributes = []; var readAttributes = [];
ListWrapper.forEach(directiveBinding.dependencies, (dep) => { ListWrapper.forEach(directiveBinding.dependencies, (dep) => {
if (isPresent(dep.propSetterName)) {
ListWrapper.push(setters, dep.propSetterName);
}
if (isPresent(dep.attributeName)) { if (isPresent(dep.attributeName)) {
ListWrapper.push(readAttributes, dep.attributeName); ListWrapper.push(readAttributes, dep.attributeName);
} }
@ -239,8 +235,8 @@ export class Compiler {
selector: ann.selector, selector: ann.selector,
compileChildren: compileChildren, compileChildren: compileChildren,
hostListeners: isPresent(ann.hostListeners) ? MapWrapper.createFromStringMap(ann.hostListeners) : null, 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, properties: isPresent(ann.properties) ? MapWrapper.createFromStringMap(ann.properties) : null,
setters: setters,
readAttributes: readAttributes readAttributes: readAttributes
}); });
} }

View File

@ -5,7 +5,7 @@ import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoBindingError, import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoBindingError,
AbstractBindingError, CyclicDependencyError} from 'angular2/di'; AbstractBindingError, CyclicDependencyError} from 'angular2/di';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; 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 * as viewModule from 'angular2/src/core/compiler/view';
import {ViewContainer} from 'angular2/src/core/compiler/view_container'; import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {NgElement} from 'angular2/src/core/compiler/ng_element'; import {NgElement} from 'angular2/src/core/compiler/ng_element';
@ -195,15 +195,13 @@ export class TreeNode {
export class DirectiveDependency extends Dependency { export class DirectiveDependency extends Dependency {
depth:int; depth:int;
propSetterName:string;
attributeName:string; attributeName:string;
queryDirective; queryDirective;
constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean, properties:List, 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); super(key, asPromise, lazy, optional, properties);
this.depth = depth; this.depth = depth;
this.propSetterName = propSetterName;
this.attributeName = attributeName; this.attributeName = attributeName;
this.queryDirective = queryDirective; this.queryDirective = queryDirective;
this._verify(); this._verify();
@ -211,17 +209,15 @@ export class DirectiveDependency extends Dependency {
_verify():void { _verify():void {
var count = 0; var count = 0;
if (isPresent(this.propSetterName)) count++;
if (isPresent(this.queryDirective)) count++; if (isPresent(this.queryDirective)) count++;
if (isPresent(this.attributeName)) count++; if (isPresent(this.attributeName)) count++;
if (count > 1) throw new BaseException( 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 { static createFrom(d:Dependency):Dependency {
return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional, return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional,
d.properties, DirectiveDependency._depth(d.properties), d.properties, DirectiveDependency._depth(d.properties),
DirectiveDependency._propSetterName(d.properties),
DirectiveDependency._attributeName(d.properties), DirectiveDependency._attributeName(d.properties),
DirectiveDependency._query(d.properties) DirectiveDependency._query(d.properties)
); );
@ -234,11 +230,6 @@ export class DirectiveDependency extends Dependency {
return 0; 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 { static _attributeName(properties):string {
var p = ListWrapper.find(properties, (p) => p instanceof Attribute); var p = ListWrapper.find(properties, (p) => p instanceof Attribute);
return isPresent(p) ? p.attributeName : null; return isPresent(p) ? p.attributeName : null;
@ -268,6 +259,10 @@ export class DirectiveBinding extends ResolvedBinding {
} }
} }
get displayName() {
return this.key.displayName;
}
get eventEmitters():List<string> { get eventEmitters():List<string> {
return isPresent(this.annotation) && isPresent(this.annotation.events) ? this.annotation.events : []; 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) { _getByDependency(dep:DirectiveDependency, requestor:Key) {
if (isPresent(dep.propSetterName)) return this._buildPropSetter(dep);
if (isPresent(dep.attributeName)) return this._buildAttribute(dep); if (isPresent(dep.attributeName)) return this._buildAttribute(dep);
if (isPresent(dep.queryDirective)) return this._findQuery(dep.queryDirective).list; if (isPresent(dep.queryDirective)) return this._findQuery(dep.queryDirective).list;
if (dep.key.id === StaticKeys.instance().elementRefId) { 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); 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 { _buildAttribute(dep): string {
var attributes = this._proto.attributes; var attributes = this._proto.attributes;
if (isPresent(attributes) && MapWrapper.contains(attributes, dep.attributeName)) { if (isPresent(attributes) && MapWrapper.contains(attributes, dep.attributeName)) {

View File

@ -3,7 +3,7 @@ import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {reflector} from 'angular2/src/reflection/reflection'; 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 {Component, Viewport, DynamicComponent} from '../annotations/annotations';
import * as renderApi from 'angular2/src/render/api'; import * as renderApi from 'angular2/src/render/api';
@ -18,7 +18,8 @@ export class ProtoViewFactory {
this._changeDetection = changeDetection; this._changeDetection = changeDetection;
} }
createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto, directives:List<DirectiveBinding>):AppProtoView { createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto,
directives:List<DirectiveBinding>):AppProtoView {
var protoChangeDetector; var protoChangeDetector;
if (isBlank(componentBinding)) { if (isBlank(componentBinding)) {
protoChangeDetector = this._changeDetection.createProtoChangeDetector('root', null); protoChangeDetector = this._changeDetection.createProtoChangeDetector('root', null);
@ -43,7 +44,7 @@ export class ProtoViewFactory {
this._createElementBinder( this._createElementBinder(
protoView, renderElementBinder, protoElementInjector, sortedDirectives protoView, renderElementBinder, protoElementInjector, sortedDirectives
); );
this._createDirectiveBinders(protoView, sortedDirectives); this._createDirectiveBinders(protoView, i, sortedDirectives);
} }
MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => { MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => {
protoView.bindVariable(varName, mappedName); protoView.bindVariable(varName, mappedName);
@ -128,27 +129,34 @@ export class ProtoViewFactory {
return elBinder; return elBinder;
} }
_createDirectiveBinders(protoView, sortedDirectives) { _createDirectiveBinders(protoView, boundElementIndex, sortedDirectives) {
for (var i=0; i<sortedDirectives.renderDirectives.length; i++) { for (var i = 0; i < sortedDirectives.renderDirectives.length; i++) {
var renderDirectiveMetadata = sortedDirectives.renderDirectives[i]; var directiveBinder = sortedDirectives.renderDirectives[i];
// directive properties // directive properties
MapWrapper.forEach(renderDirectiveMetadata.propertyBindings, (astWithSource, propertyName) => { MapWrapper.forEach(directiveBinder.propertyBindings, (astWithSource, propertyName) => {
// TODO: these setters should eventually be created by change detection, to make // TODO: these setters should eventually be created by change detection, to make
// it monomorphic! // it monomorphic!
var setter = reflector.setter(propertyName); var setter = reflector.setter(propertyName);
protoView.bindDirectiveProperty(i, astWithSource, propertyName, setter); 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 // directive events
protoView.bindEvent(renderDirectiveMetadata.eventBindings, i); protoView.bindEvent(directiveBinder.eventBindings, i);
} }
} }
} }
class SortedDirectives { class SortedDirectives {
componentDirective: DirectiveBinding; componentDirective: DirectiveBinding;
viewportDirective: DirectiveBinding; viewportDirective: DirectiveBinding;
renderDirectives: List<renderApi.DirectiveMetadata>; renderDirectives: List<renderApi.DirectiveBinder>;
directives: List<DirectiveBinding>; directives: List<DirectiveBinding>;
constructor(renderDirectives, allDirectives) { constructor(renderDirectives, allDirectives) {
@ -156,18 +164,18 @@ class SortedDirectives {
this.directives = []; this.directives = [];
this.viewportDirective = null; this.viewportDirective = null;
this.componentDirective = null; this.componentDirective = null;
ListWrapper.forEach(renderDirectives, (renderDirectiveMetadata) => { ListWrapper.forEach(renderDirectives, (renderDirectiveBinder) => {
var directiveBinding = allDirectives[renderDirectiveMetadata.directiveIndex]; var directiveBinding = allDirectives[renderDirectiveBinder.directiveIndex];
if ((directiveBinding.annotation instanceof Component) || (directiveBinding.annotation instanceof DynamicComponent)) { if ((directiveBinding.annotation instanceof Component) || (directiveBinding.annotation instanceof DynamicComponent)) {
// component directives need to be the first binding in ElementInjectors! // component directives need to be the first binding in ElementInjectors!
this.componentDirective = directiveBinding; this.componentDirective = directiveBinding;
ListWrapper.insert(this.renderDirectives, 0, renderDirectiveMetadata); ListWrapper.insert(this.renderDirectives, 0, renderDirectiveBinder);
ListWrapper.insert(this.directives, 0, directiveBinding); ListWrapper.insert(this.directives, 0, directiveBinding);
} else { } else {
if (directiveBinding.annotation instanceof Viewport) { if (directiveBinding.annotation instanceof Viewport) {
this.viewportDirective = directiveBinding; this.viewportDirective = directiveBinding;
} }
ListWrapper.push(this.renderDirectives, renderDirectiveMetadata); ListWrapper.push(this.renderDirectives, renderDirectiveBinder);
ListWrapper.push(this.directives, directiveBinding); ListWrapper.push(this.directives, directiveBinding);
} }
}); });

View File

@ -1,6 +1,6 @@
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {AST, Locals, ChangeDispatcher, ProtoChangeDetector, ChangeDetector, 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 {ProtoElementInjector, ElementInjector, PreBuiltObjects, DirectiveBinding} from './element_injector';
import {ElementBinder} from './element_binder'; import {ElementBinder} from './element_binder';
@ -124,12 +124,12 @@ export class AppView {
} }
} }
getDirectiveFor(directive:DirectiveRecord) { getDirectiveFor(directive:DirectiveIndex) {
var elementInjector = this.elementInjectors[directive.elementIndex]; var elementInjector = this.elementInjectors[directive.elementIndex];
return elementInjector.getDirectiveAtIndex(directive.directiveIndex); return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
} }
getDetectorFor(directive:DirectiveRecord) { getDetectorFor(directive:DirectiveIndex) {
var elementInjector = this.elementInjectors[directive.elementIndex]; var elementInjector = this.elementInjectors[directive.elementIndex];
return elementInjector.getChangeDetector(); return elementInjector.getChangeDetector();
} }
@ -267,6 +267,14 @@ export class AppProtoView {
ListWrapper.push(this.bindings, b); 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. * Adds an event binding for the last created ElementBinder via bindElement.
* *
@ -323,7 +331,7 @@ export class AppProtoView {
var changeDetection = binding.changeDetection; var changeDetection = binding.changeDetection;
MapWrapper.set(this._directiveRecordsMap, id, MapWrapper.set(this._directiveRecordsMap, id,
new DirectiveRecord(elementInjectorIndex, directiveIndex, new DirectiveRecord(new DirectiveIndex(elementInjectorIndex, directiveIndex),
binding.callOnAllChangesDone, binding.callOnChange, changeDetection)); binding.callOnAllChangesDone, binding.callOnChange, changeDetection));
} }

View File

@ -1,5 +1,5 @@
import {MapWrapper} from 'angular2/src/facade/collection'; 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 // TODO: uncoment `int` once https://github.com/angular/angular/issues/1414 is fixed
@ -23,6 +23,10 @@ export class Key {
this.id = id; this.id = id;
} }
get displayName() {
return stringify(this.token);
}
/** /**
* Retrieves a `Key` for a token. * Retrieves a `Key` for a token.
*/ */

View File

@ -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 {Optional} from 'angular2/di';
import {Renderer} from 'angular2/src/render/api';
import {isBlank, isPresent, isString, CONST} from 'angular2/src/facade/lang'; import {isBlank, isPresent, isString, CONST} from 'angular2/src/facade/lang';
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {ControlGroup, Control} from './model'; import {ControlGroup, Control} from './model';
@ -27,19 +28,21 @@ import {Validators} from './validators';
hostListeners: { hostListeners: {
'change' : 'onChange($event.target.value)', 'change' : 'onChange($event.target.value)',
'input' : 'onChange($event.target.value)' 'input' : 'onChange($event.target.value)'
},
hostProperties: {
'value' : 'value'
} }
}) })
export class DefaultValueAccessor { export class DefaultValueAccessor {
_setValueProperty:Function; value;
onChange:Function; onChange:Function;
constructor(@PropertySetter('value') setValueProperty:Function) { constructor() {
this._setValueProperty = setValueProperty;
this.onChange = (_) => {}; this.onChange = (_) => {};
} }
writeValue(value) { writeValue(value) {
this._setValueProperty(value); this.value = value
} }
} }
@ -58,20 +61,28 @@ export class DefaultValueAccessor {
selector: 'input[type=checkbox][control]', selector: 'input[type=checkbox][control]',
hostListeners: { hostListeners: {
'change' : 'onChange($event.target.checked)' 'change' : 'onChange($event.target.checked)'
},
hostProperties: {
'checked' : 'checked'
} }
}) })
export class CheckboxControlValueAccessor { export class CheckboxControlValueAccessor {
_setCheckedProperty:Function; _elementRef:ElementRef;
_renderer:Renderer;
checked:boolean;
onChange:Function; onChange:Function;
constructor(cd:ControlDirective, @PropertySetter('checked') setCheckedProperty:Function) { constructor(cd:ControlDirective, elementRef:ElementRef, renderer:Renderer) {
this._setCheckedProperty = setCheckedProperty;
this.onChange = (_) => {}; this.onChange = (_) => {};
this._elementRef = elementRef;
this._renderer = renderer;
cd.valueAccessor = this; //ControlDirective should inject CheckboxControlDirective cd.valueAccessor = this; //ControlDirective should inject CheckboxControlDirective
} }
writeValue(value) { writeValue(value) {
this._setCheckedProperty(value); this._renderer.setElementProperty(this._elementRef.hostView.render, this._elementRef.boundElementIndex,
'checked', value)
} }
} }

View File

@ -72,12 +72,14 @@ export class DirectiveBinder {
// that replaced the values that should be extracted from the element // that replaced the values that should be extracted from the element
// with a local name // with a local name
eventBindings: List<EventBinding>; eventBindings: List<EventBinding>;
hostPropertyBindings: Map<string, ASTWithSource>;
constructor({ constructor({
directiveIndex, propertyBindings, eventBindings directiveIndex, propertyBindings, eventBindings, hostPropertyBindings
}) { }) {
this.directiveIndex = directiveIndex; this.directiveIndex = directiveIndex;
this.propertyBindings = propertyBindings; this.propertyBindings = propertyBindings;
this.eventBindings = eventBindings; this.eventBindings = eventBindings;
this.hostPropertyBindings = hostPropertyBindings;
} }
} }
@ -114,17 +116,17 @@ export class DirectiveMetadata {
selector:string; selector:string;
compileChildren:boolean; compileChildren:boolean;
hostListeners:Map<string, string>; hostListeners:Map<string, string>;
hostProperties:Map<string, string>;
properties:Map<string, string>; properties:Map<string, string>;
setters:List<string>;
readAttributes:List<string>; readAttributes:List<string>;
type:number; type:number;
constructor({id, selector, compileChildren, hostListeners, properties, setters, readAttributes, type}) { constructor({id, selector, compileChildren, hostListeners, hostProperties, properties, readAttributes, type}) {
this.id = id; this.id = id;
this.selector = selector; this.selector = selector;
this.compileChildren = isPresent(compileChildren) ? compileChildren : true; this.compileChildren = isPresent(compileChildren) ? compileChildren : true;
this.hostListeners = hostListeners; this.hostListeners = hostListeners;
this.hostProperties = hostProperties;
this.properties = properties; this.properties = properties;
this.setters = setters;
this.readAttributes = readAttributes; this.readAttributes = readAttributes;
this.type = type; this.type = type;
} }
@ -248,7 +250,7 @@ export class Renderer {
/** /**
* Sets a property on an element. * 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. * in the View.
*/ */
setElementProperty(view:ViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {} setElementProperty(view:ViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {}

View File

@ -56,21 +56,21 @@ export class DirectiveParser extends CompileStep {
this._selectorMatcher.match(cssSelector, (selector, directiveIndex) => { this._selectorMatcher.match(cssSelector, (selector, directiveIndex) => {
var elementBinder = current.bindElement(); var elementBinder = current.bindElement();
var directive = this._directives[directiveIndex]; var directive = this._directives[directiveIndex];
var directiveBinder = elementBinder.bindDirective(directiveIndex); var directiveBinderBuilder = elementBinder.bindDirective(directiveIndex);
current.compileChildren = current.compileChildren && directive.compileChildren; current.compileChildren = current.compileChildren && directive.compileChildren;
if (isPresent(directive.properties)) { if (isPresent(directive.properties)) {
MapWrapper.forEach(directive.properties, (bindConfig, dirProperty) => { MapWrapper.forEach(directive.properties, (bindConfig, dirProperty) => {
this._bindDirectiveProperty(dirProperty, bindConfig, current, directiveBinder); this._bindDirectiveProperty(dirProperty, bindConfig, current, directiveBinderBuilder);
}); });
} }
if (isPresent(directive.hostListeners)) { if (isPresent(directive.hostListeners)) {
MapWrapper.forEach(directive.hostListeners, (action, eventName) => { MapWrapper.forEach(directive.hostListeners, (action, eventName) => {
this._bindDirectiveEvent(eventName, action, current, directiveBinder); this._bindDirectiveEvent(eventName, action, current, directiveBinderBuilder);
}); });
} }
if (isPresent(directive.setters)) { if (isPresent(directive.hostProperties)) {
ListWrapper.forEach(directive.setters, (propertyName) => { MapWrapper.forEach(directive.hostProperties, (hostPropertyName, directivePropertyName) => {
elementBinder.bindPropertySetter(propertyName); this._bindHostProperty(hostPropertyName, directivePropertyName, current, directiveBinderBuilder);
}); });
} }
if (isPresent(directive.readAttributes)) { 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 pipes = this._splitBindConfig(bindConfig);
var elProp = ListWrapper.removeAt(pipes, 0); 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. // Bindings are optional, so this binding only needs to be set up if an expression is given.
if (isPresent(bindingAst)) { if (isPresent(bindingAst)) {
var fullExpAstWithBindPipes = this._parser.addPipes(bindingAst, pipes); var fullExpAstWithBindPipes = this._parser.addPipes(bindingAst, pipes);
directiveBinder.bindProperty( directiveBinderBuilder.bindProperty(
dirProperty, fullExpAstWithBindPipes dirProperty, fullExpAstWithBindPipes
); );
} }
} }
_bindDirectiveEvent(eventName, action, compileElement, directiveBinder) { _bindDirectiveEvent(eventName, action, compileElement, directiveBinderBuilder) {
var ast = this._parser.parseAction(action, compileElement.elementDescription); var ast = this._parser.parseAction(action, compileElement.elementDescription);
if (StringWrapper.contains(eventName, EVENT_TARGET_SEPARATOR)) { if (StringWrapper.contains(eventName, EVENT_TARGET_SEPARATOR)) {
var parts = eventName.split(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 { } 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) { _splitBindConfig(bindConfig:string) {

View File

@ -5,7 +5,6 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
import { import {
ASTWithSource, AST, AstTransformer, AccessMember, LiteralArray, ImplicitReceiver ASTWithSource, AST, AstTransformer, AccessMember, LiteralArray, ImplicitReceiver
} from 'angular2/change_detection'; } from 'angular2/change_detection';
import {SetterFn} from 'angular2/src/reflection/types';
import {RenderProtoView} from './proto_view'; import {RenderProtoView} from './proto_view';
import {ElementBinder, Event} from './element_binder'; import {ElementBinder, Event} from './element_binder';
@ -57,17 +56,26 @@ export class ProtoViewBuilder {
var apiElementBinders = []; var apiElementBinders = [];
ListWrapper.forEach(this.elements, (ebb) => { ListWrapper.forEach(this.elements, (ebb) => {
var propertySetters = MapWrapper.create(); 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({ return new api.DirectiveBinder({
directiveIndex: db.directiveIndex, directiveIndex: dbb.directiveIndex,
propertyBindings: db.propertyBindings, propertyBindings: dbb.propertyBindings,
eventBindings: db.eventBindings 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 = var nestedProtoView =
isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build() : null; isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build() : null;
var parentIndex = isPresent(ebb.parent) ? ebb.parent.index : -1; var parentIndex = isPresent(ebb.parent) ? ebb.parent.index : -1;
@ -119,7 +127,6 @@ export class ElementBinderBuilder {
textBindingIndices: List<number>; textBindingIndices: List<number>;
textBindings: List<ASTWithSource>; textBindings: List<ASTWithSource>;
contentTagSelector:string; contentTagSelector:string;
propertySetters: Map<string, SetterFn>;
readAttributes: Map<string, string>; readAttributes: Map<string, string>;
componentId: string; componentId: string;
@ -137,7 +144,6 @@ export class ElementBinderBuilder {
this.textBindings = []; this.textBindings = [];
this.textBindingIndices = []; this.textBindingIndices = [];
this.contentTagSelector = null; this.contentTagSelector = null;
this.propertySetters = MapWrapper.create();
this.componentId = null; this.componentId = null;
this.readAttributes = MapWrapper.create(); this.readAttributes = MapWrapper.create();
} }
@ -172,11 +178,10 @@ export class ElementBinderBuilder {
bindProperty(name, expression) { bindProperty(name, expression) {
MapWrapper.set(this.propertyBindings, name, expression); MapWrapper.set(this.propertyBindings, name, expression);
this.bindPropertySetter(name);
}
bindPropertySetter(name) { //TODO: required for Dart transformers. Remove when Dart transformers
MapWrapper.set(this.propertySetters, name, setterFactory(name)); //run all the steps of the render compiler
setterFactory(name);
} }
bindVariable(name, value) { bindVariable(name, value) {
@ -216,12 +221,14 @@ export class ElementBinderBuilder {
export class DirectiveBuilder { export class DirectiveBuilder {
directiveIndex:number; directiveIndex:number;
propertyBindings: Map<string, ASTWithSource>; propertyBindings: Map<string, ASTWithSource>;
hostPropertyBindings: Map<string, ASTWithSource>;
eventBindings: List<api.EventBinding>; eventBindings: List<api.EventBinding>;
eventBuilder: EventBuilder; eventBuilder: EventBuilder;
constructor(directiveIndex) { constructor(directiveIndex) {
this.directiveIndex = directiveIndex; this.directiveIndex = directiveIndex;
this.propertyBindings = MapWrapper.create(); this.propertyBindings = MapWrapper.create();
this.hostPropertyBindings = MapWrapper.create();
this.eventBindings = ListWrapper.create(); this.eventBindings = ListWrapper.create();
this.eventBuilder = new EventBuilder(); this.eventBuilder = new EventBuilder();
} }
@ -230,6 +237,10 @@ export class DirectiveBuilder {
MapWrapper.set(this.propertyBindings, name, expression); MapWrapper.set(this.propertyBindings, name, expression);
} }
bindHostProperty(name, expression) {
MapWrapper.set(this.hostPropertyBindings, name, expression);
}
bindEvent(name, expression, target = null) { bindEvent(name, expression, target = null) {
ListWrapper.push(this.eventBindings, this.eventBuilder.add(name, expression, target)); ListWrapper.push(this.eventBindings, this.eventBuilder.add(name, expression, target));
} }

View File

@ -45,7 +45,7 @@ class _DirectiveMetadataVisitor extends Object
compileChildren: false, compileChildren: false,
properties: {}, properties: {},
hostListeners: {}, hostListeners: {},
setters: [], hostProperties: {},
readAttributes: []); readAttributes: []);
super.visitInstanceCreationExpression(node); super.visitInstanceCreationExpression(node);
} }
@ -76,6 +76,9 @@ class _DirectiveMetadataVisitor extends Object
case 'properties': case 'properties':
_populateProperties(node.expression); _populateProperties(node.expression);
break; break;
case 'hostProperties':
_populateHostProperties(node.expression);
break;
case 'hostListeners': case 'hostListeners':
_populateHostListeners(node.expression); _populateHostListeners(node.expression);
} }
@ -135,4 +138,19 @@ class _DirectiveMetadataVisitor extends Object
meta.hostListeners[sKey] = sVal; 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;
}
}
} }

View File

@ -7,7 +7,7 @@ import {Parser} from 'angular2/src/change_detection/parser/parser';
import {Lexer} from 'angular2/src/change_detection/parser/lexer'; import {Lexer} from 'angular2/src/change_detection/parser/lexer';
import {Locals} from 'angular2/src/change_detection/parser/locals'; 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'; 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'; import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'angular2/src/change_detection/proto_change_detector';
@ -22,8 +22,9 @@ export function main() {
if (name == "JIT" && IS_DARTIUM) return; if (name == "JIT" && IS_DARTIUM) return;
var parser = new Parser(new Lexer());
function ast(exp:string, location:string = 'location') { function ast(exp:string, location:string = 'location') {
var parser = new Parser(new Lexer());
return parser.parseBinding(exp, location); return parser.parseBinding(exp, location);
} }
@ -204,7 +205,6 @@ export function main() {
}); });
it("should support interpolation", () => { it("should support interpolation", () => {
var parser = new Parser(new Lexer());
var pcd = createProtoChangeDetector(); var pcd = createProtoChangeDetector();
var ast = parser.parseInterpolation("B{{a}}A", "location"); var ast = parser.parseInterpolation("B{{a}}A", "location");
@ -246,9 +246,9 @@ export function main() {
}); });
describe("updating directives", () => { describe("updating directives", () => {
var dirRecord1 = new DirectiveRecord(0, 0, true, true, DEFAULT); var dirRecord1 = new DirectiveRecord(new DirectiveIndex(0, 0), true, true, DEFAULT);
var dirRecord2 = new DirectiveRecord(0, 1, true, true, DEFAULT); var dirRecord2 = new DirectiveRecord(new DirectiveIndex(0, 1), true, true, DEFAULT);
var dirRecordNoCallbacks = new DirectiveRecord(0, 0, false, false, DEFAULT); var dirRecordNoCallbacks = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, DEFAULT);
function updateA(exp:string, dirRecord) { function updateA(exp:string, dirRecord) {
return BindingRecord.createForDirective(ast(exp), "a", (o,v) => o.a = v, 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", () => { describe("enforce no new changes", () => {
it("should throw when a record gets changed after it has been checked", () => { it("should throw when a record gets changed after it has been checked", () => {
var pcd = createProtoChangeDetector(); var pcd = createProtoChangeDetector();
@ -567,7 +585,7 @@ export function main() {
checkedDetector.mode = CHECKED; checkedDetector.mode = CHECKED;
// this directive is a component with ON_PUSH change detection // 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 // a record updating a component
updateDirWithOnPushRecord = updateDirWithOnPushRecord =
@ -898,12 +916,12 @@ class FakeDirectives {
this.detectors = detectors; this.detectors = detectors;
} }
getDirectiveFor(directiveRecord:DirectiveRecord) { getDirectiveFor(di:DirectiveIndex) {
return this.directives[directiveRecord.directiveIndex]; return this.directives[di.directiveIndex];
} }
getDetectorFor(directiveRecord:DirectiveRecord) { getDetectorFor(di:DirectiveIndex) {
return this.detectors[directiveRecord.directiveIndex]; return this.detectors[di.directiveIndex];
} }
} }

View File

@ -5,7 +5,7 @@ import {RECORD_TYPE_SELF, ProtoRecord} from 'angular2/src/change_detection/proto
export function main() { export function main() {
function r(funcOrValue, args, contextIndex, selfIndex, lastInBinding = false) { 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); null, null, lastInBinding, false);
} }
@ -73,7 +73,7 @@ export function main() {
expect(rs[1]).toEqual(new ProtoRecord( expect(rs[1]).toEqual(new ProtoRecord(
RECORD_TYPE_SELF, "self", null, RECORD_TYPE_SELF, "self", null,
[], null, 1, 2, [], null, 1, null, 2,
null, null, null, null,
true, false) true, false)
); );

View File

@ -22,7 +22,7 @@ import {AppProtoView} from 'angular2/src/core/compiler/view';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder'; import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
import {Component, DynamicComponent, Viewport, Decorator} from 'angular2/src/core/annotations/annotations'; 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 {View} from 'angular2/src/core/annotations/view';
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector'; import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
@ -185,18 +185,20 @@ export function main() {
}); });
})); }));
it('should set directive.bind', inject([AsyncTestCompleter], (async) => { it('should set directive.hostProperties', inject([AsyncTestCompleter], (async) => {
captureDirective(DirectiveWithBind).then( (renderDir) => { captureDirective(DirectiveWithProperties).then( (renderDir) => {
expect(renderDir.properties).toEqual(MapWrapper.createFromStringMap({ expect(renderDir.hostProperties).toEqual(MapWrapper.createFromStringMap({
'a': 'b' 'someField': 'someProp'
})); }));
async.done(); async.done();
}); });
})); }));
it('should read @PropertySetter', inject([AsyncTestCompleter], (async) => { it('should set directive.bind', inject([AsyncTestCompleter], (async) => {
captureDirective(DirectiveWithPropertySetters).then( (renderDir) => { captureDirective(DirectiveWithBind).then( (renderDir) => {
expect(renderDir.setters).toEqual(['someProp']); expect(renderDir.properties).toEqual(MapWrapper.createFromStringMap({
'a': 'b'
}));
async.done(); async.done();
}); });
})); }));
@ -500,16 +502,16 @@ class IgnoreChildrenDecoratorDirective {}
}) })
class DirectiveWithEvents {} class DirectiveWithEvents {}
@Decorator({
hostProperties: {'someField': 'someProp'}
})
class DirectiveWithProperties {}
@Decorator({ @Decorator({
properties: {'a': 'b'} properties: {'a': 'b'}
}) })
class DirectiveWithBind {} class DirectiveWithBind {}
@Decorator()
class DirectiveWithPropertySetters {
constructor(@PropertySetter('someProp') someProp) {}
}
@Decorator() @Decorator()
class DirectiveWithAttributes { class DirectiveWithAttributes {
constructor(@Attribute('someAttr') someAttr:string) {} constructor(@Attribute('someAttr') someAttr:string) {}
@ -568,7 +570,8 @@ class FakeProtoViewFactory extends ProtoViewFactory {
this._results = results; this._results = results;
} }
createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto, directives:List<DirectiveBinding>):AppProtoView { createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto,
directives:List<DirectiveBinding>):AppProtoView {
ListWrapper.push(this.requests, [componentBinding, renderProtoView, directives]); ListWrapper.push(this.requests, [componentBinding, renderProtoView, directives]);
return ListWrapper.removeAt(this._results, 0); return ListWrapper.removeAt(this._results, 0);
} }

View File

@ -4,7 +4,7 @@ import {ListWrapper, MapWrapper, List, StringMapWrapper, iterateListLike} from '
import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding, TreeNode, ElementRef} import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding, TreeNode, ElementRef}
from 'angular2/src/core/compiler/element_injector'; from 'angular2/src/core/compiler/element_injector';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; 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 {onDestroy} from 'angular2/src/core/annotations/annotations';
import {Optional, Injector, Inject, bind} from 'angular2/di'; import {Optional, Injector, Inject, bind} from 'angular2/di';
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; 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 { class NeedsAttribute {
typeAttribute; typeAttribute;
titleAttribute; 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', () => { describe('static attributes', () => {
it('should be injectable', () => { it('should be injectable', () => {
var attributes = MapWrapper.create(); var attributes = MapWrapper.create();

View File

@ -190,7 +190,6 @@ export function main() {
})); }));
tb.createView(MyComp, {context: ctx}).then((view) => { tb.createView(MyComp, {context: ctx}).then((view) => {
ctx.ctxProp = 'a'; ctx.ctxProp = 'a';
view.detectChanges(); 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: '<div update-host-properties></div>',
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()) { if (DOM.supportsDOMEvents()) {
it('should support preventing default on render events', inject([TestBed, AsyncTestCompleter], (tb, async) => { it('should support preventing default on render events', inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideView(MyComp, new View({ 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({ @Decorator({
selector: '[listener]', selector: '[listener]',
hostListeners: {'event': 'onEvent($event)'} hostListeners: {'event': 'onEvent($event)'}

View File

@ -16,7 +16,7 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
import {Inject} from 'angular2/di'; 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'; import {TestBed} from 'angular2/src/test_lib/test_bed';
@ -374,8 +374,8 @@ export function main() {
ControlDirective, ControlDirective,
WrappedValue, WrappedValue,
RequiredValidatorDirective, RequiredValidatorDirective,
CheckboxControlValueAccessor, DefaultValueAccessor,
DefaultValueAccessor]}) CheckboxControlValueAccessor]})
class MyComp { class MyComp {
form:any; form:any;
name:string; name:string;
@ -390,19 +390,21 @@ class MyComp {
selector:'[wrapped-value]', selector:'[wrapped-value]',
hostListeners: { hostListeners: {
'change' : 'handleOnChange($event.target.value)' 'change' : 'handleOnChange($event.target.value)'
},
hostProperties: {
'value' : 'value'
} }
}) })
class WrappedValue { class WrappedValue {
_setProperty:Function; value;
onChange:Function; onChange:Function;
constructor(cd:ControlDirective, @PropertySetter('value') setProperty:Function) { constructor(cd:ControlDirective) {
this._setProperty = setProperty;
cd.valueAccessor = this; cd.valueAccessor = this;
} }
writeValue(value) { writeValue(value) {
this._setProperty(`!${value}!`); this.value = `!${value}!`;
} }
handleOnChange(value) { handleOnChange(value) {

View File

@ -23,6 +23,7 @@ export function main() {
someDecorator, someDecorator,
someDecoratorIgnoringChildren, someDecoratorIgnoringChildren,
someDecoratorWithProps, someDecoratorWithProps,
someDecoratorWithHostProperties,
someDecoratorWithEvents, someDecoratorWithEvents,
someDecoratorWithGlobalEvents someDecoratorWithGlobalEvents
]; ];
@ -112,12 +113,14 @@ export function main() {
expect(simpleProp.source).toEqual('someValue'); expect(simpleProp.source).toEqual('someValue');
}); });
it('should store working property setters', () => { it('should bind host directive properties', () => {
var element = el('<input some-decor-props>'); var element = el('<input some-decor-with-host-props>');
var results = process(element); var results = process(element);
var setter = MapWrapper.get(results[0].propertySetters, 'value');
setter(element, 'abc'); var directiveBinding = results[0].directives[0];
expect(element.value).toEqual('abc');
var ast = MapWrapper.get(directiveBinding.hostPropertyBindings, 'hostProperty');
expect(ast.source).toEqual('dirProp');
}); });
it('should read attribute values', () => { it('should read attribute values', () => {
@ -256,10 +259,16 @@ var someDecoratorWithProps = new DirectiveMetadata({
'dirProp': 'elProp', 'dirProp': 'elProp',
'doubleProp': 'elProp | double' 'doubleProp': 'elProp | double'
}), }),
setters: ['value'],
readAttributes: ['some-attr'] readAttributes: ['some-attr']
}); });
var someDecoratorWithHostProperties = new DirectiveMetadata({
selector: '[some-decor-with-host-props]',
hostProperties: MapWrapper.createFromStringMap({
'dirProp': 'hostProperty'
})
});
var someDecoratorWithEvents = new DirectiveMetadata({ var someDecoratorWithEvents = new DirectiveMetadata({
selector: '[some-decor-events]', selector: '[some-decor-events]',
hostListeners: MapWrapper.createFromStringMap({ hostListeners: MapWrapper.createFromStringMap({

View File

@ -52,6 +52,12 @@ export function main() {
expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('{{b}}'); expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('{{b}}');
}); });
it('should store property setters as camel case', () => {
var element = el('<div bind-some-prop="1">');
var results = process(element);
expect(MapWrapper.get(results[0].propertyBindings, 'someProp')).toBeTruthy();
});
it('should detect var- syntax', () => { it('should detect var- syntax', () => {
var results = process(el('<template var-a="b"></template>')); var results = process(el('<template var-a="b"></template>'));
expect(MapWrapper.get(results[0].variableBindings, 'b')).toEqual('a'); 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(), 'a')).toEqual('b');
expect(MapWrapper.get(results[0].attrs(), 'c')).toEqual('d'); expect(MapWrapper.get(results[0].attrs(), 'c')).toEqual('d');
}); });
it('should store working property setters', () => {
var element = el('<input bind-value="1">');
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('<div bind-some-prop="1">');
var results = process(element);
expect(MapWrapper.get(results[0].propertySetters, 'someProp')).toBeTruthy();
});
}); });
} }

View File

@ -1,7 +1,6 @@
import {Component, View} from 'angular2/angular2'; import {Component, View} from 'angular2/angular2';
import {PropertySetter, EventEmitter} from 'angular2/src/core/annotations/di';
import {onChange} from 'angular2/src/core/annotations/annotations'; 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])'}) @Component({selector: '[md-button]:not([href])'})
@ -17,21 +16,18 @@ export class MdButton {
'disabled': 'disabled' 'disabled': 'disabled'
}, },
hostListeners: {'click': 'onClick($event)'}, hostListeners: {'click': 'onClick($event)'},
hostProperties: {'tabIndex': 'tabIndex'},
lifecycle: [onChange] lifecycle: [onChange]
}) })
@View({ @View({
templateUrl: 'angular2_material/src/components/button/button.html' templateUrl: 'angular2_material/src/components/button/button.html'
}) })
export class MdAnchor { export class MdAnchor {
tabIndexSetter: Function; tabIndex: number;
/** Whether the component is disabled. */ /** Whether the component is disabled. */
disabled: boolean; disabled: boolean;
constructor(@PropertySetter('tabIndex') tabIndexSetter: Function) {
this.tabIndexSetter = tabIndexSetter;
}
onClick(event) { onClick(event) {
// A disabled anchor shouldn't navigate anywhere. // A disabled anchor shouldn't navigate anywhere.
if (isPresent(this.disabled) && this.disabled !== false) { if (isPresent(this.disabled) && this.disabled !== false) {
@ -42,6 +38,6 @@ export class MdAnchor {
/** Invoked when a change is detected. */ /** Invoked when a change is detected. */
onChange(_) { onChange(_) {
// A disabled anchor should not be in the tab flow. // A disabled anchor should not be in the tab flow.
this.tabIndexSetter(this.disabled ? -1 : 0); this.tabIndex = this.disabled ? -1 : 0;
} }
} }

View File

@ -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 {isPresent} from 'angular2/src/facade/lang';
import {KEY_SPACE} from 'angular2_material/src/core/constants' import {KEY_SPACE} from 'angular2_material/src/core/constants'
import {KeyboardEvent} from 'angular2/src/facade/browser'; import {KeyboardEvent} from 'angular2/src/facade/browser';
@ -11,6 +11,12 @@ import {KeyboardEvent} from 'angular2/src/facade/browser';
}, },
hostListeners: { hostListeners: {
'keydown': 'onKeydown($event)' 'keydown': 'onKeydown($event)'
},
hostProperties: {
'tabindex': 'tabindex',
'role': 'attr.role',
'checked': 'attr.aria-checked',
'disabled_': 'attr.aria-disabled'
} }
}) })
@View({ @View({
@ -19,38 +25,21 @@ import {KeyboardEvent} from 'angular2/src/facade/browser';
}) })
export class MdCheckbox { export class MdCheckbox {
/** Whether this checkbox is checked. */ /** Whether this checkbox is checked. */
checked_: boolean; checked: boolean;
/** Whether this checkbox is disabled. */ /** Whether this checkbox is disabled. */
disabled_: boolean; disabled_: boolean;
/** Setter for `aria-checked` attribute. */ /** Setter for `role` attribute. */
ariaCheckedSetter: Function; role: string;
/** Setter for `aria-disabled` attribute. */ /** Setter for tabindex */
ariaDisabledSetter: Function; tabindex: any;
constructor( constructor(@Attribute('tabindex') tabindex: string) {
@Attribute('tabindex') tabindex: string, this.role = 'checkbox';
@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');
this.checked = false; this.checked = false;
tabindexSetter(isPresent(tabindex) ? tabindex : '0'); this.tabindex = isPresent(tabindex) ? tabindex : '0';
}
get checked() {
return this.checked_;
}
set checked(value) {
this.checked_ = value;
this.ariaCheckedSetter(value);
} }
get disabled() { get disabled() {
@ -59,7 +48,6 @@ export class MdCheckbox {
set disabled(value) { set disabled(value) {
this.disabled_ = isPresent(value) && value !== false; this.disabled_ = isPresent(value) && value !== false;
this.ariaDisabledSetter(this.disabled_);
} }
onKeydown(event: KeyboardEvent) { onKeydown(event: KeyboardEvent) {
@ -76,6 +64,5 @@ export class MdCheckbox {
} }
this.checked = !this.checked; this.checked = !this.checked;
this.ariaCheckedSetter(this.checked);
} }
} }

View File

@ -2,7 +2,6 @@ import {Component, View, onAllChangesDone, Parent} from 'angular2/angular2';
import {onDestroy, onChange} from 'angular2/src/core/annotations/annotations'; import {onDestroy, onChange} from 'angular2/src/core/annotations/annotations';
import {ListWrapper} from 'angular2/src/facade/collection'; import {ListWrapper} from 'angular2/src/facade/collection';
import {isPresent, isString, NumberWrapper, stringify} from 'angular2/src/facade/lang'; 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. // TODO(jelbourn): Set appropriate aria attributes for grid list elements.
@ -172,6 +171,15 @@ export class MdGridList {
'rowspan': 'rowspan', 'rowspan': 'rowspan',
'colspan': 'colspan' '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] lifecycle: [onDestroy, onChange]
}) })
@View({ @View({
@ -181,40 +189,28 @@ export class MdGridTile {
gridList: MdGridList; gridList: MdGridList;
rowspan: number; rowspan: number;
colspan: number; colspan: number;
heightSetter;
widthSetter; styleHeight:any;
topSetter; styleWidth:any;
leftSetter; styleTop:any;
marginTopSetter; styleLeft:any;
paddingTopSetter; styleMarginTop:any;
stylePaddingTop:any;
role:any;
isRegisteredWithGridList: boolean; isRegisteredWithGridList: boolean;
constructor( constructor(@Parent() gridList: MdGridList) {
@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
) {
this.gridList = gridList; this.gridList = gridList;
this.heightSetter = heightSetter;
this.widthSetter = widthSetter; this.role = 'listitem';
this.topSetter = topSetter;
this.leftSetter = leftSetter;
this.marginTopSetter = marginTopSetter;
this.paddingTopSetter = paddingTopSetter;
roleSetter('listitem');
// Tiles default to 1x1, but rowspan and colspan can be changed via binding. // Tiles default to 1x1, but rowspan and colspan can be changed via binding.
this.rowspan = 1; this.rowspan = 1;
this.colspan = 1; this.colspan = 1;
// DEBUG // DEBUG
heightSetter(`${gridList.tiles.length * 100}px`); this.styleHeight = `${gridList.tiles.length * 100}px`;
} }
/** /**

View File

@ -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 {isPresent, isBlank} from 'angular2/src/facade/lang';
import {Math} from 'angular2/src/facade/math'; import {Math} from 'angular2/src/facade/math';
@ -8,6 +8,12 @@ import {Math} from 'angular2/src/facade/math';
properties: { properties: {
'value': 'value', 'value': 'value',
'bufferValue': 'buffer-value' 'bufferValue': 'buffer-value'
},
hostProperties: {
'role': 'attr.role',
'ariaValuemin': 'attr.aria-valuemin',
'ariaValuemax': 'attr.aria-valuemax',
'ariaValuenow': 'attr.aria-valuenow'
} }
}) })
@View({ @View({
@ -33,19 +39,20 @@ export class MdProgressLinear {
/** CSS `transform` property applied to the secondary bar. */ /** CSS `transform` property applied to the secondary bar. */
secondaryBarTransform: string; secondaryBarTransform: string;
constructor(
@Attribute('md-mode') mode: string, role:any;
@PropertySetter('attr.role') roleSetter: Function, ariaValuemin:any;
@PropertySetter('attr.aria-valuemin') ariaValueMinSetter: Function, ariaValuemax:any;
@PropertySetter('attr.aria-valuemax') ariaValueMaxSetter: Function, ariaValuenow:any;
@PropertySetter('attr.aria-valuenow') ariaValueNowSetter: Function) {
constructor(@Attribute('md-mode') mode: string) {
this.ariaValueNowSetter = ariaValueNowSetter; this.ariaValueNowSetter = ariaValueNowSetter;
this.primaryBarTransform = ''; this.primaryBarTransform = '';
this.secondaryBarTransform = ''; this.secondaryBarTransform = '';
roleSetter('progressbar'); this.role = 'progressbar';
ariaValueMinSetter('0'); this.ariaValuemin = '0';
ariaValueMaxSetter('100'); this.ariaValuemax = '100';
this.mode = isPresent(mode) ? mode : Mode.DETERMINATE; this.mode = isPresent(mode) ? mode : Mode.DETERMINATE;
} }

View File

@ -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 {Optional} from 'angular2/src/di/annotations';
import {MdRadioDispatcher} from 'angular2_material/src/components/radio/radio_dispatcher' import {MdRadioDispatcher} from 'angular2_material/src/components/radio/radio_dispatcher'
import {onChange} from 'angular2/src/core/annotations/annotations'; import {onChange} from 'angular2/src/core/annotations/annotations';
@ -34,6 +34,13 @@ var _uniqueIdCounter:number = 0;
}, },
hostListeners: { hostListeners: {
'keydown': 'onKeydown($event)' 'keydown': 'onKeydown($event)'
},
hostProperties: {
'id': 'id',
'tabindex': 'tabindex',
'role': 'attr.role',
'checked': 'attr.aria-checked',
'disabled': 'attr.aria-disabled'
} }
}) })
@View({ @View({
@ -42,7 +49,7 @@ var _uniqueIdCounter:number = 0;
}) })
export class MdRadioButton { export class MdRadioButton {
/** Whether this radio is checked. */ /** Whether this radio is checked. */
checked_: boolean; checked: boolean;
/** Whether the radio is disabled. */ /** Whether the radio is disabled. */
disabled_: boolean; disabled_: boolean;
@ -62,36 +69,26 @@ export class MdRadioButton {
/** Dispatcher for coordinating radio unique-selection by name. */ /** Dispatcher for coordinating radio unique-selection by name. */
radioDispatcher: MdRadioDispatcher; radioDispatcher: MdRadioDispatcher;
/** Setter for `aria-checked` attribute. */ tabindex:any;
ariaCheckedSetter: Function;
role:any;
/** Setter for `aria-disabled` attribute. */
ariaDisabledSetter: Function;
constructor( constructor(
@Optional() @Parent() radioGroup: MdRadioGroup, @Optional() @Parent() radioGroup: MdRadioGroup,
@Attribute('id') id: string, @Attribute('id') id: string,
@Attribute('tabindex') tabindex: 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) { radioDispatcher: MdRadioDispatcher) {
// Assertions. Ideally these should be stripped out by the compiler. // Assertions. Ideally these should be stripped out by the compiler.
// TODO(jelbourn): Assert that there's no name binding AND a parent radio group. // TODO(jelbourn): Assert that there's no name binding AND a parent radio group.
this.radioGroup = radioGroup; this.radioGroup = radioGroup;
this.radioDispatcher = radioDispatcher; this.radioDispatcher = radioDispatcher;
this.ariaCheckedSetter = ariaCheckedSetter;
this.ariaDisabledSetter = ariaDisabledSetter;
this.value = null; this.value = null;
roleSetter('radio'); this.role = 'radio';
this.checked = false; this.checked = false;
this.id = isPresent(id) ? id : `md-radio-${_uniqueIdCounter++}`; this.id = isPresent(id) ? id : `md-radio-${_uniqueIdCounter++}`;;
idSetter(this.id);
// Whenever a radio button with the same name is checked, uncheck this radio button. // Whenever a radio button with the same name is checked, uncheck this radio button.
radioDispatcher.listen((name) => { 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 the user has not set a tabindex, default to zero (in the normal document flow).
if (!isPresent(radioGroup)) { 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); (isPresent(this.radioGroup) && this.radioGroup.disabled);
} }
get checked() {
return this.checked_;
}
set checked(value) {
this.checked_ = value;
this.ariaCheckedSetter(value);
}
get disabled() { get disabled() {
return this.disabled_; return this.disabled_;
} }
set disabled(value) { set disabled(value) {
this.disabled_ = isPresent(value) && value !== false; this.disabled_ = isPresent(value) && value !== false;
this.ariaDisabledSetter(this.disabled_);
} }
/** Select this radio button. */ /** Select this radio button. */
@ -183,6 +170,13 @@ export class MdRadioButton {
}, },
hostListeners: { hostListeners: {
'keydown': 'onKeydown($event)' 'keydown': 'onKeydown($event)'
},
hostProperties: {
'tabindex': 'tabindex',
'role': 'attr.role',
'checked': 'attr.aria-checked',
'disabled': 'attr.aria-disabled',
'activedescendant': 'attr.aria-activedescendant'
} }
}) })
@View({ @View({
@ -201,11 +195,7 @@ export class MdRadioGroup {
/** List of child radio buttons. */ /** List of child radio buttons. */
radios_: List<MdRadioButton>; radios_: List<MdRadioButton>;
changeEmitter: Function; activedescendant: any;
ariaActiveDescendantSetter: Function;
ariaDisabledSetter: Function;
disabled_: boolean; disabled_: boolean;
@ -214,30 +204,28 @@ export class MdRadioGroup {
change:EventEmitter; change:EventEmitter;
tabindex:any;
role:any;
constructor( constructor(
@Attribute('tabindex') tabindex: string, @Attribute('tabindex') tabindex: string,
@Attribute('disabled') disabled: 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) { radioDispatcher: MdRadioDispatcher) {
this.name_ = `md-radio-group-${_uniqueIdCounter++}`; this.name_ = `md-radio-group-${_uniqueIdCounter++}`;
this.radios_ = []; this.radios_ = [];
this.change = new EventEmitter(); this.change = new EventEmitter();
this.ariaActiveDescendantSetter = ariaActiveDescendantSetter;
this.ariaDisabledSetter = ariaDisabledSetter;
this.radioDispatcher = radioDispatcher; this.radioDispatcher = radioDispatcher;
this.selectedRadioId = ''; this.selectedRadioId = '';
this.disabled_ = false; this.disabled_ = false;
roleSetter('radiogroup'); this.role = 'radiogroup';
// The simple presence of the `disabled` attribute dictates disabled state. // The simple presence of the `disabled` attribute dictates disabled state.
this.disabled = isPresent(disabled); this.disabled = isPresent(disabled);
// If the user has not set a tabindex, default to zero (in the normal document flow). // 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. */ /** 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) { set disabled(value) {
this.disabled_ = isPresent(value) && value !== false; this.disabled_ = isPresent(value) && value !== false;
this.ariaDisabledSetter(this.disabled_);
} }
/** Change handler invoked when bindings are resolved or when bindings have changed. */ /** Change handler invoked when bindings are resolved or when bindings have changed. */
@ -267,7 +254,7 @@ export class MdRadioGroup {
if (radio.value == this.value) { if (radio.value == this.value) {
radio.checked = true; radio.checked = true;
this.selectedRadioId = radio.id; this.selectedRadioId = radio.id;
this.ariaActiveDescendantSetter(radio.id); this.activedescendant = radio.id;
} }
}); });
} }
@ -277,7 +264,7 @@ export class MdRadioGroup {
updateValue(value: any, id: string) { updateValue(value: any, id: string) {
this.value = value; this.value = value;
this.selectedRadioId = id; this.selectedRadioId = id;
this.ariaActiveDescendantSetter(id); this.activedescendant = id;
ObservableWrapper.callNext(this.change, null); ObservableWrapper.callNext(this.change, null);
} }
@ -335,6 +322,6 @@ export class MdRadioGroup {
this.value = radio.value; this.value = radio.value;
this.selectedRadioId = radio.id; this.selectedRadioId = radio.id;
this.ariaActiveDescendantSetter(radio.id); this.activedescendant = radio.id;
} }
} }

View File

@ -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 {isPresent} from 'angular2/src/facade/lang';
import {KEY_SPACE} from 'angular2_material/src/core/constants' import {KEY_SPACE} from 'angular2_material/src/core/constants'
import {KeyboardEvent} from 'angular2/src/facade/browser'; import {KeyboardEvent} from 'angular2/src/facade/browser';
@ -13,6 +13,11 @@ import {KeyboardEvent} from 'angular2/src/facade/browser';
}, },
hostListeners: { hostListeners: {
'keydown': 'onKeydown($event)' 'keydown': 'onKeydown($event)'
},
hostProperties: {
'checked': 'attr.aria-checked',
'disabled_': 'attr.aria-disabled',
'role': 'attr.role'
} }
}) })
@View({ @View({
@ -21,38 +26,18 @@ import {KeyboardEvent} from 'angular2/src/facade/browser';
}) })
export class MdSwitch { export class MdSwitch {
/** Whether this switch is checked. */ /** Whether this switch is checked. */
checked_: boolean; checked: boolean;
/** Whether this switch is disabled. */ /** Whether this switch is disabled. */
disabled_: boolean; disabled_: boolean;
/** Setter for `aria-checked` attribute. */ tabindex:any;
ariaCheckedSetter: Function; role:any;
/** Setter for `aria-disabled` attribute. */ constructor(@Attribute('tabindex') tabindex: string) {
ariaDisabledSetter: Function; this.role = 'checkbox';
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');
this.checked = false; this.checked = false;
tabindexSetter(isPresent(tabindex) ? tabindex : '0'); this.tabindex = isPresent(tabindex) ? tabindex : '0';
}
get checked() {
return this.checked_;
}
set checked(value) {
this.checked_ = value;
this.ariaCheckedSetter(value);
} }
get disabled() { get disabled() {
@ -61,7 +46,6 @@ export class MdSwitch {
set disabled(value) { set disabled(value) {
this.disabled_ = isPresent(value) && value !== false; this.disabled_ = isPresent(value) && value !== false;
this.ariaDisabledSetter(this.disabled_);
} }
onKeydown(event: KeyboardEvent) { onKeydown(event: KeyboardEvent) {
@ -78,7 +62,6 @@ export class MdSwitch {
} }
this.checked = !this.checked; this.checked = !this.checked;
this.ariaCheckedSetter(this.checked);
} }
} }

View File

@ -12,6 +12,7 @@ import {
JitChangeDetection, JitChangeDetection,
BindingRecord, BindingRecord,
DirectiveRecord, DirectiveRecord,
DirectiveIndex,
DEFAULT DEFAULT
} from 'angular2/change_detection'; } from 'angular2/change_detection';
@ -191,7 +192,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations, objec
var proto = changeDetection.createProtoChangeDetector("proto"); 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 = [ var bindings = [
BindingRecord.createForDirective(parser.parseBinding('field0', null), "field0", reflector.setter("field0"), directiveRecord), BindingRecord.createForDirective(parser.parseBinding('field0', null), "field0", reflector.setter("field0"), directiveRecord),
BindingRecord.createForDirective(parser.parseBinding('field1', null), "field1", reflector.setter("field1"), directiveRecord), BindingRecord.createForDirective(parser.parseBinding('field1', null), "field1", reflector.setter("field1"), directiveRecord),