diff --git a/src/components/transfer/nz-transfer.component.ts b/src/components/transfer/nz-transfer.component.ts index 1ea2aad27d4..277047a4b79 100644 --- a/src/components/transfer/nz-transfer.component.ts +++ b/src/components/transfer/nz-transfer.component.ts @@ -1,9 +1,47 @@ // tslint:disable:member-ordering -import { Component, ContentChild, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef, ViewEncapsulation } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ContentChild, + ElementRef, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, + TemplateRef, + ViewEncapsulation +} from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { ArrayObservable } from 'rxjs/observable/ArrayObservable'; import { NzLocaleService } from '../locale/index'; import { toBoolean } from '../util/convert'; import { TransferItem } from './item'; +export interface TransferCanMove { + direction: string; + list: TransferItem[]; +} + +export interface TransferChange { + from: string; + to: string; + list: TransferItem[]; +} + +export interface TransferSearchChange { + direction: string; + value: string; +} + +export interface TransferSelectChange { + direction: string; + checked: boolean; + list: TransferItem[]; + item: TransferItem; +} + @Component({ selector: 'nz-transfer', template: ` @@ -54,7 +92,8 @@ import { TransferItem } from './item'; // tslint:disable-next-line:use-host-property-decorator host: { '[class.ant-transfer]': 'true' - } + }, + changeDetection: ChangeDetectionStrategy.OnPush }) export class NzTransferComponent implements OnChanges { private _showSearch = false; @@ -70,6 +109,7 @@ export class NzTransferComponent implements OnChanges { @Input() nzListStyle: object; @Input() nzItemUnit = this._locale.translate('Transfer.itemUnit'); @Input() nzItemsUnit = this._locale.translate('Transfer.itemsUnit'); + @Input() canMove: (arg: TransferCanMove) => Observable = (arg: TransferCanMove) => ArrayObservable.of(arg.list); @ContentChild('render') render: TemplateRef; @ContentChild('footer') footer: TemplateRef; @@ -88,10 +128,9 @@ export class NzTransferComponent implements OnChanges { @Input() nzNotFoundContent = this._locale.translate('Transfer.notFoundContent'); // events - // TODO: use named interface - @Output() nzChange: EventEmitter<{ from: string, to: string, list: TransferItem[] }> = new EventEmitter(); - @Output() nzSearchChange: EventEmitter<{ direction: string, value: string }> = new EventEmitter(); - @Output() nzSelectChange: EventEmitter<{ direction: string, checked: boolean, list: TransferItem[], item: TransferItem }> = new EventEmitter(); + @Output() nzChange: EventEmitter = new EventEmitter(); + @Output() nzSearchChange: EventEmitter = new EventEmitter(); + @Output() nzSelectChange: EventEmitter = new EventEmitter(); // endregion @@ -133,6 +172,7 @@ export class NzTransferComponent implements OnChanges { handleFilterChange(ret: { direction: string, value: string }): void { this.nzSearchChange.emit(ret); + this.cd.detectChanges(); } // endregion @@ -142,46 +182,57 @@ export class NzTransferComponent implements OnChanges { leftActive = false; rightActive = false; - private updateOperationStatus(direction: string, count: number): void { - this[direction === 'right' ? 'leftActive' : 'rightActive'] = count > 0; + private updateOperationStatus(direction: string, count?: number): void { + this[direction === 'right' ? 'leftActive' : 'rightActive'] = (typeof count === 'undefined' ? this.getCheckedData(direction).filter(w => !w.disabled).length : count) > 0; + this.cd.detectChanges(); } moveToLeft = () => this.moveTo('left'); moveToRight = () => this.moveTo('right'); moveTo(direction: string): void { + const oppositeDirection = direction === 'left' ? 'right' : 'left'; + this.updateOperationStatus(oppositeDirection, 0); + const datasource = direction === 'left' ? this.rightDataSource : this.leftDataSource; + const moveList = datasource.filter(item => item.checked === true && !item.disabled); + this.canMove({ direction, list: moveList }) + .subscribe( + newMoveList => this.truthMoveTo(direction, newMoveList.filter(i => !!i)), + () => moveList.forEach(i => i.checked = false) + ); + } + + private truthMoveTo(direction: string, list: TransferItem[]): void { const oppositeDirection = direction === 'left' ? 'right' : 'left'; const datasource = direction === 'left' ? this.rightDataSource : this.leftDataSource; const targetDatasource = direction === 'left' ? this.leftDataSource : this.rightDataSource; - const moveList: TransferItem[] = []; - for (let i = 0; i < datasource.length; i++) { - const item = datasource[i]; - if (item.checked === true && !item.disabled) { - item.checked = false; - moveList.push(item); - targetDatasource.push(item); - datasource.splice(i, 1); - --i; - } + for (const item of list) { + const idx = datasource.indexOf(item); + if (idx === -1) continue; + item.checked = false; + targetDatasource.push(item); + datasource.splice(idx, 1); } - this.updateOperationStatus(oppositeDirection, 0); + this.updateOperationStatus(oppositeDirection); this.nzChange.emit({ from: oppositeDirection, to: direction, - list: moveList + list }); // this.nzSelectChange.emit({ direction: oppositeDirection, list: [] }); } // endregion - constructor(private _locale: NzLocaleService) {} + constructor(private _locale: NzLocaleService, private el: ElementRef, private cd: ChangeDetectorRef) { + } ngOnChanges(changes: SimpleChanges): void { if ('nzDataSource' in changes || 'nzTargetKeys' in changes) { this.splitDataSource(); - this.updateOperationStatus('left', this.leftDataSource.filter(w => w.checked && !w.disabled).length); - this.updateOperationStatus('right', this.rightDataSource.filter(w => w.checked && !w.disabled).length); + this.updateOperationStatus('left'); + this.updateOperationStatus('right'); } + this.cd.detectChanges(); } } diff --git a/src/components/transfer/nz-transfer.spec.ts b/src/components/transfer/nz-transfer.spec.ts index 9f18b0ad308..ab8e800849e 100644 --- a/src/components/transfer/nz-transfer.spec.ts +++ b/src/components/transfer/nz-transfer.spec.ts @@ -4,6 +4,7 @@ import { async, fakeAsync, tick, ComponentFixture, ComponentFixtureAutoDetect, T import { FormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { ArrayObservable } from 'rxjs/observable/ArrayObservable'; import { NzButtonModule } from '../button/nz-button.module'; import { NzTransferModule } from '../ng-zorro-antd.module'; import { TransferItem } from './item'; @@ -38,6 +39,17 @@ const RENDER = ` `; +const CANMOVE = ` + + + [OK]{{item.title}}-{{item.description}} + + + + + +`; + const DATA = []; for (let i = 0; i < 20; i++) { DATA.push({ @@ -82,6 +94,7 @@ describe('NzTransferModule', () => { spyOn(context, 'change'); spyOn(context, 'search'); spyOn(context, 'reload'); + spyOn(context, 'canMove'); spyOn(context, 'nzFilterOption'); dl = fixture.debugElement; el = fixture.nativeElement; @@ -176,6 +189,23 @@ describe('NzTransferModule', () => { }); }); + describe('#canMove', () => { + beforeEach(() => { + createTestModule(CANMOVE); + }); + + it('should be', () => { + context.list = [ ...DATA ].map((item, idx) => { + if (idx <= 3) item.checked = true; + return item; + }); + fixture.detectChanges(); + (el.querySelectorAll(`.ant-transfer-operation .ant-btn`)[1] as HTMLButtonElement).click(); + fixture.detectChanges(); + expect(context.canMove).toHaveBeenCalled(); + }); + }); + }); @Component({ template: '' }) @@ -186,7 +216,6 @@ class TestTransferComponent { nzSearchPlaceholder = 'nzSearchPlaceholder'; nzNotFoundContent = 'nzNotFoundContent'; nzFilterOption(inputValue: string, option: TransferItem): boolean { - console.log(inputValue, option); return option.description.indexOf(inputValue) > -1; } @@ -194,4 +223,10 @@ class TestTransferComponent { change(): void { } search(): void { } reload(): void { } + canMove(arg: any) { + if (arg.direction === 'right' && arg.list.length > 0) arg.list.splice(0, 1); + // or + // if (arg.direction === 'right' && arg.list.length > 0) delete arg.list[0]; + return ArrayObservable.of(arg.list); + } } diff --git a/src/showcase/nz-demo-transfer/nz-demo-transfer-can-move.component.ts b/src/showcase/nz-demo-transfer/nz-demo-transfer-can-move.component.ts new file mode 100644 index 00000000000..f574148a30a --- /dev/null +++ b/src/showcase/nz-demo-transfer/nz-demo-transfer-can-move.component.ts @@ -0,0 +1,46 @@ +import { Component, OnInit } from '@angular/core'; +import { ArrayObservable } from 'rxjs/observable/ArrayObservable'; +import { delay } from 'rxjs/operators'; +import { NzMessageService } from '../../../index.showcase'; + +@Component({ + selector: 'nz-demo-transfer-can-move', + template: ` + + + ` +}) +export class NzDemoTransferCanMoveComponent implements OnInit { + list: any[] = []; + ngOnInit() { + for (let i = 0; i < 20; i++) { + this.list.push({ + key: i.toString(), + title: `content${i + 1}`, + disabled: i % 3 < 1, + }); + } + + [ 2, 3 ].forEach(idx => this.list[idx].direction = 'right'); + } + + canMove(arg: any) { + if (arg.direction === 'right' && arg.list.length > 0) arg.list.splice(0, 1); + // or + // if (arg.direction === 'right' && arg.list.length > 0) delete arg.list[0]; + return ArrayObservable.of(arg.list).pipe(delay(1000)); + } + + select(ret: any) { + console.log('nzSelectChange', ret); + } + + change(ret: any) { + console.log('nzChange', ret); + } +} diff --git a/src/showcase/nz-demo-transfer/nz-demo-transfer.component.ts b/src/showcase/nz-demo-transfer/nz-demo-transfer.component.ts index 4409ee7a223..06d49676a8b 100644 --- a/src/showcase/nz-demo-transfer/nz-demo-transfer.component.ts +++ b/src/showcase/nz-demo-transfer/nz-demo-transfer.component.ts @@ -10,4 +10,5 @@ export class NzDemoTransferComponent { NzDemoTransferSearchCode = require('!!raw-loader!./nz-demo-transfer-search.component'); NzDemoTransferAdvancedCode = require('!!raw-loader!./nz-demo-transfer-advanced.component'); NzDemoTransferCustomItemCode = require('!!raw-loader!./nz-demo-transfer-custom-item.component'); + NzDemoTransferCanMoveCode = require('!!raw-loader!./nz-demo-transfer-can-move.component'); } diff --git a/src/showcase/nz-demo-transfer/nz-demo-transfer.html b/src/showcase/nz-demo-transfer/nz-demo-transfer.html index d152e8084d9..a3e3a9e2f54 100644 --- a/src/showcase/nz-demo-transfer/nz-demo-transfer.html +++ b/src/showcase/nz-demo-transfer/nz-demo-transfer.html @@ -42,6 +42,14 @@

