mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2024-11-21 03:15:49 +08:00
Merge pull request #508 from smartxworks/feat/validation-trait
feat: refactor the validation trait (#breaking-chagnes)
This commit is contained in:
commit
fa79e120a9
@ -139,10 +139,29 @@
|
||||
{
|
||||
"type": "core/v1/validation",
|
||||
"properties": {
|
||||
"value": "{{ phoneInput.value || \"\" }}",
|
||||
"maxLength": 100,
|
||||
"minLength": 0,
|
||||
"rule": "phoneNumber"
|
||||
"validators": [
|
||||
{
|
||||
"name": "phone",
|
||||
"value": "{{ phoneInput.value || \"\" }}",
|
||||
"rules": [
|
||||
{
|
||||
"type": "regex",
|
||||
"regex": "^1[3456789]\\d{9}$",
|
||||
"error": {
|
||||
"message": "Please input the correct phone number."
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "length",
|
||||
"minLength": 0,
|
||||
"maxLength": 100,
|
||||
"error": {
|
||||
"message": "Please input the length between 0 and 100."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -314,9 +314,22 @@
|
||||
{
|
||||
"type": "core/v1/validation",
|
||||
"properties": {
|
||||
"value": "{{ nameInput.value || \"\" }}",
|
||||
"maxLength": 10,
|
||||
"minLength": 2
|
||||
"validators": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "{{ nameInput.value || \"\" }}",
|
||||
"rules": [
|
||||
{
|
||||
"type": "length",
|
||||
"maxLength": 10,
|
||||
"minLength": 2,
|
||||
"error": {
|
||||
"message": "Please input the length between 2 and 10."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
855
examples/form/formValidation.json
Normal file
855
examples/form/formValidation.json
Normal file
@ -0,0 +1,855 @@
|
||||
{
|
||||
"app": {
|
||||
"version": "sunmao/v1",
|
||||
"kind": "Application",
|
||||
"metadata": {
|
||||
"name": "some App"
|
||||
},
|
||||
"spec": {
|
||||
"components": [
|
||||
{
|
||||
"id": "name_form",
|
||||
"type": "arco/v1/formControl",
|
||||
"properties": {
|
||||
"label": {
|
||||
"format": "plain",
|
||||
"raw": "name"
|
||||
},
|
||||
"layout": "horizontal",
|
||||
"required": true,
|
||||
"hidden": false,
|
||||
"extra": "",
|
||||
"errorMsg": "{{name_form.validatedResult.name?.errors[0]?.message || '';}}",
|
||||
"labelAlign": "left",
|
||||
"colon": false,
|
||||
"help": "",
|
||||
"labelCol": {
|
||||
"span": 3,
|
||||
"offset": 0
|
||||
},
|
||||
"wrapperCol": {
|
||||
"span": 21,
|
||||
"offset": 0
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/validation",
|
||||
"properties": {
|
||||
"validators": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "{{name_input.value;}}",
|
||||
"rules": [
|
||||
{
|
||||
"type": "required",
|
||||
"validate": null,
|
||||
"error": {
|
||||
"message": "Please input the name."
|
||||
},
|
||||
"minLength": 0,
|
||||
"maxLength": 0,
|
||||
"list": [],
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"regex": "",
|
||||
"flags": "",
|
||||
"customOptions": {}
|
||||
},
|
||||
{
|
||||
"type": "length",
|
||||
"validate": null,
|
||||
"error": {
|
||||
"message": "The name is limited in length to between 1 and 10."
|
||||
},
|
||||
"minLength": 1,
|
||||
"maxLength": 10,
|
||||
"list": [],
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"regex": "",
|
||||
"flags": "",
|
||||
"customOptions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "name_input",
|
||||
"type": "arco/v1/input",
|
||||
"properties": {
|
||||
"allowClear": false,
|
||||
"disabled": false,
|
||||
"readOnly": false,
|
||||
"defaultValue": "",
|
||||
"updateWhenDefaultValueChanges": false,
|
||||
"placeholder": "please input the name.",
|
||||
"error": "{{name_form.validatedResult.name.isInvalid}}",
|
||||
"size": "default"
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "name_form",
|
||||
"slot": "content"
|
||||
},
|
||||
"ifCondition": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "core/v1/event",
|
||||
"properties": {
|
||||
"handlers": [
|
||||
{
|
||||
"type": "onChange",
|
||||
"componentId": "name_form",
|
||||
"method": {
|
||||
"name": "validateFields",
|
||||
"parameters": {
|
||||
"names": "{{['name']}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onBlur",
|
||||
"componentId": "name_form",
|
||||
"method": {
|
||||
"name": "validateAllFields",
|
||||
"parameters": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onChange",
|
||||
"componentId": "check_name",
|
||||
"method": {
|
||||
"name": "triggerFetch",
|
||||
"parameters": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onBlur",
|
||||
"componentId": "check_name",
|
||||
"method": {
|
||||
"name": "triggerFetch",
|
||||
"parameters": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "email_form",
|
||||
"type": "arco/v1/formControl",
|
||||
"properties": {
|
||||
"label": {
|
||||
"format": "plain",
|
||||
"raw": "email"
|
||||
},
|
||||
"layout": "horizontal",
|
||||
"required": true,
|
||||
"hidden": false,
|
||||
"extra": "",
|
||||
"errorMsg": "{{email_form.validatedResult.email?.errors[0]?.message;}}",
|
||||
"labelAlign": "left",
|
||||
"colon": false,
|
||||
"help": "",
|
||||
"labelCol": {
|
||||
"span": 3,
|
||||
"offset": 0
|
||||
},
|
||||
"wrapperCol": {
|
||||
"span": 21,
|
||||
"offset": 0
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/validation",
|
||||
"properties": {
|
||||
"validators": [
|
||||
{
|
||||
"name": "email",
|
||||
"value": "{{email_input.value;}}",
|
||||
"rules": [
|
||||
{
|
||||
"type": "required",
|
||||
"validate": null,
|
||||
"error": {
|
||||
"message": "Please input the email."
|
||||
},
|
||||
"minLength": 0,
|
||||
"maxLength": 0,
|
||||
"list": [],
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"regex": "",
|
||||
"flags": "",
|
||||
"customOptions": {}
|
||||
},
|
||||
{
|
||||
"type": "email",
|
||||
"validate": null,
|
||||
"error": {
|
||||
"message": "Please input the correct email."
|
||||
},
|
||||
"minLength": 0,
|
||||
"maxLength": 0,
|
||||
"list": [],
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"regex": "",
|
||||
"flags": "",
|
||||
"customOptions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "email_input",
|
||||
"type": "arco/v1/input",
|
||||
"properties": {
|
||||
"allowClear": false,
|
||||
"disabled": false,
|
||||
"readOnly": false,
|
||||
"defaultValue": "",
|
||||
"updateWhenDefaultValueChanges": false,
|
||||
"placeholder": "please input the email.",
|
||||
"error": "{{email_form.validatedResult.email.isInvalid}}",
|
||||
"size": "default"
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "email_form",
|
||||
"slot": "content"
|
||||
},
|
||||
"ifCondition": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "core/v1/event",
|
||||
"properties": {
|
||||
"handlers": [
|
||||
{
|
||||
"type": "onChange",
|
||||
"componentId": "email_form",
|
||||
"method": {
|
||||
"name": "validateAllFields",
|
||||
"parameters": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onBlur",
|
||||
"componentId": "email_form",
|
||||
"method": {
|
||||
"name": "validateAllFields",
|
||||
"parameters": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "url_form",
|
||||
"type": "arco/v1/formControl",
|
||||
"properties": {
|
||||
"label": {
|
||||
"format": "plain",
|
||||
"raw": "URL"
|
||||
},
|
||||
"layout": "horizontal",
|
||||
"required": true,
|
||||
"hidden": false,
|
||||
"extra": "",
|
||||
"errorMsg": "{{url_form.validatedResult.url?.errors[0]?.message}}",
|
||||
"labelAlign": "left",
|
||||
"colon": false,
|
||||
"help": "",
|
||||
"labelCol": {
|
||||
"span": 3,
|
||||
"offset": 0
|
||||
},
|
||||
"wrapperCol": {
|
||||
"span": 21,
|
||||
"offset": 0
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/validation",
|
||||
"properties": {
|
||||
"validators": [
|
||||
{
|
||||
"name": "url",
|
||||
"value": "{{url_input.value}}",
|
||||
"rules": [
|
||||
{
|
||||
"type": "required",
|
||||
"validate": null,
|
||||
"error": {
|
||||
"message": "Please input the URL. "
|
||||
},
|
||||
"minLength": 0,
|
||||
"maxLength": 0,
|
||||
"list": [],
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"regex": "",
|
||||
"flags": "",
|
||||
"customOptions": {}
|
||||
},
|
||||
{
|
||||
"type": "url",
|
||||
"validate": null,
|
||||
"error": {
|
||||
"message": "Please input the correct URL."
|
||||
},
|
||||
"minLength": 0,
|
||||
"maxLength": 0,
|
||||
"list": [],
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"regex": "",
|
||||
"flags": "",
|
||||
"customOptions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "url_input",
|
||||
"type": "arco/v1/input",
|
||||
"properties": {
|
||||
"allowClear": false,
|
||||
"disabled": false,
|
||||
"readOnly": false,
|
||||
"defaultValue": "",
|
||||
"updateWhenDefaultValueChanges": false,
|
||||
"placeholder": "please input the URL.",
|
||||
"error": "{{url_form.validatedResult.url.isInvalid}}",
|
||||
"size": "default"
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "url_form",
|
||||
"slot": "content"
|
||||
},
|
||||
"ifCondition": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "core/v1/event",
|
||||
"properties": {
|
||||
"handlers": [
|
||||
{
|
||||
"type": "onChange",
|
||||
"componentId": "url_form",
|
||||
"method": {
|
||||
"name": "validateAllFields",
|
||||
"parameters": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onBlur",
|
||||
"componentId": "url_form",
|
||||
"method": {
|
||||
"name": "validateAllFields",
|
||||
"parameters": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ip_form",
|
||||
"type": "arco/v1/formControl",
|
||||
"properties": {
|
||||
"label": {
|
||||
"format": "plain",
|
||||
"raw": "IP"
|
||||
},
|
||||
"layout": "horizontal",
|
||||
"required": true,
|
||||
"hidden": false,
|
||||
"extra": "",
|
||||
"errorMsg": "{{ip_form.validatedResult.ip?.errors[0]?.message}}",
|
||||
"labelAlign": "left",
|
||||
"colon": false,
|
||||
"help": "",
|
||||
"labelCol": {
|
||||
"span": 3,
|
||||
"offset": 0
|
||||
},
|
||||
"wrapperCol": {
|
||||
"span": 21,
|
||||
"offset": 0
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/validation",
|
||||
"properties": {
|
||||
"validators": [
|
||||
{
|
||||
"name": "ip",
|
||||
"value": "{{ip_input.value}}",
|
||||
"rules": [
|
||||
{
|
||||
"type": "required",
|
||||
"validate": null,
|
||||
"error": {
|
||||
"message": "Please input the IP."
|
||||
},
|
||||
"minLength": 0,
|
||||
"maxLength": 0,
|
||||
"list": [],
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"regex": "",
|
||||
"flags": "",
|
||||
"customOptions": {}
|
||||
},
|
||||
{
|
||||
"type": "ipv4",
|
||||
"validate": null,
|
||||
"error": {
|
||||
"message": "Please input the correct IP."
|
||||
},
|
||||
"minLength": 0,
|
||||
"maxLength": 0,
|
||||
"list": [],
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"regex": "",
|
||||
"flags": "",
|
||||
"customOptions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ip_input",
|
||||
"type": "arco/v1/input",
|
||||
"properties": {
|
||||
"allowClear": false,
|
||||
"disabled": false,
|
||||
"readOnly": false,
|
||||
"defaultValue": "",
|
||||
"updateWhenDefaultValueChanges": false,
|
||||
"placeholder": "please input the IP.",
|
||||
"error": "{{ip_form.validatedResult.ip.isInvalid}}",
|
||||
"size": "default"
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "ip_form",
|
||||
"slot": "content"
|
||||
},
|
||||
"ifCondition": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "core/v1/event",
|
||||
"properties": {
|
||||
"handlers": [
|
||||
{
|
||||
"type": "onChange",
|
||||
"componentId": "ip_form",
|
||||
"method": {
|
||||
"name": "validateAllFields",
|
||||
"parameters": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onBlur",
|
||||
"componentId": "ip_form",
|
||||
"method": {
|
||||
"name": "validateAllFields",
|
||||
"parameters": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "phone_form",
|
||||
"type": "arco/v1/formControl",
|
||||
"properties": {
|
||||
"label": {
|
||||
"format": "plain",
|
||||
"raw": "phone"
|
||||
},
|
||||
"layout": "horizontal",
|
||||
"required": true,
|
||||
"hidden": false,
|
||||
"extra": "",
|
||||
"errorMsg": "{{phone_form.validatedResult.phone.errors[0]?.message}}",
|
||||
"labelAlign": "left",
|
||||
"colon": false,
|
||||
"help": "",
|
||||
"labelCol": {
|
||||
"span": 3,
|
||||
"offset": 0
|
||||
},
|
||||
"wrapperCol": {
|
||||
"span": 21,
|
||||
"offset": 0
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/validation",
|
||||
"properties": {
|
||||
"validators": [
|
||||
{
|
||||
"name": "phone",
|
||||
"value": "{{phone_input.value}}",
|
||||
"rules": [
|
||||
{
|
||||
"type": "required",
|
||||
"validate": null,
|
||||
"error": {
|
||||
"message": "Please input the phone number."
|
||||
},
|
||||
"minLength": 0,
|
||||
"maxLength": 0,
|
||||
"list": [],
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"regex": "",
|
||||
"flags": "",
|
||||
"customOptions": {}
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"validate": null,
|
||||
"error": {
|
||||
"message": "Please input the correct phone number."
|
||||
},
|
||||
"minLength": 0,
|
||||
"maxLength": 0,
|
||||
"list": [],
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"regex": "^1[3456789]\\d{9}$",
|
||||
"flags": "",
|
||||
"customOptions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "phone_input",
|
||||
"type": "arco/v1/input",
|
||||
"properties": {
|
||||
"allowClear": false,
|
||||
"disabled": false,
|
||||
"readOnly": false,
|
||||
"defaultValue": "",
|
||||
"updateWhenDefaultValueChanges": false,
|
||||
"placeholder": "please input the phone number.",
|
||||
"error": "{{phone_form.validatedResult.phone.isInvalid}}",
|
||||
"size": "default"
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "phone_form",
|
||||
"slot": "content"
|
||||
},
|
||||
"ifCondition": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "core/v1/event",
|
||||
"properties": {
|
||||
"handlers": [
|
||||
{
|
||||
"type": "onChange",
|
||||
"componentId": "phone_form",
|
||||
"method": {
|
||||
"name": "validateAllFields",
|
||||
"parameters": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onBlur",
|
||||
"componentId": "phone_form",
|
||||
"method": {
|
||||
"name": "validateAllFields",
|
||||
"parameters": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "city_form",
|
||||
"type": "arco/v1/formControl",
|
||||
"properties": {
|
||||
"label": {
|
||||
"format": "plain",
|
||||
"raw": "city"
|
||||
},
|
||||
"layout": "horizontal",
|
||||
"required": false,
|
||||
"hidden": false,
|
||||
"extra": "",
|
||||
"errorMsg": "{{city_form.validatedResult.city.errors[0]?.message}}",
|
||||
"labelAlign": "left",
|
||||
"colon": false,
|
||||
"help": "",
|
||||
"labelCol": {
|
||||
"span": 3,
|
||||
"offset": 0
|
||||
},
|
||||
"wrapperCol": {
|
||||
"span": 21,
|
||||
"offset": 0
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/validation",
|
||||
"properties": {
|
||||
"validators": [
|
||||
{
|
||||
"name": "city",
|
||||
"value": "{{city_select.value}}",
|
||||
"rules": [
|
||||
{
|
||||
"type": "include",
|
||||
"validate": null,
|
||||
"error": {
|
||||
"message": "Please select \"Beijing\"."
|
||||
},
|
||||
"minLength": 0,
|
||||
"maxLength": 0,
|
||||
"includeList": ["Beijing"],
|
||||
"excludeList": [],
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"regex": "",
|
||||
"flags": "",
|
||||
"customOptions": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "city_select",
|
||||
"type": "arco/v1/select",
|
||||
"properties": {
|
||||
"allowClear": false,
|
||||
"multiple": false,
|
||||
"allowCreate": false,
|
||||
"bordered": true,
|
||||
"defaultValue": "Beijing",
|
||||
"disabled": false,
|
||||
"labelInValue": false,
|
||||
"loading": false,
|
||||
"showSearch": false,
|
||||
"unmountOnExit": false,
|
||||
"options": [
|
||||
{
|
||||
"value": "Beijing",
|
||||
"text": "Beijing"
|
||||
},
|
||||
{
|
||||
"value": "London",
|
||||
"text": "London"
|
||||
},
|
||||
{
|
||||
"value": "NewYork",
|
||||
"text": "NewYork"
|
||||
}
|
||||
],
|
||||
"placeholder": "Select city",
|
||||
"size": "default",
|
||||
"error": false,
|
||||
"updateWhenDefaultValueChanges": false
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "city_form",
|
||||
"slot": "content"
|
||||
},
|
||||
"ifCondition": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "core/v1/event",
|
||||
"properties": {
|
||||
"handlers": [
|
||||
{
|
||||
"type": "onChange",
|
||||
"componentId": "city_form",
|
||||
"method": {
|
||||
"name": "validateAllFields",
|
||||
"parameters": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "clear_button",
|
||||
"type": "arco/v1/button",
|
||||
"properties": {
|
||||
"type": "default",
|
||||
"status": "default",
|
||||
"long": false,
|
||||
"size": "default",
|
||||
"disabled": false,
|
||||
"loading": false,
|
||||
"shape": "square",
|
||||
"text": "clear"
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/event",
|
||||
"properties": {
|
||||
"handlers": [
|
||||
{
|
||||
"type": "onClick",
|
||||
"componentId": "name_form",
|
||||
"method": {
|
||||
"name": "clearAllErrors",
|
||||
"parameters": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onClick",
|
||||
"componentId": "email_form",
|
||||
"method": {
|
||||
"name": "clearAllErrors",
|
||||
"parameters": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onClick",
|
||||
"componentId": "url_form",
|
||||
"method": {
|
||||
"name": "clearAllErrors",
|
||||
"parameters": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onClick",
|
||||
"componentId": "ip_form",
|
||||
"method": {
|
||||
"name": "clearErrors",
|
||||
"parameters": {
|
||||
"names": "{{['ip']}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onClick",
|
||||
"componentId": "phone_form",
|
||||
"method": {
|
||||
"name": "clearErrors",
|
||||
"parameters": {
|
||||
"names": "{{['phone']}}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "onClick",
|
||||
"componentId": "city_form",
|
||||
"method": {
|
||||
"name": "clearAllErrors",
|
||||
"parameters": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "check_name",
|
||||
"type": "core/v1/dummy",
|
||||
"properties": {},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/fetch",
|
||||
"properties": {
|
||||
"url": "",
|
||||
"method": "get",
|
||||
"lazy": false,
|
||||
"disabled": false,
|
||||
"headers": {},
|
||||
"body": {},
|
||||
"bodyType": "json",
|
||||
"onComplete": [
|
||||
{
|
||||
"componentId": "name_form",
|
||||
"method": {
|
||||
"name": "setErrors",
|
||||
"parameters": {
|
||||
"errorsMap": "{{\n{\n name: [...name_form.validatedResult.name.errors, { message: 'The name is exist.' }]\n}\n}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"onError": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -21,10 +21,28 @@
|
||||
{
|
||||
"type": "core/v1/validation",
|
||||
"properties": {
|
||||
"value": "{{ emailInput.value || \"\" }}",
|
||||
"maxLength": 20,
|
||||
"minLength": 10,
|
||||
"rule": "email"
|
||||
"validators": [
|
||||
{
|
||||
"name": "email",
|
||||
"value": "{{ emailInput.value || \"\" }}",
|
||||
"rules": [
|
||||
{
|
||||
"type": "email",
|
||||
"error": {
|
||||
"message": "Please input the email."
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "length",
|
||||
"maxLength": 20,
|
||||
"minLength": 10,
|
||||
"error": {
|
||||
"message": "Please input the length between 10 and 20."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -34,7 +52,7 @@
|
||||
"type": "core/v1/text",
|
||||
"properties": {
|
||||
"value": {
|
||||
"raw": "{{ emailInput.validResult.errorMsg }}",
|
||||
"raw": "{{ emailInput.validatedResult.email.errors[0]?.message }}",
|
||||
"format": "plain"
|
||||
}
|
||||
},
|
||||
@ -54,10 +72,29 @@
|
||||
{
|
||||
"type": "core/v1/validation",
|
||||
"properties": {
|
||||
"value": "{{ phoneInput.value || \"\" }}",
|
||||
"maxLength": 100,
|
||||
"minLength": 0,
|
||||
"rule": "phoneNumber"
|
||||
"validators": [
|
||||
{
|
||||
"name": "phone",
|
||||
"value": "{{ phoneInput.value || \"\" }}",
|
||||
"rules": [
|
||||
{
|
||||
"type": "regex",
|
||||
"regex": "^1[3456789]\\d{9}$",
|
||||
"error": {
|
||||
"message": "Please input the correct phone number."
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "length",
|
||||
"maxLength": 100,
|
||||
"minLength": 0,
|
||||
"error": {
|
||||
"message": "Please input the length between 0 and 100."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -95,7 +132,7 @@
|
||||
"parameters": "{{ `email:${ emailInput.value } phone:${ phoneInput.value }` }}"
|
||||
},
|
||||
"wait": {},
|
||||
"disabled": "{{ emailInput.validResult.isInvalid || phoneInput.validResult.isInvalid }}"
|
||||
"disabled": "{{ emailInput.validatedResult.email.isInvalid || phoneInput.validatedResult.phone.isInvalid }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -264,9 +264,22 @@
|
||||
{
|
||||
"type": "core/v1/validation",
|
||||
"properties": {
|
||||
"value": "{{ nameInput.value || \"\" }}",
|
||||
"maxLength": 10,
|
||||
"minLength": 2
|
||||
"validators": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "{{ nameInput.value || \"\" }}",
|
||||
"rules": [
|
||||
{
|
||||
"type": "length",
|
||||
"maxLength": 10,
|
||||
"minLength": 2,
|
||||
"error": {
|
||||
"message": "Please input the length between 2 and 10."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -14,7 +14,9 @@
|
||||
import { initSunmaoUI } from './src';
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
import { sunmaoChakraUILib } from '@sunmao-ui/chakra-ui-lib';
|
||||
import { ArcoDesignLib } from '@sunmao-ui/arco-lib';
|
||||
import examples from '@example.json';
|
||||
import '@arco-design/web-react/dist/css/arco.css';
|
||||
|
||||
const selectEl = document.querySelector('select');
|
||||
for (const example of examples) {
|
||||
@ -29,7 +31,9 @@
|
||||
const rootEl = document.querySelector('#root');
|
||||
const render = example => {
|
||||
ReactDOM.unmountComponentAtNode(rootEl);
|
||||
const { App, registry } = initSunmaoUI({ libs: [sunmaoChakraUILib] });
|
||||
const { App, registry } = initSunmaoUI({
|
||||
libs: [sunmaoChakraUILib, ArcoDesignLib],
|
||||
});
|
||||
const { app, modules = [] } = example.value;
|
||||
window.registry = registry;
|
||||
modules.forEach(m => {
|
||||
|
@ -1,112 +1,384 @@
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { isEqual } from 'lodash';
|
||||
import { Type, Static, TSchema } from '@sinclair/typebox';
|
||||
import { implementRuntimeTrait } from '../../utils/buildKit';
|
||||
import { CORE_VERSION, CoreTraitName } from '@sunmao-ui/shared';
|
||||
import { CORE_VERSION, CoreTraitName, StringUnion } from '@sunmao-ui/shared';
|
||||
|
||||
type ValidationResult = Static<typeof ResultSpec>;
|
||||
type ValidationRule = (text: string) => ValidationResult;
|
||||
type ParseValidateOption<
|
||||
T extends Record<string, TSchema>,
|
||||
OptionalKeys extends keyof T = ''
|
||||
> = {
|
||||
[K in keyof T as K extends OptionalKeys ? never : K]: Static<T[K]>;
|
||||
} & {
|
||||
[K in OptionalKeys]?: Static<T[K]>;
|
||||
};
|
||||
type UnionToIntersection<TUnion> = (
|
||||
TUnion extends unknown ? (params: TUnion) => unknown : never
|
||||
) extends (params: infer Params) => unknown
|
||||
? Params
|
||||
: never;
|
||||
|
||||
const ResultSpec = Type.Object({
|
||||
isInvalid: Type.Boolean(),
|
||||
errorMsg: Type.String(),
|
||||
});
|
||||
const validateOptionMap = {
|
||||
length: {
|
||||
minLength: Type.Number({ title: 'Min length' }),
|
||||
maxLength: Type.Number({ title: 'Max length' }),
|
||||
},
|
||||
include: {
|
||||
includeList: Type.Array(Type.String(), { title: 'Include list' }),
|
||||
},
|
||||
exclude: {
|
||||
excludeList: Type.Array(Type.String(), { title: 'Exclude List' }),
|
||||
},
|
||||
number: {
|
||||
min: Type.Number({ title: 'Min' }),
|
||||
max: Type.Number({ title: 'Max' }),
|
||||
},
|
||||
regex: {
|
||||
regex: Type.String({ title: 'Regex', description: 'The regular expression string.' }),
|
||||
flags: Type.String({
|
||||
title: 'Flags',
|
||||
description: 'The flags of the regular expression.',
|
||||
}),
|
||||
},
|
||||
};
|
||||
const validateFnMap = {
|
||||
required(value: string) {
|
||||
return value !== undefined && value !== null && value !== '';
|
||||
},
|
||||
length(
|
||||
value: string,
|
||||
{
|
||||
minLength,
|
||||
maxLength,
|
||||
}: ParseValidateOption<typeof validateOptionMap['length'], 'minLength' | 'maxLength'>
|
||||
) {
|
||||
if (minLength !== undefined && value.length < minLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export const ValidationTraitStateSpec = Type.Object({
|
||||
validResult: ResultSpec,
|
||||
if (maxLength !== undefined && value.length > maxLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
include(
|
||||
value: string,
|
||||
{ includeList }: ParseValidateOption<typeof validateOptionMap['include']>
|
||||
) {
|
||||
return includeList.includes(value);
|
||||
},
|
||||
exclude(
|
||||
value: string,
|
||||
{ excludeList }: ParseValidateOption<typeof validateOptionMap['exclude']>
|
||||
) {
|
||||
return !excludeList.includes(value);
|
||||
},
|
||||
regex(
|
||||
value: string,
|
||||
{ regex, flags }: ParseValidateOption<typeof validateOptionMap['regex'], 'flags'>
|
||||
) {
|
||||
return new RegExp(regex, flags).test(value);
|
||||
},
|
||||
number(
|
||||
value: string | number,
|
||||
{ min, max }: ParseValidateOption<typeof validateOptionMap['number'], 'min' | 'max'>
|
||||
) {
|
||||
const num = Number(value);
|
||||
|
||||
if (min !== undefined && num < min) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (max !== undefined && num > max) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
ipv4(value: string) {
|
||||
return /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(
|
||||
value
|
||||
);
|
||||
},
|
||||
email(value: string) {
|
||||
return /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(value);
|
||||
},
|
||||
url(value: string) {
|
||||
return /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)$/.test(
|
||||
value
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
type AllFields = UnionToIntersection<
|
||||
typeof validateOptionMap[keyof typeof validateOptionMap]
|
||||
>;
|
||||
type AllFieldsKeys = keyof UnionToIntersection<AllFields>;
|
||||
|
||||
const ValidatorSpec = Type.Object({
|
||||
name: Type.String({
|
||||
title: 'Name',
|
||||
description: 'The name is used for getting the validated result.',
|
||||
}),
|
||||
value: Type.Any({ title: 'Value', description: 'The value need to be validated.' }),
|
||||
rules: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
type: StringUnion(
|
||||
Object.keys(validateFnMap).concat(['custom']) as [
|
||||
keyof typeof validateFnMap,
|
||||
'custom'
|
||||
],
|
||||
{
|
||||
type: 'Type',
|
||||
description:
|
||||
'The type of the rule. Setting it as `custom` to use custom validate function.',
|
||||
}
|
||||
),
|
||||
validate:
|
||||
Type.Any({
|
||||
title: 'Validate',
|
||||
description:
|
||||
'The validate function for the rule. Return `false` means it is invalid.',
|
||||
conditions: [
|
||||
{
|
||||
key: 'type',
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
}) ||
|
||||
Type.Function(
|
||||
[Type.String(), Type.Record(Type.String(), Type.Any())],
|
||||
Type.Boolean(),
|
||||
{
|
||||
conditions: [
|
||||
{
|
||||
key: 'type',
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
error: Type.Object(
|
||||
{
|
||||
message: Type.String({
|
||||
title: 'Message',
|
||||
description: 'The message to display when the value is invalid.',
|
||||
}),
|
||||
},
|
||||
{ title: 'Error' }
|
||||
),
|
||||
...(Object.keys(validateOptionMap) as [keyof typeof validateOptionMap]).reduce(
|
||||
(result, key) => {
|
||||
const option = validateOptionMap[key] as AllFields;
|
||||
|
||||
(Object.keys(option) as [AllFieldsKeys]).forEach(optionKey => {
|
||||
if (result[optionKey]) {
|
||||
// if the different validate functions have the same parameter
|
||||
throw Error(
|
||||
"[Validation Trait]: The different validate function has the same parameter, please change the parameter's name."
|
||||
);
|
||||
} else {
|
||||
result[optionKey] = {
|
||||
...option[optionKey],
|
||||
conditions: [{ key: 'type', value: key }],
|
||||
} as any;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
{} as AllFields
|
||||
),
|
||||
customOptions: Type.Record(Type.String(), Type.Any(), {
|
||||
title: 'Custom options',
|
||||
description:
|
||||
'The custom options would pass to the custom validate function as the second parameter.',
|
||||
conditions: [
|
||||
{
|
||||
key: 'type',
|
||||
value: 'custom',
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'Rules',
|
||||
}
|
||||
),
|
||||
{
|
||||
title: 'Rules',
|
||||
widget: 'core/v1/array',
|
||||
widgetOptions: { displayedKeys: ['type'] },
|
||||
}
|
||||
),
|
||||
});
|
||||
|
||||
export const ValidationTraitPropertiesSpec = Type.Object({
|
||||
value: Type.String(),
|
||||
rule: Type.Optional(Type.String()),
|
||||
maxLength: Type.Optional(Type.Integer()),
|
||||
minLength: Type.Optional(Type.Integer()),
|
||||
validators: Type.Array(ValidatorSpec, {
|
||||
title: 'Validators',
|
||||
widget: 'core/v1/array',
|
||||
widgetOptions: { displayedKeys: ['name'] },
|
||||
}),
|
||||
});
|
||||
|
||||
const ErrorSpec = Type.Object({
|
||||
message: Type.String(),
|
||||
});
|
||||
|
||||
const ValidatedResultSpec = Type.Record(
|
||||
Type.String(),
|
||||
Type.Object({
|
||||
isInvalid: Type.Boolean(),
|
||||
errors: Type.Array(ErrorSpec),
|
||||
})
|
||||
);
|
||||
|
||||
export const ValidationTraitStateSpec = Type.Object({
|
||||
validatedResult: ValidatedResultSpec,
|
||||
});
|
||||
|
||||
export default implementRuntimeTrait({
|
||||
version: CORE_VERSION,
|
||||
metadata: {
|
||||
name: CoreTraitName.Validation,
|
||||
description: 'validation trait',
|
||||
description: 'A trait for the form validation.',
|
||||
},
|
||||
spec: {
|
||||
properties: ValidationTraitPropertiesSpec,
|
||||
state: ValidationTraitStateSpec,
|
||||
methods: [],
|
||||
methods: [
|
||||
{
|
||||
name: 'setErrors',
|
||||
parameters: Type.Object({
|
||||
errorsMap: Type.Record(Type.String(), Type.Array(ErrorSpec)),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'validateFields',
|
||||
parameters: Type.Object({
|
||||
names: Type.Array(Type.String()),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'validateAllFields',
|
||||
parameters: Type.Object({}),
|
||||
},
|
||||
{
|
||||
name: 'clearErrors',
|
||||
parameters: Type.Object({ names: Type.Array(Type.String()) }),
|
||||
},
|
||||
{
|
||||
name: 'clearAllErrors',
|
||||
parameters: Type.Object({}),
|
||||
},
|
||||
],
|
||||
},
|
||||
})(() => {
|
||||
const rules = new Map<string, ValidationRule>();
|
||||
|
||||
function addValidationRule(name: string, rule: ValidationRule) {
|
||||
rules.set(name, rule);
|
||||
}
|
||||
|
||||
addValidationRule('email', text => {
|
||||
if (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(text)) {
|
||||
return {
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
isInvalid: true,
|
||||
errorMsg: 'Please enter valid email.',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
addValidationRule('phoneNumber', text => {
|
||||
if (/^1[3456789]\d{9}$/.test(text)) {
|
||||
return {
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
isInvalid: true,
|
||||
errorMsg: 'Please enter valid phone number.',
|
||||
};
|
||||
}
|
||||
});
|
||||
const ValidationResultCache: Record<string, ValidationResult> = {};
|
||||
const initialMap = new Map();
|
||||
|
||||
return props => {
|
||||
const { value, minLength, maxLength, mergeState, componentId, rule } = props;
|
||||
const { validators, componentId, subscribeMethods, mergeState } = props;
|
||||
const validatorMap = validators.reduce((result, validator) => {
|
||||
result[validator.name] = validator;
|
||||
|
||||
const result: ValidationResult = {
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
return result;
|
||||
}, {} as Record<string, Static<typeof ValidatorSpec>>);
|
||||
|
||||
if (maxLength !== undefined && value.length > maxLength) {
|
||||
result.isInvalid = true;
|
||||
result.errorMsg = `Can not be longer than ${maxLength}.`;
|
||||
} else if (minLength !== undefined && value.length < minLength) {
|
||||
result.isInvalid = true;
|
||||
result.errorMsg = `Can not be shorter than ${minLength}.`;
|
||||
} else {
|
||||
const rulesArr = rule ? rule.split(',') : [];
|
||||
for (const ruleName of rulesArr) {
|
||||
const validateFunc = rules.get(ruleName);
|
||||
if (validateFunc) {
|
||||
const { isInvalid, errorMsg } = validateFunc(value);
|
||||
if (isInvalid) {
|
||||
result.isInvalid = true;
|
||||
result.errorMsg = errorMsg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
function setErrors({
|
||||
errorsMap,
|
||||
}: {
|
||||
errorsMap: Record<string, Static<typeof ErrorSpec>[]>;
|
||||
}) {
|
||||
const validatedResult = Object.keys(errorsMap).reduce(
|
||||
(result: Static<typeof ValidatedResultSpec>, name) => {
|
||||
result[name] = {
|
||||
isInvalid: errorsMap[name].length !== 0,
|
||||
errors: errorsMap[name],
|
||||
};
|
||||
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
mergeState({
|
||||
validatedResult,
|
||||
});
|
||||
}
|
||||
function validateFields({ names }: { names: string[] }) {
|
||||
const validatedResult = names
|
||||
.map(name => {
|
||||
const validator = validatorMap[name];
|
||||
const { value, rules } = validator;
|
||||
const errors = rules
|
||||
.map(rule => {
|
||||
const { type, error, validate, customOptions, ...options } = rule;
|
||||
let isValid = true;
|
||||
|
||||
if (type === 'custom') {
|
||||
isValid = validate(value, customOptions);
|
||||
} else {
|
||||
isValid = validateFnMap[type](value, options);
|
||||
}
|
||||
|
||||
return isValid ? null : { message: error.message };
|
||||
})
|
||||
.filter((error): error is Static<typeof ErrorSpec> => error !== null);
|
||||
|
||||
return {
|
||||
name,
|
||||
isInvalid: errors.length !== 0,
|
||||
errors,
|
||||
};
|
||||
})
|
||||
.reduce((result: Static<typeof ValidatedResultSpec>, validatedResultItem) => {
|
||||
result[validatedResultItem.name] = {
|
||||
isInvalid: validatedResultItem.isInvalid,
|
||||
errors: validatedResultItem.errors,
|
||||
};
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
mergeState({ validatedResult });
|
||||
}
|
||||
function validateAllFields() {
|
||||
validateFields({ names: validators.map(({ name }) => name) });
|
||||
}
|
||||
function clearErrors({ names }: { names: string[] }) {
|
||||
setErrors({
|
||||
errorsMap: names.reduce((result: Record<string, []>, name) => {
|
||||
result[name] = [];
|
||||
|
||||
return result;
|
||||
}, {}),
|
||||
});
|
||||
}
|
||||
function clearAllErrors() {
|
||||
clearErrors({ names: validators.map(({ name }) => name) });
|
||||
}
|
||||
|
||||
if (!isEqual(result, ValidationResultCache[componentId])) {
|
||||
ValidationResultCache[componentId] = result;
|
||||
mergeState({
|
||||
validResult: result,
|
||||
});
|
||||
subscribeMethods({
|
||||
setErrors,
|
||||
validateFields,
|
||||
validateAllFields,
|
||||
clearErrors,
|
||||
clearAllErrors,
|
||||
});
|
||||
|
||||
if (initialMap.has(componentId) === false) {
|
||||
clearAllErrors();
|
||||
initialMap.set(componentId, true);
|
||||
}
|
||||
|
||||
return {
|
||||
props: null,
|
||||
props: {
|
||||
componentDidUnmount: [
|
||||
() => {
|
||||
initialMap.delete(componentId);
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user