前提
最近新增一个表单提交的功能
实现
1.html页面使用form表单,表单监听了submit事件,只要有提交就会触发该submit事件。
如下面这段代码所示:
<form [formGroup]="formGroup" (ngSubmit)="submitForm()">
2.接收服务器的脚本配置如下:
<!--使用通用的表单标签-->
<tag xsi:type="define:appGeneralFormTag" style="appGeneralFormTag" name="appGeneralFormTag"
xmlns:define="http://pupuwang.com/define"
xmlns:plugin="http://pupuwang.com/plugin"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" paramName="gatherForm">
<setting formName="opportunity_share"></setting>
<table name="opportunity" value="#opportunity">
<condition field="opportunity_id" valueStr=":opportunity_id"/>
<condition field="plugin_id" valueStr=":plugin_id"/>
</table>
<formObj formType="mobileInput" formKey="account_thos__mobile" placeHolder="请输入58手机号" label="" value="" showValue="">
<rules name="required" value="true" message="请正确填写58同城账号"/>
<rules name="pattern" value="^1[35789]\d{9}$" message="请填写11位手机号码"/>
</formObj>
<formObj formType="codebut" formKey="codebut" label="获取验证码" second="60" placeHolder="输入验证码">
<rules name="required" value="true" message="请填写验证码"/>
<rules name="pattern" value="^\d{4}$" message="请填写正确的验证码"/>
<submit id="verificationCode" type="code"></submit>
</formObj>
<formObj formType="passwordInput" formKey="account_thos_password" placeHolder="请输入密码" label="" value="" showValue="">
<rules name="required" value="true" message="请正确填写58同城密码"/>
</formObj>
<formObj formType="textInput" formKey="account_thos_nickname" placeHolder="输入显示昵称" label="" value="" showValue="">
<rules name="required" value="true" message="请填写正确的昵称"/>
<rules name="pattern" value="^[\u4e00-\u9fa5_a-zA-Z_\d_\w!#$%'*+/=?.^_`{|}~-]{2,12}$" message="请输入最多12个字符"/>
</formObj>
<formObj formType="grayItem"></formObj>
<updateTable name = "account_thos">
<column name="account_thos_id" value="##opportunity_share__account_thos_id"/>
<column name="mobile" value="##opportunity_share__mobile"/>
<column name="vcode" value="##opportunity_share__vcode"/>
<column name="password" value="##opportunity_share__password"/>
<column name="nickname" value="##opportunity_share__nickname"/>
<column name="creator" value=":currentUserId()"/>
</updateTable>
</tag>
返回的json格式如下:
{
"type":"tag",
"style":"tag.AppGeneralFormTagLogic",
"name":null,
"linearLayout":"body",
"data":{
"htmlObj":[
{
"compare":false,
"formType":"mobileInput",
"showValue":"",
"canEqual":false,
"checkAll":false,
"expression":true,
"hidden":false,
"formKey":"account_thos__mobile",
"rules":[
{
"name":"required",
"message":"请正确填写58同城账号",
"value":"true"
},
{
"name":"pattern",
"message":"请填写11位手机号码",
"value":"^1[35789]\d{9}$"
}
],
"label":"",
"value":"",
"placeHolder":"请输入58手机号"
}
]
}
}
我们看上面这个json格式formType用于前端在html页面中展示不同的控件,例如手机号码formType就是mobileInput,验证码就是codeBut,
他有一个初始的显示值,用的是showValue这个字段,formKey则用于表单提交,rules则用于脚本配置这个输入框需要校验是选填还是必填,
并且可以使用正则表达式等展示校验过后的信息,placeHolder则表示表单没有输入的时侯,输入框的提示。
<div class="form-page-bg" (click)="closeClick()" *ngIf="tagData">
<div class="section">
<div class="title" [ngStyle]="titleStyle">{{title}}</div>
<div class="content">
<p class="sub-title">{{subTitle}}</p>
<div class="login-form" *ngIf="this.formTagData&&this.formTagData.data" (click)="formClick($event)">
<form [formGroup]="formGroup" (ngSubmit)="submitForm()">
<List *ngFor="let item of this.formTagData.data.htmlObj,let i = index" [ngSwitch]="item.formType">
<ng-container *ngSwitchCase="'mobileInput'">
<ion-item class="post-item" lines="inset">
<ion-input type="text" [(ngModel)]="item.value" [placeholder]="item.placeHolder" maxLength="11"
[formControlName]="item.formKey">
</ion-input>
<button class="vccode" *ngIf="time>-1" [disabled]="time>-1" [ngClass]="{'lightgrey':time>-1}">({{time}})重新获取</button>
<button [ngClass]="{'lightgrey':formGroup.get('account_thos__mobile').errors}" class="vccode"
*ngIf="time===-1" [disabled]="formGroup.get('account_thos__mobile').errors"
(click)="getVC(formGroup.get('account_thos__mobile').errors)">获取验证码</button>
</ion-item>
</ng-container>
<ng-container *ngSwitchCase="'codebut'">
<ion-item class="post-item" lines="inset">
<ion-input type="text" [(ngModel)]="item.value" [placeholder]="item.placeHolder" maxLength="4"
[formControlName]="item.formKey">
</ion-input>
</ion-item>
</ng-container>
<ng-container *ngSwitchCase="'textInput'">
<ion-item class="post-item" lines="inset">
<ion-input type="text" [(ngModel)]="item.value" [placeholder]="item.placeHolder" maxLength="6"
[formControlName]="item.formKey">
</ion-input>
</ion-item>
</ng-container>
<ng-container *ngSwitchCase="'passwordInput'">
<ion-item class="post-item" lines="inset">
<ion-input required type="{{open?'text':'password'}}" [(ngModel)]="item.value"
[placeholder]="item.placeHolder" [formControlName]="item.formKey">
</ion-input>
<span (click)="item.value=''"
[ngClass]="{'close_r50': item.value!='','hideClose_r50':item.value===''}"></span>
<span class="eye" (click)="open=!open" [ngClass]="{'eyeshow':open}"></span>
</ion-item>
</ng-container>
</List>
<ng-container *ngIf="submitBtn">
<button class="btn-58" type="submit">{{submitBtn.data[0].label}}</button>
</ng-container>
</form>
</div>
</div>
</div>
</div>
我们看到这个ngModal就是对字段做双向绑定的,placeholder就是占位符用于显示提示信息。
3.接下来我们看这个component里面是怎么处理表单的事情的:
按照生命周期,表单首先会创建,
我们看到表单是在初始化component组件的时候创建的。
export class ShareFormModalComponent implements OnInit {
// 分享页面数据
shareData = {
icon: "share.png",
layout: "right",
submit: {
content: {},
id: "string"
}
};
title = '分享到58';
subTitle = '请先完善信息,无58账号请先行注册';
@Input() tagData: any;
formGroup: FormGroup = new FormGroup({});
allLabel: any = {};
formTagData: any;
submitBtn: any;
open: boolean;
time = -1;
text = "获取验证码";
constructor(
private eventService: EventService,
private router: Router,
private route: ActivatedRoute,
private httpService: HttpService,
private modalController: ModalController,
private appGlobalService: AppGlobalService,
) { }
我们看上面这一段代码,formGroup: FormGroup = new FormGroup({});就是创建表单。
表单实例化之后需要往实例化之后的表单里面添加字段名称和值:
添加值我们这边的处理方式是在component接收tagData之后回到angular的声明周期方法ngOnInit()中调用:
例如:
ngOnInit() {
if (this.tagData && this.tagData.tags) {
this.createFormGroup(this.tagData.tags);
}
}
在createFormGroup中我们做什么处理呢?我们添加字段,然后添加字段自定义校验:
createFormGroup(tags) {
try {
// tslint:disable-next-line:prefer-for-of
for (let index = 0; index < tags.length; index++) {
const element = tags[index];
if (element.style === "tag.AppGeneralFormTagLogic") {
this.formTagData = element;
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < this.formTagData.data.htmlObj.length; i++) {
const formItem = this.formTagData.data.htmlObj[i];
this.addFormCtrl(formItem);
}
} else if (element.style === "countTipsTag") {
this.subTitle = element.data.tips;
} else if (element.style === "tag.AppGeneralButtonTagLogic") {
this.submitBtn = element;
}
}
} catch (error) {
}
}
每个字段下可能有子字段也有可能有联动字段所以这里又使用函数addFormCtrl来处理
addFormCtrl(formItem) {
const that = this;
// 第一层
this.addFormControl(formItem);
}
addFormControl(formItem) {
const that = this;
if (formItem.formKey && formItem.formKey.length > 1 && !this.formGroup.controls[formItem.formKey]) {
this.formGroup.addControl(formItem.formKey, new FormControl(formItem.value, that.validators(formItem)));
}
if (formItem.rules && formItem.rules.length > 0) {
const message: any = {};
formItem.rules.forEach(rule => {
// 将key 转小写
message[rule.name.toLowerCase()] = rule.message;
});
message.label = formItem.label;
this.allLabel[formItem.formKey] = message;
}
// 设置初始值
if (formItem.formType === 'textInput' || formItem.formType === 'textInputLJ') {
if (formItem.tags && formItem.tags.length > 0) {
formItem.tags.forEach(e => {
if (e.checked) {
if (e.label.indexOf('自定义') > -1) {
formItem.disabled = false;
} else {
formItem.disabled = true;
}
formItem.value = e.showValue;
this.formGroup.patchValue({ [formItem.formKey]: e.showValue });
this.formGroup.addControl(e.formKey, new FormControl(e.value));
}
});
}
}
}
表单校验会调用如下方法:
首先是数据校验
// 数据校验
validators(item) {
const validators = [];
if (item.rules && item.rules.length) {
item.rules.forEach(rule => {
if (rule.name === 'required' && rule.value === 'true') {
validators.push(Validators.required);
} else if (rule.name === 'minLength') {
validators.push(Validators.minLength(Number(rule.value)));
} else if (rule.name === 'maxLength') {
validators.push(Validators.maxLength(Number(rule.value)));
} else if (rule.name === 'min') {
validators.push(Validators.min(Number(rule.value)));
} else if (rule.name === 'max') {
validators.push(Validators.max(Number(rule.value)));
} else if (rule.name === 'pattern') {
validators.push(Validators.pattern(rule.value));
}
});
}
return validators;
}
其次是获取错误信息
getErrors(form) {
const errs = [];
for (const name in form.controls) {
// eg: controls[name] = phone
if (form.controls[name].errors) {
errs.push({ key: [name], err: form.controls[name].errors });
}
}
if (errs.length) {
const errItem = errs[0];
const firstErrMsgKey = Object.keys(errItem.err)[0];
console.log(errItem.key, '校验的字段名');
const messageObj = this.allLabel[errItem.key];
const showMessage = messageObj[firstErrMsgKey];
this.appGlobalService.showToast(showMessage, 3000, 'top');
}
}
添加好上面这些就可以通过服务器返回的json控制输入框的值,正则表达式校验以及错误信息展示。
接下来我们看下触发表单提交动作的处理:
submitForm() {
if (this.formGroup.valid) {
// console.log(this.formGroup.value);
// tslint:disable-next-line:no-shadowed-variable
const params = this.formGroup.value;
// tslint:disable-next-line:forin
for (const prop in params) {
if (Array.isArray(params[prop])) {
params[prop] = params[prop].join(',');
}
// 删除无效值
if (prop === '') {
// delete params[prop];
}
}
console.log(params);
// this.onReset();
} else {
this.getErrors(this.formGroup);
return;
// alert('Validation failed');
}
const params = {
content: {
mobile: this.formGroup.get('account_thos__mobile').value,
codebut: this.formGroup.get('codebut').value,
passWord: this.formGroup.get('account_thos_password').value,
nickName: this.formGroup.get('account_thos_nickname').value
},
id: this.submitBtn.data[0].submit.id,
sessionId: localStorage.getItem("sessionId")
};
this.httpService.post("application/submit", params, res => {
if (res.success) {
this.formGroup.reset();
this.modalController.dismiss();
} else {
this.appGlobalService.showToast(res.message, 1500, "middle");
}
});
}
接下来我们看下这个获取验证码自动计时的方法的处理:
正常情况下,我们首先点击获取验证码,通过getVC接口,api后台向用户手机发送验证码,那什么时候可以点击这个获取验证码呢,当然是有条件的。
这个条件就是重来没有点击过,可以在component组件初始化的时候设置一个time变量初始值是-1。
页面就判断这个值是否是-1,如果是-1初始值name就可以点击。
<button [ngClass]="{'lightgrey':formGroup.get('account_thos__mobile').errors}" class="vccode"
*ngIf="time===-1" [disabled]="formGroup.get('account_thos__mobile').errors"
(click)="getVC(formGroup.get('account_thos__mobile').errors)">获取验证码</button>
其次,如果点击过了则需要展示多少秒之后重新获取,所以还需要在添加一个按钮用于显示重新获取
<button class="vccode" *ngIf="time>-1" [disabled]="time>-1" [ngClass]="{'lightgrey':time>-1}">({{time}})重新获取</button>
接下来我们看下调用api接口获取验证码时的处理:
getVC(invalid) {
console.log(this.formGroup.get("account_thos__mobile"));
if (invalid) {
this.appGlobalService.showToast("请输入正确手机号码", 3000, "top");
} else {
let submitId = '';
let second = 60;
this.formTagData.data.htmlObj.forEach(element => {
if (element && element.formType === 'codebut') {
submitId = element.submit.id;
second = element.second;
const params = {
content: {
mobile: this.formGroup.value.account_thos__mobile
},
id: submitId,
sessionId: localStorage.getItem("sessionId")
};
this.httpService.post("application/submit", params, data => {
if (data.success) {
this.appGlobalService.showToast(
"验证码已发送、请注意查收",
3000,
"top"
);
if (data.data) {
console.log('获取验证码:' + data.data.mvcode);
}
} else {
this.appGlobalService.showToast(data.message, 3000, "top");
}
this.time = second;
const timer = setInterval(() => {
this.time = this.time - 1;
if (this.time === -1) {
clearInterval(timer);
this.text = "获取验证码";
}
}, 1000);
});
}
});
}
}
我们看到发送完验证码之后会有个timer用于对重新获取验证码进行计时。
总结
将这些知识像插件或者小标签一样拼装在一起使用,也可以单独使用,比较灵活。如果有更好的方法可以留言多谢