Skip to content

Commit

Permalink
chore: merge branch 'master' into perf-standalone-self
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk committed Jan 11, 2024
2 parents f3937f2 + 385dc1c commit d529bcc
Show file tree
Hide file tree
Showing 25 changed files with 358 additions and 30 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
1 change: 1 addition & 0 deletions packages/theme/docs/locale.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Note: `en_US` is the package name, follow below.
| French | fr_FR |
| Spanish | es_ES |
| Italian | it_IT |
| Vietnamese | vi_VI |

## Add a new language

Expand Down
1 change: 1 addition & 0 deletions packages/theme/docs/locale.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ switchLanguage() {
| 法文 | fr_FR |
| 西班牙语 | es_ES |
| 意大利语 | it_IT |
| 越南语 | vi_VI |

## 增加语言包

Expand Down
1 change: 1 addition & 0 deletions packages/theme/layout-default/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ The layout can be dynamically managed at runtime through the `LayoutDefaultServi
| `[disabledAcl]` | Displayed `disabled` state when `acl` check fails. | `boolean` | `false` |
| `[autoCloseUnderPad]` | When the route width is less than the Pad width, the sidebar is automatically closed. | `boolean` | `true` |
| `[recursivePath]` | Automatic up recursive lookup, menu data source contains `/ware`, then `/ware/1` is also treated as `/ware` | `boolean` | `true` |
| `[hideEmptyChildren]` | When all children are hidden, whether to hide the parent as well | `boolean` | `true` |
| `[openStrictly]` | Precise check open status, does not auto closed other open item | `boolean` | `false` |
| `[maxLevelIcon]` | Icon displays up to which level | `number` | `3` |
| `(select)` | Callback when clicking menu (including `disabled`) | `EventEmitter<Menu>` | - |
Expand Down
1 change: 1 addition & 0 deletions packages/theme/layout-default/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export class LayoutBasicComponent {
| `[disabledAcl]` | `acl` 校验失败时以 `disabled` 状态显示 | `boolean` | `false` |
| `[autoCloseUnderPad]` | 小于Pad宽度时路由切换后自动关闭侧边栏 | `boolean` | `true` |
| `[recursivePath]` | 自动向上递归查找,菜单数据源包含 `/ware`,则 `/ware/1` 也视为 `/ware`| `boolean` | `true` |
| `[hideEmptyChildren]` | 当所有子项都为隐藏时,是否也隐藏父级 | `boolean` | `true` |
| `[openStrictly]` | 展开完全受控,不再自动关闭已展开的项 | `boolean` | `false` |
| `[maxLevelIcon]` | Icon最多显示到第几层 | `number` | `3` |
| `(select)` | 点击菜单时回调(包含 `disabled`| `EventEmitter<Menu>` | - |
Expand Down
73 changes: 51 additions & 22 deletions packages/theme/layout-default/layout-nav.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,28 +223,55 @@ describe('theme: layout-default-nav', () => {
page.checkText('.sidebar-nav__item', `i18n 1`);
});

it('should be hide when children are hide', () => {
createComp();
menuSrv.add([
{
text: 'parent',
children: [
{ text: 'l1', hide: true },
{ text: 'l2', hide: false }
]
}
]);
page.checkCount('.sidebar-nav__group-title', 1);
menuSrv.add([
{
text: 'parent',
children: [
{ text: 'l1', hide: true },
{ text: 'l2', hide: true }
]
}
]);
page.checkCount('.sidebar-nav__group-title', 0);
describe('#hideEmptyChildren', () => {
it('with true', () => {
createComp();
menuSrv.add([
{
text: 'parent',
children: [
{ text: 'l1', hide: true },
{ text: 'l2', hide: false }
]
}
]);
page.checkCount('.sidebar-nav__group-title', 1);
menuSrv.add([
{
text: 'parent',
children: [
{ text: 'l1', hide: true },
{ text: 'l2', hide: true }
]
}
]);
page.checkCount('.sidebar-nav__group-title', 0);
});
it('with false', () => {
createComp();
context.hideEmptyChildren = false;
fixture.detectChanges();
menuSrv.add([
{
text: 'parent',
children: [
{ text: 'l1', hide: true },
{ text: 'l2', hide: false }
]
}
]);
page.checkCount('.sidebar-nav__group-title', 1);
menuSrv.add([
{
text: 'parent',
children: [
{ text: 'l1', hide: true },
{ text: 'l2', hide: true }
]
}
]);
page.checkCount('.sidebar-nav__group-title', 1);
});
});
});

Expand Down Expand Up @@ -639,6 +666,7 @@ describe('theme: layout-default-nav', () => {
[disabledAcl]="disabledAcl"
[autoCloseUnderPad]="autoCloseUnderPad"
[recursivePath]="recursivePath"
[hideEmptyChildren]="hideEmptyChildren"
[openStrictly]="openStrictly"
(select)="select()"
/>
Expand All @@ -650,6 +678,7 @@ class TestComponent {
disabledAcl = false;
autoCloseUnderPad = false;
recursivePath = false;
hideEmptyChildren = true;
openStrictly = false;
select(): void {}
}
Expand Down
Loading

0 comments on commit d529bcc

Please sign in to comment.