diff --git a/tests/app/data/observable-array-tests.ts b/tests/app/data/observable-array-tests.ts index 835fce96e..2482f3509 100644 --- a/tests/app/data/observable-array-tests.ts +++ b/tests/app/data/observable-array-tests.ts @@ -1,53 +1,52 @@ import * as TKUnit from "../TKUnit"; -import { ViewBase } from "ui/core/view-base"; -require("globals"); +import { Label } from "ui/label"; // >> observable-array-require -import * as observableArrayModule from "data/observable-array"; +import { ObservableArray, ChangedData, ChangeType } from "data/observable-array"; // << observable-array-require -require("globals"); - export const test_ObservableArray_shouldCopySourceArrayItems = function () { // >> observable-array-create const sa = [1, 2, 3]; - const array = new observableArrayModule.ObservableArray(sa); + const array = new ObservableArray(sa); // << observable-array-create - TKUnit.assert(sa.length === array.length && array.length === 3, "ObservableArray should copy all source array items!"); + TKUnit.assertEqual(array.length, 3, "ObservableArray length should be 3"); + TKUnit.assertEqual(sa.length, array.length, "ObservableArray should copy all source array items!"); }; export const test_ObservableArray_shouldCopyMultipleItemsAsSource = function () { // >> observable-array-arguments - const array = new observableArrayModule.ObservableArray(1, 2, 3); + const array = new ObservableArray(1, 2, 3); // << observable-array-arguments - TKUnit.assert(array.length === 3 && array.getItem(1) === 2, "ObservableArray should copy multiple items from source!"); + TKUnit.assertEqual(array.length, 3, "ObservableArray length should be 3"); + TKUnit.assertEqual(array.getItem(1), 2, "ObservableArray should copy multiple items from source!"); }; export const test_ObservableArray_shouldCreateArrayFromSpecifiedLength = function () { // >> observable-array-length - const array = new observableArrayModule.ObservableArray(100); + const array = new ObservableArray(100); // << observable-array-length - TKUnit.assert(array.length === 100, "ObservableArray should create array from specified length!"); + TKUnit.assertEqual(array.length, 100, "ObservableArray should create array from specified length!"); }; export const test_ObservableArray_shouldBeAbleToSetLength = function () { // >> observable-array-newvalue - const array = new observableArrayModule.ObservableArray(100); + const array = new ObservableArray(100); // >> (hide) - TKUnit.assert(array.length === 100, "ObservableArray should create array from specified length!"); + TKUnit.assertEqual(array.length, 100, "ObservableArray should create array from specified length!"); // << (hide) array.length = 50; // << observable-array-newvalue - TKUnit.assert(array.length === 50, "ObservableArray should respect new length!"); + TKUnit.assertEqual(array.length, 50, "ObservableArray should respect new length!"); }; export const test_ObservableArray_getItemShouldReturnCorrectItem = function () { // >> observable-array-getitem - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); const firstItem = array.getItem(0); const secondItem = array.getItem(1); const thirdItem = array.getItem(2); @@ -58,7 +57,7 @@ export const test_ObservableArray_getItemShouldReturnCorrectItem = function () { export const test_ObservableArray_setItemShouldSetCorrectItem = function () { // >> observable-array-setitem - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); array.setItem(1, 5); // << observable-array-setitem TKUnit.assert(array.getItem(1) === 5, "ObservableArray setItem() should set correct item!"); @@ -71,7 +70,7 @@ export const test_ObservableArray_setItemShouldRaiseCorrectEvent = function () { let addedCount: number; let removed: Array; - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); array.on("change", (args) => { index = args.index; // Index of the changed item. action = args.action; // Action. In this case Update. @@ -81,14 +80,14 @@ export const test_ObservableArray_setItemShouldRaiseCorrectEvent = function () { array.setItem(1, 5); // << observable-array-eventdata TKUnit.assertEqual(index, 1); - TKUnit.assertEqual(action, observableArrayModule.ChangeType.Update); + TKUnit.assertEqual(action, ChangeType.Update); TKUnit.assertEqual(addedCount, 1); TKUnit.assertEqual(removed[0], 2); }; export const test_ObservableArray_concatShouldReturnNewArrayWithNewItemsAtTheEnd = function () { // >> observable-array-combine - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); const result = array.concat([4, 5, 6]); // << observable-array-combine TKUnit.assert(result.length === 6 && result[4] === 5, "ObservableArray concat() should add items at the end!"); @@ -96,7 +95,7 @@ export const test_ObservableArray_concatShouldReturnNewArrayWithNewItemsAtTheEnd export const test_ObservableArray_joinShouldReturnStringWithAllItemsSeparatedWithComma = function () { // >> observable-array-join - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); const result = array.join(); // << observable-array-join TKUnit.assert(result === "1,2,3", "ObservableArray join() should return string with all items separated with comma!"); @@ -104,7 +103,7 @@ export const test_ObservableArray_joinShouldReturnStringWithAllItemsSeparatedWit export const test_ObservableArray_joinShouldReturnStringWithAllItemsSeparatedWithDot = function () { // >> observable-array-join-separator - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); const result = array.join("."); // << observable-array-join-separator TKUnit.assert(result === "1.2.3", "ObservableArray join() should return string with all items separated with dot!"); @@ -112,9 +111,9 @@ export const test_ObservableArray_joinShouldReturnStringWithAllItemsSeparatedWit export const test_ObservableArray_popShouldRemoveTheLastElement = function () { // >> observable-array-join-pop - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); // >> (hide) - const viewBase = new ViewBase(); + const viewBase = new Label(); viewBase.set("testProperty", 0); viewBase.bind({ sourceProperty: "length", targetProperty: "testProperty" }, array); // << (hide) @@ -125,15 +124,15 @@ export const test_ObservableArray_popShouldRemoveTheLastElement = function () { }; export const test_ObservableArray_popShouldRemoveTheLastElementAndRaiseChangeEventWithCorrectArgs = function () { - let result: observableArrayModule.ChangedData; + let result: ChangedData; // >> observable-array-join-change - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); // >> (hide) const index = array.length - 1; // << (hide) - array.on(observableArrayModule.ObservableArray.changeEvent, (args: observableArrayModule.ChangedData) => { + array.on(ObservableArray.changeEvent, (args: ChangedData) => { // Argument (args) is ChangedData. // args.eventName is "change". // args.action is "delete". @@ -149,15 +148,15 @@ export const test_ObservableArray_popShouldRemoveTheLastElementAndRaiseChangeEve array.pop(); // << observable-array-join-change - TKUnit.assert(result.eventName === observableArrayModule.ObservableArray.changeEvent && result.action === observableArrayModule.ChangeType.Delete && + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Delete && result.removed.length === 1 && result.index === index && result.addedCount === 0, "ObservableArray pop() should raise 'change' event with correct args!"); }; export const test_ObservableArray_pushShouldAppendNewElement = function () { // >> observable-array-push - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); // >> (hide) - const viewBase = new ViewBase(); + const viewBase = new Label(); viewBase.set("testProperty", 0); viewBase.bind({ sourceProperty: "length", targetProperty: "testProperty" }, array); // << (hide) @@ -168,11 +167,11 @@ export const test_ObservableArray_pushShouldAppendNewElement = function () { }; export const test_ObservableArray_pushShouldAppendNewElementAndRaiseChangeEventWithCorrectArgs = function () { - let result: observableArrayModule.ChangedData; + let result: ChangedData; // >> observable-array-change-push - const array = new observableArrayModule.ObservableArray([1, 2, 3]); - array.on(observableArrayModule.ObservableArray.changeEvent, (args: observableArrayModule.ChangedData) => { + const array = new ObservableArray([1, 2, 3]); + array.on(ObservableArray.changeEvent, (args: ChangedData) => { // Argument (args) is ChangedData. // args.eventName is "change". // args.action is "add". @@ -188,15 +187,15 @@ export const test_ObservableArray_pushShouldAppendNewElementAndRaiseChangeEventW array.push(4); // << observable-array-change-push - TKUnit.assert(result.eventName === observableArrayModule.ObservableArray.changeEvent && result.action === observableArrayModule.ChangeType.Add && + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Add && result.removed.length === 0 && result.index === 3 && result.addedCount === 1, "ObservableArray push() should raise 'change' event with correct args!"); }; export const test_ObservableArray_pushShouldAppendNewElements = function () { // >> observable-array-push-multiple - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); // >> (hide) - const viewBase = new ViewBase(); + const viewBase = new Label(); viewBase.set("testProperty", 0); viewBase.bind({ sourceProperty: "length", targetProperty: "testProperty" }, array); // << (hide) @@ -207,11 +206,11 @@ export const test_ObservableArray_pushShouldAppendNewElements = function () { }; export const test_ObservableArray_pushShouldAppendNewElementsAndRaiseChangeEventWithCorrectArgs = function () { - let result: observableArrayModule.ChangedData; + let result: ChangedData; // >> observable-array-push-multiple-info - const array = new observableArrayModule.ObservableArray([1, 2, 3]); - array.on(observableArrayModule.ObservableArray.changeEvent, (args: observableArrayModule.ChangedData) => { + const array = new ObservableArray([1, 2, 3]); + array.on(ObservableArray.changeEvent, (args: ChangedData) => { // Argument (args) is ChangedData. // args.eventName is "change". // args.action is "add". @@ -227,15 +226,15 @@ export const test_ObservableArray_pushShouldAppendNewElementsAndRaiseChangeEvent array.push(4, 5, 6); // << observable-array-push-multiple-info - TKUnit.assert(result.eventName === observableArrayModule.ObservableArray.changeEvent && result.action === observableArrayModule.ChangeType.Add && + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Add && result.removed.length === 0 && result.index === 3 && result.addedCount === 3, "ObservableArray push() should raise 'change' event with correct args!"); }; export const test_ObservableArray_pushShouldAppendNewElementsFromSourceArray = function () { // >> observable-array-push-source - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); // >> (hide) - const viewBase = new ViewBase(); + const viewBase = new Label(); viewBase.set("testProperty", 0); viewBase.bind({ sourceProperty: "length", targetProperty: "testProperty" }, array); // << (hide) @@ -246,11 +245,11 @@ export const test_ObservableArray_pushShouldAppendNewElementsFromSourceArray = f }; export const test_ObservableArray_pushShouldAppendNewElementsFromSourceArrayAndRaiseChangeEventWithCorrectArgs = function () { - let result: observableArrayModule.ChangedData; + let result: ChangedData; // >> observable-array-push-source-info - const array = new observableArrayModule.ObservableArray([1, 2, 3]); - array.on(observableArrayModule.ObservableArray.changeEvent, (args: observableArrayModule.ChangedData) => { + const array = new ObservableArray([1, 2, 3]); + array.on(ObservableArray.changeEvent, (args: ChangedData) => { // Argument (args) is ChangedData. // args.eventName is "change". // args.action is "add". @@ -266,13 +265,13 @@ export const test_ObservableArray_pushShouldAppendNewElementsFromSourceArrayAndR array.push([4, 5, 6]); // << observable-array-push-source-info - TKUnit.assert(result.eventName === observableArrayModule.ObservableArray.changeEvent && result.action === observableArrayModule.ChangeType.Add && + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Add && result.removed.length === 0 && result.index === 3 && result.addedCount === 3, "ObservableArray push() should raise 'change' event with correct args!"); }; export const test_ObservableArray_reverseShouldReturnNewReversedArray = function () { // >> observable-array-reverse - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); const result = array.reverse(); // << observable-array-reverse TKUnit.assert(result.length === 3 && result[0] === 3, "ObservableArray reverse() should return new reversed array!"); @@ -280,9 +279,9 @@ export const test_ObservableArray_reverseShouldReturnNewReversedArray = function export const test_ObservableArray_shiftShouldRemoveTheFirstElement = function () { // >> observable-array-shift - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); // >> (hide) - const viewBase = new ViewBase(); + const viewBase = new Label(); viewBase.set("testProperty", 0); viewBase.bind({ sourceProperty: "length", targetProperty: "testProperty" }, array); // << (hide) @@ -293,12 +292,12 @@ export const test_ObservableArray_shiftShouldRemoveTheFirstElement = function () }; export const test_ObservableArray_shiftShouldRemoveTheFirstElementAndRaiseChangeEventWithCorrectArgs = function () { - let result: observableArrayModule.ChangedData; + let result: ChangedData; // >> observable-array-shift-change - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); - array.on(observableArrayModule.ObservableArray.changeEvent, (args: observableArrayModule.ChangedData) => { + array.on(ObservableArray.changeEvent, (args: ChangedData) => { // Argument (args) is ChangedData. // args.eventName is "change". // args.action is "delete". @@ -314,13 +313,13 @@ export const test_ObservableArray_shiftShouldRemoveTheFirstElementAndRaiseChange array.shift(); // << observable-array-shift-change - TKUnit.assert(result.eventName === observableArrayModule.ObservableArray.changeEvent && result.action === observableArrayModule.ChangeType.Delete && + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Delete && result.removed.length === 1 && result.index === 0 && result.addedCount === 0, "ObservableArray shift() should raise 'change' event with correct args!"); }; export const test_ObservableArray_sliceShouldReturnSectionAsNewArray = function () { // >> observable-array-slice - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); const result = array.slice(); // << observable-array-slice TKUnit.assert(result[2] === 3 && result.length === 3, "ObservableArray slice() should return section!"); @@ -328,7 +327,7 @@ export const test_ObservableArray_sliceShouldReturnSectionAsNewArray = function export const test_ObservableArray_sliceWithParamsShouldReturnSectionAsNewArray = function () { // >> observable-array-slice-args - const array = new observableArrayModule.ObservableArray([1, 2, 3, 4, 5]); + const array = new ObservableArray([1, 2, 3, 4, 5]); const result = array.slice(2, 4); // << observable-array-slice-args TKUnit.assert(result[1] === 4 && result.length === 2, "ObservableArray slice() should return section according to specified arguments!"); @@ -336,7 +335,7 @@ export const test_ObservableArray_sliceWithParamsShouldReturnSectionAsNewArray = export const test_ObservableArray_sortShouldReturnNewSortedArray = function () { // >> observable-array-sort - const array = new observableArrayModule.ObservableArray([3, 2, 1]); + const array = new ObservableArray([3, 2, 1]); const result = array.sort(); // << observable-array-sort TKUnit.assert(result[0] === 1 && result.length === 3, "ObservableArray sort() should return new sorted array!"); @@ -344,7 +343,7 @@ export const test_ObservableArray_sortShouldReturnNewSortedArray = function () { export const test_ObservableArray_sortShouldReturnNewSortedArrayAccordingSpecifiedOrder = function () { // >> observable-array-sort-comparer - const array = new observableArrayModule.ObservableArray([10, 100, 1]); + const array = new ObservableArray([10, 100, 1]); const result = array.sort((a: number, b: number) => { return a - b; }); // << observable-array-sort-comparer TKUnit.assert(result[2] === 100 && result.length === 3, "ObservableArray sort() should return new sorted array according to specified order!"); @@ -352,9 +351,9 @@ export const test_ObservableArray_sortShouldReturnNewSortedArrayAccordingSpecifi export const test_ObservableArray_spliceShouldRemoveSpecifiedNumberOfElementsStartingFromSpecifiedIndex = function () { // >> observable-array-splice - const array = new observableArrayModule.ObservableArray(["one", "two", "three"]); + const array = new ObservableArray(["one", "two", "three"]); // >> (hide) - const viewBase = new ViewBase(); + const viewBase = new Label(); viewBase.set("testProperty", 0); viewBase.bind({ sourceProperty: "length", targetProperty: "testProperty" }, array); // << (hide) @@ -366,12 +365,12 @@ export const test_ObservableArray_spliceShouldRemoveSpecifiedNumberOfElementsSta }; export const test_ObservableArray_spliceShouldRemoveSpecifiedNumberOfElementsStartingFromSpecifiedIndexAndRaiseChangeEventWithCorrectArgs = function () { - let result: observableArrayModule.ChangedData; + let result: ChangedData; // >> observable-array-splice-change - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); - array.on(observableArrayModule.ObservableArray.changeEvent, (args: observableArrayModule.ChangedData) => { + array.on(ObservableArray.changeEvent, (args: ChangedData) => { // Argument (args) is ChangedData. // args.eventName is "change". // args.action is "splice". @@ -387,13 +386,13 @@ export const test_ObservableArray_spliceShouldRemoveSpecifiedNumberOfElementsSta array.splice(1, 2); // << observable-array-splice-change - TKUnit.assert(result.eventName === observableArrayModule.ObservableArray.changeEvent && result.action === observableArrayModule.ChangeType.Splice && + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Splice && result.removed.length === 2 && result.index === 1 && result.addedCount === 0, "ObservableArray splice() should raise 'change' event with correct args!"); }; export const test_ObservableArray_spliceShouldInsertNewItemsInPlaceOfRemovedItemsStartingFromSpecifiedIndex = function () { // >> observable-array-splice-args - const array = new observableArrayModule.ObservableArray(["one", "two", "three"]); + const array = new ObservableArray(["one", "two", "three"]); const result = array.splice(1, 2, "six", "seven"); // << observable-array-splice-args TKUnit.assert(result.length === 2 && result[0] === "two" && array.length === 3 && array.getItem(2) === "seven", @@ -401,12 +400,12 @@ export const test_ObservableArray_spliceShouldInsertNewItemsInPlaceOfRemovedItem }; export const test_ObservableArray_spliceShouldRemoveAndInertSpecifiedNumberOfElementsStartingFromSpecifiedIndexAndRaiseChangeEventWithCorrectArgs = function () { - let result: observableArrayModule.ChangedData; + let result: ChangedData; // >> observable-array-splice-args-change - const array = new observableArrayModule.ObservableArray(["one", "two", "three"]); + const array = new ObservableArray(["one", "two", "three"]); - array.on(observableArrayModule.ObservableArray.changeEvent, (args: observableArrayModule.ChangedData) => { + array.on(ObservableArray.changeEvent, (args: ChangedData) => { // Argument (args) is ChangedData. // args.eventName is "change". // args.action is "splice". @@ -422,15 +421,15 @@ export const test_ObservableArray_spliceShouldRemoveAndInertSpecifiedNumberOfEle array.splice(1, 2, "six", "seven", "eight"); // << observable-array-splice-args-change - TKUnit.assert(result.eventName === observableArrayModule.ObservableArray.changeEvent && result.action === observableArrayModule.ChangeType.Splice && + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Splice && result.removed.length === 2 && result.index === 1 && result.addedCount === 1, "ObservableArray splice() should raise 'change' event with correct args!"); }; export const test_ObservableArray_unshiftShouldInsertNewElementsFromTheStart = function () { // >> observable-array-unshift - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); // >> (hide) - const viewBase = new ViewBase(); + const viewBase = new Label(); viewBase.set("testProperty", 0); viewBase.bind({ sourceProperty: "length", targetProperty: "testProperty" }, array); // << (hide) @@ -442,11 +441,11 @@ export const test_ObservableArray_unshiftShouldInsertNewElementsFromTheStart = f }; export const test_ObservableArray_unshiftShouldInsertNewElementsFromTheStartAndRaiseChangeEventWithCorrectArgs = function () { - let result: observableArrayModule.ChangedData; + let result: ChangedData; // >> observable-array-unshift-change - const array = new observableArrayModule.ObservableArray([1, 2, 3]); - array.on(observableArrayModule.ObservableArray.changeEvent, (args: observableArrayModule.ChangedData) => { + const array = new ObservableArray([1, 2, 3]); + array.on(ObservableArray.changeEvent, (args: ChangedData) => { //// Argument (args) is ChangedData. //// args.eventName is "change". //// args.action is "add". @@ -462,13 +461,13 @@ export const test_ObservableArray_unshiftShouldInsertNewElementsFromTheStartAndR array.unshift(4, 5); // << observable-array-unshift-change - TKUnit.assert(result.eventName === observableArrayModule.ObservableArray.changeEvent && result.action === observableArrayModule.ChangeType.Add && + TKUnit.assert(result.eventName === ObservableArray.changeEvent && result.action === ChangeType.Add && result.removed.length === 0 && result.index === 0 && result.addedCount === 2, "ObservableArray unshift() should raise 'change' event with correct args!"); }; export const test_ObservableArray_indexOfShouldReturnCorrectIndex = function () { // >> observable-array-indexof - const array = new observableArrayModule.ObservableArray(["one", "two", "three"]); + const array = new ObservableArray(["one", "two", "three"]); const result = array.indexOf("two"); // << observable-array-indexof TKUnit.assert(result === 1, "ObservableArray indexOf() should return correct index!"); @@ -476,14 +475,14 @@ export const test_ObservableArray_indexOfShouldReturnCorrectIndex = function () export const test_ObservableArray_indexOfShouldReturnCorrectIndexStartingFrom = function () { // >> observable-array-indexof-args - const array = new observableArrayModule.ObservableArray(["one", "two", "three"]); + const array = new ObservableArray(["one", "two", "three"]); const result = array.indexOf("two", 2); // << observable-array-indexof-args TKUnit.assert(result === -1, "ObservableArray indexOf() should return correct index!"); }; export const test_ObservableArray_lastIndexOfShouldReturnCorrectIndex = function () { - const array = new observableArrayModule.ObservableArray(["one", "two", "two", "three"]); + const array = new ObservableArray(["one", "two", "two", "three"]); // >> observable-array-lastindexof const result = array.lastIndexOf("two"); // << observable-array-lastindexof @@ -492,21 +491,21 @@ export const test_ObservableArray_lastIndexOfShouldReturnCorrectIndex = function export const test_ObservableArray_lastIndexOfShouldReturnCorrectIndexStartingFrom = function () { // >> observable-array-lastindexof-args - const array = new observableArrayModule.ObservableArray(["one", "two", "two", "one", "three"]); + const array = new ObservableArray(["one", "two", "two", "one", "three"]); const result = array.lastIndexOf("two", 1); // << observable-array-lastindexof-args TKUnit.assert(result === 1, "ObservableArray lastIndexOf() should return correct index!"); }; export const test_ObservableArray_settingLengthToZeroPerformsSplice = function () { - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); let changeRaised = false; - array.on("change", (args: observableArrayModule.ChangedData) => { + array.on("change", (args: ChangedData) => { changeRaised = true; TKUnit.assertEqual(args.object, array); TKUnit.assertEqual(args.eventName, "change"); - TKUnit.assertEqual(args.action, observableArrayModule.ChangeType.Splice); + TKUnit.assertEqual(args.action, ChangeType.Splice); TKUnit.assertEqual(args.index, 0); TKUnit.assertEqual(args.addedCount, 0); TKUnit.arrayAssert(args.removed, [1, 2, 3]); @@ -519,14 +518,14 @@ export const test_ObservableArray_settingLengthToZeroPerformsSplice = function ( }; export const test_ObservableArray_settingLengthToSomethingPerformsSplice = function () { - const array = new observableArrayModule.ObservableArray([1, 2, 3]); + const array = new ObservableArray([1, 2, 3]); let changeRaised = false; - array.on("change", (args: observableArrayModule.ChangedData) => { + array.on("change", (args: ChangedData) => { changeRaised = true; TKUnit.assertEqual(args.object, array); TKUnit.assertEqual(args.eventName, "change"); - TKUnit.assertEqual(args.action, observableArrayModule.ChangeType.Splice); + TKUnit.assertEqual(args.action, ChangeType.Splice); TKUnit.assertEqual(args.index, 1); TKUnit.assertEqual(args.addedCount, 0); TKUnit.arrayAssert(args.removed, [2, 3]); @@ -538,7 +537,7 @@ export const test_ObservableArray_settingLengthToSomethingPerformsSplice = funct TKUnit.assertTrue(changeRaised); }; -const array = new observableArrayModule.ObservableArray(); +const array = new ObservableArray(); // We do not have indexer! export const test_getItem_isDefined = function () { @@ -636,4 +635,4 @@ export const test_reduce_isDefined = function () { export const test_reduceRight_isDefined = function () { TKUnit.assert(typeof (array.reduceRight) === "function", "Method 'reduceRight()' should be defined!"); -}; +}; \ No newline at end of file diff --git a/tests/app/data/observable-tests.ts b/tests/app/data/observable-tests.ts index 554cdf8ae..59632047b 100644 --- a/tests/app/data/observable-tests.ts +++ b/tests/app/data/observable-tests.ts @@ -2,10 +2,8 @@ import { Observable, fromObject, fromObjectRecursive, PropertyChangeData, EventData, WrappedValue } from "data/observable"; // << observable-require -import * as dependencyObservable from "ui/core/dependency-observable"; import * as TKUnit from "../TKUnit"; import * as types from "utils/types"; -import * as proxy from "ui/core/proxy"; import { ObservableArray } from "data/observable-array"; var TESTED_NAME = "tested"; @@ -87,45 +85,45 @@ export var test_Observable_UpdateAnotherPropertyWithinChangedCallback = function TKUnit.assert(obj.get("test") === "Changed test", "Changed value for property test is not correct!"); } -export var test_DependencyObservable_UpdateAnotherPropertyWithinChangedCallback = function () { - var obj = new dependencyObservable.DependencyObservable(); +// export var test_DependencyObservable_UpdateAnotherPropertyWithinChangedCallback = function () { +// var obj = new dependencyObservable.DependencyObservable(); - function onFirstPropertyChanged(data: dependencyObservable.PropertyChangeData) { - var testObj = data.object; - testObj._setValue(secondProperty, "Changed test"); - }; +// function onFirstPropertyChanged(data: dependencyObservable.PropertyChangeData) { +// var testObj = data.object; +// testObj._setValue(secondProperty, "Changed test"); +// }; - var firstProperty = new dependencyObservable.Property( - "first", - "obj", - new proxy.PropertyMetadata( - "", - dependencyObservable.PropertyMetadataSettings.None, - onFirstPropertyChanged - ) - ); +// var firstProperty = new dependencyObservable.Property( +// "first", +// "obj", +// new proxy.PropertyMetadata( +// "", +// dependencyObservable.PropertyMetadataSettings.None, +// onFirstPropertyChanged +// ) +// ); - var secondProperty = new dependencyObservable.Property( - "second", - "obj", - new proxy.PropertyMetadata( - "", - dependencyObservable.PropertyMetadataSettings.None, - null - ) - ); +// var secondProperty = new dependencyObservable.Property( +// "second", +// "obj", +// new proxy.PropertyMetadata( +// "", +// dependencyObservable.PropertyMetadataSettings.None, +// null +// ) +// ); - obj._setValue(firstProperty, "Initial name"); - obj._setValue(secondProperty, "Initial test"); +// obj._setValue(firstProperty, "Initial name"); +// obj._setValue(secondProperty, "Initial test"); - TKUnit.assert(obj._getValue(firstProperty) === "Initial name", "Initial value for property name is not correct!"); - TKUnit.assert(obj._getValue(secondProperty) === "Initial test", "Initial value for property test is not correct!"); +// TKUnit.assert(obj._getValue(firstProperty) === "Initial name", "Initial value for property name is not correct!"); +// TKUnit.assert(obj._getValue(secondProperty) === "Initial test", "Initial value for property test is not correct!"); - obj._setValue(firstProperty, "Changed name"); +// obj._setValue(firstProperty, "Changed name"); - TKUnit.assert(obj._getValue(firstProperty) === "Changed name", "Changed value for property name is not correct!"); - TKUnit.assert(obj._getValue(secondProperty) === "Changed test", "Changed value for property test is not correct!"); -} +// TKUnit.assert(obj._getValue(firstProperty) === "Changed name", "Changed value for property name is not correct!"); +// TKUnit.assert(obj._getValue(secondProperty) === "Changed test", "Changed value for property test is not correct!"); +// } export var test_Observable_addEventListener_SingleEvent = function () { var obj = new Observable(); diff --git a/tests/app/testRunner.ts b/tests/app/testRunner.ts index 43382c99c..bffc8d561 100644 --- a/tests/app/testRunner.ts +++ b/tests/app/testRunner.ts @@ -43,7 +43,7 @@ allTests["OBSERVABLE"] = require("./data/observable-tests"); allTests["TIMER"] = require("./timer-tests"); allTests["COLOR"] = require("./color-tests"); -allTests["DEPENDENCY-OBSERVABLE"] = require("./ui/dependency-observable-tests"); +// allTests["DEPENDENCY-OBSERVABLE"] = require("./ui/dependency-observable-tests"); allTests["BINDABLE"] = require("./ui/bindable-tests"); allTests["BINDING-EXPRESSIONS"] = require("./ui/binding-expressions-tests"); allTests["XML-PARSER"] = require("./xml-parser-tests/xml-parser-tests"); @@ -196,6 +196,7 @@ function printRunTestStats() { btn.on("tap", () => runAll(testsSelector)); stack.addChild(btn); let messageContainer = new TextView(); + messageContainer.editable = messageContainer.autocorrect = false; messageContainer.text = finalMessage; stack.addChild(messageContainer); topmost().currentPage.content = stack; diff --git a/tests/app/text/formatted-string-tests.ts b/tests/app/text/formatted-string-tests.ts index f7d672bd4..c140d4418 100644 --- a/tests/app/text/formatted-string-tests.ts +++ b/tests/app/text/formatted-string-tests.ts @@ -53,9 +53,7 @@ export function test_FormattedTextProperty_IsChanged_When_SpanIsChanged() { formattedTextChanged = true; }); - firstSpan.beginEdit(); firstSpan.fontSize = expectedValue; - firstSpan.endEdit(); TKUnit.assertTrue(formattedTextChanged, "FormattedText property is not changed."); TKUnit.assert(formattedString.spans.getItem(0).fontSize === expectedValue, "FormattedString internal span is not changed as expected"); @@ -67,7 +65,7 @@ export function test_FormattedTextProperty_DoNotCrash_When_KnownColorIsSetForFor const expectedValue2 = "blue"; const firstSpan = new Span(); - firstSpan.foregroundColor = expectedValue1; + firstSpan.color = expectedValue1; firstSpan.text = "LoremIpsum1"; formattedString.spans.push(firstSpan); @@ -76,6 +74,6 @@ export function test_FormattedTextProperty_DoNotCrash_When_KnownColorIsSetForFor secondSpan.text = "LoremIpsum2"; formattedString.spans.push(secondSpan); - TKUnit.assertEqual(formattedString.spans.getItem(0).foregroundColor.name, expectedValue1); + TKUnit.assertEqual(formattedString.spans.getItem(0).color.name, expectedValue1); TKUnit.assertEqual(formattedString.spans.getItem(1).backgroundColor.name, expectedValue2); }; \ No newline at end of file diff --git a/tests/app/ui/helper.ts b/tests/app/ui/helper.ts index 36a1258e6..55bcca323 100644 --- a/tests/app/ui/helper.ts +++ b/tests/app/ui/helper.ts @@ -9,7 +9,6 @@ import * as platform from "platform"; import * as colorModule from "color"; import * as formattedStringModule from "text/formatted-string"; import * as spanModule from "text/span"; -import * as enums from "ui/enums"; import { ActionBar } from "ui/action-bar"; import { unsetValue } from "ui/core/view"; @@ -212,22 +211,20 @@ export function _generateFormattedString(): formattedStringModule.FormattedStrin span = new spanModule.Span(); span.fontFamily = "serif"; span.fontSize = 10; - span.fontAttributes = enums.FontAttributes.Bold; - span.foregroundColor = new colorModule.Color("red"); + span.fontWeight = "bold"; + span.color = new colorModule.Color("red"); span.backgroundColor = new colorModule.Color("blue"); - span.underline = 0; - span.strikethrough = 1; + span.textDecoration = "line-through"; span.text = "Formatted"; formattedString.spans.push(span); span = new spanModule.Span(); span.fontFamily = "sans-serif"; span.fontSize = 20; - span.fontAttributes = enums.FontAttributes.Italic; - span.foregroundColor = new colorModule.Color("green"); + span.fontStyle = "italic"; + span.color = new colorModule.Color("green"); span.backgroundColor = new colorModule.Color("yellow"); - span.underline = 1; - span.strikethrough = 0; + span.textDecoration = "underline"; span.text = "Text"; formattedString.spans.push(span); diff --git a/tns-core-modules/data/observable/observable.ts b/tns-core-modules/data/observable/observable.ts index eb4afffc2..9ff1d92e4 100644 --- a/tns-core-modules/data/observable/observable.ts +++ b/tns-core-modules/data/observable/observable.ts @@ -110,7 +110,7 @@ export class Observable implements ObservableDefinition { } public notify(data: T) { - const observers = this._getEventList(data.eventName); + const observers = >this._observers[data.eventName]; if (!observers) { return; } diff --git a/tns-core-modules/text/formatted-string.d.ts b/tns-core-modules/text/formatted-string.d.ts index 8f5323aa7..7ab249b78 100644 --- a/tns-core-modules/text/formatted-string.d.ts +++ b/tns-core-modules/text/formatted-string.d.ts @@ -3,100 +3,62 @@ */ declare module "text/formatted-string" { import { Span } from "text/span"; - import { Observable } from "data/observable"; import { ObservableArray } from "data/observable-array"; - import { View, AddArrayFromBuilder, AddChildFromBuilder } from "ui/core/view"; + import { ViewBase } from "ui/core/view"; import { Color } from "color"; + import { FontStyle, FontWeight } from "ui/styling/font"; + import { TextDecoration } from "ui/text-base"; - /** - * Interface that specifies View that have formattedText property (like TextBase and Button). - */ - export interface FormattedStringView { - formattedText: FormattedString; - } - + export { Span }; + /** * A class used to create a formatted (rich text) string. */ - class FormattedString extends Observable implements AddArrayFromBuilder, AddChildFromBuilder { + export class FormattedString extends ViewBase { + /** * An observable collection of Span objects used to define common text properties. */ public spans: ObservableArray; - /** - * Initializes a new instance of FormattedString class. - */ - constructor(); - /** * A human readable representation of the formatted string. */ public toString(): string; /** - * Gets or sets the font family which will be used for all spans that not have a specific value for font family. + * Gets or sets the font family which will be used for all spans that doesn't have a specific value. */ public fontFamily: string; /** - * Gets or sets the font size which will be used for all spans that not have a specific value for font size. + * Gets or sets the font size which will be used for all spans that doesn't have a specific value. */ public fontSize: number; /** - * Gets or sets the font attributes which will be used for all spans that not have a specific value for font attributes. + * Gets or sets the font style which will be used for all spans that doesn't have a specific value. */ - public fontAttributes: number; + public fontStyle: FontStyle; /** - * Gets or sets the font foreground color which will be used for all spans that not have a specific value for font foreground color. + * Gets or sets the font weight which will be used for all spans that doesn't have a specific value. */ - public foregroundColor: Color; + public fontWeight: FontWeight; /** - * Gets or sets the font background color which will be used for all spans that not have a specific value for font background color. + * Gets or sets text decorations which will be used for all spans that doesn't have a specific value. + */ + public textDecoration: TextDecoration; + + /** + * Gets or sets the font foreground color which will be used for all spans that doesn't have a specific value. + */ + public color: Color; + + /** + * Gets or sets the font background color which will be used for all spans that doesn't have a specific value. */ public backgroundColor: Color; - - /** - * Gets or sets underline which will be used for all spans that not have a specific value for underline. - */ - public underline: number; - - /** - * Gets or sets strikethrough which will be used for all spans that not have a specific value for strikethrough. - */ - public strikethrough: number; - - /** - * Propogates binding context through the spans collection. - * @param newBindingContext The value of the newly set binding context. - */ - public updateSpansBindingContext(newBindingContext: any): void - - /** - * Gets the parent view of the formatted string. - */ - public parent: View; - - /** - * A function that is called when an array declaration is found in xml. - * @param name - Name of the array. - * @param value - The actual value of the array. - */ - public _addArrayFromBuilder(name: string, value: Array): void; - - /** - * Called for every child element declared in xml. - * @param name - Name of the element. - * @param value - Value of the element. - */ - public _addChildFromBuilder(name: string, value: any): void; - - /** - * A static method used to add child elements of the FormattedString class to a View declared in xml. - */ - public static addFormattedStringToView(view: FormattedStringView, name: string, value: any): void; } } \ No newline at end of file diff --git a/tns-core-modules/text/formatted-string.ts b/tns-core-modules/text/formatted-string.ts index 66218cfb4..0a3ada0e2 100644 --- a/tns-core-modules/text/formatted-string.ts +++ b/tns-core-modules/text/formatted-string.ts @@ -1,145 +1,78 @@ -import { FormattedString as FormattedStringDefinition, FormattedStringView } from "text/formatted-string"; +import { FormattedString as FormattedStringDefinition } from "text/formatted-string"; import { Span } from "text/span"; import { Observable, PropertyChangeData } from "data/observable"; import { ObservableArray, ChangedData } from "data/observable-array"; -import { View, AddArrayFromBuilder, AddChildFromBuilder } from "ui/core/view"; -import { isString } from "utils/types"; +import { ViewBase, AddArrayFromBuilder, AddChildFromBuilder } from "ui/core/view"; import { Color } from "color"; +import { FontStyle, FontWeight } from "ui/styling/font"; +import { TextDecoration } from "ui/text-base"; +export { Span }; + export module knownCollections { export const spans = "spans"; } const CHILD_SPAN = "Span"; -const CHILD_FORMATTED_TEXT = "formattedText"; -const CHILD_FORMATTED_STRING = "FormattedString"; -export class FormattedString extends Observable implements FormattedStringDefinition, AddArrayFromBuilder, AddChildFromBuilder { +export class FormattedString extends ViewBase implements FormattedStringDefinition, AddArrayFromBuilder, AddChildFromBuilder { private _spans: ObservableArray; - private _fontFamily: string; - private _fontSize: number; - private _foregroundColor: Color; - private _backgroundColor: Color; - private _underline: number; - private _strikethrough: number; - private _fontAttributes: number; - private _parent: View; - private _dummyPropertyChangedData: PropertyChangeData; - - public _formattedText: any; constructor() { super(); this._spans = new ObservableArray(); this._spans.addEventListener(ObservableArray.changeEvent, this.onSpansCollectionChanged, this); - this._dummyPropertyChangedData = this._createPropertyChangeData("", this); - } - - get parent(): View { - return this._parent; - } - set parent(value: View) { - if (this._parent !== value) { - this._parent = value; - } } get fontFamily(): string { - return this._fontFamily; + return this.style.fontFamily; } set fontFamily(value: string) { - if (this._fontFamily !== value) { - this._fontFamily = value; - } + this.style.fontFamily = value; } get fontSize(): number { - return this._fontSize; + return this.style.fontSize; } set fontSize(value: number) { - let fSize: number; - if (isString(value)) { - fSize = parseInt(value); - } - else { - fSize = value; - } - if (this._fontSize !== fSize) { - this._fontSize = fSize; - } + this.style.fontSize = value; } - get foregroundColor(): Color { - return this._foregroundColor; + // Italic + get fontStyle(): FontStyle { + return this.style.fontStyle; } - set foregroundColor(value: Color) { - let foreColor; - if (isString(value)) { - foreColor = new Color(value); - } - else { - foreColor = value; - } - if (this._foregroundColor !== foreColor) { - this._foregroundColor = foreColor; - } + set fontStyle(value: FontStyle) { + this.style.fontStyle = value; + } + + // Bold + get fontWeight(): FontWeight { + return this.style.fontWeight; + } + set fontWeight(value: FontWeight) { + this.style.fontWeight = value; + } + + get textDecoration(): TextDecoration { + return this.style.textDecoration; + } + set textDecoration(value: TextDecoration) { + this.style.textDecoration = value; + } + + get color(): Color { + return this.style.color; + } + set color(value: Color) { + this.style.color = value; } get backgroundColor(): Color { - return this._backgroundColor; + return this.style.backgroundColor; } set backgroundColor(value: Color) { - let backColor; - if (isString(value)) { - backColor = new Color(value); - } - else { - backColor = value; - } - if (this._backgroundColor !== backColor) { - this._backgroundColor = backColor; - } - } - - get underline(): number { - return this._underline; - } - set underline(value: number) { - let underlineIntValue: number; - if (isString(value)) { - underlineIntValue = parseInt(value); - } - else { - underlineIntValue = value; - } - if (this._underline !== underlineIntValue) { - this._underline = underlineIntValue; - } - } - - get strikethrough(): number { - return this._strikethrough; - } - set strikethrough(value: number) { - let strikethroughIntValue: number; - if (isString(value)) { - strikethroughIntValue = parseInt(value); - } - else { - strikethroughIntValue = value; - } - if (this._strikethrough !== strikethroughIntValue) { - this._strikethrough = strikethroughIntValue; - } - } - - get fontAttributes(): number { - return this._fontAttributes; - } - set fontAttributes(value: number) { - if (this._fontAttributes !== value) { - this._fontAttributes = value; - } + this.style.backgroundColor = value; } get spans(): ObservableArray { @@ -158,13 +91,8 @@ export class FormattedString extends Observable implements FormattedStringDefini } public _addArrayFromBuilder(name: string, value: Array) { - let i; - let span; if (name === knownCollections.spans) { - for (i = 0; i < value.length; i++) { - span = value[i]; - this.spans.push(span); - } + this.spans.push(value); } } @@ -174,44 +102,61 @@ export class FormattedString extends Observable implements FormattedStringDefini } } - public updateSpansBindingContext(newBindingContext) { - for (let i = 0, length = this.spans.length; i < length; i++) { - let span = this.spans.getItem(i); - span.bindingContext = newBindingContext; - } - } - - public static addFormattedStringToView(view: FormattedStringView, name: string, value: any): void { - if (name === CHILD_SPAN) { - // NOTE: getter should either initialize the value or do it in the constructor. - // if (!view.formattedText) { - // view.formattedText = new FormattedString(); - // } - view.formattedText.spans.push(value); - } - else if (name === CHILD_FORMATTED_TEXT || name === CHILD_FORMATTED_STRING) { - view.formattedText = value; - } - } - private onSpansCollectionChanged(eventData: ChangedData) { if (eventData.addedCount > 0) { for (let i = 0; i < eventData.addedCount; i++) { - let addedSpan: Span = (>eventData.object).getItem(eventData.index + i); - addedSpan.parentFormattedString = this; - addedSpan.addEventListener(Observable.propertyChangeEvent, this.onSpanChanged, this); + const span = (>eventData.object).getItem(eventData.index + i); + + // First add to logical tree so that inherited properties are set. + this._addView(span); + + // Then attach handlers - we skip the first nofitication because + // we raise change for the whole instance. + this.addPropertyChangeHandler(span); } } + if (eventData.removed && eventData.removed.length > 0) { for (let p = 0; p < eventData.removed.length; p++) { - let removedSpan = eventData.removed[p]; - removedSpan.removeEventListener(Observable.propertyChangeEvent, this.onSpanChanged, this); + const span = eventData.removed[p]; + + // First remove handlers so that we don't listen for changes + // on inherited properties. + this.removePropertyChangeHandler(span); + + // Then remove the element. + this._removeView(span); } } - this.notify(this._dummyPropertyChangedData); + + this.notifyPropertyChange('.', this); } - private onSpanChanged(eventData: PropertyChangeData) { - this.notify(this._dummyPropertyChangedData); + private addPropertyChangeHandler(span: Span) { + const style = span.style; + span.on(Observable.propertyChangeEvent, this.onPropertyChange, this); + style.on("fontFamilyChange", this.onPropertyChange, this); + style.on("fontSizeChange", this.onPropertyChange, this); + style.on("fontStyleChange", this.onPropertyChange, this); + style.on("fontWeightChange", this.onPropertyChange, this); + style.on("textDecorationChange", this.onPropertyChange, this); + style.on("colorChange", this.onPropertyChange, this); + style.on("backgroundColorChange", this.onPropertyChange, this); + } + + private removePropertyChangeHandler(span: Span) { + const style = span.style; + span.off(Observable.propertyChangeEvent, this.onPropertyChange, this); + style.off("fontFamilyChange", this.onPropertyChange, this); + style.off("fontSizeChange", this.onPropertyChange, this); + style.off("fontStyleChange", this.onPropertyChange, this); + style.off("fontWeightChange", this.onPropertyChange, this); + style.off("textDecorationChange", this.onPropertyChange, this); + style.off("colorChange", this.onPropertyChange, this); + style.off("backgroundColorChange", this.onPropertyChange, this); + } + + private onPropertyChange(data: PropertyChangeData) { + this.notifyPropertyChange(data.propertyName, this); } } \ No newline at end of file diff --git a/tns-core-modules/text/span-common.ts b/tns-core-modules/text/span-common.ts deleted file mode 100644 index 8b97bb248..000000000 --- a/tns-core-modules/text/span-common.ts +++ /dev/null @@ -1,220 +0,0 @@ -import * as colorModule from "color"; -import * as definition from "text/span"; -import * as bindable from "ui/core/bindable"; -import * as types from "utils/types"; -import * as view from "ui/core/view"; -import * as enums from "ui/enums"; -import * as formattedString from "text/formatted-string"; - -export class Span extends bindable.Bindable implements definition.Span, view.ApplyXmlAttributes { - private _fontFamily: string; - private _fontSize: number; - private _foregroundColor: colorModule.Color; - private _backgroundColor: colorModule.Color; - private _text: string; - private _underline: number; - private _strikethrough: number; - private _fontAttributes: number; - private _spanModifiers: Array; - private _parentFormattedString: formattedString.FormattedString; - private _isInEditMode: boolean; - - get fontFamily(): string { - return this._fontFamily; - } - - set fontFamily(value: string) { - if (this._fontFamily !== value) { - this._fontFamily = value; - this.updateAndNotify(); - } - } - - get fontSize(): number { - return this._fontSize; - } - set fontSize(value: number) { - var fSize: number; - if (types.isString(value)) { - fSize = parseInt(value); - } - else { - fSize = value; - } - if (this._fontSize !== fSize) { - this._fontSize = fSize; - this.updateAndNotify(); - } - } - - private _getColorValue(value: any): colorModule.Color { - var result; - if (types.isString(value) && colorModule.Color.isValid(value)) { - result = new colorModule.Color(value); - } - else { - result = value; - } - return result; - } - - get foregroundColor(): colorModule.Color { - return this._foregroundColor; - } - set foregroundColor(value: colorModule.Color) { - var convertedColor = this._getColorValue(value); - if (this._foregroundColor !== convertedColor) { - this._foregroundColor = convertedColor; - this.updateAndNotify(); - } - } - - get backgroundColor(): colorModule.Color { - return this._backgroundColor; - } - set backgroundColor(value: colorModule.Color) { - var convertedColor = this._getColorValue(value); - if (this._backgroundColor !== convertedColor) { - this._backgroundColor = convertedColor; - this.updateAndNotify(); - } - } - - get underline(): number { - return this._underline; - } - - set underline(value: number) { - var underlineIntValue: number; - if (types.isString(value)) { - underlineIntValue = parseInt(value); - } - else { - underlineIntValue = value; - } - if (this._underline !== underlineIntValue) { - this._underline = underlineIntValue; - this.updateAndNotify(); - } - } - - get strikethrough(): number { - return this._strikethrough; - } - - set strikethrough(value: number) { - var strikethroughIntValue: number; - if (types.isString(value)) { - strikethroughIntValue = parseInt(value); - } - else { - strikethroughIntValue = value; - } - if (this._strikethrough !== strikethroughIntValue) { - this._strikethrough = strikethroughIntValue; - this.updateAndNotify(); - } - } - - get fontAttributes(): number { - return this._fontAttributes; - } - - set fontAttributes(value: number) { - if (this._fontAttributes !== value) { - this._fontAttributes = value; - this.updateAndNotify(); - } - } - - get spanModifiers(): Array { - if (!this._spanModifiers) { - this._spanModifiers = new Array(); - } - return this._spanModifiers; - } - - get text(): string { - return this._text; - } - - set text(value: string) { - if (this._text !== value) { - this._setTextInternal(value); - this.updateAndNotify(); - } - } - - _setTextInternal(value: string): void { - this._text = value; - } - - get parentFormattedString(): formattedString.FormattedString { - return this._parentFormattedString; - } - - set parentFormattedString(value: formattedString.FormattedString) { - if (this._parentFormattedString !== value) { - this._parentFormattedString = value; - this.updateAndNotify(); - } - } - - public updateSpanModifiers(parent: formattedString.FormattedString) { - // a virtual method overridden in platform specific implementations. - if (this._isInEditMode) { - throw new Error("Cannot update span modifiers during update!"); - } - this._spanModifiers = new Array(); - } - - public beginEdit(): void { - this._isInEditMode = true; - } - - private updateAndNotify() { - if (!this._isInEditMode) { - this.updateSpanModifiers(this.parentFormattedString); - this.notify(this._createPropertyChangeData(".", this)); - } - } - - public endEdit(): void { - this._isInEditMode = false; - this.updateAndNotify(); - } - - public _applyXmlAttribute(attribute, value): boolean { - if (attribute === "fontAttributes") { - if (value.indexOf(",")) { - var fontAttr = value.split(","); - var fontAttrValue; - var j; - for (j = 0; j < fontAttr.length; j++) { - fontAttrValue = Span.getFontAttributeFromString(fontAttr[j]); - this.fontAttributes |= fontAttrValue; - } - } - else { - this.fontAttributes |= value; - } - return true; - } - else { - return false; - } - } - - private static getFontAttributeFromString(fontAttr: string) { - var fontAttrTrimmedAndLowerCase = fontAttr.trim().toLowerCase(); - if (fontAttrTrimmedAndLowerCase === "bold") { - return enums.FontAttributes.Bold; - } - else if (fontAttrTrimmedAndLowerCase === "italic") { - return enums.FontAttributes.Italic; - } - else { - return enums.FontAttributes.Normal; - } - } -} \ No newline at end of file diff --git a/tns-core-modules/text/span.android.ts b/tns-core-modules/text/span.android.ts deleted file mode 100644 index d8888795d..000000000 --- a/tns-core-modules/text/span.android.ts +++ /dev/null @@ -1,112 +0,0 @@ -import * as spanCommon from "./span-common"; -import * as enums from "ui/enums"; -import * as formattedString from "text/formatted-string"; -import * as utils from "utils/utils"; -import * as fontModule from "ui/styling/font"; - -global.moduleMerge(spanCommon, exports); - -var CustomTypefaceSpanClass; -function ensureCustomTypefaceSpanClass() { - if (CustomTypefaceSpanClass) { - return; - } - - class CustomTypefaceSpan extends android.text.style.TypefaceSpan { - private typeface: any; - - constructor(family: string, typeface: any) { - super(family); - this.typeface = typeface; - return global.__native(this); - } - - public updateDrawState(ds: any): void { - CustomTypefaceSpan.applyCustomTypeFace(ds, this.typeface); - } - - public updateMeasureState(paint: any): void { - CustomTypefaceSpan.applyCustomTypeFace(paint, this.typeface); - } - - private static applyCustomTypeFace(paint: any, tf: any) { - let oldStyle; - let old = paint.getTypeface(); - if (old === null) { - oldStyle = 0; - } else { - oldStyle = old.getStyle(); - } - - let fake = oldStyle & ~tf.getStyle(); - if ((fake & android.graphics.Typeface.BOLD) !== 0) { - paint.setFakeBoldText(true); - } - - if ((fake & android.graphics.Typeface.ITALIC) !== 0) { - paint.setTextSkewX(-0.25); - } - - paint.setTypeface(tf); - } - } - - CustomTypefaceSpanClass = CustomTypefaceSpan; -} - -export class Span extends spanCommon.Span { - public updateSpanModifiers(parent: formattedString.FormattedString) { - super.updateSpanModifiers(parent); - var realFontFamily = this.fontFamily || (parent ? parent.fontFamily : undefined); - if (realFontFamily) { - let font = new fontModule.Font(realFontFamily, - 0, - (realFontAttributes & enums.FontAttributes.Italic) ? enums.FontStyle.italic : enums.FontStyle.normal, - (realFontAttributes & enums.FontAttributes.Bold) ? enums.FontWeight.bold : enums.FontWeight.normal); - ensureCustomTypefaceSpanClass(); - let typefaceSpan: android.text.style.TypefaceSpan = new CustomTypefaceSpanClass(realFontFamily, font.getAndroidTypeface()); - this.spanModifiers.push(typefaceSpan); - } - var realFontSize = this.fontSize || - (parent ? parent.fontSize : undefined) || - (parent && parent.parent ? parent.parent.style.fontSize : undefined); - if (realFontSize) { - this.spanModifiers.push(new android.text.style.AbsoluteSizeSpan(realFontSize * utils.layout.getDisplayDensity())); - } - - var realForegroundColor = this.foregroundColor || - (parent ? parent.foregroundColor : undefined) || - (parent && parent.parent ? parent.parent.style.color : undefined); - if (realForegroundColor) { - this.spanModifiers.push(new android.text.style.ForegroundColorSpan(realForegroundColor.android)); - } - - var realBackgroundColor = this.backgroundColor || - (parent ? parent.backgroundColor : undefined) || - (parent && parent.parent ? parent.parent.style.backgroundColor : undefined); - if (realBackgroundColor) { - this.spanModifiers.push(new android.text.style.BackgroundColorSpan(realBackgroundColor.android)); - } - - var realFontAttributes = this.fontAttributes || (parent ? parent.fontAttributes : undefined); - if (realFontAttributes) { - if ((realFontAttributes & enums.FontAttributes.Bold) && (realFontAttributes & enums.FontAttributes.Italic)) { - this.spanModifiers.push(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD_ITALIC)); - } - else if (realFontAttributes & enums.FontAttributes.Bold) { - this.spanModifiers.push(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD)); - } - else if (realFontAttributes & enums.FontAttributes.Italic) { - this.spanModifiers.push(new android.text.style.StyleSpan(android.graphics.Typeface.ITALIC)); - } - } - var realUnderline = this.underline || (parent ? parent.underline : undefined); - if (realUnderline) { - this.spanModifiers.push(new android.text.style.UnderlineSpan()); - } - var realStrikethrough = this.strikethrough || (parent ? parent.strikethrough : undefined); - if (realStrikethrough) { - this.spanModifiers.push(new android.text.style.StrikethroughSpan()); - } - } -} diff --git a/tns-core-modules/text/span.d.ts b/tns-core-modules/text/span.d.ts index d1ba45b48..b6ea2c0fa 100644 --- a/tns-core-modules/text/span.d.ts +++ b/tns-core-modules/text/span.d.ts @@ -1,12 +1,13 @@ declare module "text/span" { - import * as colorModule from "color"; - import * as bindable from "ui/core/bindable"; - import * as formattedString from "text/formatted-string"; + import { Color } from "color"; + import { ViewBase } from "ui/core/view-base"; + import { FontStyle, FontWeight } from "ui/styling/font"; + import { TextDecoration } from "ui/text-base"; /** * A class used to create a single part of formatted string with a common text properties. */ - class Span extends bindable.Bindable { + class Span extends ViewBase { /** * Gets or sets the font family of the span. */ @@ -16,66 +17,39 @@ * Gets or sets the font size of the span. */ public fontSize: number; + + /** + * Gets or sets the font style of the span. + */ + public fontStyle: FontStyle; /** - * Gets or sets the font attributes of the span. - * It could be set to more than one value e.g. (Bold | Italic). + * Gets or sets the font weight of the span. */ - public fontAttributes: number; + public fontWeight: FontWeight; + + /** + * Gets or sets text decorations for the span. + */ + public textDecoration: TextDecoration; /** * Gets or sets the font foreground color of the span. */ - public foregroundColor: colorModule.Color; + public color: Color; /** * Gets or sets the font background color of the span. */ - public backgroundColor: colorModule.Color; - - /** - * Gets or sets underline for the span. - */ - public underline: number; - - /** - * Gets or sets strikethrough for the span. - */ - public strikethrough: number; - - /** - * A collection of modifiers build upon all text related properties. - */ - public spanModifiers: Array; + public backgroundColor: Color; /** * Gets or sets the text for the span. */ public text: string; - - /** - * An instance of the parent formatted string (used internally to support some short hand property settings). - */ - public parentFormattedString: formattedString.FormattedString; - - /** - * Updates all span modifiers according to current values of all text related properties. - */ - public updateSpanModifiers(parent: formattedString.FormattedString): void; - - /** - * Initializes a process of updating a span (text related property(s)). - */ - public beginEdit(): void; - - /** - * Ends the process previously initiated by beginEdit and updates the span modifiers collection. - */ - public endEdit(): void; - + //@private _setTextInternal(value: string): void; //@endprivate - } } \ No newline at end of file diff --git a/tns-core-modules/text/span.ios.ts b/tns-core-modules/text/span.ios.ts deleted file mode 100644 index 6de56fff8..000000000 --- a/tns-core-modules/text/span.ios.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as spanCommon from "./span-common"; -import * as enums from "ui/enums"; -import * as formattedString from "text/formatted-string"; - -import * as utils from "utils/utils"; - -global.moduleMerge(spanCommon, exports); - -export class Span extends spanCommon.Span { - public updateSpanModifiers(parent: formattedString.FormattedString) { - super.updateSpanModifiers(parent); - var realFontFamily = this.fontFamily || (parent ? parent.fontFamily : undefined); - var realFontSize = this.fontSize || - (parent ? parent.fontSize : undefined) || - (parent && parent.parent ? parent.parent.style.fontSize : undefined); - - var realFontAttributes = this.fontAttributes || (parent ? parent.fontAttributes : undefined); - if (realFontAttributes || realFontFamily || realFontSize) { - var font; - if (!realFontSize) { - realFontSize = utils.ios.getter(UIFont, UIFont.systemFontSize); - } - if (realFontFamily) { - font = UIFont.fontWithNameSize(realFontFamily, realFontSize); - } - - if (!font) { - var fontDescriptor = UIFontDescriptor.new(); - var symbolicTraits; - if (realFontAttributes & enums.FontAttributes.Bold) { - symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitBold; - } - if (realFontAttributes & enums.FontAttributes.Italic) { - symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitItalic; - } - font = UIFont.fontWithDescriptorSize(fontDescriptor.fontDescriptorWithSymbolicTraits(symbolicTraits), realFontSize); - } - - this.spanModifiers.push({ - key: NSFontAttributeName, - value: font - }); - } - - var realForegroundColor = this.foregroundColor || - (parent ? parent.foregroundColor : undefined) || - (parent && parent.parent ? parent.parent.style.color : undefined); - if (realForegroundColor) { - this.spanModifiers.push({ - key: NSForegroundColorAttributeName, - value: realForegroundColor.ios - }); - } - - var realBackgroundColor = this.backgroundColor || - (parent ? parent.backgroundColor : undefined) || - (parent && parent.parent ? parent.parent.style.backgroundColor : undefined); - if (realBackgroundColor) { - this.spanModifiers.push({ - key: NSBackgroundColorAttributeName, - value: realBackgroundColor.ios - }); - } - - var realUnderline = this.underline || (parent ? parent.underline : undefined); - if (realUnderline) { - this.spanModifiers.push({ - key: NSUnderlineStyleAttributeName, - value: realUnderline - }); - } - var realStrikethrough = this.strikethrough || (parent ? parent.strikethrough : undefined); - if (realStrikethrough) { - this.spanModifiers.push({ - key: NSStrikethroughStyleAttributeName, - value: realStrikethrough - }); - } - } -} diff --git a/tns-core-modules/text/span.ts b/tns-core-modules/text/span.ts new file mode 100644 index 000000000..605336fc2 --- /dev/null +++ b/tns-core-modules/text/span.ts @@ -0,0 +1,74 @@ +import { Color } from "color"; +import { Span as SpanDefinition } from "text/span"; +import { ViewBase } from "ui/core/view"; +import { FontStyle, FontWeight, } from "ui/styling/font"; +import { TextDecoration } from "ui/text-base"; + +export class Span extends ViewBase implements SpanDefinition { + private _text: string; + + get fontFamily(): string { + return this.style.fontFamily; + } + set fontFamily(value: string) { + this.style.fontFamily = value; + } + + get fontSize(): number { + return this.style.fontSize; + } + set fontSize(value: number) { + this.style.fontSize = value; + } + + // Italic + get fontStyle(): FontStyle { + return this.style.fontStyle; + } + set fontStyle(value: FontStyle) { + this.style.fontStyle = value; + } + + // Bold + get fontWeight(): FontWeight { + return this.style.fontWeight; + } + set fontWeight(value: FontWeight) { + this.style.fontWeight = value; + } + + get textDecoration(): TextDecoration { + return this.style.textDecoration; + } + set textDecoration(value: TextDecoration) { + this.style.textDecoration = value; + } + + get color(): Color { + return this.style.color; + } + set color(value: Color) { + this.style.color = value; + } + + get backgroundColor(): Color { + return this.style.backgroundColor; + } + set backgroundColor(value: Color) { + this.style.backgroundColor = value; + } + + get text(): string { + return this._text; + } + set text(value: string) { + if (this._text !== value) { + this._text = value; + this.notifyPropertyChange("text", value); + } + } + + _setTextInternal(value: string): void { + this._text = value; + } +} \ No newline at end of file diff --git a/tns-core-modules/tns-core-modules.base.d.ts b/tns-core-modules/tns-core-modules.base.d.ts index 3ad3f62f8..ca4676ee5 100644 --- a/tns-core-modules/tns-core-modules.base.d.ts +++ b/tns-core-modules/tns-core-modules.base.d.ts @@ -45,7 +45,6 @@ /// /// /// -/// /// /// /// diff --git a/tns-core-modules/ui/core/bindable.d.ts b/tns-core-modules/ui/core/bindable.d.ts index a57ff9d75..97937a69b 100644 --- a/tns-core-modules/ui/core/bindable.d.ts +++ b/tns-core-modules/ui/core/bindable.d.ts @@ -1,5 +1,4 @@ declare module "ui/core/bindable" { - import { DependencyObservable } from "ui/core/dependency-observable"; import { ViewBase } from "ui/core/view-base"; /** @@ -41,37 +40,6 @@ toView: (...params: any[]) => any; } - /** - * Represents an extended DependencyObservable object that supports data-binding. - */ - export class Bindable extends DependencyObservable { - /** - * Represents the dependency Property used to back the bindingContext value. - */ - // public static bindingContextProperty: dependencyObservable.Property; - - /** - * Gets or sets the binding context of this instance. This object is used as a source for each Binding that does not have a source object specified. - */ - bindingContext: any; - /** - * Establishes a binding between the source object and this Bindable instance. - * @param options The options for the binding. - * @param source An optional parameter, specifying the source object to bind to. If no source is specified the bindingContext value (if any) will be used as a source. - */ - bind(options: BindingOptions, source?: Object); - /** - * Removes the existing binding (if any) for the specified property. - * @param property The name of the property to unbind. - */ - unbind(property: string); - - //@private - // _onBindingContextChanged(oldValue: any, newValue: any); - // _updateTwoWayBinding(propertyName: string, value: any); - //@endprivate - } - export class Binding { constructor(target: ViewBase, options: BindingOptions); public bind(source: Object): void; diff --git a/tns-core-modules/ui/core/bindable.ts b/tns-core-modules/ui/core/bindable.ts index 75adc1ddd..f69052561 100644 --- a/tns-core-modules/ui/core/bindable.ts +++ b/tns-core-modules/ui/core/bindable.ts @@ -1,10 +1,9 @@ -import * as definition from "ui/core/bindable"; +import { BindingOptions } from "ui/core/bindable"; import { Observable, PropertyChangeData } from "data/observable"; -import { unsetValue, DependencyObservable } from "ui/core/dependency-observable"; import { addWeakEventListener, removeWeakEventListener } from "ui/core/weak-event-listener"; import types = require("utils/types"); import bindingBuilder = require("../builder/binding-builder"); -import { ViewBase, isEventOrGesture, bindingContextProperty } from "ui/core/view-base"; +import { ViewBase, isEventOrGesture, unsetValue } from "ui/core/view-base"; import * as application from "application"; import * as polymerExpressions from "js-libs/polymer-expressions"; import * as utils from "utils/utils"; @@ -19,115 +18,6 @@ let paramsRegex = /\[\s*(['"])*(\w*)\1\s*\]/; let bc = bindingBuilder.bindingConstants; -let defaultBindingSource = {}; - -export class Bindable extends DependencyObservable implements definition.Bindable { - - public static bindingContextProperty = bindingContextProperty; - - private bindings = new Map(); - - get bindingContext(): Object { - throw new Error("Not implemented"); - } - set bindingContext(value: Object) { - throw new Error("Not implemented"); - } - - public bind(options: definition.BindingOptions, source: Object = defaultBindingSource) { - throw new Error("Not implemented"); - // let binding: Binding = this.bindings.get(options.targetProperty); - // if (binding) { - // binding.unbind(); - // } - - // binding = new Binding(this, options); - // this.bindings.set(options.targetProperty, binding); - - // let bindingSource = source; - // if (bindingSource === defaultBindingSource) { - // bindingSource = this.bindingContext; - // binding.sourceIsBindingContext = true; - // } - - // // if (!types.isNullOrUndefined(bindingSource)) { - // binding.bind(bindingSource); - // // } - } - - public unbind(property: string) { - let binding: Binding = this.bindings.get(property); - if (binding) { - binding.unbind(); - this.bindings.delete(property); - } - } - - // public _updateTwoWayBinding(propertyName: string, value: any) { - // let binding: Binding = this.bindings.get(propertyName); - // if (binding) { - // binding.updateTwoWay(value); - // } - // } - - // public _setCore(data: PropertyChangeData) { - // super._setCore(data); - // this._updateTwoWayBinding(data.propertyName, data.value); - // } - - // public _onPropertyChanged(property: Property, oldValue: any, newValue: any) { - // if (traceEnabled()) { - // traceWrite(`${this}._onPropertyChanged(${property.name}, ${oldValue}, ${newValue})`, traceCategories.Binding); - // } - // super._onPropertyChanged(property, oldValue, newValue); - // // if (this instanceof viewModule.View) { - // // if (property.inheritable && ((this))._isInheritedChange() === true) { - // // return; - // // } - // // } - - // let binding = this.bindings.get(property.name); - // if (binding && !binding.updating) { - // if (binding.options.twoWay) { - // if (traceEnabled()) { - // traceWrite(`${this}._updateTwoWayBinding(${property.name}, ${newValue});` + property.name, traceCategories.Binding); - // } - // this._updateTwoWayBinding(property.name, newValue); - // } - // else { - // if (traceEnabled()) { - // traceWrite(`${this}.unbind(${property.name});`, traceCategories.Binding); - // } - // this.unbind(property.name); - // } - // } - // } - - // public _onBindingContextChanged(oldValue: any, newValue: any) { - // let bindingContextBinding = this.bindings.get("bindingContext"); - // if (bindingContextBinding) { - // if (!bindingContextBinding.updating) { - // bindingContextBinding.bind(newValue); - // } - // } - - // let bindingContextSource = this.bindingContext; - - // this.bindings.forEach((binding, index, bindings) => { - // if (!binding.updating && binding.sourceIsBindingContext && binding.options.targetProperty !== "bindingContext") { - // if (traceEnabled()) { - // traceWrite(`Binding ${binding.target.get()}.${binding.options.targetProperty} to new context ${bindingContextSource}`, traceCategories.Binding); - // } - // if (!types.isNullOrUndefined(bindingContextSource)) { - // binding.bind(bindingContextSource); - // } else { - // binding.clearBinding(); - // } - // } - // }); - // } -} - const emptyArray = []; function getProperties(property: string): Array { let result: Array = emptyArray; @@ -166,9 +56,9 @@ export class Binding { public updating: boolean; public sourceIsBindingContext: boolean; - public options: definition.BindingOptions; + public options: BindingOptions; - constructor(target: ViewBase, options: definition.BindingOptions) { + constructor(target: ViewBase, options: BindingOptions) { this.target = new WeakRef(target); this.options = options; this.sourceProperties = getProperties(options.sourceProperty); @@ -676,4 +566,4 @@ export class Binding { this.updating = false; } -} \ No newline at end of file +} diff --git a/tns-core-modules/ui/core/dependency-observable.d.ts b/tns-core-modules/ui/core/dependency-observable.d.ts index 8fb623001..cca37f303 100644 --- a/tns-core-modules/ui/core/dependency-observable.d.ts +++ b/tns-core-modules/ui/core/dependency-observable.d.ts @@ -5,13 +5,7 @@ declare module "ui/core/dependency-observable" { import { Observable, EventData } from "data/observable"; /** - * Value specifing that Property value should be reset. Used when bindingContext on bound property is creared/null. - */ - export const unsetValue: any; - - /** - * Interface used by Propery 'defaultValueGetter' function to specify if the default value returned by the native instance can be cached or not. - * One example is - android.widget.Button background. It is state drawable so it cannot be reused/cached. + * @deprecated */ export interface NativeValueResult { result: any; @@ -19,7 +13,7 @@ declare module "ui/core/dependency-observable" { } /** - * Represents a special Property which supports changed callback, metadata and value validation. + * @deprecated use 'ui/core/properties' module instead. */ export class Property { @@ -68,7 +62,7 @@ declare module "ui/core/dependency-observable" { } /** - * Represents an Object that describes a Property instance. + * @deprecated use 'ui/core/properties' module instead. */ export class PropertyMetadata { /** @@ -121,7 +115,7 @@ declare module "ui/core/dependency-observable" { } /** - * The data for the event raised when a value of a Property changes for a DependencyObservable instance. + * @deprecated use 'ui/core/properties' module instead. */ export interface PropertyChangeData extends EventData { /** @@ -139,28 +133,28 @@ declare module "ui/core/dependency-observable" { } /** - * Defines the signature of the function that handles the propertyChanged event. + * @deprecated use 'ui/core/properties' module instead. */ export interface PropertyChangedCallback { (data: PropertyChangeData): void; } /** - * Defines the signature of the function that handles the validateValue event. + * @deprecated use 'ui/core/properties' module instead. */ export interface PropertyValidationCallback { (value: any): boolean; } /** - * Defines the signature of the function that compares if two property values are equal. + * @deprecated use 'ui/core/properties' module instead. */ export interface PropertyEqualityComparer { (x: any, y: any): boolean; } /** - * Represents an Object that is used to back a value for a Property in a DependencyObservable Object instance. + * @deprecated */ export class PropertyEntry { /** @@ -201,8 +195,7 @@ declare module "ui/core/dependency-observable" { } /** - * Represents an extended Observable Object that uses Property instances for value backing mechanism. - * This routine allows for various value modifiers per Property, which is used for inheritance, data-binding and styling purposes. + * @deprecated use 'ui/core/view' as base class. */ export class DependencyObservable extends Observable { // TODO: Do we want to expose the get/setValue methods as public? @@ -251,7 +244,7 @@ declare module "ui/core/dependency-observable" { } /** - * Lists the possible values for the PropertyMetadata.options property. Each actual numeric value is a power of two allowing for bitwise operations. + * @deprecated use 'ui/core/properties' module instead. */ export module PropertyMetadataSettings { /** @@ -273,7 +266,7 @@ declare module "ui/core/dependency-observable" { } /** - * Lists the possible values for the PropertyEntry.valueSource property. + * @deprecated */ export module ValueSource { /** diff --git a/tns-core-modules/ui/core/dependency-observable.ts b/tns-core-modules/ui/core/dependency-observable.ts index 3ce7efb62..91b232e91 100644 --- a/tns-core-modules/ui/core/dependency-observable.ts +++ b/tns-core-modules/ui/core/dependency-observable.ts @@ -6,10 +6,11 @@ import { Observable, WrappedValue } from "data/observable"; import { getClassInfo, isString } from "utils/types"; +import { unsetValue } from "ui/core/properties"; + // use private variables in the scope of the module rather than static members of the class since a member is still accessible through JavaScript and may be changed. var propertyFromKey = {}; -var propertyIdCounter = 0; -export const unsetValue = new Object(); +// var propertyIdCounter = 0; function generatePropertyKey(name: string, ownerType: string, validate?: boolean) { if (validate) { @@ -72,16 +73,8 @@ export class PropertyMetadata implements PropertyMetadataDefinition { onChanged?: PropertyChangedCallback, onValidateValue?: PropertyValidationCallback, equalityComparer?: PropertyEqualityComparer) { - - this.defaultValue = defaultValue; - this.options = options; - this.onValueChanged = onChanged; - this.onValidateValue = onValidateValue; - this.equalityComparer = equalityComparer; - this.inheritable = (options & PropertyMetadataSettings.Inheritable) === PropertyMetadataSettings.Inheritable; - this.affectsStyle = (options & PropertyMetadataSettings.AffectsStyle) === PropertyMetadataSettings.AffectsStyle; - this.affectsLayout = (options & PropertyMetadataSettings.AffectsLayout) === PropertyMetadataSettings.AffectsLayout; - } + throw new Error("* @deprecated use 'ui/core/properties' module instead."); + } } export class Property implements PropertyDefinition { @@ -101,33 +94,7 @@ export class Property implements PropertyDefinition { public valueConverter: (value: string) => any constructor(public name: string, public ownerType: string, public metadata: PropertyMetadata, valueConverter?: (value: string) => any) { - // register key - this.key = generatePropertyKey(name, ownerType, true); - if (propertyFromKey[this.key]) { - throw new Error("Property " + name + " already registered for type " + ownerType + "."); - } - - propertyFromKey[this.key] = this; - - if (!metadata || !(metadata instanceof PropertyMetadata)) { - throw new Error("Expected valid PropertyMetadata instance."); - } - - this.name = name; - this.nameEvent = name + "Change"; - this.ownerType = ownerType; - this.metadata = metadata; - - // generate a unique numeric id for each property (faster lookup than a string key) - this.id = propertyIdCounter++; - this.valueConverter = valueConverter; - this.defaultValue = metadata.defaultValue; - this.onValueChanged = metadata.onValueChanged; - this.onValidateValue = metadata.onValidateValue; - this.equalityComparer = metadata.equalityComparer || ((x, y) => x === y); - this.inheritable = metadata.inheritable; - this.affectsStyle = metadata.affectsStyle; - this.affectsLayout = metadata.affectsLayout; + throw new Error("* @deprecated use 'ui/core/properties' module instead."); } public defaultValueGetter: (instance: DependencyObservable) => NativeValueResult; @@ -143,6 +110,7 @@ export class PropertyEntry implements PropertyEntryDefinition { public visualStateValue: any; constructor(public property: Property) { + throw new Error("* @deprecated use 'ui/core/properties' module instead."); } public resetValue() { @@ -154,6 +122,10 @@ export class PropertyEntry implements PropertyEntryDefinition { export class DependencyObservable extends Observable implements DependencyObservableDefinition { private _propertyEntries = {}; + constructor() { + super(); + throw new Error("* @deprecated use 'ui/core/view-base or ui/core/view' as base class."); + } public set(name: string, value: any) { var property = getPropertyByNameAndType(name, this); if (property) { diff --git a/tns-core-modules/ui/core/properties.ts b/tns-core-modules/ui/core/properties.ts index 25f4728b5..f5015ebc1 100644 --- a/tns-core-modules/ui/core/properties.ts +++ b/tns-core-modules/ui/core/properties.ts @@ -1,10 +1,11 @@ -import { unsetValue } from "ui/core/dependency-observable"; import { WrappedValue } from "data/observable"; import { ViewBase } from "./view-base"; import { Style } from "ui/styling/style"; import * as definitions from "ui/core/view-base"; -export { unsetValue, Style }; +export { Style }; + +export const unsetValue: any = new Object(); let symbolPropertyMap = {}; let cssSymbolPropertyMap = {}; @@ -20,6 +21,7 @@ function print(map) { } } } + export function printUnregisteredProperties(): void { print(symbolPropertyMap); print(cssSymbolPropertyMap) @@ -34,7 +36,8 @@ const enum ValueSource { export class Property implements PropertyDescriptor, definitions.Property { private registered: boolean; - private readonly name: string; + + public readonly name: string; public readonly key: symbol; public readonly native: symbol; public readonly defaultValueKey: symbol; @@ -90,12 +93,19 @@ export class Property implements PropertyDescriptor, defi const setNativeValue = this.nativeView && native in this; if (reset) { delete this[key]; + if (valueChanged) { + valueChanged(this, currentValue, unboxedValue); + } if (setNativeValue) { this[native] = this[defaultValueKey]; delete this[defaultValueKey]; } } else { this[key] = unboxedValue; + if (valueChanged) { + valueChanged(this, currentValue, unboxedValue); + } + if (setNativeValue) { if (!(defaultValueKey in this)) { this[defaultValueKey] = this[native]; @@ -105,10 +115,6 @@ export class Property implements PropertyDescriptor, defi } } - if (valueChanged) { - valueChanged(this, currentValue, unboxedValue); - } - if (this.hasListeners(eventName)) { this.notify({ eventName: eventName, @@ -237,12 +243,20 @@ export class CoercibleProperty implements PropertyDescrip const setNativeValue = this.nativeView && native in this; if (reset) { delete this[key]; + if (valueChanged) { + valueChanged(this, currentValue, unboxedValue); + } + if (setNativeValue) { this[native] = this[defaultValueKey]; delete this[defaultValueKey]; } } else { this[key] = unboxedValue; + if (valueChanged) { + valueChanged(this, currentValue, unboxedValue); + } + if (setNativeValue) { if (!(defaultValueKey in this)) { this[defaultValueKey] = this[native]; @@ -252,10 +266,6 @@ export class CoercibleProperty implements PropertyDescrip } } - if (valueChanged) { - valueChanged(this, currentValue, unboxedValue); - } - if (this.hasListeners(eventName)) { this.notify({ eventName: eventName, @@ -337,7 +347,7 @@ export class InheritedProperty extends Property imp const parent: ViewBase = that.parent; // If we have parent and it has non-default value we use as our inherited value. if (parent && parent[sourceKey] !== ValueSource.Default) { - unboxedValue = parent[key]; + unboxedValue = parent[name]; newValueSource = ValueSource.Inherited; } else { @@ -448,12 +458,20 @@ export class CssProperty implements definitions.CssProperty< const setNativeValue = view.nativeView && native in view; if (reset) { delete this[key]; + if (valueChanged) { + valueChanged(this, currentValue, value); + } + if (setNativeValue) { view[native] = this[defaultValueKey]; delete this[defaultValueKey]; } } else { this[key] = value; + if (valueChanged) { + valueChanged(this, currentValue, value); + } + if (setNativeValue) { if (!(defaultValueKey in this)) { this[defaultValueKey] = view[native]; @@ -463,10 +481,6 @@ export class CssProperty implements definitions.CssProperty< } } - if (valueChanged) { - valueChanged(this, currentValue, value); - } - if (this.hasListeners(eventName)) { this.notify({ eventName: eventName, @@ -509,12 +523,20 @@ export class CssProperty implements definitions.CssProperty< const setNativeValue = view.nativeView && native in view; if (reset) { delete this[key]; + if (valueChanged) { + valueChanged(this, currentValue, value); + } + if (setNativeValue) { view[native] = this[defaultValueKey]; delete this[defaultValueKey]; } } else { this[key] = value; + if (valueChanged) { + valueChanged(this, currentValue, value); + } + if (setNativeValue) { if (!(defaultValueKey in this)) { this[defaultValueKey] = view[native]; @@ -524,10 +546,6 @@ export class CssProperty implements definitions.CssProperty< } } - if (valueChanged) { - valueChanged(this, currentValue, value); - } - if (this.hasListeners(eventName)) { this.notify({ eventName: eventName, @@ -574,7 +592,7 @@ export class CssProperty implements definitions.CssProperty< } } -export class InheritedCssProperty extends CssProperty implements definitions.InheritedCssProperty { +export class InheritedCssProperty extends CssProperty implements definitions.InheritedCssProperty { public setInheritedValue: (value: U) => void; constructor(options: definitions.CssPropertyOptions) { @@ -614,8 +632,8 @@ export class InheritedCssProperty extends CssProperty let parent = view.parent; let style = parent ? parent.style : null // If we have parent and it has non-default value we use as our inherited value. - if (style && style[sourceKey] !== ValueSource.Default) { - newValue = style[key]; + if (style && style[sourceKey] > ValueSource.Default) { + newValue = style[name]; this[sourceKey] = ValueSource.Inherited; } else { @@ -639,12 +657,20 @@ export class InheritedCssProperty extends CssProperty const setNativeValue = view.nativeView && native in view; if (reset) { delete this[key]; + if (valueChanged) { + valueChanged(this, currentValue, newValue); + } + if (setNativeValue) { view[native] = this[defaultValueKey]; delete this[defaultValueKey]; } } else { this[key] = newValue; + if (valueChanged) { + valueChanged(this, currentValue, newValue); + } + if (setNativeValue) { if (!(defaultValueKey in this)) { this[defaultValueKey] = view[native]; @@ -654,10 +680,6 @@ export class InheritedCssProperty extends CssProperty } } - if (valueChanged) { - valueChanged(this, currentValue, newValue); - } - if (this.hasListeners(eventName)) { this.notify({ eventName: eventName, @@ -722,13 +744,9 @@ export class ShorthandProperty implements definitions.Shorth const cssName = `css-${options.cssName}`; this.cssName = cssName; - const sourceKey = Symbol(name + ":valueSourceKey"); - this.sourceKey = sourceKey; - const converter = options.converter; function setLocalValue(this: T, value: string | P): void { - this[sourceKey] = ValueSource.Local; if (this[key] !== value) { this[key] = value; for (let [p, v] of converter(value)) { @@ -738,12 +756,6 @@ export class ShorthandProperty implements definitions.Shorth } function setCssValue(this: T, value: string): void { - const currentValueSource: number = this[sourceKey] || ValueSource.Default; - // We have localValueSource - NOOP. - if (currentValueSource === ValueSource.Local) { - return; - } - if (this[key] !== value) { this[key] = value; for (let [p, v] of converter(value)) { @@ -785,7 +797,9 @@ function inheritablePropertyValuesOn(view: ViewBase): Array<{ property: Inherite const sourceKey = prop.sourceKey; const valueSource: number = view[sourceKey] || ValueSource.Default; if (valueSource !== ValueSource.Default) { - array.push({ property: prop, value: view[prop.key] }); + // use prop.name as it will return value or default value. + // prop.key will return undefined if property is set t the same value as default one. + array.push({ property: prop, value: view[prop.name] }); } } @@ -798,7 +812,9 @@ function inheritableCssPropertyValuesOn(style: Style): Array<{ property: Inherit const sourceKey = prop.sourceKey; const valueSource: number = style[sourceKey] || ValueSource.Default; if (valueSource !== ValueSource.Default) { - array.push({ property: prop, value: style[prop.key] }); + // use prop.name as it will return value or default value. + // prop.key will return undefined if property is set t the same value as default one. + array.push({ property: prop, value: style[prop.name] }); } } @@ -929,9 +945,10 @@ export function makeValidator(...values: T[]): (value: any) => value is T { const set = new Set(values); return (value: any): value is T => set.has(value); } + export function makeParser(isValid: (value: any) => boolean, def: T): (value: any) => T { return value => { const lower = value && value.toLowerCase(); return isValid(lower) ? lower : def; } -} +} \ No newline at end of file diff --git a/tns-core-modules/ui/core/proxy.d.ts b/tns-core-modules/ui/core/proxy.d.ts deleted file mode 100644 index f5879f13e..000000000 --- a/tns-core-modules/ui/core/proxy.d.ts +++ /dev/null @@ -1,65 +0,0 @@ -declare module "ui/core/proxy" { - import { DependencyObservable, Property, PropertyMetadata as PropertyMetadataBase, PropertyChangedCallback, PropertyValidationCallback } from "ui/core/dependency-observable"; - - /** - * A class that describes dependency property metadata. - */ - class PropertyMetadata extends PropertyMetadataBase { - /** - * Gets or sets a dependencyObservable.PropertyChangedCallback which is used to set the value on native side. - */ - public onSetNativeValue: PropertyChangedCallback; - - /** - * Initializes a new instance of PropertyMetadata class. - * @param defaultValue A value to be used as default value for the dependency property. - * @param options (optional) A value that states how this property affects visual tree. - * @param onChanged (optional) A callback function which will be executed when value of the dependency property is changed. - * @param onValidateValue (optional) A callback function which will be executed to validate the value of the dependency property. - * @param onSetNativeValue (optional) A callback function which will be executed to set the value on native side. - */ - constructor( - defaultValue: any, - options?: number, - onChanged?: PropertyChangedCallback, - onValidateValue?: PropertyValidationCallback, - onSetNativeValue?: PropertyChangedCallback); - } - - /** - * A class that serves as a proxy between JavaScript object and native object. - * Used in cases when native instance is not avaibale yet (stores all properties). - */ - class ProxyObject extends DependencyObservable { - - /** - * Get the nativeView created for this object. - */ - public nativeView: any; - - /** - * Gets the android-specific native instance that lies behind this proxy. Will be available if running on an Android platform. - */ - public android: any; - - /** - * Gets the ios-specific native instance that lies behind this proxy. Will be available if running on an iOS platform. - */ - public ios: any; - - /** - * A property is changed. - */ - // public _onPropertyChanged(property: dependencyObservable.Property, oldValue: any, newValue: any): void; - - /** - * A property has changed on the native side directly - e.g. the user types in a TextField. - */ - public _onPropertyChangedFromNative(property: Property, newValue: any): void; - - /** - * Synchronizes all properties with native values. - */ - // public _syncNativeProperties(): void; - } -} \ No newline at end of file diff --git a/tns-core-modules/ui/core/proxy.ts b/tns-core-modules/ui/core/proxy.ts deleted file mode 100644 index 00a2ba0f1..000000000 --- a/tns-core-modules/ui/core/proxy.ts +++ /dev/null @@ -1,112 +0,0 @@ -import * as bindable from "ui/core/bindable"; -import * as dependencyObservable from "ui/core/dependency-observable"; -import * as definition from "ui/core/proxy"; - -export class PropertyMetadata extends dependencyObservable.PropertyMetadata implements definition.PropertyMetadata { - private _onSetNativeValue: dependencyObservable.PropertyChangedCallback; - - constructor( - defaultValue: any, - options?: number, - onChanged?: dependencyObservable.PropertyChangedCallback, - onValidateValue?: dependencyObservable.PropertyValidationCallback, - onSetNativeValue?: dependencyObservable.PropertyChangedCallback) { - super(defaultValue, options, onChanged, onValidateValue); - this._onSetNativeValue = onSetNativeValue; - } - - get onSetNativeValue(): dependencyObservable.PropertyChangedCallback { - return this._onSetNativeValue; - } - set onSetNativeValue(value: dependencyObservable.PropertyChangedCallback) { - this._onSetNativeValue = value; - } -} - -export class ProxyObject extends bindable.Bindable implements definition.ProxyObject { - private _updatingJSPropertiesDict = {}; - - public nativeView: any; - - /** - * Gets the android-specific native instance that lies behind this proxy. Will be available if running on an Android platform. - */ - get android(): any { - return undefined; - } - - /** - * Gets the ios-specific native instance that lies behind this proxy. Will be available if running on an iOS platform. - */ - get ios(): any { - return undefined; - } - - // public _onPropertyChanged(property: dependencyObservable.Property, oldValue: any, newValue: any) { - // super._onPropertyChanged(property, oldValue, newValue); - - // this._trySetNativeValue(property, oldValue, newValue); - // } - - /** - * A property has changed on the native side directly - e.g. the user types in a TextField. - */ - public _onPropertyChangedFromNative(property: dependencyObservable.Property, newValue: any) { - if (this._updatingJSPropertiesDict[property.name]) { - return; - } - this._updatingJSPropertiesDict[property.name] = true; - this._setValue(property, newValue); - delete this._updatingJSPropertiesDict[property.name]; - } - - // public _syncNativeProperties() { - // // var that = this; - // // var eachPropertyCallback = function (property: dependencyObservable.Property): boolean { - // // that._trySetNativeValue(property); - // // return true; - // // } - - // // this._eachSetProperty(eachPropertyCallback); - // } - - /** - * Checks whether the proxied native object has been created and properties may be applied to it. - */ - // protected _canApplyNativeProperty(): boolean { - // return false; - // } - - // private _trySetNativeValue(property: dependencyObservable.Property, oldValue?: any, newValue?: any) { - // if (this._updatingJSPropertiesDict[property.name]) { - // // This is the case when a property has changed from the native side directly and we have received the "_onPropertyChanged" event while synchronizing our local cache - // return; - // } - - // if (!this._canApplyNativeProperty()) { - // // in android we have lazy loading and we do not have a native widget created yet, do not call the onSetNativeValue callback - // // properties will be synced when the widget is created - // return; - // } - - // var metadata = property.metadata; - // if (!(metadata instanceof PropertyMetadata)) { - // return; - // } - - // var proxyMetadata = metadata; - // if (proxyMetadata.onSetNativeValue) { - // if (types.isUndefined(newValue)) { - // newValue = this._getValue(property); - // } - - // proxyMetadata.onSetNativeValue({ - // object: this, - // property: property, - // eventName: observable.Observable.propertyChangeEvent, - // newValue: newValue, - // oldValue: oldValue - // }); - // } - // } -} \ No newline at end of file diff --git a/tns-core-modules/ui/core/view-base.ts b/tns-core-modules/ui/core/view-base.ts index 107f061e8..ac96671e4 100644 --- a/tns-core-modules/ui/core/view-base.ts +++ b/tns-core-modules/ui/core/view-base.ts @@ -1,7 +1,7 @@ import { ViewBase as ViewBaseDefinition } from "ui/core/view-base"; import { Observable, EventData, PropertyChangeData } from "data/observable"; import { Property, InheritedProperty, Style, clearInheritedProperties, propagateInheritedProperties, resetCSSProperties, applyNativeSetters, resetStyleProperties } from "./properties"; -import { Binding, BindingOptions, Bindable } from "ui/core/bindable"; +import { Binding, BindingOptions } from "ui/core/bindable"; import { isIOS, isAndroid } from "platform"; import { fromString as gestureFromString } from "ui/gestures"; import { SelectorCore } from "ui/styling/css-selector"; @@ -21,7 +21,7 @@ function ensureStyleScopeModule() { } export { - Observable, EventData, Binding, BindingOptions, Bindable, isIOS, isAndroid, + Observable, EventData, Binding, BindingOptions, isIOS, isAndroid, gestureFromString, traceEnabled, traceWrite, traceCategories, traceNotifyEvent, isCategorySet }; export * from "./properties"; diff --git a/tns-core-modules/ui/core/view-common.ts b/tns-core-modules/ui/core/view-common.ts index e4e30cca4..bedd745c5 100644 --- a/tns-core-modules/ui/core/view-common.ts +++ b/tns-core-modules/ui/core/view-common.ts @@ -5,7 +5,7 @@ import { Background } from "ui/styling/background"; import { ViewBase, getEventOrGestureName, EventData, Style, unsetValue, Property, CssProperty, ShorthandProperty, InheritedCssProperty, - gestureFromString, isIOS, traceEnabled, traceWrite, traceCategories, printUnregisteredProperties, makeParser, makeValidator + gestureFromString, isIOS, traceEnabled, traceWrite, traceCategories, makeParser, makeValidator } from "./view-base"; import { observe as gestureObserve, GesturesObserver, GestureTypes, GestureEventData } from "ui/gestures"; import { Font, parseFont, FontStyle, FontWeight } from "ui/styling/font"; @@ -28,7 +28,7 @@ export { import * as am from "ui/animation"; let animationModule: typeof am; function ensureAnimationModule() { - if (!animationModule){ + if (!animationModule) { animationModule = require("ui/animation"); } } @@ -1429,39 +1429,39 @@ function convertToTransform(value: string): [CssProperty, any][] { for (let transform in newTransform) { switch (transform) { case "scaleX": - array.push([scaleXProperty, parseFloat(newTransform[transform])]); + array.push([scaleXProperty, newTransform[transform]]); break; case "scaleY": - array.push([scaleYProperty, parseFloat(newTransform[transform])]); + array.push([scaleYProperty, newTransform[transform]]); break; case "scale": case "scale3d": values = newTransform[transform].split(","); if (values.length >= 2) { - array.push([scaleXProperty, parseFloat(values[0])]); - array.push([scaleYProperty, parseFloat(values[1])]); + array.push([scaleXProperty, values[0]]); + array.push([scaleYProperty, values[1]]); } else if (values.length === 1) { - array.push([scaleXProperty, parseFloat(values[0])]); - array.push([scaleYProperty, parseFloat(values[0])]); + array.push([scaleXProperty, values[0]]); + array.push([scaleYProperty, values[0]]); } break; case "translateX": - array.push([translateXProperty, parseFloat(newTransform[transform])]); + array.push([translateXProperty, newTransform[transform]]); break; case "translateY": - array.push([translateYProperty, parseFloat(newTransform[transform])]); + array.push([translateYProperty, newTransform[transform]]); break; case "translate": case "translate3d": values = newTransform[transform].split(","); if (values.length >= 2) { - array.push([translateXProperty, parseFloat(values[0])]); - array.push([translateYProperty, parseFloat(values[1])]); + array.push([translateXProperty, values[0]]); + array.push([translateYProperty, values[1]]); } else if (values.length === 1) { - array.push([translateXProperty, parseFloat(values[0])]); - array.push([translateYProperty, parseFloat(values[0])]); + array.push([translateXProperty, values[0]]); + array.push([translateYProperty, values[0]]); } break; case "rotate": @@ -1546,7 +1546,6 @@ backgroundImageProperty.register(Style); export const backgroundColorProperty = new CssProperty({ name: "backgroundColor", cssName: "background-color", valueChanged: (target, oldValue, newValue) => { - printUnregisteredProperties(); let background = target.backgroundInternal; target.backgroundInternal = background.withColor(newValue); }, equalityComparer: Color.equals, valueConverter: (value) => new Color(value) @@ -1564,7 +1563,8 @@ export namespace BackgroundRepeat { } export const backgroundRepeatProperty = new CssProperty({ - name: "backgroundRepeat", cssName: "background-repeat", valueConverter: BackgroundRepeat.parse, valueChanged: (target, oldValue, newValue) => { + name: "backgroundRepeat", cssName: "background-repeat", valueConverter: BackgroundRepeat.parse, + valueChanged: (target, oldValue, newValue) => { let background = target.backgroundInternal; target.backgroundInternal = background.withRepeat(newValue); } @@ -1941,7 +1941,8 @@ export const fontFamilyProperty = new InheritedCssProperty({ name: "fontFamily", cssName: "font-family", valueChanged: (target, oldValue, newValue) => { let currentFont = target.fontInternal; if (currentFont.fontFamily !== newValue) { - target.fontInternal = currentFont.withFontFamily(newValue); + const newFont = currentFont.withFontFamily(newValue); + target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont; } } }); @@ -1951,7 +1952,8 @@ export const fontSizeProperty = new InheritedCssProperty({ name: "fontSize", cssName: "font-size", valueChanged: (target, oldValue, newValue) => { let currentFont = target.fontInternal; if (currentFont.fontSize !== newValue) { - target.fontInternal = currentFont.withFontSize(newValue); + const newFont = currentFont.withFontSize(newValue); + target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont; } }, valueConverter: (v) => parseFloat(v) @@ -1962,7 +1964,8 @@ export const fontStyleProperty = new InheritedCssProperty({ name: "fontStyle", cssName: "font-style", defaultValue: FontStyle.NORMAL, valueConverter: FontStyle.parse, valueChanged: (target, oldValue, newValue) => { let currentFont = target.fontInternal; if (currentFont.fontStyle !== newValue) { - target.fontInternal = currentFont.withFontStyle(newValue); + const newFont = currentFont.withFontStyle(newValue); + target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont; } } }); @@ -1972,7 +1975,8 @@ export const fontWeightProperty = new InheritedCssProperty({ name: "fontWeight", cssName: "font-weight", defaultValue: FontWeight.NORMAL, valueConverter: FontWeight.parse, valueChanged: (target, oldValue, newValue) => { let currentFont = target.fontInternal; if (currentFont.fontWeight !== newValue) { - target.fontInternal = currentFont.withFontWeight(newValue); + const newFont = currentFont.withFontWeight(newValue); + target.fontInternal = Font.equals(Font.default, newFont) ? unsetValue : newFont; } } }); diff --git a/tns-core-modules/ui/definitions.d.ts b/tns-core-modules/ui/definitions.d.ts index 9d416c3a2..a61b5a1f9 100644 --- a/tns-core-modules/ui/definitions.d.ts +++ b/tns-core-modules/ui/definitions.d.ts @@ -3,9 +3,9 @@ declare module "ui/core/view-base" { import { Property, PropertyOptions, CoercibleProperty, CoerciblePropertyOptions, InheritedProperty, CssProperty, CssPropertyOptions, InheritedCssProperty, - ShorthandProperty, ShorthandPropertyOptions + ShorthandProperty, ShorthandPropertyOptions, unsetValue } from "ui/core/properties"; - import { Binding, BindingOptions, Bindable } from "ui/core/bindable"; + import { Binding, BindingOptions } from "ui/core/bindable"; import { Style } from "ui/styling/style"; import { SelectorCore } from "ui/styling/css-selector"; import { isIOS, isAndroid } from "platform"; @@ -15,7 +15,7 @@ declare module "ui/core/view-base" { export { Observable, EventData, KeyframeAnimation, - Binding, BindingOptions, Bindable, Style, isIOS, isAndroid, gestureFromString, + Binding, BindingOptions, Style, isIOS, isAndroid, gestureFromString, traceEnabled, traceWrite, traceCategories, traceNotifyEvent, isCategorySet }; @@ -28,6 +28,7 @@ declare module "ui/core/view-base" { * Returns an instance of a view (if found), otherwise undefined. */ export function getAncestor(view: ViewBase, criterion: string | Function): ViewBase; + export function isEventOrGesture(name: string, view: ViewBase): boolean; /** @@ -38,7 +39,7 @@ declare module "ui/core/view-base" { */ export function getViewById(view: ViewBase, id: string): ViewBase; - export class ViewBase extends Observable { + export abstract class ViewBase extends Observable { /** * String value used when hooking to loaded event. */ @@ -69,11 +70,6 @@ declare module "ui/core/view-base" { */ public id: string; - /** - * Returns the child view with the specified id. - */ - public getViewById(id: string): T; - /** * Gets or sets the CSS class name for this view. */ @@ -101,6 +97,11 @@ declare module "ui/core/view-base" { public isCollapsed: boolean; public readonly isLoaded: boolean; + /** + * Returns the child view with the specified id. + */ + public getViewById(id: string): T; + public onLoaded(): void; public onUnloaded(): void; @@ -157,6 +158,7 @@ declare module "ui/core/view-base" { _resetNativeView(): void; _isAddedToNativeVisualTree: boolean; + /** * Performs the core logic of adding a child view to the native visual tree. Returns true if the view's native representation has been successfully added, false otherwise. */ @@ -187,17 +189,19 @@ declare module "ui/core/view-base" { declare module "ui/core/properties" { import { ViewBase } from "ui/core/view-base"; import { Style } from "ui/styling/style"; - import { unsetValue } from "ui/core/dependency-observable"; - - export { unsetValue }; + /** + * Value specifing that Property should be set to its initial value. + */ + export const unsetValue: any; + export interface PropertyOptions { - readonly name: string, - readonly defaultValue?: U, - readonly affectsLayout?: boolean, - readonly equalityComparer?: (x: U, y: U) => boolean, - readonly valueChanged?: (target: T, oldValue: U, newValue: U) => void, - readonly valueConverter?: (value: string) => U + readonly name: string; + readonly defaultValue?: U; + readonly affectsLayout?: boolean; + readonly equalityComparer?: (x: U, y: U) => boolean; + readonly valueChanged?: (target: T, oldValue: U, newValue: U) => void; + readonly valueConverter?: (value: string) => U; } export interface CoerciblePropertyOptions extends PropertyOptions { diff --git a/tns-core-modules/ui/search-bar/search-bar.android.ts b/tns-core-modules/ui/search-bar/search-bar.android.ts index a49cbde13..868e26dc9 100644 --- a/tns-core-modules/ui/search-bar/search-bar.android.ts +++ b/tns-core-modules/ui/search-bar/search-bar.android.ts @@ -1,6 +1,6 @@ import { SearchBarBase, Font, Color, colorProperty, backgroundColorProperty, backgroundInternalProperty, fontInternalProperty, - textProperty, hintProperty, textFieldHintColorProperty, textFieldBackgroundColorProperty + textProperty, hintProperty, textFieldHintColorProperty, textFieldBackgroundColorProperty, fontSizeProperty } from "./search-bar-common"; import { ad } from "utils/utils"; @@ -128,34 +128,28 @@ export class SearchBar extends SearchBarBase { textView.setTextColor(color); } - get [fontInternalProperty.native](): { typeface: android.graphics.Typeface, fontSize: number } { - let textView = this._getTextView(); - return { - typeface: textView.getTypeface(), - fontSize: textView.getTextSize() - }; + get [fontSizeProperty.native](): { nativeSize: number } { + return { nativeSize: this._getTextView().getTextSize() }; } - set [fontInternalProperty.native](value: Font | { typeface: android.graphics.Typeface, fontSize: number }) { - let textView = this._getTextView(); - - if (value instanceof Font) { - // Set value - textView.setTypeface(value.getAndroidTypeface()); - if (value.fontSize !== undefined){ - textView.setTextSize(value.fontSize); - } - } - else { - // Reset value - textView.setTypeface(value.typeface); - textView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.fontSize); + set [fontSizeProperty.native](value: number | { nativeSize: number }) { + if (typeof value === "number") { + this._getTextView().setTextSize(value); + } else { + this._getTextView().setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize); } } - get [backgroundInternalProperty.native](): Font { + get [fontInternalProperty.native](): android.graphics.Typeface { + return this._getTextView().getTypeface(); + } + set [fontInternalProperty.native](value: Font | android.graphics.Typeface) { + this._getTextView().setTypeface(value instanceof Font ? value.getAndroidTypeface() : value); + } + + get [backgroundInternalProperty.native](): any { return null; } - set [backgroundInternalProperty.native](value: Font) { + set [backgroundInternalProperty.native](value: any) { // } diff --git a/tns-core-modules/ui/segmented-bar/segmented-bar.android.ts b/tns-core-modules/ui/segmented-bar/segmented-bar.android.ts index 074a5653d..aae5f6958 100644 --- a/tns-core-modules/ui/segmented-bar/segmented-bar.android.ts +++ b/tns-core-modules/ui/segmented-bar/segmented-bar.android.ts @@ -1,6 +1,6 @@ import { SegmentedBarItemBase, SegmentedBarBase, selectedIndexProperty, itemsProperty, selectedBackgroundColorProperty, - colorProperty, fontInternalProperty, Color, Font, applyNativeSetters + colorProperty, fontInternalProperty, fontSizeProperty, Color, Font, applyNativeSetters } from "./segmented-bar-common"; export * from "./segmented-bar-common"; @@ -96,29 +96,24 @@ export class SegmentedBarItem extends SegmentedBarItemBase { this._textView.setTextColor(color); } - get [fontInternalProperty.native](): { typeface: android.graphics.Typeface, fontSize: number } { - let textView = this._textView; - return { - typeface: textView.getTypeface(), - fontSize: textView.getTextSize() - }; + get [fontSizeProperty.native](): { nativeSize: number } { + return { nativeSize: this._textView.getTextSize() }; } - set [fontInternalProperty.native](value: Font | { typeface: android.graphics.Typeface, fontSize: number }) { - let textView = this._textView; - if (value instanceof Font) { - // Set value - textView.setTypeface(value.getAndroidTypeface()); - if (value.fontSize !== undefined) { - textView.setTextSize(value.fontSize); - } - } - else { - // Reset value - textView.setTypeface(value.typeface); - textView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.fontSize); + set [fontSizeProperty.native](value: number | { nativeSize: number }) { + if (typeof value === "number") { + this._textView.setTextSize(value); + } else { + this._textView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize); } } + get [fontInternalProperty.native](): android.graphics.Typeface { + return this._textView.getTypeface(); + } + set [fontInternalProperty.native](value: Font | android.graphics.Typeface) { + this._textView.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value); + } + get [selectedBackgroundColorProperty.native](): android.graphics.drawable.Drawable { let viewGroup = this._textView.getParent(); return viewGroup.getBackground(); diff --git a/tns-core-modules/ui/styling/font.android.ts b/tns-core-modules/ui/styling/font.android.ts index fdc150b37..61ca6fbff 100644 --- a/tns-core-modules/ui/styling/font.android.ts +++ b/tns-core-modules/ui/styling/font.android.ts @@ -44,8 +44,7 @@ export class Font extends FontBase { fontStyle |= android.graphics.Typeface.ITALIC; } - const typeFace = createTypeface(this); - this._typeface = android.graphics.Typeface.create(typeFace, fontStyle); + this._typeface = createTypeface(this, fontStyle); } return this._typeface; } @@ -96,7 +95,7 @@ function loadFontFromFile(fontFamily: string): android.graphics.Typeface { return result; } -function createTypeface(font: Font): android.graphics.Typeface { +function createTypeface(font: Font, fontStyle: number): android.graphics.Typeface { //http://stackoverflow.com/questions/19691530/valid-values-for-androidfontfamily-and-what-they-map-to const fonts = parseFontFamily(font.fontFamily); let result = null; @@ -107,16 +106,16 @@ function createTypeface(font: Font): android.graphics.Typeface { for (let i = 0; i < fonts.length; i++) { switch (fonts[i].toLowerCase()) { case genericFontFamilies.serif: - result = android.graphics.Typeface.create("serif" + getFontWeightSuffix(font.fontWeight), 0); + result = android.graphics.Typeface.create("serif" + getFontWeightSuffix(font.fontWeight), fontStyle); break; case genericFontFamilies.sansSerif: case genericFontFamilies.system: - result = android.graphics.Typeface.create("sans-serif" + getFontWeightSuffix(font.fontWeight), 0); + result = android.graphics.Typeface.create("sans-serif" + getFontWeightSuffix(font.fontWeight), fontStyle); break; case genericFontFamilies.monospace: - result = android.graphics.Typeface.create("monospace" + getFontWeightSuffix(font.fontWeight), 0); + result = android.graphics.Typeface.create("monospace" + getFontWeightSuffix(font.fontWeight), fontStyle); break; default: diff --git a/tns-core-modules/ui/styling/font.d.ts b/tns-core-modules/ui/styling/font.d.ts index cde0e80df..92bf09bff 100644 --- a/tns-core-modules/ui/styling/font.d.ts +++ b/tns-core-modules/ui/styling/font.d.ts @@ -10,7 +10,7 @@ public isBold: boolean; public isItalic: boolean; - constructor(family: string, size: number, style: string, weight: string); + constructor(family: string, size: number, style: FontStyle, weight: FontWeight); public getAndroidTypeface(): any /* android.graphics.Typeface */; public getUIFont(defaultFont: any /* UIFont */): any /* UIFont */; @@ -60,4 +60,4 @@ export module ios { export function registerFont(fontFile: string); } -} +} \ No newline at end of file diff --git a/tns-core-modules/ui/tab-view/tab-view.android.ts b/tns-core-modules/ui/tab-view/tab-view.android.ts index dfe69808b..d315d19e8 100644 --- a/tns-core-modules/ui/tab-view/tab-view.android.ts +++ b/tns-core-modules/ui/tab-view/tab-view.android.ts @@ -2,7 +2,8 @@ import { TabViewBase, TabViewItemBase, itemsProperty, selectedIndexProperty, tabTextColorProperty, tabBackgroundColorProperty, selectedTabTextColorProperty, androidSelectedTabHighlightColorProperty, androidOffscreenTabLimitProperty, - fontInternalProperty, traceCategory, View, layout, Color, Font, traceEnabled, traceWrite, + fontSizeProperty, fontInternalProperty, View, layout, Color, Font, + traceCategory, traceEnabled, traceWrite, applyNativeSetters } from "./tab-view-common" import { textTransformProperty, TextTransform, getTransformedText } from "ui/text-base"; @@ -36,36 +37,22 @@ export class TabViewItem extends TabViewItemBase { } } - get [fontInternalProperty.native](): { typeface: android.graphics.Typeface, fontSize: number } { - const tv = this.nativeView; - return { - typeface: tv.getTypeface(), - fontSize: tv.getTextSize() - }; + get [fontSizeProperty.native](): { nativeSize: number } { + return { nativeSize: this.nativeView.getTextSize() }; } - set [fontInternalProperty.native](value: Font | { typeface: android.graphics.Typeface, fontSize: number }) { - let typeface: android.graphics.Typeface; - let isFont: boolean; - const fontSize = value.fontSize; - if (value instanceof Font) { - isFont = true; - typeface = value.getAndroidTypeface(); - } - else { - typeface = value.typeface; + set [fontSizeProperty.native](value: number | { nativeSize: number }) { + if (typeof value === "number") { + this.nativeView.setTextSize(value); + } else { + this.nativeView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize); } + } - const tv = this.nativeView; - tv.setTypeface(typeface); - - if (isFont) { - if (fontSize !== undefined) { - tv.setTextSize(fontSize); - } - } - else { - tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, fontSize); - } + get [fontInternalProperty.native](): android.graphics.Typeface { + return this.nativeView.getTypeface(); + } + set [fontInternalProperty.native](value: Font | android.graphics.Typeface) { + this.nativeView.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value); } get [textTransformProperty.native](): TextTransform { @@ -269,7 +256,7 @@ export class TabView extends TabViewBase { public onItemsChanged(oldItems: TabViewItem[], newItems: TabViewItem[]): void { super.onItemsChanged(oldItems, newItems); - + if (oldItems) { oldItems.forEach((item: TabViewItem, i, arr) => { item.index = 0; diff --git a/tns-core-modules/ui/text-base/text-base-common.ts b/tns-core-modules/ui/text-base/text-base-common.ts index dba1f934c..fed1c1977 100644 --- a/tns-core-modules/ui/text-base/text-base-common.ts +++ b/tns-core-modules/ui/text-base/text-base-common.ts @@ -1,13 +1,16 @@ import { TextBase as TextBaseDefinition } from "ui/text-base"; import { View, Property, CssProperty, InheritedCssProperty, Style, isIOS, Observable, makeValidator, makeParser, Length } from "ui/core/view"; import { PropertyChangeData } from "data/observable"; -import { FormattedString, FormattedStringView } from "text/formatted-string"; -import { addWeakEventListener, removeWeakEventListener } from "ui/core/weak-event-listener"; +import { FormattedString, Span } from "text/formatted-string"; -export { FormattedString }; +export { FormattedString, Span }; export * from "ui/core/view"; -export abstract class TextBaseCommon extends View implements TextBaseDefinition, FormattedStringView { +const CHILD_SPAN = "Span"; +const CHILD_FORMATTED_TEXT = "formattedText"; +const CHILD_FORMATTED_STRING = "FormattedString"; + +export abstract class TextBaseCommon extends View implements TextBaseDefinition { // public abstract _setFormattedTextPropertyToNative(value: FormattedString): void; @@ -92,18 +95,25 @@ export abstract class TextBaseCommon extends View implements TextBaseDefinition, } public _onFormattedTextContentsChanged(data: PropertyChangeData) { - if (this._nativeView){ + if (this._nativeView) { // Notifications from the FormattedString start arriving before the Android view is even created. this[formattedTextProperty.native] = data.value; } } public _addChildFromBuilder(name: string, value: any): void { - if (!this.formattedText) { - this.formattedText = new FormattedString(); + if (name === CHILD_SPAN) { + if (!this.formattedText) { + const formattedText = new FormattedString(); + formattedText.spans.push(value); + this.formattedText = formattedText; + } else { + this.formattedText.spans.push(value); + } + } + else if (name === CHILD_FORMATTED_TEXT || name === CHILD_FORMATTED_STRING) { + this.formattedText = value; } - - FormattedString.addFormattedStringToView(this, name, value); } _requestLayoutOnTextChanged(): void { @@ -125,13 +135,13 @@ formattedTextProperty.register(TextBaseCommon); function onFormattedTextPropertyChanged(textBase: TextBaseCommon, oldValue: FormattedString, newValue: FormattedString) { if (oldValue) { - oldValue.parent = null; - removeWeakEventListener(oldValue, Observable.propertyChangeEvent, textBase._onFormattedTextContentsChanged, textBase); + oldValue.off(Observable.propertyChangeEvent, textBase._onFormattedTextContentsChanged, textBase); + textBase._removeView(oldValue); } if (newValue) { - newValue.parent = textBase; - addWeakEventListener(newValue, Observable.propertyChangeEvent, textBase._onFormattedTextContentsChanged, textBase); + textBase._addView(newValue); + newValue.on(Observable.propertyChangeEvent, textBase._onFormattedTextContentsChanged, textBase); } } diff --git a/tns-core-modules/ui/text-base/text-base.android.ts b/tns-core-modules/ui/text-base/text-base.android.ts index 67dbe6c82..ed7fb1ad3 100644 --- a/tns-core-modules/ui/text-base/text-base.android.ts +++ b/tns-core-modules/ui/text-base/text-base.android.ts @@ -2,13 +2,15 @@ TextBaseCommon, formattedTextProperty, textAlignmentProperty, textDecorationProperty, fontSizeProperty, textProperty, textTransformProperty, letterSpacingProperty, colorProperty, fontInternalProperty, whiteSpaceProperty, Font, Color, FormattedString, TextDecoration, TextAlignment, TextTransform, WhiteSpace, - paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length + paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length, + layout, Span } from "./text-base-common"; -import { toUIString } from "utils/types"; + +import { FontWeight, FontStyle } from "ui/styling/font"; export * from "./text-base-common"; + export class TextBase extends TextBaseCommon { - _transformationMethod: any; _nativeView: android.widget.TextView; //Text @@ -26,18 +28,17 @@ export class TextBase extends TextBaseCommon { } set [formattedTextProperty.native](value: FormattedString) { let spannableStringBuilder = createSpannableStringBuilder(value); - const text = (spannableStringBuilder === null || spannableStringBuilder === undefined) ? '' : spannableStringBuilder; - this._nativeView.setText(text); + this._nativeView.setText(spannableStringBuilder); textProperty.nativeValueChange(this, (value === null || value === undefined) ? '' : value.toString()); - if (spannableStringBuilder && this._nativeView instanceof android.widget.Button && - !(this._nativeView.getTransformationMethod() instanceof TextTransformation)){ - // Replace Android Button's default transformation (in case the developer has not already specified a text-transform) method - // with our transformation method which can handle formatted text. - // Otherwise, the default tranformation method of the Android Button will overwrite and ignore our spannableStringBuilder. - // We can't set it to NONE since it is the default value. Set it to something else first. - this.style[textTransformProperty.cssName] = TextTransform.UPPERCASE; - this.style[textTransformProperty.cssName] = TextTransform.NONE; + if (spannableStringBuilder && this._nativeView instanceof android.widget.Button && + !(this._nativeView.getTransformationMethod() instanceof TextTransformation)) { + // Replace Android Button's default transformation (in case the developer has not already specified a text-transform) method + // with our transformation method which can handle formatted text. + // Otherwise, the default tranformation method of the Android Button will overwrite and ignore our spannableStringBuilder. + // We can't set it to NONE since it is the default value. Set it to something else first. + this.style[textTransformProperty.cssName] = TextTransform.UPPERCASE; + this.style[textTransformProperty.cssName] = TextTransform.NONE; } } @@ -78,24 +79,11 @@ export class TextBase extends TextBaseCommon { } //FontInternal - get [fontInternalProperty.native](): { typeface: android.graphics.Typeface, fontSize: number } { - let textView = this._nativeView; - return { - typeface: textView.getTypeface(), - fontSize: textView.getTextSize() - }; + get [fontInternalProperty.native](): android.graphics.Typeface { + return this._nativeView.getTypeface(); } - set [fontInternalProperty.native](value: Font | { typeface: android.graphics.Typeface, fontSize: number }) { - let textView = this._nativeView; - if (value instanceof Font) { - // Set value. Note: Size is handled in fontSizeProperty.native - textView.setTypeface(value.getAndroidTypeface()); - } - else { - // Reset value. Note: Resetting fontInternal will reset the size also. - textView.setTypeface(value.typeface); - textView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.fontSize); - } + set [fontInternalProperty.native](value: Font | android.graphics.Typeface) { + this._nativeView.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value); } //TextAlignment @@ -176,6 +164,7 @@ export class TextBase extends TextBaseCommon { } } + //LetterSpacing get [letterSpacingProperty.native](): number { return org.nativescript.widgets.ViewHelper.getLetterspacing(this._nativeView); } @@ -224,7 +213,7 @@ class TextTransformation extends android.text.method.ReplacementTransformationMe } protected getOriginal(): native.Array { - return convertStringToNativeCharArray(this.formattedText ? this.formattedText.toString() : this.originalText); + return convertStringToNativeCharArray(this.formattedText ? this.formattedText.toString() : this.originalText); } protected getReplacement(): native.Array { @@ -239,7 +228,7 @@ class TextTransformation extends android.text.method.ReplacementTransformationMe replacementString = getTransformedText(this.originalText, this.textTransform); } - return convertStringToNativeCharArray(replacementString); + return convertStringToNativeCharArray(replacementString); } public getTransformation(charSeq: any, view: android.view.View): any { @@ -281,26 +270,23 @@ function getTransformedText(text: string, textTransform: TextTransform): string function createSpannableStringBuilder(formattedString: FormattedString): android.text.SpannableStringBuilder { let ssb = new android.text.SpannableStringBuilder(); - if (formattedString === null || formattedString === undefined){ + if (formattedString === null || formattedString === undefined) { return ssb; } - for (let i = 0, spanStart = 0, spanLength = 0, spanText = "", length = formattedString.spans.length; i < length; i++) { - let span = formattedString.spans.getItem(i); - spanText = toUIString(span.text); - if (formattedString.parent){ - let textTransform = (formattedString.parent).textTransform; - if (textTransform){ - spanText = getTransformedText(spanText, textTransform); - } + for (let i = 0, spanStart = 0, spanLength = 0, length = formattedString.spans.length; i < length; i++) { + const span = formattedString.spans.getItem(i); + const text = span.text; + const textTransform = (formattedString.parent).textTransform; + let spanText = (text === null || text === undefined) ? '' : text.toString(); + if (textTransform) { + spanText = getTransformedText(spanText, textTransform); } + spanLength = spanText.length; if (spanLength !== 0) { ssb.insert(spanStart, spanText); - span.updateSpanModifiers(formattedString); - for (let p = 0, spanModifiersLength = span.spanModifiers.length; p < spanModifiersLength; p++) { - ssb.setSpan(span.spanModifiers[p], spanStart, spanStart + spanLength, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } + setSpanModifiers(ssb, span, spanStart, spanStart + spanLength); spanStart += spanLength; } } @@ -315,3 +301,105 @@ function convertStringToNativeCharArray(value: string): native.Array { } return nativeCharArray; } + +function isBold(fontWeight: FontWeight): boolean { + return fontWeight === FontWeight.BOLD + || fontWeight === "700" + || fontWeight === FontWeight.EXTRA_BOLD + || fontWeight === FontWeight.BLACK; +} + +function setSpanModifiers(ssb: android.text.SpannableStringBuilder, span: Span, start: number, end: number): void { + const style = span.style; + const bold = isBold(style.fontWeight); + const italic = style.fontStyle === FontStyle.ITALIC; + + if (bold && italic) { + ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD_ITALIC), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + else if (bold) { + ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + else if (italic) { + ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.ITALIC), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + const fontFamily = span.fontFamily; + if (fontFamily) { + const font = new Font(fontFamily, 0, (italic) ? "italic" : "normal", (bold) ? "bold" : "normal"); + ensureCustomTypefaceSpanClass(); + const typefaceSpan: android.text.style.TypefaceSpan = new CustomTypefaceSpanClass(fontFamily, font.getAndroidTypeface()); + ssb.setSpan(typefaceSpan, start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + const realFontSize = span.fontSize; + if (realFontSize) { + ssb.setSpan(new android.text.style.AbsoluteSizeSpan(realFontSize * layout.getDisplayDensity()), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + const color = span.color; + if (color) { + ssb.setSpan(new android.text.style.ForegroundColorSpan(color.android), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + const backgroundColor = style.backgroundColor || (span.parent).backgroundColor || ((span.parent).parent).backgroundColor; + if (backgroundColor) { + ssb.setSpan(new android.text.style.BackgroundColorSpan(backgroundColor.android), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + const textDecorations = style.textDecoration || (span.parent).textDecoration || ((span.parent).parent).textDecoration; + const underline = textDecorations.indexOf(TextDecoration.UNDERLINE) !== -1; + if (underline) { + ssb.setSpan(new android.text.style.UnderlineSpan(), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + const strikethrough = textDecorations.indexOf(TextDecoration.LINE_THROUGH) !== -1; + if (strikethrough) { + ssb.setSpan(new android.text.style.StrikethroughSpan(), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } +} + +var CustomTypefaceSpanClass; +function ensureCustomTypefaceSpanClass() { + if (CustomTypefaceSpanClass) { + return; + } + + // TODO: Move this class in widgets. + class CustomTypefaceSpan extends android.text.style.TypefaceSpan { + private typeface: android.graphics.Typeface; + + constructor(family: string, typeface: android.graphics.Typeface) { + super(family); + this.typeface = typeface; + return global.__native(this); + } + + public updateDrawState(ds: android.text.TextPaint): void { + this.applyCustomTypeFace(ds); + } + + public updateMeasureState(paint: android.text.TextPaint): void { + this.applyCustomTypeFace(paint); + } + + private applyCustomTypeFace(paint: android.text.TextPaint) { + const old = paint.getTypeface(); + const oldStyle = old === null ? 0 : old.getStyle(); + + const typeface = this.typeface; + let fake = oldStyle & ~typeface.getStyle(); + if ((fake & android.graphics.Typeface.BOLD) !== 0) { + paint.setFakeBoldText(true); + } + + if ((fake & android.graphics.Typeface.ITALIC) !== 0) { + paint.setTextSkewX(-0.25); + } + + paint.setTypeface(typeface); + } + } + + CustomTypefaceSpanClass = CustomTypefaceSpan; +} \ No newline at end of file diff --git a/tns-core-modules/ui/text-base/text-base.ios.ts b/tns-core-modules/ui/text-base/text-base.ios.ts index 5749d8116..a2acf11d6 100644 --- a/tns-core-modules/ui/text-base/text-base.ios.ts +++ b/tns-core-modules/ui/text-base/text-base.ios.ts @@ -1,15 +1,20 @@ import { TextBaseCommon, textProperty, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, letterSpacingProperty, colorProperty, fontInternalProperty, Font, Color, FormattedString, - TextDecoration, TextAlignment, TextTransform + TextDecoration, TextAlignment, TextTransform, Span } from "./text-base-common"; + +import { FontWeight, FontStyle } from "ui/styling/font"; import * as utils from "utils/utils"; -import { toUIString } from "utils/types"; export * from "./text-base-common"; export class TextBase extends TextBaseCommon { + private textDecorationSet: boolean; + private textTransformSet: boolean; + private letterSpacingSet: boolean; + public nativeView: UITextField | UITextView | UILabel | UIButton; //Text @@ -22,21 +27,17 @@ export class TextBase extends TextBaseCommon { } } set [textProperty.native](value: string) { - let newValue = (value === undefined || value === null) ? '' : value.toString(); - let nativeView = this.nativeView; - if (nativeView instanceof UIButton) { + const newValue = (value === undefined || value === null) ? '' : value.toString(); + const nativeView = this.nativeView; + if (this.textDecorationSet || this.textTransformSet || this.letterSpacingSet) { + const style = this.style; + setTextDecorationAndTransform(newValue, nativeView, style.textDecoration, style.textTransform, style.letterSpacing, style.color); + } else if (nativeView instanceof UIButton) { nativeView.setTitleForState(newValue, UIControlState.Normal); - - //https://github.com/NativeScript/NativeScript/issues/2615 - let attributedTitle = nativeView.attributedTitleForState(UIControlState.Normal); - if (attributedTitle !== null) { - let style = this.style; - setTextDecorationAndTransform(newValue, this.nativeView, style.textDecoration, style.textTransform, style.letterSpacing, style.color); - } - } - else { + } else { nativeView.text = newValue; } + this._requestLayoutOnTextChanged(); } @@ -45,15 +46,9 @@ export class TextBase extends TextBaseCommon { return null; } set [formattedTextProperty.native](value: FormattedString) { - let mas = createNSMutableAttributedString(value); - let nativeView = this.nativeView; - if (nativeView instanceof UIButton) { - nativeView.setAttributedTitleForState(mas, UIControlState.Normal); - } - else { - nativeView.attributedText = mas; - } - textProperty.nativeValueChange(this, (value === null || value === undefined) ? '' : value.toString()); + const style = this.style; + setFormattedTextDecorationAndTransform(value, this.nativeView, style.textDecoration, style.textTransform, style.letterSpacing); + textProperty.nativeValueChange(this, !value ? '' : value.toString()); } //Color @@ -80,7 +75,7 @@ export class TextBase extends TextBaseCommon { nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView; return nativeView.font; } - set [fontInternalProperty.native](value: Font) { + set [fontInternalProperty.native](value: Font | UIFont) { let nativeView = this.nativeView; nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView; let font = value instanceof Font ? value.getUIFont(nativeView.font) : value; @@ -127,6 +122,7 @@ export class TextBase extends TextBaseCommon { return TextDecoration.NONE; } set [textDecorationProperty.native](value: TextDecoration) { + this.textDecorationSet = value !== TextDecoration.NONE; if (this.formattedText) { setFormattedTextDecorationAndTransform(this.formattedText, this.nativeView, value, this.style.textTransform, this.style.letterSpacing); } else { @@ -139,6 +135,7 @@ export class TextBase extends TextBaseCommon { return TextTransform.NONE; } set [textTransformProperty.native](value: TextTransform) { + this.textTransformSet = value !== TextTransform.NONE; if (this.formattedText) { setFormattedTextDecorationAndTransform(this.formattedText, this.nativeView, this.style.textDecoration, value, this.style.letterSpacing); } else { @@ -151,6 +148,7 @@ export class TextBase extends TextBaseCommon { return Number.NaN; } set [letterSpacingProperty.native](value: number) { + this.letterSpacingSet = value !== 0; if (this.formattedText) { setFormattedTextDecorationAndTransform(this.formattedText, this.nativeView, this.style.textDecoration, this.style.textTransform, value); } else { @@ -178,55 +176,18 @@ function NSStringFromNSAttributedString(source: NSAttributedString | string): NS return NSString.stringWithString(source instanceof NSAttributedString && source.string || source); } -function updateFormattedStringTextDecoration(formattedText: FormattedString, textDecoration: TextDecoration): void { - // TODO: Refactor this method so it doesn't modify FormattedString properties. - // Instead it should create NSAttributedString and apply it to the nativeView. - switch (textDecoration) { - case TextDecoration.NONE: - formattedText.underline = NSUnderlineStyle.StyleNone; - formattedText.strikethrough = NSUnderlineStyle.StyleNone; - break; - case TextDecoration.UNDERLINE: - formattedText.underline = NSUnderlineStyle.StyleSingle; - formattedText.strikethrough = NSUnderlineStyle.StyleNone; - break; - case TextDecoration.LINE_THROUGH: - formattedText.underline = NSUnderlineStyle.StyleNone; - formattedText.strikethrough = NSUnderlineStyle.StyleSingle; - break; - case TextDecoration.UNDERLINE_LINE_THROUGH: - formattedText.underline = NSUnderlineStyle.StyleSingle; - formattedText.strikethrough = NSUnderlineStyle.StyleSingle; - break; - default: - throw new Error(`Invalid text decoration value: ${textDecoration}. Valid values are: "${TextDecoration.NONE}", "${TextDecoration.UNDERLINE}", "${TextDecoration.LINE_THROUGH}", "${TextDecoration.UNDERLINE_LINE_THROUGH}".`); - } -} - -function updateFormattedStringTextTransformation(formattedText: FormattedString, textTransform: TextTransform): void { - // TODO: Refactor this method so it doesn't modify Span properties. - // Instead it should create NSAttributedString and apply it to the nativeView. - for (let i = 0, length = formattedText.spans.length; i < length; i++) { - let span = formattedText.spans.getItem(i); - span.text = getTransformedText(span.text, textTransform); - } -} - function setFormattedTextDecorationAndTransform(formattedText: FormattedString, nativeView: UITextField | UITextView | UILabel | UIButton, textDecoration: TextDecoration, textTransform: TextTransform, letterSpacing: number) { - updateFormattedStringTextDecoration(formattedText, textDecoration); - updateFormattedStringTextTransformation(formattedText, textTransform); - + const attrText = createNSMutableAttributedString(formattedText); const hasLetterSpacing = typeof letterSpacing === "number" && !isNaN(letterSpacing) && letterSpacing !== 0; if (hasLetterSpacing) { - if (nativeView instanceof UIButton) { - let attrText = NSMutableAttributedString.alloc().initWithAttributedString(nativeView.attributedTitleForState(UIControlState.Normal)); - attrText.addAttributeValueRange(NSKernAttributeName, letterSpacing * nativeView.font.pointSize, { location: 0, length: attrText.length }); - nativeView.setAttributedTitleForState(attrText, UIControlState.Normal); - } else { - let attrText = NSMutableAttributedString.alloc().initWithAttributedString(nativeView.attributedText); - attrText.addAttributeValueRange(NSKernAttributeName, letterSpacing * nativeView.font.pointSize, { location: 0, length: attrText.length }); - nativeView.attributedText = attrText; - } + attrText.addAttributeValueRange(NSKernAttributeName, letterSpacing * nativeView.font.pointSize, { location: 0, length: attrText.length }); + } + + if (nativeView instanceof UIButton) { + nativeView.setAttributedTitleForState(attrText, UIControlState.Normal); + } + else { + nativeView.attributedText = attrText; } } @@ -284,19 +245,91 @@ function setTextDecorationAndTransform(text: string, nativeView: UITextField | U function createNSMutableAttributedString(formattedString: FormattedString): NSMutableAttributedString { let mas = NSMutableAttributedString.alloc().init(); if (formattedString) { - for (let i = 0, spanStart = 0, spanLength = 0, length = formattedString.spans.length, spanText = ""; i < length; i++) { - let span = formattedString.spans.getItem(i); - spanText = toUIString(span.text); - spanLength = spanText.length; - span.updateSpanModifiers(formattedString); - let attrDict = NSMutableDictionary.alloc().init(); - for (let p = 0; p < span.spanModifiers.length; p++) { - attrDict.setObjectForKey(span.spanModifiers[p].value, span.spanModifiers[p].key); + for (let i = 0, spanStart = 0, spanLength = 0, length = formattedString.spans.length; i < length; i++) { + const span = formattedString.spans.getItem(i); + const text = span.text; + const textTransform = (formattedString.parent).textTransform; + let spanText = (text === null || text === undefined) ? '' : text.toString(); + if (textTransform) { + spanText = getTransformedText(spanText, textTransform); } - let nsAttributedString = NSMutableAttributedString.alloc().initWithStringAttributes(String(spanText), attrDict); + + spanLength = spanText.length; + const nsAttributedString = createMutableStringForSpan(span, spanText); mas.insertAttributedStringAtIndex(nsAttributedString, spanStart); spanStart += spanLength; } } return mas; } + +function isBold(fontWeight: FontWeight): boolean { + return fontWeight === FontWeight.BOLD + || fontWeight === "700" + || fontWeight === FontWeight.EXTRA_BOLD + || fontWeight === FontWeight.BLACK; +} + +function createMutableStringForSpan(span: Span, text: string): NSMutableAttributedString { + let attrDict = <{ key: string, value: any }>{}; + const style = span.style; + const bold = isBold(style.fontWeight); + const italic = style.fontStyle === FontStyle.ITALIC; + + let fontFamily = span.fontFamily; + let fontSize = span.fontSize; + + if (bold || italic || fontFamily || fontSize) { + let font; + if (fontFamily) { + if (!fontSize) { + fontSize = utils.ios.getter(UIFont, UIFont.systemFontSize); + } + + font = UIFont.fontWithNameSize(fontFamily, fontSize); + } + + if (!font) { + const fontDescriptor = UIFontDescriptor.new(); + let symbolicTraits; + if (bold) { + symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitBold; + } + + if (italic) { + symbolicTraits |= UIFontDescriptorSymbolicTraits.TraitItalic; + } + + font = UIFont.fontWithDescriptorSize(fontDescriptor.fontDescriptorWithSymbolicTraits(symbolicTraits), fontSize); + } + + attrDict[NSFontAttributeName] = font; + } + + const color = span.color; + if (color) { + attrDict[NSForegroundColorAttributeName] = color.ios; + } + + const backgroundColor = style.backgroundColor + || (span.parent).backgroundColor + || ((span.parent).parent).backgroundColor; + if (backgroundColor) { + attrDict[NSBackgroundColorAttributeName] = backgroundColor.ios; + } + + const textDecorations = style.textDecoration + || (span.parent).textDecoration + || ((span.parent).parent).textDecoration; + const underline = textDecorations.indexOf(TextDecoration.UNDERLINE) !== -1; + if (underline) { + attrDict[NSUnderlineStyleAttributeName] = underline; + } + + const strikethrough = textDecorations.indexOf(TextDecoration.LINE_THROUGH) !== -1; + if (strikethrough) { + attrDict[NSStrikethroughStyleAttributeName] = strikethrough; + } + + return NSMutableAttributedString.alloc().initWithStringAttributes(text, attrDict); +} \ No newline at end of file