129 lines
3.6 KiB
Plaintext
129 lines
3.6 KiB
Plaintext
/**
|
|
* 表单视图模型(1.7.2)。
|
|
*
|
|
* 单行记录的表单编辑:字段定义(可编辑/只读/必填),值的脏检测与回滚,
|
|
* 提交时产出 CellChange 交给 TableEditor 生成 UPDATE。
|
|
*/
|
|
import { CellChange } from './TableEditor';
|
|
|
|
export interface FieldSpec {
|
|
column: string;
|
|
label: string;
|
|
editable: boolean;
|
|
required: boolean;
|
|
}
|
|
|
|
export class FormViewModel {
|
|
private readonly fields: FieldSpec[];
|
|
private original: Map<string, string> = new Map<string, string>();
|
|
private current: Map<string, string> = new Map<string, string>();
|
|
|
|
constructor(fields: FieldSpec[]) {
|
|
this.fields = fields.slice();
|
|
}
|
|
|
|
fields_(): FieldSpec[] {
|
|
return this.fields.slice();
|
|
}
|
|
|
|
/** 载入一行数据(重置脏状态)。 */
|
|
load(values: Map<string, string>): void {
|
|
this.original = new Map<string, string>();
|
|
this.current = new Map<string, string>();
|
|
for (let i = 0; i < this.fields.length; i++) {
|
|
const column: string = this.fields[i].column;
|
|
const value: string | undefined = values.get(column);
|
|
this.original.set(column, value === undefined ? '' : value);
|
|
this.current.set(column, value === undefined ? '' : value);
|
|
}
|
|
}
|
|
|
|
get(column: string): string {
|
|
const value: string | undefined = this.current.get(column);
|
|
return value === undefined ? '' : value;
|
|
}
|
|
|
|
/** 修改值;只读列或未知列忽略。 */
|
|
set(column: string, value: string): boolean {
|
|
const field: FieldSpec | undefined = this.fieldOf(column);
|
|
if (field === undefined || !field.editable) {
|
|
return false;
|
|
}
|
|
this.current.set(column, value);
|
|
return true;
|
|
}
|
|
|
|
isEditable(column: string): boolean {
|
|
const field: FieldSpec | undefined = this.fieldOf(column);
|
|
return field !== undefined && field.editable;
|
|
}
|
|
|
|
/** 脏字段(值发生过变化)。 */
|
|
dirtyFields(): string[] {
|
|
const out: string[] = [];
|
|
for (let i = 0; i < this.fields.length; i++) {
|
|
const column: string = this.fields[i].column;
|
|
if (this.get(column) !== this.originalOf(column)) {
|
|
out.push(column);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
isDirty(): boolean {
|
|
return this.dirtyFields().length > 0;
|
|
}
|
|
|
|
/** 回滚到载入时的值。 */
|
|
revert(): void {
|
|
this.current = new Map<string, string>();
|
|
this.original.forEach((value: string, column: string) => {
|
|
this.current.set(column, value);
|
|
});
|
|
}
|
|
|
|
/** 必填校验;返回错误列与消息。 */
|
|
validate(): string[] {
|
|
const errors: string[] = [];
|
|
for (let i = 0; i < this.fields.length; i++) {
|
|
const field: FieldSpec = this.fields[i];
|
|
if (field.required && this.get(field.column).trim().length === 0) {
|
|
errors.push(field.column + ' is required');
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
/** 产出变更(供 TableEditor 提交);必填不通过时返回空数组。 */
|
|
changes(): CellChange[] {
|
|
if (this.validate().length > 0) {
|
|
return [];
|
|
}
|
|
const out: CellChange[] = [];
|
|
const dirty: string[] = this.dirtyFields();
|
|
for (let i = 0; i < dirty.length; i++) {
|
|
out.push({
|
|
rowIndex: 0,
|
|
column: dirty[i],
|
|
oldValue: this.originalOf(dirty[i]),
|
|
newValue: this.get(dirty[i])
|
|
});
|
|
}
|
|
return out;
|
|
}
|
|
|
|
private originalOf(column: string): string {
|
|
const value: string | undefined = this.original.get(column);
|
|
return value === undefined ? '' : value;
|
|
}
|
|
|
|
private fieldOf(column: string): FieldSpec | undefined {
|
|
for (let i = 0; i < this.fields.length; i++) {
|
|
if (this.fields[i].column === column) {
|
|
return this.fields[i];
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|