From c96bddfe42521516b52ece673e770e2e24a878ce Mon Sep 17 00:00:00 2001 From: bykov Date: Fri, 23 Dec 2016 13:23:25 +0300 Subject: [PATCH 1/6] Refactor a metadata generator unit test --- tools/spec/tests/metadata-generator.spec.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/spec/tests/metadata-generator.spec.js b/tools/spec/tests/metadata-generator.spec.js index 88ba57e0e..6fbfddd15 100644 --- a/tools/spec/tests/metadata-generator.spec.js +++ b/tools/spec/tests/metadata-generator.spec.js @@ -252,19 +252,20 @@ describe("metadata-generator", function() { .filter(args => args[0] === path).length; }; - expect(writeToPathCount(path.join("output-path", "nested", "nested-external-property.json"))).toBe(1); + expect(writeToPathCount(path.join("output-path", "complex-widget.json"))).toBe(1); + expect(writeToPathCount(path.join("output-path", "another-complex-widget.json"))).toBe(1); expect(writeToPathCount(path.join("output-path", "nested", "base", "external-property-type.json"))).toBe(1); + expect(writeToPathCount(path.join("output-path", "nested", "base", "external-property-type-dxi.json"))).toBe(1); + expect(writeToPathCount(path.join("output-path", "nested", "base", "another-complex-widget-options.json"))).toBe(1); expect(writeToPathCount(path.join("output-path", "nested", "property.json"))).toBe(1); expect(writeToPathCount(path.join("output-path", "nested", "nested.json"))).toBe(1); expect(writeToPathCount(path.join("output-path", "nested", "nested-item-dxi.json"))).toBe(1); + expect(writeToPathCount(path.join("output-path", "nested", "nested-external-property.json"))).toBe(1); expect(writeToPathCount(path.join("output-path", "nested", "collection-item.json"))).toBe(1); expect(writeToPathCount(path.join("output-path", "nested", "collection-item-dxi.json"))).toBe(1); - expect(writeToPathCount(path.join("output-path", "nested", "external-property.json"))).toBe(1); expect(writeToPathCount(path.join("output-path", "nested", "collection-item-with-template-dxi.json"))).toBe(1); - expect(writeToPathCount(path.join("output-path", "nested", "nested-external-property.json"))).toBe(1); - expect(writeToPathCount(path.join("output-path", "nested", "base", "another-complex-widget-options.json"))).toBe(1); expect(writeToPathCount(path.join("output-path", "nested", "widget-reference.json"))).toBe(1); - expect(writeToPathCount(path.join("output-path", "nested", "base", "external-property-type-dxi.json"))).toBe(1); + expect(writeToPathCount(path.join("output-path", "nested", "external-property.json"))).toBe(1); expect(writeToPathCount(path.join("output-path", "nested", "external-property-item-dxi.json"))).toBe(1); }); From a6977bc3ace80402c25386a22d84714bec2571f7 Mon Sep 17 00:00:00 2001 From: bykov Date: Fri, 23 Dec 2016 13:57:07 +0300 Subject: [PATCH 2/6] Support several complex types in the metadata type options --- tools/spec/tests/metadata-generator.spec.js | 98 +++++++++++++++++++++ tools/src/metadata-generator.ts | 68 +++++++++----- 2 files changed, 142 insertions(+), 24 deletions(-) diff --git a/tools/spec/tests/metadata-generator.spec.js b/tools/spec/tests/metadata-generator.spec.js index 6fbfddd15..95c5a3c79 100644 --- a/tools/spec/tests/metadata-generator.spec.js +++ b/tools/spec/tests/metadata-generator.spec.js @@ -383,4 +383,102 @@ describe("metadata-generator", function() { }); + describe("collection of complex types", function() { + + beforeEach(function() { + setupContext({ + Widgets: { + dxComplexWidget: { + Options: { + externalProperty: { // DxoExternalProperty + ComplexTypes: [ + 'ExternalPropertyType', + 'ExternalPropertyType2' + ] + }, + externalPropertyItems: { // DxiExternalPropertyItem + IsCollection: true, + SingularName: 'externalPropertyItem', + ComplexTypes: [ + 'ExternalPropertyType', + 'ExternalPropertyType2' + ] + } + }, + Module: 'test_widget' + } + }, + ExtraObjects: { + ExternalPropertyType: { + Options: { + property: { + Options: { + nestedProperty1: {} + } + }, + property1: { + } + } + }, + ExternalPropertyType2: { + Options: { + property: { + Options: { + nestedProperty2: {} + } + }, + property2: { + } + } + } + } + }); + }); + + it("should write generated data to a separate file for each widget", function() { + expect(store.write.calls.count()).toBe(4); + + let writeToPathCount = (path) => { + return store.write.calls + .allArgs() + .filter(args => args[0] === path).length; + }; + + expect(writeToPathCount(path.join("output-path", "complex-widget.json"))).toBe(1); + expect(writeToPathCount(path.join("output-path", "nested", "external-property.json"))).toBe(1); + expect(writeToPathCount(path.join("output-path", "nested", "external-property-item-dxi.json"))).toBe(1); + expect(writeToPathCount(path.join("output-path", "nested", "property.json"))).toBe(1); + }); + + it("should generate matadata", function() { + expect(Object.keys(metas).length).toBe(4); + + expect(metas.DxComplexWidget).not.toBe(undefined); + expect(metas.DxoExternalProperty).not.toBe(undefined); + expect(metas.DxiExternalPropertyItem).not.toBe(undefined); + expect(metas.DxoProperty).not.toBe(undefined); + }); + + it("should generate nested components with merged properties", function() { + expect(metas.DxComplexWidget.nestedComponents.map(c => c.className)).toContain('DxoExternalProperty'); + + expect(metas.DxoExternalProperty.properties.map(p => p.name)).toEqual(['property', 'property1', 'property2']); + expect(metas.DxoExternalProperty.optionName).toBe('externalProperty'); + }); + + it("should generate collection nested components with merged properties", function() { + expect(metas.DxComplexWidget.nestedComponents.map(c => c.className)).toContain('DxiExternalPropertyItem'); + + expect(metas.DxiExternalPropertyItem.properties.map(p => p.name)).toEqual(['property', 'property1', 'property2']); + expect(metas.DxiExternalPropertyItem.optionName).toBe('externalPropertyItems'); + }); + + it("should generate nested components with merged properties of external types", function() { + expect(metas.DxComplexWidget.nestedComponents.map(c => c.className)).toContain('DxoProperty'); + + expect(metas.DxoProperty.properties.map(p => p.name)).toEqual(['nestedProperty1', 'nestedProperty2']); + expect(metas.DxoProperty.optionName).toBe('property'); + }); + }); + }); diff --git a/tools/src/metadata-generator.ts b/tools/src/metadata-generator.ts index 585ee1815..5d18c8d91 100644 --- a/tools/src/metadata-generator.ts +++ b/tools/src/metadata-generator.ts @@ -12,7 +12,10 @@ function trimDx(value: string) { } function trimPrefix(prefix: string, value: string) { - return value.substr(prefix.length); + if(value.indexOf(prefix) === 0) { + return value.substr(prefix.length); + } + return value; } export interface IObjectStore { @@ -143,37 +146,54 @@ export default class DXComponentMetadataGenerator { this.generateNestedOptions(config, allNestedComponents); } + private getExternalObjectInfo(metadata, typeName) { + let externalObject = metadata.ExtraObjects[typeName]; + + if (!externalObject) { + const postfix = 'Options'; + if (typeName.endsWith(postfix)) { + let widgetName = typeName.substr(0, typeName.length - postfix.length); + externalObject = metadata.Widgets[widgetName]; + typeName = trimPrefix('dx', typeName); + } + } + + if (!externalObject) { + console.warn('WARN: missed complex type: ' + typeName); + } + else { + return { + Options: externalObject.Options, + typeName: typeName + }; + } + } + private generateComplexOptionByType(metadata, option, optionName, complexTypes) { if (option.Options) { return this.generateComplexOption(metadata, option.Options, optionName, complexTypes, option); - } else if (option.ComplexTypes && option.ComplexTypes.length === 1) { + } else if (option.ComplexTypes && option.ComplexTypes.length > 0) { if (complexTypes.indexOf(complexTypes[complexTypes.length - 1]) !== complexTypes.length - 1) { return; } - - let complexType = option.ComplexTypes[0]; - let externalObject = metadata.ExtraObjects[complexType]; - - if (!externalObject) { - const postfix = 'Options'; - if (complexType.endsWith(postfix)) { - let widgetName = complexType.substr(0, complexType.length - postfix.length); - externalObject = metadata.Widgets[widgetName]; - complexType = trimPrefix('dx', complexType); + let result = []; + option.ComplexTypes.forEach(complexType => { + let externalObjectInfo = this.getExternalObjectInfo(metadata, complexType); + if (externalObjectInfo) { + let nestedOptions = externalObjectInfo.Options, + nestedComplexTypes = complexTypes.concat(externalObjectInfo.typeName); + + result.push.apply(result, this.generateComplexOption(metadata, nestedOptions, optionName, nestedComplexTypes, option)); + } + }); + if (option.ComplexTypes.length === 1) { + let externalObjectInfo = this.getExternalObjectInfo(metadata, option.ComplexTypes[0]); + if (externalObjectInfo) { + result[0].baseClass = (option.IsCollection ? ITEM_COMPONENT_PREFIX : OPTION_COMPONENT_PREFIX) + externalObjectInfo.typeName; + result[0].basePath = inflector.dasherize(inflector.underscore(externalObjectInfo.typeName)); } } - - if (externalObject) { - let nestedOptions = externalObject.Options; - let nestedComplexTypes = complexTypes.concat(complexType); - - let components = this.generateComplexOption(metadata, nestedOptions, optionName, nestedComplexTypes, option); - components[0].baseClass = (option.IsCollection ? ITEM_COMPONENT_PREFIX : OPTION_COMPONENT_PREFIX) + complexType; - components[0].basePath = inflector.dasherize(inflector.underscore(complexType)); - return components; - } else { - logger('WARN: missed complex type: ' + complexType); - } + return result; } } From add9763dc5186cafa1ab2f354f44b0b8d3436024 Mon Sep 17 00:00:00 2001 From: bykov Date: Wed, 28 Dec 2016 21:51:10 +0300 Subject: [PATCH 3/6] Fix issues after metadata updating --- metadata/StrongMetaData.json | 520 ++++++++++++++++++++++++++++++-- src/core/nested-option.ts | 18 +- templates/nested-component.tst | 7 +- tests/src/ui/form.spec.ts | 90 ++++++ tools/src/metadata-generator.ts | 10 +- 5 files changed, 615 insertions(+), 30 deletions(-) create mode 100644 tests/src/ui/form.spec.ts diff --git a/metadata/StrongMetaData.json b/metadata/StrongMetaData.json index ad28d505b..8a5e026d0 100644 --- a/metadata/StrongMetaData.json +++ b/metadata/StrongMetaData.json @@ -2515,7 +2515,7 @@ }, "formItem": { "ComplexTypes": [ - "dxFormSimpleItem" + "FormSimpleItem" ], "Description": "The form item configuration object. Used only when the editing mode is \"form\"." }, @@ -4816,8 +4816,11 @@ "Description": "An object providing data for the form." }, "items": { - "PrimitiveTypes": [ - "any" + "ComplexTypes": [ + "FormEmptyItem", + "FormGroupItem", + "FormSimpleItem", + "FormTabbedItem" ], "IsCollection": true, "SingularName": "item", @@ -17107,25 +17110,67 @@ "SingularName": "constantLine", "Description": "Declares a collection of constant lines belonging to the argument axis.", "Options": { + "color": { + "PrimitiveTypes": [ + "string" + ], + "Description": "Specifies the color of constant lines." + }, + "dashStyle": { + "EnumName": "DashStyle", + "Description": "Specifies the dash style of constant lines." + }, "label": { "Description": "Configures the constant line label.", "Options": { - "horizontalAlignment": { - "EnumName": "HorizontalAlignment", - "Description": "Aligns constant line labels in the horizontal direction." + "font": { + "ComplexTypes": [ + "VizFont" + ], + "Description": "Specifies font options for constant line labels." }, - "verticalAlignment": { - "EnumName": "VerticalAlignment", - "Description": "Aligns constant line labels in the vertical direction." + "position": { + "EnumName": "RelativePosition", + "Description": "Specifies the position of constant line labels on the chart plot." + }, + "visible": { + "PrimitiveTypes": [ + "bool" + ], + "Description": "Makes constant line labels visible." + }, + "horizontalAlignment": { + "EnumName": "HorizontalAlignment" }, "text": { "PrimitiveTypes": [ "string" ], "Description": "Specifies the text of a constant line label. By default, equals to the value of the constant line." + }, + "verticalAlignment": { + "EnumName": "VerticalAlignment" } } }, + "paddingLeftRight": { + "PrimitiveTypes": [ + "double" + ], + "Description": "Generates a pixel-measured empty space between the left/right side of a constant line and the constant line label." + }, + "paddingTopBottom": { + "PrimitiveTypes": [ + "double" + ], + "Description": "Generates a pixel-measured empty space between the top/bottom side of a constant line and the constant line label." + }, + "width": { + "PrimitiveTypes": [ + "double" + ], + "Description": "Specifies the width of constant lines in pixels." + }, "value": { "PrimitiveTypes": [ "date", @@ -18718,25 +18763,67 @@ "SingularName": "constantLine", "Description": "Declares a collection of constant lines belonging to the value axis.", "Options": { + "color": { + "PrimitiveTypes": [ + "string" + ], + "Description": "Specifies the color of constant lines." + }, + "dashStyle": { + "EnumName": "DashStyle", + "Description": "Specifies the dash style of constant lines." + }, "label": { "Description": "Configures the constant line label.", "Options": { - "horizontalAlignment": { - "EnumName": "HorizontalAlignment", - "Description": "Aligns constant line labels in the horizontal direction." + "font": { + "ComplexTypes": [ + "VizFont" + ], + "Description": "Specifies font options for constant line labels." }, - "verticalAlignment": { - "EnumName": "VerticalAlignment", - "Description": "Aligns constant line labels in the vertical direction." + "position": { + "EnumName": "RelativePosition", + "Description": "Specifies the position of constant line labels on the chart plot." + }, + "visible": { + "PrimitiveTypes": [ + "bool" + ], + "Description": "Makes constant line labels visible." + }, + "horizontalAlignment": { + "EnumName": "HorizontalAlignment" }, "text": { "PrimitiveTypes": [ "string" ], "Description": "Specifies the text of a constant line label. By default, equals to the value of the constant line." + }, + "verticalAlignment": { + "EnumName": "VerticalAlignment" } } }, + "paddingLeftRight": { + "PrimitiveTypes": [ + "double" + ], + "Description": "Generates a pixel-measured empty space between the left/right side of a constant line and the constant line label." + }, + "paddingTopBottom": { + "PrimitiveTypes": [ + "double" + ], + "Description": "Generates a pixel-measured empty space between the top/bottom side of a constant line and the constant line label." + }, + "width": { + "PrimitiveTypes": [ + "double" + ], + "Description": "Specifies the width of constant lines in pixels." + }, "value": { "PrimitiveTypes": [ "date", @@ -24181,8 +24268,9 @@ "Description": "Indicates whether or not animation is enabled." }, "callSelectedRangeChanged": { + "IsDeprecated": true, "EnumName": "ValueChangedCallMode", - "Description": "Specifies when to call the onSelectedRangeChanged function." + "Description": "Use the callValueChanged option instead." }, "callValueChanged": { "EnumName": "ValueChangedCallMode", @@ -24378,9 +24466,10 @@ } }, "onSelectedRangeChanged": { + "IsDeprecated": true, "IsFunc": true, "IsEvent": true, - "Description": "A handler for the selectedRangeChanged event." + "Description": "Use the onValueChanged option instead." }, "onValueChanged": { "IsFunc": true, @@ -24930,7 +25019,7 @@ } }, "selectedRange": { - "Description": "Specifies the range to be selected when displaying the RangeSelector.", + "Description": "Use the value option instead.", "Options": { "endValue": { "PrimitiveTypes": [ @@ -28393,6 +28482,323 @@ } } }, + "FormEmptyItem": { + "Options": { + "colSpan": { + "PrimitiveTypes": [ + "int32" + ] + }, + "cssClass": { + "PrimitiveTypes": [ + "string" + ] + }, + "itemType": { + "EnumName": "FormItemType" + }, + "name": { + "PrimitiveTypes": [ + "string" + ] + }, + "visible": { + "PrimitiveTypes": [ + "bool" + ] + }, + "visibleIndex": { + "PrimitiveTypes": [ + "int32" + ] + } + } + }, + "FormGroupItem": { + "Options": { + "alignItemLabels": { + "PrimitiveTypes": [ + "bool" + ] + }, + "caption": { + "PrimitiveTypes": [ + "string" + ] + }, + "colCount": { + "PrimitiveTypes": [ + "int32" + ] + }, + "colCountByScreen": { + "Options": { + "lg": { + "PrimitiveTypes": [ + "int32" + ], + "Description": "The count of columns for a large screen size." + }, + "md": { + "PrimitiveTypes": [ + "int32" + ], + "Description": "The count of columns for a middle-sized screen." + }, + "sm": { + "PrimitiveTypes": [ + "int32" + ], + "Description": "The count of columns for a small-sized screen." + }, + "xs": { + "PrimitiveTypes": [ + "int32" + ], + "Description": "The count of columns for an extra small-sized screen." + } + } + }, + "colSpan": { + "PrimitiveTypes": [ + "int32" + ] + }, + "cssClass": { + "PrimitiveTypes": [ + "string" + ] + }, + "items": { + "ComplexTypes": [ + "FormEmptyItem", + "FormGroupItem", + "FormSimpleItem", + "FormTabbedItem" + ], + "IsCollection": true, + "SingularName": "item" + }, + "itemType": { + "EnumName": "FormItemType" + }, + "template": { + "IsTemplate": true + }, + "visible": { + "PrimitiveTypes": [ + "bool" + ] + }, + "visibleIndex": { + "PrimitiveTypes": [ + "int32" + ] + } + } + }, + "FormSimpleItem": { + "Options": { + "colSpan": { + "PrimitiveTypes": [ + "int32" + ] + }, + "cssClass": { + "PrimitiveTypes": [ + "string" + ] + }, + "dataField": { + "PrimitiveTypes": [ + "string" + ] + }, + "editorOptions": { + "PrimitiveTypes": [ + "any" + ] + }, + "editorType": { + "EnumName": "FormItemEditorType" + }, + "helpText": { + "PrimitiveTypes": [ + "string" + ] + }, + "isRequired": { + "PrimitiveTypes": [ + "bool" + ] + }, + "itemType": { + "EnumName": "FormItemType" + }, + "label": { + "Options": { + "alignment": { + "EnumName": "HorizontalAlignment" + }, + "location": { + "EnumName": "FormLabelLocation" + }, + "showColon": { + "PrimitiveTypes": [ + "bool" + ] + }, + "text": { + "PrimitiveTypes": [ + "string" + ] + }, + "visible": { + "PrimitiveTypes": [ + "bool" + ] + } + } + }, + "name": { + "PrimitiveTypes": [ + "string" + ] + }, + "template": { + "IsTemplate": true + }, + "validationRules": { + "PrimitiveTypes": [ + "any" + ], + "IsCollection": true, + "SingularName": "validationRule" + }, + "visible": { + "PrimitiveTypes": [ + "bool" + ] + }, + "visibleIndex": { + "PrimitiveTypes": [ + "int32" + ] + } + } + }, + "FormTabbedItem": { + "Options": { + "colSpan": { + "PrimitiveTypes": [ + "int32" + ] + }, + "cssClass": { + "PrimitiveTypes": [ + "string" + ] + }, + "itemType": { + "EnumName": "FormItemType" + }, + "tabPanelOptions": { + "ComplexTypes": [ + "dxTabPanelOptions" + ] + }, + "tabs": { + "IsCollection": true, + "SingularName": "tab", + "Options": { + "alignItemLabels": { + "PrimitiveTypes": [ + "bool" + ] + }, + "badge": { + "PrimitiveTypes": [ + "string" + ] + }, + "colCount": { + "PrimitiveTypes": [ + "int32" + ] + }, + "colCountByScreen": { + "Options": { + "lg": { + "PrimitiveTypes": [ + "int32" + ], + "Description": "The count of columns for a large screen size." + }, + "md": { + "PrimitiveTypes": [ + "int32" + ], + "Description": "The count of columns for a middle-sized screen." + }, + "sm": { + "PrimitiveTypes": [ + "int32" + ], + "Description": "The count of columns for a small-sized screen." + }, + "xs": { + "PrimitiveTypes": [ + "int32" + ], + "Description": "The count of columns for an extra small-sized screen." + } + } + }, + "disabled": { + "PrimitiveTypes": [ + "bool" + ] + }, + "icon": { + "PrimitiveTypes": [ + "string" + ] + }, + "items": { + "ComplexTypes": [ + "FormEmptyItem", + "FormGroupItem", + "FormSimpleItem", + "FormTabbedItem" + ], + "IsCollection": true, + "SingularName": "item" + }, + "tabTemplate": { + "IsTemplate": true + }, + "template": { + "IsTemplate": true + }, + "title": { + "PrimitiveTypes": [ + "string" + ] + } + } + }, + "visible": { + "PrimitiveTypes": [ + "bool" + ] + }, + "visibleIndex": { + "PrimitiveTypes": [ + "int32" + ] + } + } + }, "VizFont": { "Description": "Font options.", "Options": { @@ -33461,6 +33867,84 @@ } ] }, + "FormItemType": { + "Items": [ + { + "Name": "empty" + }, + { + "Name": "group" + }, + { + "Name": "simple" + }, + { + "Name": "tabbed" + } + ] + }, + "FormItemEditorType": { + "Items": [ + { + "Name": "dxAutocomplete" + }, + { + "Name": "dxCalendar" + }, + { + "Name": "dxCheckBox" + }, + { + "Name": "dxColorBox" + }, + { + "Name": "dxDateBox" + }, + { + "Name": "dxLookup" + }, + { + "Name": "dxNumberBox" + }, + { + "Name": "dxRadioGroup" + }, + { + "Name": "dxSelectBox" + }, + { + "Name": "dxSlider" + }, + { + "Name": "dxSwitch" + }, + { + "Name": "dxTagBox" + }, + { + "Name": "dxTextArea" + }, + { + "Name": "dxTextBox" + } + ], + "Renamings": { + "dxAutocomplete": "Autocomplete", + "dxCalendar": "Calendar", + "dxCheckBox": "CheckBox", + "dxColorBox": "ColorBox", + "dxDateBox": "DateBox", + "dxLookup": "Lookup", + "dxNumberBox": "NumberBox", + "dxRadioGroup": "RadioGroup", + "dxSelectBox": "SelectBox", + "dxSlider": "Slider", + "dxSwitch": "Switch", + "dxTagBox": "TagBox", + "dxTextArea": "TextArea", + "dxTextBox": "TextBox" + } + }, "VizTheme": { "Items": [ { diff --git a/src/core/nested-option.ts b/src/core/nested-option.ts index 152cad292..71c98ba7f 100644 --- a/src/core/nested-option.ts +++ b/src/core/nested-option.ts @@ -35,7 +35,7 @@ export abstract class NestedOption implements INestedOptionContainer, ICollectio protected abstract get _optionPath(): string; constructor(private _element: ElementRef) { - this._collectionContainerImpl = new CollectionNestedOptionContainerImpl(this._setOption.bind(this)); + this._collectionContainerImpl = new CollectionNestedOptionContainerImpl(this._setOption.bind(this), this._filterItems.bind(this)); } setHost(host: INestedOptionContainer, optionPath: OptionPathGetter) { @@ -45,7 +45,7 @@ export abstract class NestedOption implements INestedOptionContainer, ICollectio } _template(...args) { - let container = args[2]; + let container = args[2] || args[1] || args[0]; return container.append(this._element.nativeElement); } @@ -53,6 +53,10 @@ export abstract class NestedOption implements INestedOptionContainer, ICollectio return this._collectionContainerImpl.setChildren(propertyName, items); } + _filterItems(items: QueryList) { + return items.filter((item) => { return item !== this; }); + } + get instance() { return this._host && this._host.instance; } @@ -65,13 +69,16 @@ export interface ICollectionNestedOptionContainer { export class CollectionNestedOptionContainerImpl implements ICollectionNestedOptionContainer { private _activatedQueries = {}; - constructor(private _setOption: Function) { + constructor(private _setOption: Function, private _filterItems?: Function) { } setChildren(propertyName: string, items: QueryList) { if (items.length) { this._activatedQueries[propertyName] = true; } if (this._activatedQueries[propertyName]) { + if (this._filterItems) { + items = this._filterItems(items); + } let widgetItems = items.map((item, index) => { item._index = index; return item._value; @@ -90,7 +97,10 @@ export abstract class CollectionNestedOption extends NestedOption implements ICo _index: number; protected _getOptionPath() { - return this._hostOptionPath() + this._optionPath + '[' + this._index + ']' + '.'; + if (this._index !== undefined) { + return this._hostOptionPath() + this._optionPath + '[' + this._index + ']' + '.'; + } + return ''; } get _value() { diff --git a/templates/nested-component.tst b/templates/nested-component.tst index bf99d6c76..5fc0d25a4 100644 --- a/templates/nested-component.tst +++ b/templates/nested-component.tst @@ -8,13 +8,14 @@ import { SkipSelf<#? it.properties #>, Input<#?#><#? it.collectionNestedComponents.length #>, ContentChildren, + forwardRef, QueryList<#?#> } from '@angular/core'; import { NestedOptionHost } from '../../core/nested-option'; import { <#= it.baseClass #> } from '<#= it.basePath #>'; -<#~ it.collectionNestedComponents :component:i #>import { <#= component.className #>Component } from './<#= component.path #>'; -<#~#> +<#~ it.collectionNestedComponents :component:i #><#? component.className !== it.className #>import { <#= component.className #>Component } from './<#= component.path #>'; +<#?#><#~#> @Component({ selector: '<#= it.selector #>', @@ -38,7 +39,7 @@ export class <#= it.className #>Component extends <#= it.baseClass #> {<#~ it.pr } <#~ it.collectionNestedComponents :component:i #> - @ContentChildren(<#= component.className #>Component) + @ContentChildren(forwardRef(() => <#= component.className #>Component)) get <#= component.propertyName #>Children(): QueryList<<#= component.className #>Component> { return this._getOption('<#= component.propertyName #>'); } diff --git a/tests/src/ui/form.spec.ts b/tests/src/ui/form.spec.ts new file mode 100644 index 000000000..81dfeda10 --- /dev/null +++ b/tests/src/ui/form.spec.ts @@ -0,0 +1,90 @@ +/* tslint:disable:component-selector */ + +import { + Component, + ViewChildren, + QueryList +} from '@angular/core'; + +import { + TestBed, + async +} from '@angular/core/testing'; + +import DxForm from 'devextreme/ui/form'; + +import { + DxFormModule, + DxFormComponent +} from '../../../dist'; + +@Component({ + selector: 'test-container-component', + template: '' +}) +class TestContainerComponent { + formData = { + name: 'Unknown' + }; + @ViewChildren(DxFormComponent) innerWidgets: QueryList; +} + +describe('DxForm', () => { + + beforeEach(() => { + TestBed.configureTestingModule( + { + declarations: [TestContainerComponent], + imports: [DxFormModule] + }); + }); + + function getWidget(fixture) { + let widgetElement = fixture.nativeElement.querySelector('.dx-form') || fixture.nativeElement; + return DxForm['getInstance'](widgetElement); + } + + // spec + it('should be able to accept items via nested dxi components (T459714)', async(() => { + TestBed.overrideComponent(TestContainerComponent, { + set: { + template: ` + + + + + ` + } + }); + let fixture = TestBed.createComponent(TestContainerComponent); + fixture.detectChanges(); + + let instance = getWidget(fixture); + expect(instance.element().find('.dx-textbox').length).toBe(1); + })); + + it('should be able to accept items recursively', async(() => { + TestBed.overrideComponent(TestContainerComponent, { + set: { + template: ` + + + + + + + + + + + ` + } + }); + let fixture = TestBed.createComponent(TestContainerComponent); + fixture.detectChanges(); + + let instance = getWidget(fixture); + expect(instance.element().find('.dx-textbox').length).toBe(2); + })); + +}); diff --git a/tools/src/metadata-generator.ts b/tools/src/metadata-generator.ts index 5d18c8d91..631a79a82 100644 --- a/tools/src/metadata-generator.ts +++ b/tools/src/metadata-generator.ts @@ -12,7 +12,7 @@ function trimDx(value: string) { } function trimPrefix(prefix: string, value: string) { - if(value.indexOf(prefix) === 0) { + if (value.indexOf(prefix) === 0) { return value.substr(prefix.length); } return value; @@ -157,11 +157,10 @@ export default class DXComponentMetadataGenerator { typeName = trimPrefix('dx', typeName); } } - + if (!externalObject) { console.warn('WARN: missed complex type: ' + typeName); - } - else { + } else { return { Options: externalObject.Options, typeName: typeName @@ -189,7 +188,8 @@ export default class DXComponentMetadataGenerator { if (option.ComplexTypes.length === 1) { let externalObjectInfo = this.getExternalObjectInfo(metadata, option.ComplexTypes[0]); if (externalObjectInfo) { - result[0].baseClass = (option.IsCollection ? ITEM_COMPONENT_PREFIX : OPTION_COMPONENT_PREFIX) + externalObjectInfo.typeName; + result[0].baseClass = + (option.IsCollection ? ITEM_COMPONENT_PREFIX : OPTION_COMPONENT_PREFIX) + externalObjectInfo.typeName; result[0].basePath = inflector.dasherize(inflector.underscore(externalObjectInfo.typeName)); } } From b2cf0a9f5b74860144ef9c203012680d0ea92ade Mon Sep 17 00:00:00 2001 From: Yury Orlov Date: Wed, 11 Jan 2017 16:11:19 +0300 Subject: [PATCH 4/6] Support validation rules defined by nested options --- examples/app/app.component.html | 42 ++++++- examples/app/app.component.ts | 14 +-- metadata/StrongMetaData.json | 200 ++++++++++++++++++++++---------- 3 files changed, 180 insertions(+), 76 deletions(-) diff --git a/examples/app/app.component.html b/examples/app/app.component.html index 15988f38b..7bd5c3e5d 100644 --- a/examples/app/app.component.html +++ b/examples/app/app.component.html @@ -20,15 +20,16 @@

Angular 2 Forms

- + - +

+ email: '{{angularFormData.email}}', password: '{{angularFormData.password}}'

DevExtreme validation features

@@ -36,21 +37,50 @@

DevExtreme validation features

- - + + + + + - - + + + +
+ email: '{{dxFormControlsData.email}}', password: '{{dxFormControlsData.password}}' +

dxForm

+ + + + + + + + + + + email: '{{dxFormData.email}}', password: '{{dxFormData.password}}' +

Editor Widgets


diff --git a/examples/app/app.component.ts b/examples/app/app.component.ts index 28cc111f9..6bcca3640 100644 --- a/examples/app/app.component.ts +++ b/examples/app/app.component.ts @@ -99,20 +99,12 @@ import{ export class AppComponent implements OnInit { @ViewChild(DxPopoverComponent) popover: DxPopoverComponent; text = 'Initial text'; - email: string; + angularFormData = { email: '', password: '' }; emailControl: AbstractControl; - password: string; passwordControl: AbstractControl; - dxValidationRules = { - email: [ - { type: 'required', message: 'Email is required.' }, - { type: 'email', message: 'Email is invalid.' } - ], - password: [ - { type: 'required', message: 'Email is required.' } - ] - }; form: FormGroup; + dxFormControlsData = { email: '', password: '' }; + dxFormData = { email: '', password: '' }; boolValue: boolean; numberValue: number; dateValue: Date; diff --git a/metadata/StrongMetaData.json b/metadata/StrongMetaData.json index 8a5e026d0..b9627bbe9 100644 --- a/metadata/StrongMetaData.json +++ b/metadata/StrongMetaData.json @@ -2652,8 +2652,15 @@ "Description": "In a boolean column, replaces all true items with a specified text." }, "validationRules": { - "PrimitiveTypes": [ - "any" + "ComplexTypes": [ + "CompareRule", + "CustomRule", + "EmailRule", + "NumericRule", + "PatternRule", + "RangeRule", + "RequiredRule", + "StringLengthRule" ], "IsCollection": true, "SingularName": "validationRule", @@ -15004,8 +15011,15 @@ "Description": "Specifies the validation group the editor will be related to." }, "validationRules": { - "PrimitiveTypes": [ - "any" + "ComplexTypes": [ + "CompareRule", + "CustomRule", + "EmailRule", + "NumericRule", + "PatternRule", + "RangeRule", + "RequiredRule", + "StringLengthRule" ], "IsCollection": true, "SingularName": "validationRule", @@ -17140,7 +17154,8 @@ "Description": "Makes constant line labels visible." }, "horizontalAlignment": { - "EnumName": "HorizontalAlignment" + "EnumName": "HorizontalAlignment", + "Description": "Aligns constant line labels in the horizontal direction." }, "text": { "PrimitiveTypes": [ @@ -17149,7 +17164,8 @@ "Description": "Specifies the text of a constant line label. By default, equals to the value of the constant line." }, "verticalAlignment": { - "EnumName": "VerticalAlignment" + "EnumName": "VerticalAlignment", + "Description": "Aligns constant line labels in the vertical direction." } } }, @@ -18793,7 +18809,8 @@ "Description": "Makes constant line labels visible." }, "horizontalAlignment": { - "EnumName": "HorizontalAlignment" + "EnumName": "HorizontalAlignment", + "Description": "Aligns constant line labels in the horizontal direction." }, "text": { "PrimitiveTypes": [ @@ -18802,7 +18819,8 @@ "Description": "Specifies the text of a constant line label. By default, equals to the value of the constant line." }, "verticalAlignment": { - "EnumName": "VerticalAlignment" + "EnumName": "VerticalAlignment", + "Description": "Aligns constant line labels in the vertical direction." } } }, @@ -28483,55 +28501,67 @@ } }, "FormEmptyItem": { + "Description": "This article describes configuration options of an empty form item.", "Options": { "colSpan": { "PrimitiveTypes": [ "int32" - ] + ], + "Description": "Specifies the number of columns spanned by the item." }, "cssClass": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies a CSS class to be applied to the form item." }, "itemType": { - "EnumName": "FormItemType" + "EnumName": "FormItemType", + "Description": "Specifies the type of the current item." }, "name": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies the form item name." }, "visible": { "PrimitiveTypes": [ "bool" - ] + ], + "Description": "Specifies whether or not the current form item is visible." }, "visibleIndex": { "PrimitiveTypes": [ "int32" - ] + ], + "Description": "Specifies the sequence number of the item in a form, group or tab." } } }, "FormGroupItem": { + "Description": "This article describes configuration options of a group form item.", "Options": { "alignItemLabels": { "PrimitiveTypes": [ "bool" - ] + ], + "Description": "Specifies whether or not all group item labels are aligned." }, "caption": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies the group caption." }, "colCount": { "PrimitiveTypes": [ "int32" - ] + ], + "Description": "The count of columns in the group layout." }, "colCountByScreen": { + "Description": "Specifies dependency between the screen factor and the count of columns in the group layout.", "Options": { "lg": { "PrimitiveTypes": [ @@ -28562,12 +28592,14 @@ "colSpan": { "PrimitiveTypes": [ "int32" - ] + ], + "Description": "Specifies the number of columns spanned by the item." }, "cssClass": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies a CSS class to be applied to the form item." }, "items": { "ComplexTypes": [ @@ -28577,156 +28609,198 @@ "FormTabbedItem" ], "IsCollection": true, - "SingularName": "item" + "SingularName": "item", + "Description": "Holds an array of form items displayed within the group." }, "itemType": { - "EnumName": "FormItemType" + "EnumName": "FormItemType", + "Description": "Specifies the type of the current item." }, "template": { - "IsTemplate": true + "IsTemplate": true, + "Description": "A template to be used for rendering a group item." }, "visible": { "PrimitiveTypes": [ "bool" - ] + ], + "Description": "Specifies whether or not the current form item is visible." }, "visibleIndex": { "PrimitiveTypes": [ "int32" - ] + ], + "Description": "Specifies the sequence number of the item in a form, group or tab." } } }, "FormSimpleItem": { + "Description": "This article describes configuration options of a simple form item.", "Options": { "colSpan": { "PrimitiveTypes": [ "int32" - ] + ], + "Description": "Specifies the number of columns spanned by the item." }, "cssClass": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies a CSS class to be applied to the form item." }, "dataField": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies the path to the formData object field bound to the current form item." }, "editorOptions": { "PrimitiveTypes": [ "any" - ] + ], + "Description": "Specifies configuration options for the editor widget of the current form item." }, "editorType": { - "EnumName": "FormItemEditorType" + "EnumName": "FormItemEditorType", + "Description": "Specifies which editor widget is used to display and edit the form item value." }, "helpText": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies the help text displayed for the current form item." }, "isRequired": { "PrimitiveTypes": [ "bool" - ] + ], + "Description": "Specifies whether the current form item is required." }, "itemType": { - "EnumName": "FormItemType" + "EnumName": "FormItemType", + "Description": "Specifies the type of the current item." }, "label": { + "Description": "Specifies options for the form item label.", "Options": { "alignment": { - "EnumName": "HorizontalAlignment" + "EnumName": "HorizontalAlignment", + "Description": "Specifies the label horizontal alignment." }, "location": { - "EnumName": "FormLabelLocation" + "EnumName": "FormLabelLocation", + "Description": "Specifies the location of a label against the editor." }, "showColon": { "PrimitiveTypes": [ "bool" - ] + ], + "Description": "Specifies whether or not a colon is displayed at the end of the current label." }, "text": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies the label text." }, "visible": { "PrimitiveTypes": [ "bool" - ] + ], + "Description": "Specifies whether or not the label is visible." } } }, "name": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies the form item name." }, "template": { - "IsTemplate": true + "IsTemplate": true, + "Description": "A template to be used for rendering the form item." }, "validationRules": { - "PrimitiveTypes": [ - "any" + "ComplexTypes": [ + "CompareRule", + "CustomRule", + "EmailRule", + "NumericRule", + "PatternRule", + "RangeRule", + "RequiredRule", + "StringLengthRule" ], "IsCollection": true, - "SingularName": "validationRule" + "SingularName": "validationRule", + "Description": "An array of validation rules to be checked for the form item editor." }, "visible": { "PrimitiveTypes": [ "bool" - ] + ], + "Description": "Specifies whether or not the current form item is visible." }, "visibleIndex": { "PrimitiveTypes": [ "int32" - ] + ], + "Description": "Specifies the sequence number of the item in a form, group or tab." } } }, "FormTabbedItem": { + "Description": "This article describes configuration options of a tabbed form item.", "Options": { "colSpan": { "PrimitiveTypes": [ "int32" - ] + ], + "Description": "Specifies the number of columns spanned by the item." }, "cssClass": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies a CSS class to be applied to the form item." }, "itemType": { - "EnumName": "FormItemType" + "EnumName": "FormItemType", + "Description": "Specifies the type of the current item." }, "tabPanelOptions": { "ComplexTypes": [ "dxTabPanelOptions" - ] + ], + "Description": "Holds a configuration object for the TabPanel widget used to display the current form item." }, "tabs": { "IsCollection": true, "SingularName": "tab", + "Description": "An array of tab configuration objects.", "Options": { "alignItemLabels": { "PrimitiveTypes": [ "bool" - ] + ], + "Description": "Specifies whether or not labels of items displayed within the current tab are aligned." }, "badge": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies a badge text for the tab." }, "colCount": { "PrimitiveTypes": [ "int32" - ] + ], + "Description": "The count of columns in the tab layout." }, "colCountByScreen": { + "Description": "Specifies dependency between the screen factor and the count of columns in the tab layout.", "Options": { "lg": { "PrimitiveTypes": [ @@ -28757,12 +28831,14 @@ "disabled": { "PrimitiveTypes": [ "bool" - ] + ], + "Description": "A Boolean value specifying whether or not the tab can respond to user interaction." }, "icon": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies the icon to be displayed on the tab." }, "items": { "ComplexTypes": [ @@ -28772,30 +28848,36 @@ "FormTabbedItem" ], "IsCollection": true, - "SingularName": "item" + "SingularName": "item", + "Description": "Holds an array of form items displayed within the tab." }, "tabTemplate": { - "IsTemplate": true + "IsTemplate": true, + "Description": "The template to be used for rendering the tab." }, "template": { - "IsTemplate": true + "IsTemplate": true, + "Description": "The template to be used for rendering the tab content." }, "title": { "PrimitiveTypes": [ "string" - ] + ], + "Description": "Specifies the tab title." } } }, "visible": { "PrimitiveTypes": [ "bool" - ] + ], + "Description": "Specifies whether or not the current form item is visible." }, "visibleIndex": { "PrimitiveTypes": [ "int32" - ] + ], + "Description": "Specifies the sequence number of the item in a form, group or tab." } } }, From f129cbef240983256fb208e2edddf8f1313e59b3 Mon Sep 17 00:00:00 2001 From: Yury Orlov Date: Thu, 12 Jan 2017 14:48:40 +0300 Subject: [PATCH 5/6] Support dxi components without specified templates --- src/core/nested-option.ts | 35 +++++++++++++++++++++++----- templates/nested-component.tst | 19 ++++++++------- tests/src/core/nested-option.spec.ts | 8 +++---- tests/src/ui/form.spec.ts | 13 ++++------- tests/src/ui/list.spec.ts | 17 ++++++++++++++ 5 files changed, 66 insertions(+), 26 deletions(-) diff --git a/src/core/nested-option.ts b/src/core/nested-option.ts index bbf65c23c..7006c55bb 100644 --- a/src/core/nested-option.ts +++ b/src/core/nested-option.ts @@ -15,7 +15,7 @@ export abstract class BaseNestedOption implements INestedOptionContainer, IColle protected abstract get _optionPath(): string; protected abstract _fullOptionPath(): string; - constructor(private _element: ElementRef) { + constructor() { this._collectionContainerImpl = new CollectionNestedOptionContainerImpl(this._setOption.bind(this), this._filterItems.bind(this)); } @@ -40,11 +40,6 @@ export abstract class BaseNestedOption implements INestedOptionContainer, IColle this._hostOptionPath = optionPath; } - _template(...args) { - let container = args[2] || args[1] || args[0]; - return container.append(this._element.nativeElement); - } - setChildren(propertyName: string, items: QueryList) { return this._collectionContainerImpl.setChildren(propertyName, items); } @@ -121,6 +116,34 @@ export abstract class CollectionNestedOption extends BaseNestedOption implements } } +export interface OptionWithTemplate extends BaseNestedOption { + template: any; +} +export function extractTemplate(option: OptionWithTemplate, element: ElementRef) { + if (!option.template === undefined || !element.nativeElement.hasChildNodes()) { + return; + } + + let childNodes = [].slice.call(element.nativeElement.childNodes); + let userContent = childNodes.filter((n) => { + if (n.tagName) { + let tagNamePrefix = n.tagName.toLowerCase().substr(0, 3); + return !(tagNamePrefix === 'dxi' || tagNamePrefix === 'dxo'); + } else { + return n.textContent.replace(/\s/g, '').length; + } + }); + if (!userContent.length) { + return; + } + + option.template = { + render: (options) => { + return options.container.append(element.nativeElement); + } + }; +} + export class NestedOptionHost { private _host: INestedOptionContainer; private _optionPath: OptionPathGetter; diff --git a/templates/nested-component.tst b/templates/nested-component.tst index e8ed7e0d9..e9acce9da 100644 --- a/templates/nested-component.tst +++ b/templates/nested-component.tst @@ -3,8 +3,9 @@ import { Component, NgModule, - Host, + Host,<#? it.hasTemplate #> ElementRef, + AfterViewInit,<#?#> SkipSelf<#? it.properties #>, Input<#?#><#? it.collectionNestedComponents.length #>, ContentChildren, @@ -12,7 +13,7 @@ import { QueryList<#?#> } from '@angular/core'; -import { NestedOptionHost } from '../../core/nested-option'; +import { NestedOptionHost<#? it.hasTemplate #>, extractTemplate<#?#> } from '../../core/nested-option'; import { <#= it.baseClass #> } from '<#= it.basePath #>'; <#~ it.collectionNestedComponents :component:i #><#? component.className !== it.className #>import { <#= component.className #>Component } from './<#= component.path #>'; <#?#><#~#> @@ -25,7 +26,7 @@ import { <#= it.baseClass #> } from '<#= it.basePath #>'; '<#= input.name #>'<#? i < it.inputs.length-1 #>,<#?#><#~#> ]<#?#> }) -export class <#= it.className #>Component extends <#= it.baseClass #> {<#~ it.properties :prop:i #> +export class <#= it.className #>Component extends <#= it.baseClass #><#? it.hasTemplate #> implements AfterViewInit<#?#> {<#~ it.properties :prop:i #> @Input() get <#= prop.name #>() { return this._getOption('<#= prop.name #>'); @@ -48,14 +49,16 @@ export class <#= it.className #>Component extends <#= it.baseClass #> {<#~ it.pr } <#~#> - constructor(@SkipSelf() @Host() parentOptionHost: NestedOptionHost, @Host() optionHost: NestedOptionHost, element: ElementRef) { - super(element); -<#? it.hasTemplate #> - this.template = this._template.bind(this); -<#?#> + constructor(@SkipSelf() @Host() parentOptionHost: NestedOptionHost, @Host() optionHost: NestedOptionHost<#? it.hasTemplate #>, private element: ElementRef<#?#>) { + super(); parentOptionHost.setNestedOption(this); optionHost.setHost(this, this._fullOptionPath.bind(this)); } +<#? it.hasTemplate #> + ngAfterViewInit() { + extractTemplate(this, this.element); + } +<#?#> } @NgModule({ diff --git a/tests/src/core/nested-option.spec.ts b/tests/src/core/nested-option.spec.ts index 08c05e02c..c641b9936 100644 --- a/tests/src/core/nested-option.spec.ts +++ b/tests/src/core/nested-option.spec.ts @@ -60,8 +60,8 @@ export class DxoTestOptionComponent extends NestedOption { return 'testOption'; } - constructor(@SkipSelf() @Host() private _pnoh: NestedOptionHost, @Host() private _noh: NestedOptionHost, element: ElementRef) { - super(element); + constructor(@SkipSelf() @Host() private _pnoh: NestedOptionHost, @Host() private _noh: NestedOptionHost) { + super(); this._pnoh.setNestedOption(this); this._noh.setHost(this); @@ -86,8 +86,8 @@ export class DxiTestCollectionOptionComponent extends CollectionNestedOption { return 'testCollectionOption'; } - constructor(@SkipSelf() @Host() private _pnoh: NestedOptionHost, @Host() private _noh: NestedOptionHost, element: ElementRef) { - super(element); + constructor(@SkipSelf() @Host() private _pnoh: NestedOptionHost, @Host() private _noh: NestedOptionHost) { + super(); this._pnoh.setNestedOption(this); this._noh.setHost(this, this._fullOptionPath.bind(this)); diff --git a/tests/src/ui/form.spec.ts b/tests/src/ui/form.spec.ts index 81dfeda10..6b37dcb35 100644 --- a/tests/src/ui/form.spec.ts +++ b/tests/src/ui/form.spec.ts @@ -50,8 +50,7 @@ describe('DxForm', () => { set: { template: ` - - + ` } @@ -68,12 +67,10 @@ describe('DxForm', () => { set: { template: ` - - - - - - + + + + diff --git a/tests/src/ui/list.spec.ts b/tests/src/ui/list.spec.ts index 64eeaecd1..65d3f7430 100644 --- a/tests/src/ui/list.spec.ts +++ b/tests/src/ui/list.spec.ts @@ -325,6 +325,23 @@ describe('DxList', () => { expect(instance.element().find('.dx-item-content').eq(0).text()).toBe('testTemplate'); })); + it('should be able to define item without template', async(() => { + TestBed.overrideComponent(TestContainerComponent, { + set: { + template: ` + + + + ` + } + }); + let fixture = TestBed.createComponent(TestContainerComponent); + fixture.detectChanges(); + + let instance = getWidget(fixture); + expect(instance.element().find('.dx-item-content').eq(0).text()).toBe('TestText'); + })); + it('should destroy all components inside template', () => { let destroyed = false; From dd642059ef64c9a25432af18aa815002560e8187 Mon Sep 17 00:00:00 2001 From: Yury Orlov Date: Thu, 12 Jan 2017 15:02:38 +0300 Subject: [PATCH 6/6] Revert form examples --- examples/app/app.component.html | 19 +++++++------------ examples/app/app.component.ts | 4 +--- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/examples/app/app.component.html b/examples/app/app.component.html index b83cd1b90..af2ecf01f 100644 --- a/examples/app/app.component.html +++ b/examples/app/app.component.html @@ -20,16 +20,15 @@

Angular 2 Forms

- + - +

- email: '{{angularFormData.email}}', password: '{{angularFormData.password}}'

DevExtreme validation features

@@ -37,7 +36,7 @@

DevExtreme validation features

- + @@ -45,7 +44,7 @@

DevExtreme validation features

- + @@ -55,31 +54,27 @@

DevExtreme validation features

- email: '{{dxFormControlsData.email}}', password: '{{dxFormControlsData.password}}'

dxForm

+ editorType="dxTextBox"> + [editorOptions]="{ mode: 'password' }"> - email: '{{dxFormData.email}}', password: '{{dxFormData.password}}'

Editor Widgets

diff --git a/examples/app/app.component.ts b/examples/app/app.component.ts index 2ed66263c..68532d3ba 100644 --- a/examples/app/app.component.ts +++ b/examples/app/app.component.ts @@ -99,12 +99,10 @@ import{ export class AppComponent implements OnInit { @ViewChild(DxPopoverComponent) popover: DxPopoverComponent; text = 'Initial text'; - angularFormData = { email: '', password: '' }; + formData = { email: '', password: '' }; emailControl: AbstractControl; passwordControl: AbstractControl; form: FormGroup; - dxFormControlsData = { email: '', password: '' }; - dxFormData = { email: '', password: '' }; boolValue: boolean; numberValue: number; dateValue: Date;