Skip to content

Commit

Permalink
Make it possible to instantiate a standalone dxValidator (#206)
Browse files Browse the repository at this point in the history
  • Loading branch information
IlyaKhD committed Nov 15, 2018
1 parent 55dfe04 commit 8a67e91
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 18 deletions.
68 changes: 63 additions & 5 deletions example/components/validation-example.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,91 @@
<dx-validation-summary />
<dx-button text="Submit" @click="validate"/>
</dx-validation-group>
<br/>
<br/>
<h3>Custom value validation</h3>
<br/>
<div>
<button @click="validateCustom">Validate custom value</button>
<input v-model="customValue" />
<br/>
<div>Not empty: {{customValueIsNotEmpty}}</div>
<div>Not less than 0, not greater than 9: {{customValueIsDigit}}</div>
<div>Is valid: {{customValueIsValid}}</div>
<dx-validator ref="customValidator">
<dx-adapter :get-value="getCustomValue" />
<dx-required-rule message="Value is required." />
</dx-validator>
</div>
</example-block>
</template>

<script>
import ExampleBlock from "./example-block";
import { DxButton, DxTextBox, DxValidationGroup, DxValidationSummary } from "../../src";
import { DxValidator, DxEmailRule, DxRequiredRule} from "../../src/validator";
import {
DxButton,
DxTextBox,
DxValidationGroup,
DxValidationSummary,
DxValidator
} from "../../src";
import { DxAdapter, DxEmailRule, DxRequiredRule } from "../../src/validator";
export default {
components: {
ExampleBlock,
DxButton,
DxTextBox,
DxTextBox,
DxValidator,
DxAdapter,
DxEmailRule,
DxRequiredRule,
DxValidationGroup,
DxValidationSummary
},
created() {
const validatorComponent = new DxValidator({
propsData: {
adapter: {
getValue: this.getCustomValue
},
validationRules: [
{ type: "range", min: 0, max: 9, message: "From 1 to 10" }
]
}
}).$mount();
this.validator = validatorComponent.instance;
},
data() {
return {
customValue: 1,
customValueIsDigit: true,
customValueIsNotEmpty: true
};
},
computed: {
customValueIsValid() {
return this.customValueIsNotEmpty && this.customValueIsDigit;
}
},
methods: {
validate(params) {
const result = params.validationGroup.validate();
if (result.isValid) {
// form data is valid
params.validationGroup.reset();
// form data is valid
params.validationGroup.reset();
}
},
getCustomValue() {
return this.customValue;
},
validateCustom() {
var result1 = this.$refs.customValidator.instance.validate();
var result2 = this.validator.validate();
this.customValueIsNotEmpty = result1.isValid;
this.customValueIsDigit = result2.isValid;
}
}
};
Expand Down
39 changes: 29 additions & 10 deletions src/core/component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1017,22 +1017,32 @@ describe("extension component", () => {
}
});

it("doesn't render", () => {
new TestExtensionComponent().$mount();
it("renders once if mounted manually and targets self element", () => {
const component = new TestExtensionComponent().$mount();

expect(ExtensionWidgetClass).toHaveBeenCalledTimes(0);
const expectedElement = component.$el;
const actualElement = ExtensionWidgetClass.mock.calls[0][0];

expect(Widget.beginUpdate).toHaveBeenCalledTimes(0);
expect(Widget.endUpdate).toHaveBeenCalledTimes(0);
expect(ExtensionWidgetClass).toHaveBeenCalledTimes(1);
expect(actualElement).toBe(expectedElement);
});

it("destroys correctly", () => {
const component = new TestExtensionComponent().$mount();
it("renders once without parent element and targets self element", () => {
const vue = new Vue({
template: `<test-extension-component/>`,
components: {
TestExtensionComponent
}
}).$mount();

expect(component.$destroy.bind(component)).not.toThrow();
const expectedElement = vue.$el;
const actualElement = ExtensionWidgetClass.mock.calls[0][0];

expect(ExtensionWidgetClass).toHaveBeenCalledTimes(1);
expect(actualElement).toBe(expectedElement);
});

it("renders inside component on parent element", () => {
it("renders once inside component and targets parent element", () => {
new Vue({
template: `<test-component>
<test-extension-component/>
Expand All @@ -1043,8 +1053,17 @@ describe("extension component", () => {
}
}).$mount();

const expectedElement = WidgetClass.mock.calls[0][0];
const actualElement = ExtensionWidgetClass.mock.calls[0][0];

expect(ExtensionWidgetClass).toHaveBeenCalledTimes(1);
expect(ExtensionWidgetClass.mock.calls[0][0]).toBe(WidgetClass.mock.calls[0][0]);
expect(actualElement).toBe(expectedElement);
});

it("destroys correctly", () => {
const component = new TestExtensionComponent().$mount();

expect(component.$destroy.bind(component)).not.toThrow();
});
});

Expand Down
18 changes: 16 additions & 2 deletions src/core/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as events from "devextreme/events";
import { pullAllChildren } from "./children-processing";
import Configuration, { bindOptionWatchers, subscribeOnUpdates } from "./configuration";
import { IConfigurable } from "./configuration-component";
import { IExtension, IExtensionComponentNode } from "./extension-component";
import { camelize, toComparable } from "./helpers";

interface IWidgetComponent extends IConfigurable {
Expand All @@ -25,6 +26,7 @@ const BaseComponent: VueConstructor = Vue.extend({
render(createElement: (...args) => VNode): VNode {
const children: VNode[] = [];
pullAllChildren(this.$slots.default, children, (this as any as IWidgetComponent).$_config);
this.$_processChildren(children);

return createElement(
"div",
Expand Down Expand Up @@ -117,6 +119,10 @@ const BaseComponent: VueConstructor = Vue.extend({
return {};
},

$_processChildren(_children: VNode[]): void {
return;
},

$_fillTemplate(template: any, name: string): object {
return {
render: (data: any) => {
Expand Down Expand Up @@ -158,13 +164,21 @@ const DxComponent: VueConstructor = BaseComponent.extend({
(this as any).beginUpdate();
}
};
}
},

$_processChildren(children: VNode[]): void {
children.forEach((childNode: VNode) => {
if (!childNode.componentOptions) { return; }

(childNode.componentOptions as any as IExtensionComponentNode).$_hasOwner = true;
});
},
},

mounted(): void {
(this as any).$_createWidget(this.$el);
(this as any as IWidgetComponent).$_instance.endUpdate();
this.$children.forEach((child: any) => {
this.$children.forEach((child: IExtension) => {
if (child.$_isExtension) {
child.attachTo(this.$el);
}
Expand Down
21 changes: 20 additions & 1 deletion src/core/extension-component.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
import { VueConstructor } from "vue";
import { BaseComponent } from "./component";

interface IExtension {
$_isExtension: boolean;
attachTo(element: any);
}

interface IExtensionComponentNode {
$_hasOwner: boolean;
}

const DxExtensionComponent: VueConstructor = BaseComponent.extend({

created(): void {
(this as any).$_isExtension = true;
},

mounted() {
if (this.$vnode && (this.$vnode.componentOptions as any as IExtensionComponentNode).$_hasOwner) { return; }

this.attachTo(this.$el);
},

methods: {
attachTo(element: any) {
(this as any).$_createWidget(element);
}
}
});

export { DxExtensionComponent };
export {
DxExtensionComponent,
IExtension,
IExtensionComponentNode
};

0 comments on commit 8a67e91

Please sign in to comment.