Skip to content

Commit

Permalink
feat(form): add setDisabled, setRequired method (#1733)
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk authored Jan 11, 2024
1 parent b6ab5f0 commit 581f197
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 2 deletions.
2 changes: 2 additions & 0 deletions packages/form/docs/getting-started.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ export class HomeComponent {
| `getProperty` | Get a form property via path | `FormProperty` |
| `getValue` | Get value via path | `any` |
| `setValue` | Set value via path, should be throw error when invalid path | `this` |
| `setDisabled` | Set `disabled` status via path, should be throw error when invalid path | `this` |
| `setRequired` | Set `required` status via path, should be throw error when invalid path | `this` |
| `updateFeedback` | Set feedback status via path | `this` |

> **Note:** All paths are separated by `/`, for example: `/user/name`, `/arr/0/name`.
Expand Down
2 changes: 2 additions & 0 deletions packages/form/docs/getting-started.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ export class HomeComponent {
| `getProperty` | 根据路径获取表单元素属性 | `FormProperty` |
| `getValue` | 根据路径获取表单元素当前值 | `any` |
| `setValue` | 根据路径设置某个表单元素属性值,若路径不存在会产生异常 | `this` |
| `setDisabled` | 根据路径设置某个表单元素 `disabled` 值,若路径不存在会产生异常 | `this` |
| `setRequired` | 根据路径设置某个表单元素 `required` 值,若路径不存在会产生异常 | `this` |
| `updateFeedback` | 根据路径设置某个表单元素反馈状态 | `this` |

> **注:** 所有 path 采用 `/` 来分隔,例如:`/user/name``/arr/0/name`
Expand Down
7 changes: 7 additions & 0 deletions packages/form/examples/acl/index.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: acl
subtitle: ACL
type: Examples
---

Combined with `@delon/acl` permissions, a Schema can be used to build forms for different roles or permission points.
File renamed without changes.
89 changes: 89 additions & 0 deletions packages/form/examples/method/demo/simple.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
title:
zh-CN: 基础样例
en-US: Basic Usage
order: 0
---

## zh-CN

最简单的用法。

## en-US

Simplest of usage.

```ts
import { Component, DestroyRef, ViewChild, inject } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { timer } from 'rxjs';

import { DelonFormModule, SFComponent, SFSchema } from '@delon/form';
import { NzButtonModule } from 'ng-zorro-antd/button';
import { NzMessageService } from 'ng-zorro-antd/message';

@Component({
selector: 'app-demo',
template: `
<p class="mb-md">
<button nz-button (click)="getUserNameValue()">Get userName value</button>
<button nz-button (click)="modifyUserNameValue()">Modify userName value</button>
<button nz-button (click)="toggleUserNameRequired()">Toggle userName required</button>
<button nz-button (click)="toggleUserNameDisabled()">Toggle userName disabled</button>
<button nz-button (click)="triggerUserNameAsyncStatus()">userName async status</button>
</p>
<sf #sf [schema]="schema" (formSubmit)="submit($event)" />
`,
standalone: true,
imports: [DelonFormModule, NzButtonModule]
})
export class DemoComponent {
@ViewChild('sf') private readonly sf!: SFComponent;
private readonly msg = inject(NzMessageService);
private readonly d$ = inject(DestroyRef);
schema: SFSchema = {
properties: {
userName: { type: 'string', title: '用户名' },
pwd: { type: 'string', title: '密码' }
},
required: ['userName', 'pwd']
};

private get userNameRequired(): boolean {
return (this.sf.getProperty('/userName')?.parent?.schema.required ?? []).includes('userName');
}

private get userNameDisabled(): boolean {
return this.sf.getProperty('/userName')?.schema?.readOnly === true;
}

toggleUserNameRequired(): void {
this.sf.setRequired(`/userName`, !this.userNameRequired);
}

toggleUserNameDisabled(): void {
this.sf.setDisabled(`/userName`, !this.userNameDisabled);
}

modifyUserNameValue(): void {
this.sf.setValue(`/userName`, `Mock text ${+new Date()}`);
}

getUserNameValue(): void {
this.msg.info(this.sf.getValue('/userName'));
}

triggerUserNameAsyncStatus(): void {
this.sf.updateFeedback('/userName', 'validating');
timer(1000 * 2)
.pipe(takeUntilDestroyed(this.d$))
.subscribe(() => {
this.sf.updateFeedback('/userName', 'success');
});
}

submit(value: {}): void {
this.msg.success(JSON.stringify(value));
}
}
```
7 changes: 7 additions & 0 deletions packages/form/examples/method/index.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: method
subtitle: Built-in methods
type: Examples
---

`SFComponent` provides some shortcut methods, such as: `setValue`, `setDisabled`, `setRequired`, etc.
7 changes: 7 additions & 0 deletions packages/form/examples/method/index.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: method
subtitle: 内置方法
type: Examples
---

`SFComponent` 提供一些快捷方法,例如:`setValue``setDisabled``setRequired`
18 changes: 18 additions & 0 deletions packages/form/examples/modal/index.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: modal
subtitle: Modal
type: Examples
---

Using a form in a modal box is a very common scenario. In fact, when you run `ng g ng-alain:edit edit`, you will get a complete example; you will get an HTML template like this:

```html
<sf mode="edit" [schema]="schema" [ui]="ui" [formData]="i" button="none">
<div class="modal-footer">
<button nz-button type="button" (click)="close()">Close</button>
<button nz-button type="submit" [nzType]="'primary'" (click)="save(sf.value)" [disabled]="!sf.valid" [nzLoading]="http.loading">Save</button>
</div>
</sf>
```

`.modal-footer` has been very friendly to integrate custom dynamic boxes.
File renamed without changes.
21 changes: 21 additions & 0 deletions packages/form/spec/form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,27 @@ describe('form: component', () => {
context.comp.setValue('/invalid-path', 'name');
}).toThrow();
});
it('#setDisabled', () => {
context.comp.setDisabled('/name', true);
page.checkSchema('/name', 'readOnly', true);
context.comp.setDisabled('/name', false);
page.checkSchema('/name', 'readOnly', false);
});
it('#setDisabled, shoule be throw error when invlaid path', () => {
expect(() => {
context.comp.setDisabled('/invalid-path', true);
}).toThrow();
});
it('#setRequired', () => {
expect(context.comp.getProperty('/name')?.valid).toBe(false);
context.comp.setRequired('/name', false);
expect(context.comp.getProperty('/name')?.valid).toBe(true);
});
it('#setRequired, shoule be throw error when invlaid path', () => {
expect(() => {
context.comp.setRequired('/invalid-path', true);
}).toThrow();
});
it('#updateFeedback', () => {
const namePath = '/name';
context.comp.updateFeedback(namePath, 'error');
Expand Down
4 changes: 2 additions & 2 deletions packages/form/src/model/form.property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ export abstract class FormProperty {
// 同步更新 required
if (typeof viFnRes === 'object') {
const fixViFnRes = { show: false, required: false, ...viFnRes } as SFVisibleIfReturn;
const parentRequired = this.parent?.schema.required!;
const parentRequired = this.parent?.schema.required;
if (parentRequired && this.propertyId) {
const idx = parentRequired.findIndex(w => w === this.propertyId);
if (fixViFnRes.required) {
Expand All @@ -352,7 +352,7 @@ export abstract class FormProperty {
}
this.ui._required = fixViFnRes.required;
}
return fixViFnRes.show!;
return fixViFnRes.show;
}
return viFnRes;
}
Expand Down
42 changes: 42 additions & 0 deletions packages/form/src/sf.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import type { NzSafeAny } from 'ng-zorro-antd/core/types';
import type { NzFormControlStatusType } from 'ng-zorro-antd/form';

import { mergeConfig } from './config';
import { SF_SEQ } from './const';
import type { ErrorData } from './errors';
import type { SFButton, SFLayout, SFMode, SFValueChange } from './interface';
import { FormProperty, PropertyGroup } from './model/form.property';
Expand Down Expand Up @@ -238,6 +239,47 @@ export class SFComponent implements OnInit, OnChanges, OnDestroy {
return this;
}

/**
* Set form element new `disabled` based on [path](https://ng-alain.com/form/qa#path)
*
* 根据[路径](https://ng-alain.com/form/qa#path)设置某个表单元素 `disabled` 状态
*/
setDisabled(path: string, status: boolean): this {
const property = this.getProperty(path);
if (!property) {
throw new Error(`Invalid path: ${path}`);
}
property.schema.readOnly = status;
property.widget.detectChanges();
return this;
}

/**
* Set form element new `required` based on [path](https://ng-alain.com/form/qa#path)
*
* 根据[路径](https://ng-alain.com/form/qa#path)设置某个表单元素 `required` 状态
*/
setRequired(path: string, status: boolean): this {
const property = this.getProperty(path);
if (!property) {
throw new Error(`Invalid path: ${path}`);
}

const key = path.split(SF_SEQ).pop()!;
const parentRequired = property.parent?.schema.required || [];
const idx = parentRequired.findIndex(w => w === key);
if (status) {
if (idx === -1) parentRequired.push(key);
} else {
if (idx !== -1) parentRequired.splice(idx, 1);
}
property.parent!.schema.required = parentRequired;
property.ui._required = status;
property.widget.detectChanges();
this.validator({ onlyRoot: false });
return this;
}

/**
* Update the feedback status of the widget
*
Expand Down
2 changes: 2 additions & 0 deletions scripts/site/route-paths.txt
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@
/form/layout/zh
/form/mention/en
/form/mention/zh
/form/method/en
/form/method/zh
/form/modal/en
/form/modal/zh
/form/monaco-editor/en
Expand Down

0 comments on commit 581f197

Please sign in to comment.