在設計介面時, 有時會利用duel list 去顯示現有item 及可以加入的item. 在Angular 中, 可以安裝 angular-dual-listbox 實現. 而它亦可以自己修改設計. 示範中會利用它自建主題及加入add/ remove all 功能.安裝 angular-dual-listbox 指令
npm install angular-dual-listbox
ItemAddRemoveEventArgs
import { BasicList } from 'angular-dual-listbox'; export class ItemAddRemoveEventArgs { public items: any; constructor() { // this.items = new Array<any>(); } public static createItem(targetItem): ItemAddRemoveEventArgs { const itemAddRemoveEventArgs: ItemAddRemoveEventArgs = new ItemAddRemoveEventArgs(); itemAddRemoveEventArgs.items = targetItem; return itemAddRemoveEventArgs; } public static createItems(targetItems: BasicList): ItemAddRemoveEventArgs { const itemAddRemoveEventArgs: ItemAddRemoveEventArgs = new ItemAddRemoveEventArgs(); itemAddRemoveEventArgs.items = new Array<any>(); itemAddRemoveEventArgs.items.concat(targetItems); return itemAddRemoveEventArgs; } }
duel-list-component.scss
div.record-picker { overflow-x: hidden; overflow-y: auto; border: 1px solid #ddd; position: relative; cursor: pointer; font-family: sans-serif; } div.record-picker::-webkit-scrollbar { width: 12px; } div.record-picker::-webkit-scrollbar-button { width: 0px; height: 0px; } // div.record-picker { // scrollbar-base-color: indigo; // scrollbar-3dlight-color: indigo; // scrollbar-highlight-color: indigo; // scrollbar-track-color: #eee; // scrollbar-arrow-color: gray; // scrollbar-shadow-color: gray; // scrollbar-darkshadow-color: gray; // } div.record-picker::-webkit-scrollbar-track { background:#eee; box-shadow: 0px 0px 3px #dfdfdf inset; } div.record-picker::-webkit-scrollbar-thumb { background: indigo; border: thin solid gray; } div.record-picker::-webkit-scrollbar-thumb:hover { background: purple; } .record-picker ul { margin: 0; padding: 0 0 1px 0; } .record-picker li { border-top: thin solid #ddd; border-bottom: 1px solid #ddd; display: block; padding: 2px 2px 2px 10px; margin-bottom: -1px; font-size: 0.85em; cursor: pointer; white-space: nowrap; min-height:16px; text-align: left; } .record-picker li:hover { background-color: #93b1a7; } .record-picker li.selected { background-color: #93b1a7; } .record-picker li.selected:hover { background-color: #93b1a7; } .record-picker li.disabled { opacity: 0.5; cursor: default; } .record-picker li:first-child { border-top: none; } .record-picker li:last-child { border-bottom: none; } .record-picker label { cursor: pointer; font-weight: inherit; font-size: 14px; padding: 4px; margin-bottom: -1px; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .record-picker ul.over { background-color:lightgray; } .dual-list { display: flex; flex-direction: row; align-content: flex-start; justify-content: space-evenly; } .dual-list .listbox { width: 35%; margin: 0px; flex-grow: 2; } .buttonbox { margin: 0 10px; display: flex; flex-direction: column; justify-content: center; } .btn-block { display: block; width: 70px; margin: 4px; } button { color: white; font-size: 18px; background: #005643; padding: 5px 10px; // border: solid black 1px; text-decoration: none; cursor: pointer; } button:hover { background: #93b1a7; text-decoration: none; } button:disabled { background: rgba(0, 85, 67, .5); cursor: default; } p { font-family: sans-serif; font-weight: 600; margin-bottom: 4px; }
duel-list-component.html
<div class="dual-list"> <div class="listbox"> <p>{{sourceName}}</p> <div class="record-picker"> <ul [ngStyle]="{'max-height': height, 'min-height': height}" [ngClass]="{over:available.dragOver}" (drop)="drop($event, confirmed)" (dragover)="allowDrop($event, available)" (dragleave)="dragLeave()"> <li *ngFor="let item of available.sift; let idx=index;" (click)="selectItem(available.pick, item); shiftClick($event, idx, available, item)" [ngClass]="{selected: isItemSelected(available.pick, item)}" draggable="true" (dragstart)="drag($event, item, available)" (dragend)="dragEnd(available)"><label>{{item._name}}</label></li> </ul> </div> </div> <div class="buttonbox"> <button type="button" class="btn btn-primary btn-block" (click)="addItem()" [disabled]="available.pick.length === 0"><i class="fa fa-arrow-right"></i></button> <button type="button" class="btn btn-primary btn-block" (click)="addAll()" [disabled]="isAllSelected(available)"><i class="fa fa-arrow-right"></i><i class="fa fa-arrow-right"></i></button> <button type="button" class="btn btn-primary btn-block" (click)="removeItem()" [disabled]="confirmed.pick.length === 0"><i class="fa fa-arrow-left"></i></button> <button type="button" class="btn btn-primary btn-block" (click)="removeAll()" [disabled]="isAllSelected(confirmed)"><i class="fa fa-arrow-left"></i><i class="fa fa-arrow-left"></i></button> <!-- <button type="button" class="btn btn-primary btn-block" (click)="moveItem(available, confirmed)" [disabled]="available.pick.length === 0"><i class="fa fa-arrow-right"></i></button> <button type="button" class="btn btn-primary btn-block" (click)="moveAll()" [disabled]="isAllSelected(available)"><i class="fa fa-arrow-right"></i><i class="fa fa-arrow-right"></i></button> <button type="button" class="btn btn-primary btn-block" (click)="moveItem(confirmed, available)" [disabled]="confirmed.pick.length === 0"><i class="fa fa-arrow-left"></i></button> <button type="button" class="btn btn-primary btn-block" (click)="removeAll()" [disabled]="isAllSelected(confirmed)"><i class="fa fa-arrow-left"></i><i class="fa fa-arrow-left"></i></button> --> </div> <div class="listbox"> <p>{{targetName}}</p> <div class="record-picker"> <ul [ngStyle]="{'max-height': height, 'min-height': height}" [ngClass]="{over:confirmed.dragOver}" (drop)="drop($event, available)" (dragover)="allowDrop($event, confirmed)" (dragleave)="dragLeave()"> <li *ngFor="let item of confirmed.sift; let idx=index;" (click)="selectItem(confirmed.pick, item); shiftClick($event, idx, confirmed, item)" [ngClass]="{selected: isItemSelected(confirmed.pick, item)}" draggable="true" (dragstart)="drag($event, item, confirmed)" (dragend)="dragEnd(confirmed)"><label>{{item._name}}</label></li> </ul> </div> </div> </div>
duel-list-component.ts
import { Component, EventEmitter, OnInit, IterableDiffers, Input, Output } from '@angular/core'; import { DualListComponent } from 'angular-dual-listbox'; import { ItemAddRemoveEventArgs } from './item-add-remove-event-args'; import { Role } from 'src/app/security/role'; @Component({ selector: 'app-duel-list', templateUrl: './duel-list.component.html', styleUrls: ['./duel-list.component.scss'] }) export class DuelListComponent extends DualListComponent implements OnInit { @Input() sourceName = ''; @Input() targetName = ''; @Output() selectChange = new EventEmitter(); @Output() itemAdd = new EventEmitter(); @Output() itemRemove = new EventEmitter(); constructor(differs: IterableDiffers) { super(differs); } ngOnInit() { } addItem() { this.moveItem(this.available, this.confirmed); const args: ItemAddRemoveEventArgs = ItemAddRemoveEventArgs.createItem(this.confirmed.list); this.itemAdd.emit(args); } removeItem() { this.moveItem(this.confirmed, this.available); const args: ItemAddRemoveEventArgs = ItemAddRemoveEventArgs.createItem(this.available.list); this.itemRemove.emit(args); } addAll() { this.selectAll(this.available); this.moveItem(this.available, this.confirmed); const args: ItemAddRemoveEventArgs = ItemAddRemoveEventArgs.createItems(this.confirmed); this.itemAdd.emit(args); } removeAll() { this.selectAll(this.confirmed); this.moveItem(this.confirmed, this.available); // this.itemRemove.emit({ item: this.available }); const args: ItemAddRemoveEventArgs = ItemAddRemoveEventArgs.createItems(this.available); this.itemRemove.emit(args); } // Override function in DualListComponent to add custom selectChange event. selectItem(list: Array<any>, item: any) { const pk = list.filter((e: any) => { return Object.is(e, item); }); if (pk.length > 0) { // Already in list, so deselect. for (let i = 0, len = pk.length; i < len; i += 1) { const idx = list.indexOf(pk[i]); if (idx !== -1) { list.splice(idx, 1); this.selectChange.emit({ key: item._id, selected: false }); } } } else { list.push(item); this.selectChange.emit({ key: item._id, selected: true }); } } }
使用方法
<app-duel-list [(source)]="roleSource" [(destination)]="currentRoles" [key]="'roleId'" [display]="'roleCode'" height="350px" sourceName="Available Roles" targetName="Selected Roles" [sort]="true" (selectChange)="roleSelectChange($event)" (itemAdd)="roleItemAdd($event)" (itemRemove)="roleItemRemove($event)" ></app-duel-list>
please can you show how can i do this if the list is a tree , like a options which conatins more options
General speaking, you need to extend my sample on using custom object, and used it for data binding.