Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions modules/angular2/src/core/compiler/element_injector.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {NgElement} from 'angular2/src/core/dom/element';
import {Directive, onChange, onDestroy} from 'angular2/src/core/annotations/annotations';
import {BindingPropagationConfig} from 'angular2/src/core/compiler/binding_propagation_config';
import * as pclModule from 'angular2/src/core/compiler/private_component_location';
import {reflector} from 'angular2/src/reflection/reflection';
import {setterFactory} from './property_setter_factory';

var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;

Expand Down Expand Up @@ -527,7 +527,7 @@ export class ElementInjector extends TreeNode {
_buildPropSetter(dep) {
var ngElement = this._getPreBuiltObjectByKeyId(StaticKeys.instance().ngElementId);
var domElement = ngElement.domElement;
var setter = reflector.setter(dep.propSetterName);
var setter = setterFactory(dep.propSetterName);
return function(v) { setter(domElement, v) };
}

Expand Down
115 changes: 4 additions & 111 deletions modules/angular2/src/core/compiler/pipeline/element_binder_builder.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {int, isPresent, isBlank, Type, BaseException, StringWrapper, RegExpWrapper, isString, stringify} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {int, isPresent, isBlank} from 'angular2/src/facade/lang';
import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';

import {reflector} from 'angular2/src/reflection/reflection';
Expand All @@ -11,88 +10,8 @@ import {DirectiveMetadata} from '../directive_metadata';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {dashCaseToCamelCase, camelCaseToDashCase} from './util';

var DOT_REGEXP = RegExpWrapper.create('\\.');

const ATTRIBUTE_PREFIX = 'attr.';
var attributeSettersCache = StringMapWrapper.create();

function _isValidAttributeValue(attrName:string, value: any) {
if (attrName == "role") {
return isString(value);
} else {
return isPresent(value);
}
}

function attributeSetterFactory(attrName:string) {
var setterFn = StringMapWrapper.get(attributeSettersCache, attrName);
var dashCasedAttributeName;

if (isBlank(setterFn)) {
dashCasedAttributeName = camelCaseToDashCase(attrName);
setterFn = function(element, value) {
if (_isValidAttributeValue(dashCasedAttributeName, value)) {
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
} else {
DOM.removeAttribute(element, dashCasedAttributeName);
if (isPresent(value)) {
throw new BaseException("Invalid " + dashCasedAttributeName + " attribute, only string values are allowed, got '" + stringify(value) + "'");
}
}
};
StringMapWrapper.set(attributeSettersCache, attrName, setterFn);
}

return setterFn;
}

const CLASS_PREFIX = 'class.';
var classSettersCache = StringMapWrapper.create();

function classSetterFactory(className:string) {
var setterFn = StringMapWrapper.get(classSettersCache, className);

if (isBlank(setterFn)) {
setterFn = function(element, value) {
if (value) {
DOM.addClass(element, className);
} else {
DOM.removeClass(element, className);
}
};
StringMapWrapper.set(classSettersCache, className, setterFn);
}

return setterFn;
}

const STYLE_PREFIX = 'style.';
var styleSettersCache = StringMapWrapper.create();

function styleSetterFactory(styleName:string, stylesuffix:string) {
var cacheKey = styleName + stylesuffix;
var setterFn = StringMapWrapper.get(styleSettersCache, cacheKey);
var dashCasedStyleName;

if (isBlank(setterFn)) {
dashCasedStyleName = camelCaseToDashCase(styleName);
setterFn = function(element, value) {
var valAsStr;
if (isPresent(value)) {
valAsStr = stringify(value);
DOM.setStyle(element, dashCasedStyleName, valAsStr + stylesuffix);
} else {
DOM.removeStyle(element, dashCasedStyleName);
}
};
StringMapWrapper.set(classSettersCache, cacheKey, setterFn);
}

return setterFn;
}

import {dashCaseToCamelCase} from './util';
import {setterFactory} from '../property_setter_factory'

/**
* Creates the ElementBinders and adds watches to the
Expand Down Expand Up @@ -178,28 +97,7 @@ export class ElementBinderBuilder extends CompileStep {

_bindElementProperties(protoView, compileElement) {
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
var setterFn, styleParts, styleSuffix;

if (StringWrapper.startsWith(property, ATTRIBUTE_PREFIX)) {
setterFn = attributeSetterFactory(StringWrapper.substring(property, ATTRIBUTE_PREFIX.length));
} 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);
styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : '';
setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix);
} else if (StringWrapper.equals(property, 'innerHtml')) {
setterFn = (element, value) => DOM.setInnerHTML(element, value);
} else {
property = this._resolvePropertyName(property);
var propertySetterFn = reflector.setter(property);
setterFn = function(receiver, value) {
if (DOM.hasProperty(receiver, property)) {
return propertySetterFn(receiver, value);
}
}
}

var setterFn = setterFactory(property);
protoView.bindElementProperty(expression.ast, property, setterFn);
});
}
Expand Down Expand Up @@ -263,9 +161,4 @@ export class ElementBinderBuilder extends CompileStep {
_splitBindConfig(bindConfig:string) {
return ListWrapper.map(bindConfig.split('|'), (s) => s.trim());
}

_resolvePropertyName(attrName:string) {
var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, attrName);
return isPresent(mappedPropName) ? mappedPropName : attrName;
}
}
138 changes: 138 additions & 0 deletions modules/angular2/src/core/compiler/property_setter_factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {StringWrapper, RegExpWrapper, BaseException, isPresent, isBlank, isString, stringify} from 'angular2/src/facade/lang';
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {reflector} from 'angular2/src/reflection/reflection';

var DASH_CASE_REGEXP = RegExpWrapper.create('-([a-z])');
var CAMEL_CASE_REGEXP = RegExpWrapper.create('([A-Z])');

export function dashCaseToCamelCase(input:string): string {
return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP, (m) => {
return m[1].toUpperCase();
});
}

export function camelCaseToDashCase(input:string): string {
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP, (m) => {
return '-' + m[1].toLowerCase();
});
}

const STYLE_SEPARATOR = '.';
var propertySettersCache = StringMapWrapper.create();
var innerHTMLSetterCache;

export function setterFactory(property: string): Function {
var setterFn, styleParts, styleSuffix;
if (StringWrapper.startsWith(property, ATTRIBUTE_PREFIX)) {
setterFn = attributeSetterFactory(StringWrapper.substring(property, ATTRIBUTE_PREFIX.length));
} else if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
} else if (StringWrapper.startsWith(property, STYLE_PREFIX)) {
styleParts = property.split(STYLE_SEPARATOR);
styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : '';
setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix);
} else if (StringWrapper.equals(property, 'innerHtml')) {
if (isBlank(innerHTMLSetterCache)) {
innerHTMLSetterCache = (el, value) => DOM.setInnerHTML(el, value);
}
setterFn = innerHTMLSetterCache;
} else {
property = resolvePropertyName(property);
setterFn = StringMapWrapper.get(propertySettersCache, property);
if (isBlank(setterFn)) {
var propertySetterFn = reflector.setter(property);
setterFn = function(receiver, value) {
if (DOM.hasProperty(receiver, property)) {
return propertySetterFn(receiver, value);
}
}
StringMapWrapper.set(propertySettersCache, property, setterFn);
}
}
return setterFn;
}

const ATTRIBUTE_PREFIX = 'attr.';
var attributeSettersCache = StringMapWrapper.create();

function _isValidAttributeValue(attrName:string, value: any): boolean {
if (attrName == "role") {
return isString(value);
} else {
return isPresent(value);
}
}

function attributeSetterFactory(attrName:string): Function {
var setterFn = StringMapWrapper.get(attributeSettersCache, attrName);
var dashCasedAttributeName;

if (isBlank(setterFn)) {
dashCasedAttributeName = camelCaseToDashCase(attrName);
setterFn = function(element, value) {
if (_isValidAttributeValue(dashCasedAttributeName, value)) {
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
} else {
if (isPresent(value)) {
throw new BaseException("Invalid " + dashCasedAttributeName +
" attribute, only string values are allowed, got '" + stringify(value) + "'");
}
DOM.removeAttribute(element, dashCasedAttributeName);
}
};
StringMapWrapper.set(attributeSettersCache, attrName, setterFn);
}

return setterFn;
}

const CLASS_PREFIX = 'class.';
var classSettersCache = StringMapWrapper.create();

function classSetterFactory(className:string): Function {
var setterFn = StringMapWrapper.get(classSettersCache, className);

if (isBlank(setterFn)) {
setterFn = function(element, value) {
if (value) {
DOM.addClass(element, className);
} else {
DOM.removeClass(element, className);
}
};
StringMapWrapper.set(classSettersCache, className, setterFn);
}

return setterFn;
}

const STYLE_PREFIX = 'style.';
var styleSettersCache = StringMapWrapper.create();

function styleSetterFactory(styleName:string, styleSuffix:string): Function {
var cacheKey = styleName + styleSuffix;
var setterFn = StringMapWrapper.get(styleSettersCache, cacheKey);
var dashCasedStyleName;

if (isBlank(setterFn)) {
dashCasedStyleName = camelCaseToDashCase(styleName);
setterFn = function(element, value) {
var valAsStr;
if (isPresent(value)) {
valAsStr = stringify(value);
DOM.setStyle(element, dashCasedStyleName, valAsStr + styleSuffix);
} else {
DOM.removeStyle(element, dashCasedStyleName);
}
};
StringMapWrapper.set(styleSettersCache, cacheKey, setterFn);
}

return setterFn;
}

function resolvePropertyName(attrName:string): string {
var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, attrName);
return isPresent(mappedPropName) ? mappedPropName : attrName;
}
37 changes: 34 additions & 3 deletions modules/angular2/test/core/compiler/element_injector_spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, SpyObject, proxy, el} from 'angular2/test_lib';
import {isBlank, isPresent, IMPLEMENTS} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper, List, StringMapWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {EventEmitter, PropertySetter} from 'angular2/src/core/annotations/di';
Expand Down Expand Up @@ -76,13 +77,34 @@ class NeedsEventEmitter {

class NeedsPropertySetter {
propSetter;
constructor(@PropertySetter('title') propSetter: Function) {
roleSetter;
classSetter;
styleSetter;
unitSetter;
constructor(@PropertySetter('title') propSetter: Function, @PropertySetter('attr.role') roleSetter: Function,
@PropertySetter('class.active') classSetter: Function, @PropertySetter('style.width') styleSetter: Function,
@PropertySetter('style.height.px') unitSetter: Function) {
this.propSetter = propSetter;
this.roleSetter = roleSetter;
this.classSetter = classSetter;
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 A_Needs_B {
Expand Down Expand Up @@ -529,9 +551,18 @@ export function main() {

var preBuildObject = new PreBuiltObjects(null, ngElement, null, null);
var inj = injector([NeedsPropertySetter], null, null, preBuildObject);
inj.get(NeedsPropertySetter).setProp('foobar');
var component = inj.get(NeedsPropertySetter);
component.setProp('foobar');
component.setRole('button');
component.setClass(true);
component.setStyle('40px')
component.setStyleWithUnit(50);

expect(div.title).toEqual('foobar');
expect(DOM.getAttribute(div, 'role')).toEqual('button');
expect(DOM.hasClass(div, 'active')).toEqual(true);
expect(DOM.getStyle(div, 'width')).toEqual('40px');
expect(DOM.getStyle(div, 'height')).toEqual('50px');
});
});

Expand Down
Loading