Skip to content

Commit

Permalink
feat(module:cascader): support option render template (#4127)
Browse files Browse the repository at this point in the history
* feat(module:cascader): support option render template

close #3699

* chore: use prefixed types internally
  • Loading branch information
Wendell authored and vthinkxie committed Oct 12, 2019
1 parent b33533c commit 8345c54
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 75 deletions.
15 changes: 15 additions & 0 deletions components/cascader/demo/custom-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
order: 17
title:
zh-CN: 自定义选择项
en-US: Custom option template
---

## zh-CN

自定义选项的模板。

## en-US

Custom cascader option template.

56 changes: 56 additions & 0 deletions components/cascader/demo/custom-template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// tslint:disable:no-any
import { Component } from '@angular/core';

const options = [
{
label: 'Ant Design',
value: 'antd',
children: [
{
label: 'ng-zorro-antd',
value: 'ng-zorro-antd',
isLeaf: true
}
]
},
{
label: 'Angular',
value: 'angular',
children: [
{
label: 'CDK',
value: 'cdk',
isLeaf: true
}
]
}
];

@Component({
selector: 'nz-demo-cascader-custom-template',
template: `
<nz-cascader
[nzOptionRender]="renderTpl"
[nzOptions]="nzOptions"
[(ngModel)]="values"
(ngModelChange)="onChanges($event)"
>
</nz-cascader>
<ng-template #renderTpl let-option let-index="index"> {{ index + 1 }}. {{ option.label }} </ng-template>
`,
styles: [
`
.ant-cascader-picker {
width: 300px;
}
`
]
})
export class NzDemoCascaderCustomTemplateComponent {
nzOptions = options;
values: any[] | null = null;

onChanges(values: any): void {
console.log(values, this.values);
}
}
7 changes: 4 additions & 3 deletions components/cascader/doc/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { NzCascaderModule } from 'ng-zorro-antd/cascader';
| `[nzNotFoundContent]` | Specify content to show when no result matches. | `string\|TemplateRef<void>` | - |
| `[nzLabelProperty]` | the label property name of options | `string` | `'label'` |
| `[nzLabelRender]` | render template of displaying selected options | `TemplateRef<any>` | - |
| `[nzOptionRender]` | render template of cascader options | `TemplateRef<{ $implicit: NzCascaderOption, index: number }>` | |
| `[nzLoadData]` | To load option lazily. If setting `ngModel` with an array value and `nzOptions` is not setting, lazy load will be call immediately | `(option: any, index?: index) => PromiseLike<any>` | - |
| `[nzOptions]` | data options of cascade | `object[]` | - |
| `[nzPlaceHolder]` | input placeholder | `string` | `'Please select'` |
Expand All @@ -54,14 +55,14 @@ import { NzCascaderModule } from 'ng-zorro-antd/cascader';
| `(ngModelChange)` | Emit on values change | `EventEmitter<any[]>` | - |
| `(nzClear)` | Emit on clear values | `EventEmitter<void>` | - |
| `(nzVisibleChange)` | Emit on popup menu visible or hide | `EventEmitter<boolean>` | - |
| `(nzSelectionChange)` | Emit on values change | `EventEmitter<CascaderOption[]>` | - |
| `(nzSelectionChange)` | Emit on values change | `EventEmitter<NzCascaderOption[]>` | - |

When `nzShowSearch` is an object it should implements `NzShowSearchOptions`

| Params | Explanation | Type | Default |
| --- | --- | --- | --- |
| `filter` | Optional. Be aware that all non-leaf CascaderOptions would be filtered | `(inputValue: string, path: CascaderOption[]): boolean` | - |
| `sorter` | Optional | `(a: CascaderOption[], b: CascaderOption[], inputValue: string): number` | - |
| `filter` | Optional. Be aware that all non-leaf CascaderOptions would be filtered | `(inputValue: string, path: NzCascaderOption[]): boolean` | - |
| `sorter` | Optional | `(a: NzCascaderOption[], b: NzCascaderOption[], inputValue: string): number` | - |

#### Methods

Expand Down
7 changes: 4 additions & 3 deletions components/cascader/doc/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { NzCascaderModule } from 'ng-zorro-antd/cascader';
| `[nzNotFoundContent]` | 当下拉列表为空时显示的内容 | `string\|TemplateRef<void>` | - |
| `[nzLabelProperty]` | 选项的显示值的属性名 | `string` | `'label'` |
| `[nzLabelRender]` | 选择后展示的渲染模板 | `TemplateRef<any>` | - |
| `[nzOptionRender]` | 选项的渲染模板 | `TemplateRef<{ $implicit: NzCascaderOption, index: number }>` | |
| `[nzLoadData]` | 用于动态加载选项。如果提供了`ngModel`初始值,且未提供`nzOptions`值,则会立即触发动态加载。 | `(option: any, index?: index) => PromiseLike<any>` | - |
| `[nzOptions]` | 可选项数据源 | `object[]` | - |
| `[nzPlaceHolder]` | 输入框占位文本 | `string` | `'请选择'` |
Expand All @@ -54,14 +55,14 @@ import { NzCascaderModule } from 'ng-zorro-antd/cascader';
| `[nzValueProperty]` | 选项的实际值的属性名 | `string` | `'value'` |
| `(ngModelChange)` | 值发生变化时触发 | `EventEmitter<any[]>` | - |
| `(nzVisibleChange)` | 菜单浮层的显示/隐藏 | `EventEmitter<boolean>` | - |
| `(nzSelectionChange)` | 值发生变化时触发 | `EventEmitter<CascaderOption[]>` |- |
| `(nzSelectionChange)` | 值发生变化时触发 | `EventEmitter<NzCascaderOption[]>` |- |

`nzShowSearch` 为对象时需遵守 `NzShowSearchOptions` 接口:

| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| `filter` | 可选,选择是否保留选项的过滤函数,每级菜单的选项都会被匹配 | `(inputValue: string, path: CascaderOption[]): boolean` | - |
| `sorter` | 可选,按照到每个最终选项的路径进行排序,默认按照原始数据的顺序 | `(a: CascaderOption[], b: CascaderOption[], inputValue: string): number` | - |
| `filter` | 可选,选择是否保留选项的过滤函数,每级菜单的选项都会被匹配 | `(inputValue: string, path: NzCascaderOption[]): boolean` | - |
| `sorter` | 可选,按照到每个最终选项的路径进行排序,默认按照原始数据的顺序 | `(a: NzCascaderOption[], b: NzCascaderOption[], inputValue: string): number` | - |

#### 方法

Expand Down
26 changes: 18 additions & 8 deletions components/cascader/nz-cascader-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,36 @@ export type NzCascaderExpandTrigger = 'click' | 'hover';
export type NzCascaderTriggerType = 'click' | 'hover';
export type NzCascaderSize = 'small' | 'large' | 'default';

export type NzCascaderFilter = (searchValue: string, path: CascaderOption[]) => boolean;
export type NzCascaderSorter = (a: CascaderOption[], b: CascaderOption[], inputValue: string) => number;
export type NzCascaderFilter = (searchValue: string, path: NzCascaderOption[]) => boolean;
export type NzCascaderSorter = (a: NzCascaderOption[], b: NzCascaderOption[], inputValue: string) => number;

/**
* @deprecated Use the prefixed version.
*/
export interface CascaderOption {
value?: any; // tslint:disable-line:no-any
label?: string;
title?: string;
disabled?: boolean;
loading?: boolean;
isLeaf?: boolean;
parent?: CascaderOption;
children?: CascaderOption[];
parent?: NzCascaderOption;
children?: NzCascaderOption[];

[key: string]: any; // tslint:disable-line:no-any
}

export interface CascaderSearchOption extends CascaderOption {
path: CascaderOption[];
export type NzCascaderOption = CascaderOption;

/**
* @deprecated Use the prefixed version.
*/
export interface CascaderSearchOption extends NzCascaderOption {
path: NzCascaderOption[];
}

export type NzCascaderSearchOption = CascaderSearchOption;

export interface NzShowSearchOptions {
filter?: NzCascaderFilter;
sorter?: NzCascaderSorter;
Expand All @@ -50,8 +60,8 @@ export interface NzCascaderComponentAsSource {
nzValueProperty: string;
nzChangeOnSelect: boolean;

nzChangeOn?(option: CascaderOption, level: number): boolean;
nzChangeOn?(option: NzCascaderOption, level: number): boolean;

// tslint:disable-next-line:no-any
nzLoadData?(node: CascaderOption, index?: number): PromiseLike<any>;
nzLoadData?(node: NzCascaderOption, index?: number): PromiseLike<any>;
}
16 changes: 11 additions & 5 deletions components/cascader/nz-cascader-li.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<span [innerHTML]="optionLabel | nzHighlight: highlightText: 'g': 'ant-cascader-menu-item-keyword'"></span>
<span
*ngIf="!option.isLeaf || option.children && option.children.length || option.loading"
class="ant-cascader-menu-item-expand-icon">
<i nz-icon [nzType]="option.loading ? 'loading' : 'right'"></i>
<ng-container *ngIf="optionTemplate; else defaultOptionTemplate">
<ng-template [ngTemplateOutlet]="optionTemplate"
[ngTemplateOutletContext]="{ $implicit: option, index: columnIndex }"></ng-template>
</ng-container>
<ng-template #defaultOptionTemplate>
<span [innerHTML]="optionLabel | nzHighlight: highlightText: 'g': 'ant-cascader-menu-item-keyword'"></span>
</ng-template>
<span *ngIf="!option.isLeaf || option.children?.length || option.loading"
class="ant-cascader-menu-item-expand-icon">
<i nz-icon
[nzType]="option.loading ? 'loading' : 'right'"></i>
</span>
7 changes: 5 additions & 2 deletions components/cascader/nz-cascader-li.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import {
ElementRef,
Input,
Renderer2,
TemplateRef,
ViewEncapsulation
} from '@angular/core';

import { CascaderOption } from './nz-cascader-definitions';
import { NzCascaderOption } from './nz-cascader-definitions';

@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
Expand All @@ -32,10 +33,12 @@ import { CascaderOption } from './nz-cascader-definitions';
}
})
export class NzCascaderOptionComponent {
@Input() option: CascaderOption;
@Input() optionTemplate: TemplateRef<NzCascaderOption> | null = null;
@Input() option: NzCascaderOption;
@Input() activated = false;
@Input() highlightText: string;
@Input() nzLabelProperty = 'label';
@Input() columnIndex: number;

constructor(private cdr: ChangeDetectorRef, elementRef: ElementRef, renderer: Renderer2) {
renderer.addClass(elementRef.nativeElement, 'ant-cascader-menu-item');
Expand Down
6 changes: 3 additions & 3 deletions components/cascader/nz-cascader-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/

import { CascaderOption } from './nz-cascader-definitions';
import { NzCascaderOption } from './nz-cascader-definitions';

export function isChildOption(o: CascaderOption): boolean {
export function isChildOption(o: NzCascaderOption): boolean {
return o.isLeaf || !o.children || !o.children.length;
}

export function isParentOption(o: CascaderOption): boolean {
export function isParentOption(o: NzCascaderOption): boolean {
return !!o.children && !!o.children.length && !o.isLeaf;
}
2 changes: 2 additions & 0 deletions components/cascader/nz-cascader.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@
<li
nz-cascader-option
*ngFor="let option of options"
[columnIndex]="i"
[nzLabelProperty]="nzLabelProperty"
[optionTemplate]="nzOptionRender"
[activated]="isOptionActivated(option, i)"
[highlightText]="inSearchingMode ? inputValue : ''"
[option]="option"
Expand Down
34 changes: 18 additions & 16 deletions components/cascader/nz-cascader.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,18 @@ import {
DEFAULT_DROPDOWN_POSITIONS,
InputBoolean,
NgClassType,
NgStyleInterface,
NzConfigService,
NzNoAnimationDirective,
WithConfig
} from 'ng-zorro-antd/core';

import { NzCascaderI18nInterface, NzI18nService } from 'ng-zorro-antd/i18n';
import {
CascaderOption,
CascaderSearchOption,
NzCascaderComponentAsSource,
NzCascaderExpandTrigger,
NzCascaderOption,
NzCascaderSearchOption,
NzCascaderSize,
NzCascaderTriggerType,
NzShowSearchOptions
Expand Down Expand Up @@ -105,6 +106,7 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
@ViewChild(CdkConnectedOverlay, { static: false }) overlay: CdkConnectedOverlay;
@ViewChildren(NzCascaderOptionComponent) cascaderItems: QueryList<NzCascaderOptionComponent>;

@Input() nzOptionRender: TemplateRef<{ $implicit: NzCascaderOption; index: number }> | null = null;
@Input() @InputBoolean() nzShowInput = true;
@Input() @InputBoolean() nzShowArrow = true;
@Input() @InputBoolean() nzAllowClear = true;
Expand All @@ -121,30 +123,30 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
@Input() nzShowSearch: boolean | NzShowSearchOptions;
@Input() nzPlaceHolder: string;
@Input() nzMenuClassName: string;
@Input() nzMenuStyle: { [key: string]: string };
@Input() nzMenuStyle: NgStyleInterface;
@Input() nzMouseEnterDelay: number = 150; // ms
@Input() nzMouseLeaveDelay: number = 150; // ms
@Input() nzTriggerAction: NzCascaderTriggerType | NzCascaderTriggerType[] = ['click'] as NzCascaderTriggerType[];
@Input() nzChangeOn: (option: CascaderOption, level: number) => boolean;
@Input() nzLoadData: (node: CascaderOption, index?: number) => PromiseLike<any>; // tslint:disable-line:no-any
@Input() nzChangeOn: (option: NzCascaderOption, level: number) => boolean;
@Input() nzLoadData: (node: NzCascaderOption, index?: number) => PromiseLike<any>; // tslint:disable-line:no-any

@Input()
get nzOptions(): CascaderOption[] | null {
get nzOptions(): NzCascaderOption[] | null {
return this.cascaderService.nzOptions;
}

set nzOptions(options: CascaderOption[] | null) {
set nzOptions(options: NzCascaderOption[] | null) {
this.cascaderService.withOptions(options);
}

@Output() readonly nzVisibleChange = new EventEmitter<boolean>();

@Output() readonly nzSelectionChange = new EventEmitter<CascaderOption[]>();
@Output() readonly nzSelectionChange = new EventEmitter<NzCascaderOption[]>();

/**
* @deprecated 9.0.0. This api is a duplication of `ngModelChange`.
*/
@Output() readonly nzSelect = new EventEmitter<{ option: CascaderOption; index: number } | null>();
@Output() readonly nzSelect = new EventEmitter<{ option: NzCascaderOption; index: number } | null>();

@Output() readonly nzClear = new EventEmitter<void>();

Expand Down Expand Up @@ -466,7 +468,7 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
this.delaySetMenuVisible(false, this.nzMouseLeaveDelay);
}

onOptionMouseEnter(option: CascaderOption, columnIndex: number, event: Event): void {
onOptionMouseEnter(option: NzCascaderOption, columnIndex: number, event: Event): void {
event.preventDefault();
if (this.nzExpandTrigger === 'hover') {
if (!option.isLeaf) {
Expand All @@ -477,14 +479,14 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
}
}

onOptionMouseLeave(option: CascaderOption, _columnIndex: number, event: Event): void {
onOptionMouseLeave(option: NzCascaderOption, _columnIndex: number, event: Event): void {
event.preventDefault();
if (this.nzExpandTrigger === 'hover' && !option.isLeaf) {
this.clearDelaySelectTimer();
}
}

onOptionClick(option: CascaderOption, columnIndex: number, event: Event): void {
onOptionClick(option: NzCascaderOption, columnIndex: number, event: Event): void {
if (event) {
event.preventDefault();
}
Expand All @@ -493,7 +495,7 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
}
this.el.focus();
this.inSearchingMode
? this.cascaderService.setSearchOptionSelected(option as CascaderSearchOption)
? this.cascaderService.setSearchOptionSelected(option as NzCascaderSearchOption)
: this.cascaderService.setOptionActivated(option, columnIndex, true);
}

Expand All @@ -508,7 +510,7 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
const option = this.cascaderService.activatedOptions[columnIndex];
if (option && !option.disabled) {
this.inSearchingMode
? this.cascaderService.setSearchOptionSelected(option as CascaderSearchOption)
? this.cascaderService.setSearchOptionSelected(option as NzCascaderSearchOption)
: this.cascaderService.setOptionActivated(option, columnIndex, true);
}
}
Expand Down Expand Up @@ -565,7 +567,7 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
}
}

private delaySetOptionActivated(option: CascaderOption, columnIndex: number, performSelect: boolean): void {
private delaySetOptionActivated(option: NzCascaderOption, columnIndex: number, performSelect: boolean): void {
this.clearDelaySelectTimer();
this.delaySelectTimer = setTimeout(() => {
this.cascaderService.setOptionActivated(option, columnIndex, performSelect);
Expand All @@ -583,7 +585,7 @@ export class NzCascaderComponent implements NzCascaderComponentAsSource, OnInit,
}
}

isOptionActivated(option: CascaderOption, index: number): boolean {
isOptionActivated(option: NzCascaderOption, index: number): boolean {
const activeOpt = this.cascaderService.activatedOptions[index];
return activeOpt === option;
}
Expand Down
Loading

0 comments on commit 8345c54

Please sign in to comment.