From 10d0d5c3cef34577646a652b1d0bc038ac23bb04 Mon Sep 17 00:00:00 2001 From: Vladimir Enchev Date: Wed, 16 Mar 2016 10:44:45 +0200 Subject: [PATCH] Plain component properties now can be applied from CSS --- apps/tests/ui/style/style-tests.ts | 22 +++++++++ ui/builder/component-builder.ts | 17 ++----- ui/styling/css-selector.ts | 73 ++++++++++++++++++------------ utils/utils-common.ts | 20 ++++++++ utils/utils.d.ts | 6 +++ 5 files changed, 96 insertions(+), 42 deletions(-) diff --git a/apps/tests/ui/style/style-tests.ts b/apps/tests/ui/style/style-tests.ts index f32865dc9..059779653 100644 --- a/apps/tests/ui/style/style-tests.ts +++ b/apps/tests/ui/style/style-tests.ts @@ -30,6 +30,28 @@ export function test_css_dataURI_is_applied_to_backgroundImageSource() { }); } +export function test_css_is_applied_to_normal_properties() { + var stack = new stackModule.StackLayout(); + + helper.buildUIAndRunTest(stack, function (views: Array) { + var page = views[1]; + var expected = "horizontal"; + page.css = `StackLayout { orientation: ${expected}; }`; + TKUnit.assertEqual(stack.orientation, expected); + }); +} + +export function test_css_is_applied_to_special_properties() { + var stack = new stackModule.StackLayout(); + + helper.buildUIAndRunTest(stack, function (views: Array) { + var page = views[1]; + var expected = "test"; + page.css = `StackLayout { class: ${expected}; }`; + TKUnit.assertEqual(stack.className, expected); + }); +} + // Test for inheritance in different containers export function test_css_is_applied_inside_StackLayout() { var testButton = new buttonModule.Button(); diff --git a/ui/builder/component-builder.ts b/ui/builder/component-builder.ts index 6ffdb2771..7fbef2f21 100644 --- a/ui/builder/component-builder.ts +++ b/ui/builder/component-builder.ts @@ -6,6 +6,7 @@ import {File, Folder, path, knownFolders} from "file-system"; import {getBindingOptions, bindingConstants} from "./binding-builder"; import * as debugModule from "utils/debug"; import * as platformModule from "platform"; +import {convertString} from "utils/utils"; //the imports below are needed for special property registration import "ui/layouts/dock-layout"; @@ -182,20 +183,8 @@ export function setPropertyValue(instance: View, instanceModule: Object, exports if (!attrHandled && (instance)._applyXmlAttribute) { attrHandled = (instance)._applyXmlAttribute(propertyName, propertyValue); } - if (!attrHandled) { - if (propertyValue.trim() === "") { - instance[propertyName] = propertyValue; - } else { - // Try to convert value to number. - var valueAsNumber = +propertyValue; - if (!isNaN(valueAsNumber)) { - instance[propertyName] = valueAsNumber; - } else if (propertyValue && (propertyValue.toLowerCase() === "true" || propertyValue.toLowerCase() === "false")) { - instance[propertyName] = propertyValue.toLowerCase() === "true" ? true : false; - } else { - instance[propertyName] = propertyValue; - } - } + if (!attrHandled) { + instance[propertyName] = convertString(propertyValue); } } } diff --git a/ui/styling/css-selector.ts b/ui/styling/css-selector.ts index c5f0ea0e5..90eb5856b 100644 --- a/ui/styling/css-selector.ts +++ b/ui/styling/css-selector.ts @@ -5,6 +5,7 @@ import * as trace from "trace"; import * as styleProperty from "ui/styling/style-property"; import * as types from "utils/types"; import * as utils from "utils/utils"; +import {getSpecialPropertySetter} from "ui/builder/special-properties"; var ID_SPECIFICITY = 1000000; var ATTR_SPECIFITY = 10000; @@ -38,7 +39,7 @@ export class CssSelector { get expression(): string { return this._expression; } - + get attrExpression(): string { return this._attrExpression; } @@ -57,11 +58,25 @@ export class CssSelector { public apply(view: view.View) { this.eachSetter((property, value) => { - try { - view.style._setValue(property, value, observable.ValueSource.Css); - } - catch (ex) { - trace.write("Error setting property: " + property.name + " view: " + view + " value: " + value + " " + ex, trace.categories.Style, trace.messageType.error); + if(types.isString(property)) { + let attrHandled = false; + let specialSetter = getSpecialPropertySetter(property); + + if (!attrHandled && specialSetter) { + specialSetter(view, value); + attrHandled = true; + } + + if (!attrHandled && property in view) { + view[property] = utils.convertString(value); + } + } else { + try { + view.style._setValue(property, value, observable.ValueSource.Css); + } + catch (ex) { + trace.write("Error setting property: " + property.name + " view: " + view + " value: " + value + " " + ex, trace.categories.Style, trace.messageType.error); + } } }); } @@ -74,7 +89,7 @@ export class CssSelector { let property = styleProperty.getPropertyByCssName(name); - if (property) { + if (property) { // The property.valueConverter is now used to convert the value later on in DependencyObservable._setValueInternal. callback(property, resolvedValue); } @@ -85,6 +100,8 @@ export class CssSelector { let pair = pairs[j]; callback(pair.property, pair.value); } + } else { + callback(declaration.property, declaration.value); } } } @@ -108,10 +125,10 @@ function matchesType(expression: string, view: view.View): boolean { let exprArr = expression.split("."); let exprTypeName = exprArr[0]; let exprClassName = exprArr[1]; - - let typeCheck = exprTypeName.toLowerCase() === view.typeName.toLowerCase() || + + let typeCheck = exprTypeName.toLowerCase() === view.typeName.toLowerCase() || exprTypeName.toLowerCase() === view.typeName.split(/(?=[A-Z])/).join("-").toLowerCase(); - + if (typeCheck) { if (exprClassName) { return view._cssClasses.some((cssClass, i, arr) => { return cssClass === exprClassName }); @@ -155,14 +172,14 @@ class CssClassSelector extends CssSelector { class CssCompositeSelector extends CssSelector { get specificity(): number { let result = 0; - for(let i = 0; i < this.parentCssSelectors.length; i++) { + for (let i = 0; i < this.parentCssSelectors.length; i++) { result += this.parentCssSelectors[i].selector.specificity; } return result; } - - private parentCssSelectors: [{ selector: CssSelector, onlyDirectParent: boolean}]; - + + private parentCssSelectors: [{ selector: CssSelector, onlyDirectParent: boolean }]; + private splitExpression(expression) { let result = []; let tempArr = []; @@ -191,29 +208,29 @@ class CssCompositeSelector extends CssSelector { } return result; } - + constructor(expr: string, declarations: cssParser.Declaration[]) { super(expr, declarations); let expressions = this.splitExpression(expr); let onlyParent = false; this.parentCssSelectors = []; - for(let i = expressions.length - 1; i >= 0; i--) { + for (let i = expressions.length - 1; i >= 0; i--) { if (expressions[i].trim() === GTHAN) { onlyParent = true; continue; } - this.parentCssSelectors.push({selector: createSelector(expressions[i].trim(), null), onlyDirectParent: onlyParent}); + this.parentCssSelectors.push({ selector: createSelector(expressions[i].trim(), null), onlyDirectParent: onlyParent }); onlyParent = false; } } - + public matches(view: view.View): boolean { let result = this.parentCssSelectors[0].selector.matches(view); if (!result) { return result; } let tempView = view.parent; - for(let i = 1; i < this.parentCssSelectors.length; i++) { + for (let i = 1; i < this.parentCssSelectors.length; i++) { let parentCounter = 0; while (tempView && parentCounter === 0) { result = this.parentCssSelectors[i].selector.matches(tempView); @@ -238,7 +255,7 @@ class CssAttrSelector extends CssSelector { get specificity(): number { return ATTR_SPECIFITY; } - + public matches(view: view.View): boolean { return matchesAttr(this.attrExpression, view); } @@ -256,7 +273,7 @@ function matchesAttr(attrExpression: string, view: view.View): boolean { attrValue = nameValueRegexRes[2].trim().replace(/^(["'])*(.*)\1$/, '$2'); } // extract entire sign (=, ~=, |=, ^=, $=, *=) - let escapedAttrValue = utils.escapeRegexSymbols(attrValue); + let escapedAttrValue = utils.escapeRegexSymbols(attrValue); let attrCheckRegex; switch (attrExpression.charAt(equalSignIndex - 1)) { case "~": @@ -277,10 +294,10 @@ function matchesAttr(attrExpression: string, view: view.View): boolean { // only = (EQUAL) default: - attrCheckRegex = new RegExp("^"+escapedAttrValue+"$"); + attrCheckRegex = new RegExp("^" + escapedAttrValue + "$"); break; } - return !types.isNullOrUndefined(view[attrName]) && attrCheckRegex.test(view[attrName]+""); + return !types.isNullOrUndefined(view[attrName]) && attrCheckRegex.test(view[attrName] + ""); } else { return !types.isNullOrUndefined(view[attrExpression]); } @@ -297,7 +314,7 @@ export class CssVisualStateSelector extends CssSelector { get specificity(): number { return (this._isById ? ID_SPECIFICITY : 0) + - (this._isByAttr ? ATTR_SPECIFITY : 0) + + (this._isByAttr ? ATTR_SPECIFITY : 0) + (this._isByClass ? CLASS_SPECIFICITY : 0) + (this._isByType ? TYPE_SPECIFICITY : 0); } @@ -347,7 +364,7 @@ export class CssVisualStateSelector extends CssSelector { if (this._isByType) { matches = matchesType(this._match, view); } - + if (this._isByAttr) { matches = matchesAttr(this._key, view); } @@ -371,12 +388,12 @@ export function createSelector(expression: string, declarations: cssParser.Decla if (spaceIndex >= 0) { return new CssCompositeSelector(goodExpr, declarations); } - + let leftSquareBracketIndex = goodExpr.indexOf(LSBRACKET); if (leftSquareBracketIndex === 0) { return new CssAttrSelector(goodExpr, declarations); - } - + } + var colonIndex = goodExpr.indexOf(COLON); if (colonIndex >= 0) { return new CssVisualStateSelector(goodExpr, declarations); diff --git a/utils/utils-common.ts b/utils/utils-common.ts index 94cfa5a3f..d3db424b9 100644 --- a/utils/utils-common.ts +++ b/utils/utils-common.ts @@ -34,6 +34,26 @@ export function escapeRegexSymbols(source: string): string { return source.replace(escapeRegex, "\\$&"); } +export function convertString(value: string): any { + var result; + + if (value.trim() === "") { + result = value; + } else { + // Try to convert value to number. + var valueAsNumber = +value; + if (!isNaN(valueAsNumber)) { + result = valueAsNumber; + } else if (value && (value.toLowerCase() === "true" || value.toLowerCase() === "false")) { + result = value.toLowerCase() === "true" ? true : false; + } else { + result = value; + } + } + + return result; +} + export module layout { var MODE_SHIFT = 30; diff --git a/utils/utils.d.ts b/utils/utils.d.ts index 17cb6e9b0..ffab8394e 100644 --- a/utils/utils.d.ts +++ b/utils/utils.d.ts @@ -235,4 +235,10 @@ * @param source The original value. */ export function escapeRegexSymbols(source: string): string + + /** + * Converts string value to number or boolean. + * @param value The original value. + */ + export function convertString(value: string): any }