在設計介面時, 有時會利用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.