From 5dc640f3163141ef099164626a00fbb4fd037826 Mon Sep 17 00:00:00 2001 From: wendzhue Date: Sun, 19 Aug 2018 16:15:02 +0800 Subject: [PATCH] fix(module:cascader): fix dynamic loading error (#1931) * fix(module:cascader): fix dynamic loading error close #1927 * fix test --- components/cascader/doc/index.en-US.md | 2 +- components/cascader/doc/index.zh-CN.md | 2 +- components/cascader/nz-cascader.component.ts | 61 ++++++++++++++------ components/cascader/nz-cascader.spec.ts | 42 ++++++++++++++ 4 files changed, 86 insertions(+), 21 deletions(-) diff --git a/components/cascader/doc/index.en-US.md b/components/cascader/doc/index.en-US.md index 91d8f7d28dc..9e365980d80 100755 --- a/components/cascader/doc/index.en-US.md +++ b/components/cascader/doc/index.en-US.md @@ -39,7 +39,7 @@ Cascade selection box. | `[nzPlaceHolder]` | input placeholder | string | 'Please select' | | `[nzShowArrow]` | Whether show arrow | boolean | true | | `[nzShowInput]` | Whether show input | boolean | true | -| `[nzShowSearch]` | Whether support search | `boolean` `NzShowSearchOptions` | `false` | +| `[nzShowSearch]` | Whether support search. Cannot be used with `[nzLoadData]` at the same time | `boolean` `NzShowSearchOptions` | `false` | | `[nzSize]` | input size, one of `large` `default` `small` | string | `default` | | `[nzValueProperty]` | the value property name of options | string | 'value' | | `(ngModelChange)` | Emit on values change | `EventEmitter` | - | diff --git a/components/cascader/doc/index.zh-CN.md b/components/cascader/doc/index.zh-CN.md index f6d40a2e925..1fb2a4699b6 100755 --- a/components/cascader/doc/index.zh-CN.md +++ b/components/cascader/doc/index.zh-CN.md @@ -40,7 +40,7 @@ subtitle: 级联选择 | `[nzPlaceHolder]` | 输入框占位文本 | string | '请选择' | | `[nzShowArrow]` | 是否显示箭头 | boolean | true | | `[nzShowInput]` | 显示输入框 | boolean | true | -| `[nzShowSearch]` | 是否支持搜索,默认情况下对 `label` 进行全匹配搜索 | `boolean` `NzShowSearchOptions` | `false` | +| `[nzShowSearch]` | 是否支持搜索,默认情况下对 `label` 进行全匹配搜索,不能和 `[nzLoadData]` 同时使用 | `boolean` `NzShowSearchOptions` | `false` | | `[nzSize]` | 输入框大小,可选 `large` `default` `small` | string | `default` | | `[nzValueProperty]` | 选项的实际值的属性名 | string | 'value' | | `(ngModelChange)` | 值发生变化时触发 | `EventEmitter` | - | diff --git a/components/cascader/nz-cascader.component.ts b/components/cascader/nz-cascader.component.ts index e6521e3c640..1eeac575b70 100644 --- a/components/cascader/nz-cascader.component.ts +++ b/components/cascader/nz-cascader.component.ts @@ -166,20 +166,25 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces set inputValue(inputValue: string) { this._inputValue = inputValue; + const willBeInSearch = !!inputValue; - if (!this.inSearch) { + // 搜索状态变动之前,如要进入则要保留之前激活选项的快照,退出搜索状态要还原该快照 + if (!this.inSearch && willBeInSearch) { this.oldActivatedOptions = this.activatedOptions; this.activatedOptions = []; - } else { + this.searchWidthStyle = `${this.input.nativeElement.offsetWidth}px`; + } else if (this.inSearch && !willBeInSearch) { this.activatedOptions = this.oldActivatedOptions; } - this.inSearch = !!inputValue; + // 搜索状态变更之后 + this.inSearch = !!willBeInSearch; if (this.inSearch) { - this.searchWidthStyle = `${this.input.nativeElement.offsetWidth}px`; this.prepareSearchValue(); } else { - this.nzColumns = this.oldColumnsHolder; + if (this.showSearch) { + this.nzColumns = this.oldColumnsHolder; + } this.searchWidthStyle = ''; } this.setClassMap(); @@ -258,6 +263,7 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces set nzShowSearch(value: boolean | NzShowSearchOptions) { this.showSearch = value; } + get nzShowSearch(): boolean | NzShowSearchOptions { return this.showSearch; } @@ -460,8 +466,8 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces private setLabelClass(): void { this._labelCls = { [ `${this.prefixCls}-picker-label` ]: true, - [ `${this.prefixCls}-show-search`]: !!this.nzShowSearch, - [ `${this.prefixCls}-focused`]: !!this.nzShowSearch && this.isFocused && !this._inputValue + [ `${this.prefixCls}-show-search` ] : !!this.nzShowSearch, + [ `${this.prefixCls}-focused` ] : !!this.nzShowSearch && this.isFocused && !this._inputValue }; } @@ -686,7 +692,9 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces return; } this.onTouched(); // set your control to 'touched' - if (this.nzShowSearch) { this.focus(); } + if (this.nzShowSearch) { + this.focus(); + } if (this.isClickTiggerAction()) { this.delaySetMenuVisible(!this.menuVisible, 100); @@ -941,7 +949,9 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces * @param event 鼠标事件 */ onOptionClick(option: CascaderOption, index: number, event: Event): void { - if (event) { event.preventDefault(); } + if (event) { + event.preventDefault(); + } // Keep focused state for keyboard support this.el.focus(); @@ -1193,7 +1203,9 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces const defaultFilter = (inputValue: string, p: CascaderOption[]): boolean => { let flag = false; p.forEach(n => { - if (n.label.indexOf(inputValue) > -1) { flag = true; } + if (n.label.indexOf(inputValue) > -1) { + flag = true; + } }); return flag; }; @@ -1207,9 +1219,16 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces const disabled = forceDisabled || node.disabled; path.push(node); node.children.forEach((sNode) => { - if (!sNode.parent) { sNode.parent = node; } /** 搜索的同时建立 parent 连接,因为用户直接搜索的话是没有建立连接的,会提升从叶子节点回溯的难度 */ - if (!sNode.isLeaf) { loopParent(sNode, disabled); } - if (sNode.isLeaf || !sNode.children) { loopChild(sNode, disabled); } + if (!sNode.parent) { + sNode.parent = node; + } + /** 搜索的同时建立 parent 连接,因为用户直接搜索的话是没有建立连接的,会提升从叶子节点回溯的难度 */ + if (!sNode.isLeaf) { + loopParent(sNode, disabled); + } + if (sNode.isLeaf || !sNode.children) { + loopChild(sNode, disabled); + } }); path.pop(); }; @@ -1221,15 +1240,17 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces results.push({ disabled, isLeaf: true, - path: cPath, - label: cPath.map(p => p.label).join(' / ') + path : cPath, + label : cPath.map(p => p.label).join(' / ') } as CascaderSearchOption); } path.pop(); }; - this.oldColumnsHolder[0].forEach(node => loopParent(node)); - if (sorter) { results.sort((a, b) => sorter(a.path, b.path, this._inputValue)); } + this.oldColumnsHolder[ 0 ].forEach(node => loopParent(node)); + if (sorter) { + results.sort((a, b) => sorter(a.path, b.path, this._inputValue)); + } this.nzColumns = [ results ]; } @@ -1245,9 +1266,11 @@ export class NzCascaderComponent implements OnInit, OnDestroy, ControlValueAcces setTimeout(() => { this.inputValue = ''; // Not only remove `inputValue` but also reverse `nzColumns` in the hook. const index = result.path.length - 1; - const destiNode = result.path[index]; + const destiNode = result.path[ index ]; const mockClickParent = (node: CascaderOption, cIndex: number) => { - if (node && node.parent) { mockClickParent(node.parent, cIndex - 1); } + if (node && node.parent) { + mockClickParent(node.parent, cIndex - 1); + } this.onOptionClick(node, cIndex, event); }; mockClickParent(destiNode, index); diff --git a/components/cascader/nz-cascader.spec.ts b/components/cascader/nz-cascader.spec.ts index 228da7ffe8c..ba4c96a867c 100644 --- a/components/cascader/nz-cascader.spec.ts +++ b/components/cascader/nz-cascader.spec.ts @@ -1311,6 +1311,13 @@ describe('cascader', () => { it('should support search', (done) => { fixture.detectChanges(); testComponent.nzShowSearch = true; + fixture.detectChanges(); + // const input: HTMLElement = cascader.nativeElement.querySelector('.ant-cascader-input'); + const spy = spyOn(testComponent.cascader, 'focus'); + cascader.nativeElement.click(); + fixture.detectChanges(); + // expect(document.activeElement).toBe(input); + expect(spy).toHaveBeenCalled(); testComponent.cascader.inputValue = 'o'; testComponent.cascader.setMenuVisible(true); fixture.detectChanges(); @@ -1548,6 +1555,41 @@ describe('cascader', () => { expect(testComponent.values.join(',')).toBe('zhejiang,hangzhou,xihu'); })); + it('should not emit error after clear search and reopen it', fakeAsync(() => { + fixture.detectChanges(); + testComponent.cascader.setMenuVisible(true); + fixture.detectChanges(); + tick(1000); // wait for first row to load finish + fixture.detectChanges(); + const itemEl1 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(1) .ant-cascader-menu-item:nth-child(1)') as HTMLElement; // 第1列第1个 + itemEl1.click(); + fixture.detectChanges(); + tick(600); + fixture.detectChanges(); + const itemEl2 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(2) .ant-cascader-menu-item:nth-child(1)') as HTMLElement; // 第2列第1个 + itemEl2.click(); + fixture.detectChanges(); + tick(600); + fixture.detectChanges(); + const itemEl3 = overlayContainerElement.querySelector('.ant-cascader-menu:nth-child(3) .ant-cascader-menu-item:nth-child(1)') as HTMLElement; // 第3列第1个 + itemEl3.click(); + fixture.detectChanges(); + tick(600); + fixture.detectChanges(); + flush(); // wait for cdk-overlay to close + fixture.detectChanges(); + itemEl3.click(); // re-click again, this time it is a leaf + fixture.detectChanges(); + tick(600); + fixture.detectChanges(); + flush(); // wait for cdk-overlay to close + fixture.detectChanges(); + cascader.nativeElement.querySelector('.ant-cascader-picker-clear').click(); + testComponent.cascader.setMenuVisible(true); + fixture.detectChanges(); + expect(testComponent.values.length).toBe(0); + })); + }); });