<template>
<div id="form-demo">
<div class="widget-container">
<form
action="your-action"
@submit="handleSubmit"
>
<DxForm
v-model:form-data="customer"
:read-only="false"
@initialized="saveFormInstance"
@optionChanged="onOptionChanged"
:show-colon-after-label="true"
:show-validation-summary="true"
validation-group="customerData"
>
<DxGroupItem caption="Credentials">
<DxSimpleItem
data-field="Email"
:editor-options="emailEditorOptions"
>
<DxRequiredRule message="Email is required"/>
<DxEmailRule message="Email is invalid"/>
<DxAsyncRule
:validation-callback="asyncValidation"
message="Email is already registered"
/>
</DxSimpleItem>
<DxSimpleItem
:editor-options="passwordEditorOptions"
data-field="Password"
>
<DxRequiredRule message="Password is required"/>
</DxSimpleItem>
<DxSimpleItem
name="ConfirmPassword"
data-field="ConfirmPassword"
:editor-options="confirmPasswordEditorOptions"
editor-type="dxTextBox"
>
<DxLabel text="Confirm Password"/>
<DxRequiredRule message="Confirm Password is required"/>
<DxCompareRule
:comparison-target="passwordComparison"
message="Password and Confirm Password do not match"
/>
</DxSimpleItem>
</DxGroupItem>
<DxGroupItem caption="Personal Data">
<DxSimpleItem
data-field="Name"
:editor-options="nameEditorOptions"
>
<DxRequiredRule message="Name is required"/>
<DxPatternRule
:pattern="namePattern"
message="Do not use digits in the Name"
/>
</DxSimpleItem>
<DxSimpleItem
:editor-options="dateBoxOptions"
data-field="Date"
editor-type="dxDateBox"
>
<DxLabel text="Date of birth"/>
<DxRequiredRule message="Date of birth is required"/>
<DxRangeRule
:max="maxDate"
message="You must be at least 21 years old"
/>
</DxSimpleItem>
<DxSimpleItem
:editor-options="dateRangeBoxOptions"
data-field="VacationDates"
editor-type="dxDateRangeBox"
>
<DxLabel text="Vacation Dates"/>
<DxCustomRule
:validation-callback="validateVacationDatesRange"
message="The vacation period must not exceed 25 days"
/>
<DxCustomRule
:validation-callback="validateVacationDatesPresence"
message="Both start and end dates must be selected"
/>
</DxSimpleItem>
</DxGroupItem>
<DxGroupItem caption="Billing address">
<DxSimpleItem
:editor-options="countryEditorOptions"
data-field="Country"
editor-type="dxSelectBox"
>
<DxRequiredRule message="Country is required"/>
</DxSimpleItem>
<DxSimpleItem
:editor-options="cityEditorOptions"
data-field="City"
editor-type="dxAutocomplete"
>
<DxPatternRule
:pattern="cityPattern"
message="Do not use digits in the City name"
/>
<DxStringLengthRule
:min="2"
message="City must have at least 2 symbols"
/>
<DxRequiredRule message="City is required"/>
</DxSimpleItem>
<DxSimpleItem
data-field="Address"
:editor-options="addressEditorOptions"
>
<DxRequiredRule message="Address is required"/>
</DxSimpleItem>
<DxSimpleItem
:editor-options="phoneEditorOptions"
data-field="Phone"
help-text="Enter the phone number in USA phone format"
>
<DxPatternRule
:pattern="phonePattern"
message="The phone must have a correct USA phone format"
/>
</DxSimpleItem>
</DxGroupItem>
<DxGroupItem
css-class="last-group"
:col-count-by-screen="colCountByScreen"
>
<DxSimpleItem
:editor-options="checkBoxOptions"
data-field="Accepted"
editor-type="dxCheckBox"
>
<DxLabel :visible="false"/>
<DxCompareRule
:comparison-target="checkComparison"
type="compare"
message="You must agree to the Terms and Conditions"
/>
</DxSimpleItem>
<DxGroupItem
css-class="buttons-group"
:col-count-by-screen="colCountByScreen"
>
<DxButtonItem
:button-options="resetButtonOptions"
name="Reset"
/>
<DxButtonItem
:button-options="registerButtonOptions"
/>
</DxGroupItem>
</DxGroupItem>
</DxForm>
</form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import DxForm, {
DxGroupItem,
DxSimpleItem,
DxButtonItem,
DxLabel,
DxRequiredRule,
DxCompareRule,
DxRangeRule,
DxStringLengthRule,
DxPatternRule,
DxEmailRule,
DxAsyncRule,
DxCustomRule,
} from 'devextreme-vue/form';
// eslint-disable-next-line
import DxAutocomplete from 'devextreme-vue/autocomplete'; // for editor-type=dxAutocomplete
import 'devextreme-vue/date-range-box';
import notify from 'devextreme/ui/notify';
import Validator from 'devextreme/ui/validator';
import service from './data.ts';
const formInstance = ref(null);
const customer = ref(service.getCustomer());
const registerButtonOptions = ref({
text: 'Register',
type: 'default',
width: '120px',
useSubmitBehavior: true,
});
const resetButtonOptions = ref({
icon: 'refresh',
text: 'Reset',
disabled: true,
width: '120px',
onClick: () => {
formInstance.value.reset();
},
});
const colCountByScreen = ref({
xs: 2,
sm: 2,
md: 2,
lg: 2,
});
const passwordEditorOptions = ref({
mode: 'password',
valueChangeEvent: 'keyup',
onValueChanged: () => {
const editor = formInstance.value.getEditor('ConfirmPassword');
if (editor.option('value')) {
const instance = Validator.getInstance(editor.element()) as Validator;
instance.validate();
}
},
buttons: [
{
name: 'password',
location: 'after',
options: {
icon: 'eyeopen',
stylingMode: 'text',
onClick: () => changePasswordMode('Password'),
},
},
],
});
const confirmPasswordEditorOptions = ref({
mode: 'password',
valueChangeEvent: 'keyup',
buttons: [
{
name: 'password',
location: 'after',
options: {
icon: 'eyeopen',
stylingMode: 'text',
onClick: () => changePasswordMode('ConfirmPassword'),
},
},
],
});
const emailEditorOptions = ref({
valueChangeEvent: 'keyup',
});
const nameEditorOptions = ref({
valueChangeEvent: 'keyup',
});
const addressEditorOptions = ref({
valueChangeEvent: 'keyup',
});
const dateBoxOptions = ref({
placeholder: 'Birth Date',
acceptCustomValue: false,
openOnFieldClick: true,
});
const dateRangeBoxOptions = ref({
endDatePlaceholder: 'End Date',
startDatePlaceholder: 'Start Date',
acceptCustomValue: false,
});
const checkBoxOptions = ref({
text: 'I agree to the Terms and Conditions',
width: 270,
value: false,
});
const phoneEditorOptions = ref({
mask: '+1 (X00) 000-0000',
valueChangeEvent: 'keyup',
maskRules: {
X: /[02-9]/,
},
maskInvalidMessage: 'The phone must have a correct USA phone format',
});
const cityEditorOptions = ref({
dataSource: service.getCities(),
valueChangeEvent: 'keyup',
minSearchLength: 2,
});
const countryEditorOptions = ref({
dataSource: service.getCountries(),
});
const maxDate = ref(new Date().setFullYear(new Date().getFullYear() - 21));
const namePattern = ref(/^[^0-9]+$/);
const cityPattern = ref(/^[^0-9]+$/);
const phonePattern = ref(/^[02-9]\d{9}$/);
function onOptionChanged(e) {
if (e.name === 'isDirty') {
const resetButton = formInstance.value.getButton('Reset');
resetButton.option('disabled', !e.value);
}
}
function saveFormInstance(e) {
formInstance.value = e.component;
}
function changePasswordMode(name) {
const editor = formInstance.value.getEditor(name);
editor.option(
'mode',
editor.option('mode') === 'text' ? 'password' : 'text',
);
}
function passwordComparison() {
return customer.value.Password;
}
function checkComparison() {
return true;
}
function asyncValidation(params) {
return sendRequest(params.value);
}
function validateVacationDatesRange({ value }) {
const [startDate, endDate] = value;
if (startDate === null || endDate === null) {
return true;
}
const millisecondsPerDay = 24 * 60 * 60 * 1000;
const daysDifference = Math.abs((endDate - startDate) / millisecondsPerDay);
return daysDifference < 25;
}
function validateVacationDatesPresence({ value }) {
const [startDate, endDate] = value;
if (startDate === null && endDate === null) {
return true;
}
return startDate !== null && endDate !== null;
}
function handleSubmit(e) {
notify({
message: 'You have submitted the form',
position: {
my: 'center top',
at: 'center top',
},
}, 'success', 3000);
e.preventDefault();
}
const sendRequest = function(value) {
const invalidEmail = 'test@dx-email.com';
return new Promise((resolve) => {
setTimeout(() => {
resolve(value !== invalidEmail);
}, 1000);
});
};
</script>
<style scoped>
form {
margin: 10px 10px 15px;
}
.last-group {
margin-top: 30px;
margin-bottom: 10px;
}
.last-group .dx-item-content {
align-items: start;
justify-content: center;
}
.last-group .dx-field-item {
padding: 0 !important;
}
.buttons-group {
display: flex;
width: 100%;
justify-content: end;
}
.buttons-group .dx-item-content {
gap: 10px;
}
</style>
window.exports = window.exports || {};
window.config = {
transpiler: 'plugin-babel',
meta: {
'*.vue': {
loader: 'vue-loader',
},
'*.ts': {
loader: 'demo-ts-loader',
},
'*.svg': {
loader: 'svg-loader',
},
'devextreme/time_zone_utils.js': {
'esModule': true,
},
'devextreme/localization.js': {
'esModule': true,
},
'devextreme/viz/palette.js': {
'esModule': true,
},
},
paths: {
'root:': '../../../../',
'npm:': 'https://unpkg.com/',
},
map: {
'vue': 'npm:vue@3.2.47/dist/vue.esm-browser.js',
'vue-loader': 'npm:dx-systemjs-vue-browser@1.1.1/index.js',
'demo-ts-loader': 'root:utils/demo-ts-loader.js',
'svg-loader': 'root:utils/svg-loader.js',
'mitt': 'npm:mitt/dist/mitt.umd.js',
'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js',
'luxon': 'npm:luxon@1.28.1/build/global/luxon.min.js',
'es6-object-assign': 'npm:es6-object-assign@1.1.0',
'devextreme': 'npm:devextreme@24.1.7/cjs',
'devextreme-vue': 'npm:devextreme-vue@24.1.7/cjs',
'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js',
'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js',
'devexpress-diagram': 'npm:devexpress-diagram@2.2.12/dist/dx-diagram.js',
'devexpress-gantt': 'npm:devexpress-gantt@4.1.56/dist/dx-gantt.js',
'@devextreme/runtime': 'npm:@devextreme/runtime@3.0.13',
'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js',
'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js',
'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js',
'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js',
'inferno-hydrate': 'npm:inferno-hydrate@7.4.11/dist/inferno-hydrate.min.js',
'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js',
'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js',
'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js',
'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js',
'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js',
// Prettier
'prettier/standalone': 'npm:prettier@2.8.8/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js',
},
packages: {
'devextreme-vue': {
main: 'index.js',
},
'devextreme': {
defaultExtension: 'js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/events': {
main: 'index',
},
'es6-object-assign': {
main: './index.js',
defaultExtension: 'js',
},
},
packageConfigPaths: [
'npm:@devextreme/*/package.json',
'npm:@devextreme/runtime@3.0.13/inferno/package.json',
],
babelOptions: {
sourceMaps: false,
stage0: true,
},
};
System.config(window.config);
const customer = {
Email: '',
Password: '',
Name: 'Peter',
Date: null,
VacationDates: [null, null],
Country: '',
City: null,
Address: '',
Phone: '',
Accepted: false,
};
const countries = [
'Afghanistan', 'Albania', 'Algeria', 'Andorra', 'Angola', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Australia', 'Austria', 'Azerbaijan', 'The Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin', 'Bhutan', 'Bolivia', 'Bosnia and Herzegovina', 'Botswana', 'Brazil', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burma', 'Burundi', 'Cambodia', 'Cameroon', 'Canada', 'Cape Verde', 'Central African Republic', 'Chad', 'Chile', 'China', 'Colombia', 'Comoros', 'Democratic Republic of the Congo', 'Republic of the Congo', 'Costa Rica', 'Ivory Coast', 'Croatia', 'Cuba', 'Cyprus', 'Czech Republic', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic', 'East Timor', 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Ethiopia', 'Fiji', 'Finland', 'France', 'Gabon', 'The Gambia', 'Georgia', 'Germany', 'Ghana', 'Greece', 'Grenada', 'Guatemala', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Honduras', 'Hungary', 'Iceland', 'India', 'Indonesia', 'Iran', 'Iraq', 'Republic of Ireland', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jordan', 'Kazakhstan', 'Kenya', 'Kiribati', 'North Korea', 'South Korea', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon', 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Republic of Macedonia', 'Madagascar', 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Mauritania', 'Mauritius', 'Mexico', 'Federated States of Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Morocco', 'Mozambique', 'Namibia', 'Nauru', 'Nepal', 'Kingdom of the Netherlands', 'New Zealand', 'Nicaragua', 'Niger', 'Nigeria', 'Norway', 'Oman', 'Pakistan', 'Palau', 'State of Palestine', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russia', 'Rwanda', 'Saint Kitts and Nevis', 'Saint Lucia', 'Saint Vincent and the Grenadines', 'Samoa', 'San Marino', 'São Tomé and Príncipe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa', 'South Sudan', 'Spain', 'Sri Lanka', 'Sudan', 'Suriname', 'Swaziland', 'Sweden', 'Switzerland', 'Syria', 'Tajikistan', 'Tanzania', 'Thailand', 'Togo', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey', 'Turkmenistan', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates', 'United Kingdom', 'United States', 'Uruguay', 'Uzbekistan', 'Vanuatu', 'Vatican City', 'Venezuela', 'Vietnam', 'Yemen', 'Zambia', 'Zimbabwe'];
const cities = [
'New York', 'Los Angeles', 'Chicago', 'Houston', 'Philadelphia', 'Phoenix', 'San Antonio', 'San Diego', 'Dallas', 'San Jose', 'Austin', 'Indianapolis', 'Jacksonville', 'San Francisco', 'Columbus', 'Charlotte', 'Fort Worth', 'Detroit', 'El Paso', 'Memphis', 'Seattle', 'Denver', 'Washington', 'Boston', 'Nashville', 'Baltimore', 'Oklahoma City', 'Louisville', 'Portland', 'Las Vegas', 'Milwaukee', 'Albuquerque', 'Tucson', 'Fresno', 'Sacramento', 'Long Beach', 'Kansas City', 'Mesa', 'Virginia Beach', 'Atlanta', 'Colorado Springs', 'Omaha', 'Raleigh', 'Miami', 'Oakland', 'Minneapolis', 'Tulsa', 'Cleveland', 'Wichita', 'Arlington', 'New Orleans', 'Bakersfield', 'Tampa', 'Honolulu', 'Aurora', 'Anaheim', 'Santa Ana', 'St. Louis', 'Riverside', 'Corpus Christi', 'Lexington', 'Pittsburgh', 'Anchorage', 'Stockton', 'Cincinnati', 'Saint Paul', 'Toledo', 'Greensboro', 'Newark', 'Plano', 'Henderson', 'Lincoln', 'Buffalo', 'Jersey City', 'Chula Vista', 'Fort Wayne', 'Orlando', 'St. Petersburg', 'Chandler', 'Laredo', 'Norfolk', 'Durham', 'Madison', 'Lubbock', 'Irvine', 'Winston–Salem', 'Glendale', 'Garland', 'Hialeah', 'Reno', 'Chesapeake', 'Gilbert', 'Baton Rouge', 'Irving', 'Scottsdale', 'North Las Vegas', 'Fremont', 'Boise', 'Richmond'];
export default {
getCustomer() {
return customer;
},
getCountries() {
return countries;
},
getCities() {
return cities;
},
};
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
<!DOCTYPE html>
<html lang="en">
<head>
<title>DevExtreme Demo</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
<link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.1.7/css/dx.light.css" />
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" />
<script src="https://unpkg.com/typescript@4.9.5/lib/typescript.js"></script>
<script type="module">
import * as vueCompilerSFC from "https://unpkg.com/@vue/compiler-sfc@3.4.16/dist/compiler-sfc.esm-browser.js";
window.vueCompilerSFC = vueCompilerSFC;
</script>
<script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script>
<script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script>
<script type="text/javascript" src="config.js"></script>
<script type="text/javascript">
System.import("./index.ts");
</script>
</head>
<body class="dx-viewport">
<div class="demo-container">
<div id="app"> </div>
</div>
</body>
</html>