From 7ce4f66cdcd030132d16c46b66ffd0f0ea5b008b Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Thu, 5 Feb 2015 14:03:58 -0800 Subject: [PATCH] feat: support binding to class.classname Closes #551 --- .../pipeline/element_binder_builder.js | 34 +++++++++++++++++-- .../core/test/compiler/integration_spec.js | 17 ++++++++++ .../pipeline/element_binder_builder_spec.js | 21 ++++++++++++ modules/facade/src/dom.dart | 3 ++ modules/facade/src/dom.es6 | 3 ++ modules/facade/src/lang.dart | 4 +++ modules/facade/src/lang.es6 | 6 +++- 7 files changed, 84 insertions(+), 4 deletions(-) diff --git a/modules/core/src/compiler/pipeline/element_binder_builder.js b/modules/core/src/compiler/pipeline/element_binder_builder.js index 094d0d1453..f48b50868e 100644 --- a/modules/core/src/compiler/pipeline/element_binder_builder.js +++ b/modules/core/src/compiler/pipeline/element_binder_builder.js @@ -1,4 +1,4 @@ -import {int, isPresent, isBlank, Type, BaseException, stringify} from 'facade/src/lang'; +import {int, isPresent, isBlank, Type, BaseException, StringWrapper, stringify} from 'facade/src/lang'; import {Element, DOM} from 'facade/src/dom'; import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'facade/src/collection'; @@ -16,6 +16,26 @@ import {CompileStep} from './compile_step'; import {CompileElement} from './compile_element'; import {CompileControl} from './compile_control'; +const CLASS_PREFIX = 'class.'; +var classSettersCache = StringMapWrapper.create(); + +function classSetterFactory(className:string) { + var setterFn = StringMapWrapper.get(classSettersCache, className); + + if (isBlank(setterFn)) { + setterFn = function(element:Element, value) { + if (value) { + DOM.addClass(element, className); + } else { + DOM.removeClass(element, className); + } + }; + StringMapWrapper.set(classSettersCache, className, setterFn); + } + + return setterFn; +} + /** * Creates the ElementBinders and adds watches to the * ProtoChangeDetector. @@ -72,8 +92,16 @@ export class ElementBinderBuilder extends CompileStep { _bindElementProperties(protoView, compileElement) { MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => { - if (DOM.hasProperty(compileElement.element, property)) { - protoView.bindElementProperty(expression.ast, property, reflector.setter(property)); + var setterFn; + + if (StringWrapper.startsWith(property, CLASS_PREFIX)) { + setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length)); + } else if (DOM.hasProperty(compileElement.element, property)) { + setterFn = reflector.setter(property); + } + + if (isPresent(setterFn)) { + protoView.bindElementProperty(expression.ast, property, setterFn); } }); } diff --git a/modules/core/test/compiler/integration_spec.js b/modules/core/test/compiler/integration_spec.js index c191b7a57c..f2fc902c3c 100644 --- a/modules/core/test/compiler/integration_spec.js +++ b/modules/core/test/compiler/integration_spec.js @@ -69,6 +69,22 @@ export function main() { }); }); + it('should consume element binding for class attribute', (done) => { + compiler.compile(MyComp, el('
')).then((pv) => { + createView(pv); + + ctx.boolProp = true; + cd.detectChanges(); + expect(view.nodes[0].className).toEqual('foo ng-binding bar'); + + ctx.boolProp = false; + cd.detectChanges(); + expect(view.nodes[0].className).toEqual('foo ng-binding'); + + done(); + }); + }); + it('should support nested components.', (done) => { compiler.compile(MyComp, el('')).then((pv) => { createView(pv); @@ -147,6 +163,7 @@ class MyDir { }) class MyComp { ctxProp:string; + boolProp:boolean; constructor() { this.ctxProp = 'initial value'; } diff --git a/modules/core/test/compiler/pipeline/element_binder_builder_spec.js b/modules/core/test/compiler/pipeline/element_binder_builder_spec.js index 1cc6b5605a..3cea16d18c 100644 --- a/modules/core/test/compiler/pipeline/element_binder_builder_spec.js +++ b/modules/core/test/compiler/pipeline/element_binder_builder_spec.js @@ -176,6 +176,27 @@ export function main() { expect(view.nodes[0].hidden).toEqual(false); }); + it('should bind class with a dot', () => { + var propertyBindings = MapWrapper.createFromStringMap({ + 'class.bar': '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(view.nodes[0].className).toEqual('foo ng-binding bar'); + + evalContext.prop1 = false; + changeDetector.detectChanges(); + expect(view.nodes[0].className).toEqual('foo ng-binding'); + }); + it('should bind events', () => { var eventBindings = MapWrapper.createFromStringMap({ 'event1': '1+1' diff --git a/modules/facade/src/dom.dart b/modules/facade/src/dom.dart index b8607afa6f..2eefdcbc76 100644 --- a/modules/facade/src/dom.dart +++ b/modules/facade/src/dom.dart @@ -102,6 +102,9 @@ class DOM { static void addClass(Element element, String classname) { element.classes.add(classname); } + static void removeClass(Element element, String classname) { + element.classes.remove(classname); + } static bool hasClass(Element element, String classname) => element.classes.contains(classname); diff --git a/modules/facade/src/dom.es6 b/modules/facade/src/dom.es6 index 134dc2ff1c..64bc98af9e 100644 --- a/modules/facade/src/dom.es6 +++ b/modules/facade/src/dom.es6 @@ -121,6 +121,9 @@ export class DOM { static addClass(element:Element, classname:string) { element.classList.add(classname); } + static removeClass(element:Element, classname:string) { + element.classList.remove(classname); + } static hasClass(element:Element, classname:string) { return element.classList.contains(classname); } diff --git a/modules/facade/src/lang.dart b/modules/facade/src/lang.dart index 67dd5ff8d3..24cf5cac1e 100644 --- a/modules/facade/src/lang.dart +++ b/modules/facade/src/lang.dart @@ -62,6 +62,10 @@ class StringWrapper { return s.replaceAll(from, replace); } + static startsWith(String s, String start) { + return s.startsWith(start); + } + static String substring(String s, int start, [int end]) { return s.substring(start, end); } diff --git a/modules/facade/src/lang.es6 b/modules/facade/src/lang.es6 index ea12b81891..108efbcda6 100644 --- a/modules/facade/src/lang.es6 +++ b/modules/facade/src/lang.es6 @@ -68,6 +68,10 @@ export class StringWrapper { return s.replace(from.multiple, replace); } + static startsWith(s:string, start:string) { + return s.startsWith(start); + } + static substring(s:string, start:int, end:int = undefined) { return s.substring(start, end); } @@ -219,4 +223,4 @@ export function assertionsEnabled():boolean { export function print(obj) { console.log(obj); -} \ No newline at end of file +}