From 1846ce8c687b9ce86c2802876b564f4fd8041bf1 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Fri, 13 Feb 2015 16:32:49 +0100 Subject: [PATCH] feat: support binding to aria-* attributes Closes #643 --- .../pipeline/element_binder_builder.js | 24 +++++++++- modules/angular2/src/facade/dom.dart | 6 +++ modules/angular2/src/facade/dom.es6 | 3 ++ .../test/core/compiler/integration_spec.js | 18 ++++++++ .../pipeline/element_binder_builder_spec.js | 46 +++++++++++++++++++ 5 files changed, 96 insertions(+), 1 deletion(-) diff --git a/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js b/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js index 4d85a62b16..5b9094587f 100644 --- a/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js +++ b/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js @@ -18,6 +18,26 @@ import {CompileControl} from './compile_control'; var DOT_REGEXP = RegExpWrapper.create('\\.'); +const ARIA_PREFIX = 'aria-'; +var ariaSettersCache = StringMapWrapper.create(); + +function ariaSetterFactory(attrName:string) { + var setterFn = StringMapWrapper.get(ariaSettersCache, attrName); + + if (isBlank(setterFn)) { + setterFn = function(element:Element, value) { + if (isPresent(value)) { + DOM.setAttribute(element, attrName, stringify(value)); + } else { + DOM.removeAttribute(element, attrName); + } + }; + StringMapWrapper.set(ariaSettersCache, attrName, setterFn); + } + + return setterFn; +} + const CLASS_PREFIX = 'class.'; var classSettersCache = StringMapWrapper.create(); @@ -133,7 +153,9 @@ export class ElementBinderBuilder extends CompileStep { MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => { var setterFn, styleParts, styleSuffix; - if (StringWrapper.startsWith(property, CLASS_PREFIX)) { + if (StringWrapper.startsWith(property, ARIA_PREFIX)) { + setterFn = ariaSetterFactory(property); + } else if (StringWrapper.startsWith(property, CLASS_PREFIX)) { setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length)); } else if (StringWrapper.startsWith(property, STYLE_PREFIX)) { styleParts = StringWrapper.split(property, DOT_REGEXP); diff --git a/modules/angular2/src/facade/dom.dart b/modules/angular2/src/facade/dom.dart index 47f8e3b48c..7822d9518f 100644 --- a/modules/angular2/src/facade/dom.dart +++ b/modules/angular2/src/facade/dom.dart @@ -148,6 +148,12 @@ class DOM { element.setAttribute(name, value); } + static void removeAttribute(Element element, String name) { + //there is no removeAttribute method as of now in Dart: + //https://code.google.com/p/dart/issues/detail?id=19934 + element.attributes.remove(name); + } + static Node templateAwareRoot(Element el) => el is TemplateElement ? el.content : el; diff --git a/modules/angular2/src/facade/dom.es6 b/modules/angular2/src/facade/dom.es6 index 2721928d35..0d38f2e8e2 100644 --- a/modules/angular2/src/facade/dom.es6 +++ b/modules/angular2/src/facade/dom.es6 @@ -166,6 +166,9 @@ export class DOM { static setAttribute(element:Element, name:string, value:string) { element.setAttribute(name, value); } + static removeAttribute(element:Element, attribute:string) { + return element.removeAttribute(attribute); + } static templateAwareRoot(el:Element):Node { return el instanceof TemplateElement ? el.content : el; } diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index 66a4311d91..bc29590e36 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -71,6 +71,24 @@ export function main() { }); }); + it('should consume binding to aria-* attributes', (done) => { + tplResolver.setTemplate(MyComp, new Template({inline: '
'})); + + compiler.compile(MyComp).then((pv) => { + createView(pv); + + ctx.ctxProp = 'Initial aria label'; + cd.detectChanges(); + expect(DOM.getAttribute(view.nodes[0], 'aria-label')).toEqual('Initial aria label'); + + ctx.ctxProp = 'Changed aria label'; + cd.detectChanges(); + expect(DOM.getAttribute(view.nodes[0], 'aria-label')).toEqual('Changed aria label'); + + done(); + }); + }); + it('should consume directive watch expression change.', (done) => { var tpl = '
' + diff --git a/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js b/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js index 4d48fdedd8..9846c76620 100644 --- a/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js +++ b/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js @@ -195,6 +195,52 @@ export function main() { expect(view.nodes[0].hidden).toEqual(false); }); + it('should bind to aria-* attributes when exp evaluates to strings', () => { + var propertyBindings = MapWrapper.createFromStringMap({ + 'aria-label': 'prop1' + }); + var pipeline = createPipeline({propertyBindings: propertyBindings}); + var results = pipeline.process(el('
')); + var pv = results[0].inheritedProtoView; + + expect(pv.elementBinders[0].hasElementPropertyBindings).toBe(true); + + instantiateView(pv); + + evalContext.prop1 = 'some label'; + changeDetector.detectChanges(); + expect(DOM.getAttribute(view.nodes[0], 'aria-label')).toEqual('some label'); + + evalContext.prop1 = 'some other label'; + changeDetector.detectChanges(); + expect(DOM.getAttribute(view.nodes[0], 'aria-label')).toEqual('some other label'); + + evalContext.prop1 = null; + changeDetector.detectChanges(); + expect(DOM.getAttribute(view.nodes[0], 'aria-label')).toBeNull(); + }); + + it('should bind to aria-* attributes when exp evaluates to booleans', () => { + var propertyBindings = MapWrapper.createFromStringMap({ + 'aria-busy': 'prop1' + }); + var pipeline = createPipeline({propertyBindings: propertyBindings}); + var results = pipeline.process(el('
')); + var pv = results[0].inheritedProtoView; + + expect(pv.elementBinders[0].hasElementPropertyBindings).toBe(true); + + instantiateView(pv); + + evalContext.prop1 = true; + changeDetector.detectChanges(); + expect(DOM.getAttribute(view.nodes[0], 'aria-busy')).toEqual('true'); + + evalContext.prop1 = false; + changeDetector.detectChanges(); + expect(DOM.getAttribute(view.nodes[0], 'aria-busy')).toEqual('false'); + }); + it('should bind class with a dot', () => { var propertyBindings = MapWrapper.createFromStringMap({ 'class.bar': 'prop1',