代码演示 +
+ + +
+

利用 canMove 允许在穿梭过程中二次校验;示例默认向右移时强制第一项不可穿梭。

+
+
+

API @@ -129,6 +137,12 @@

API 列表为空 + + canMove + 穿梭时二次校验。

注意:穿梭组件内部始终只保留一份数据,二次校验过程中需取消穿梭项则直接删除该项;具体用法见示例。

+ + + (nzChange) 选项在两栏之间转移时的回调函数 diff --git a/src/showcase/nz-demo-transfer/nz-demo-transfer.module.ts b/src/showcase/nz-demo-transfer/nz-demo-transfer.module.ts index fef6148554f..6d57bfeef03 100644 --- a/src/showcase/nz-demo-transfer/nz-demo-transfer.module.ts +++ b/src/showcase/nz-demo-transfer/nz-demo-transfer.module.ts @@ -10,10 +10,11 @@ import { NzDemoTransferBasicComponent } from './nz-demo-transfer-basic.component import { NzDemoTransferSearchComponent } from './nz-demo-transfer-search.component'; import { NzDemoTransferAdvancedComponent } from './nz-demo-transfer-advanced.component'; import { NzDemoTransferCustomItemComponent } from './nz-demo-transfer-custom-item.component'; +import { NzDemoTransferCanMoveComponent } from './nz-demo-transfer-can-move.component'; @NgModule({ imports : [ NzDemoTransferRoutingModule, CommonModule, NzCodeBoxModule, NgZorroAntdModule, FormsModule ], - declarations: [ NzDemoTransferComponent, NzDemoTransferBasicComponent, NzDemoTransferSearchComponent, NzDemoTransferAdvancedComponent, NzDemoTransferCustomItemComponent ] + declarations: [ NzDemoTransferComponent, NzDemoTransferBasicComponent, NzDemoTransferSearchComponent, NzDemoTransferAdvancedComponent, NzDemoTransferCustomItemComponent, NzDemoTransferCanMoveComponent ] }) export class NzDemoTransferModule {