Refactor filterable dropdowns to allow intermediate state

This commit is contained in:
Michael Shamoon 2020-12-19 00:13:08 -08:00
parent 01d448ecde
commit 275bd96ba8
5 changed files with 62 additions and 26 deletions

View File

@ -1,7 +1,7 @@
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-left-0 border-right-0 border-bottom" role="menuitem" (click)="toggleItem()">
<div class="selected-icon mr-1">
<svg *ngIf="selected" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#check" />
<svg *ngIf="getSelectedIconName() !== ''" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use attr.xlink:href="assets/bootstrap-icons.svg#{{getSelectedIconName()}}" />
</svg>
</div>
<div class="mr-1">

View File

@ -2,6 +2,7 @@ import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core';
import { PaperlessTag } from 'src/app/data/paperless-tag';
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
import { SelectableItem, SelectableItemState } from '../filterable-dropdown.component';
@Component({
selector: 'app-filterable-dropdown-button',
@ -11,10 +12,11 @@ import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
export class FilterableDropdownButtonComponent implements OnInit {
@Input()
item: PaperlessTag | PaperlessDocumentType | PaperlessCorrespondent
selectableItem: SelectableItem
@Input()
selected: boolean
get item(): PaperlessTag | PaperlessDocumentType | PaperlessCorrespondent {
return this.selectableItem?.item
}
@Output()
toggle = new EventEmitter()
@ -26,7 +28,14 @@ export class FilterableDropdownButtonComponent implements OnInit {
}
toggleItem(): void {
this.selected = !this.selected
this.toggle.emit(this.item)
this.selectableItem.state = (this.selectableItem.state == SelectableItemState.NotSelected || this.selectableItem.state == SelectableItemState.PartiallySelected) ? SelectableItemState.Selected : SelectableItemState.NotSelected
this.toggle.emit(this.selectableItem)
}
getSelectedIconName() {
let iconName = ''
if (this.selectableItem?.state == SelectableItemState.Selected) iconName = 'check'
else if (this.selectableItem?.state == SelectableItemState.PartiallySelected) iconName = 'minus'
return iconName
}
}

View File

@ -19,9 +19,9 @@
<input class="form-control" type="text" [(ngModel)]="filterText" placeholder="Filter {{title}}" (keyup.enter)="listFilterEnter()" #listFilterTextInput>
</div>
</div>
<div *ngIf="items" class="items">
<ng-container *ngFor="let item of items | filter: filterText; let i = index">
<app-filterable-dropdown-button [item]="item" [selected]="isItemSelected(item)" (toggle)="toggleItem($event)"></app-filterable-dropdown-button>
<div *ngIf="selectableItems" class="items">
<ng-container *ngFor="let selectableItem of selectableItems | filter: filterText">
<app-filterable-dropdown-button [selectableItem]="selectableItem" (toggle)="toggleItem($event)"></app-filterable-dropdown-button>
</ng-container>
</div>
</div>

View File

@ -1,8 +1,22 @@
import { Component, EventEmitter, Input, Output, ElementRef, ViewChild } from '@angular/core';
import { ObjectWithId } from 'src/app/data/object-with-id';
import { PaperlessTag } from 'src/app/data/paperless-tag';
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
import { FilterPipe } from 'src/app/pipes/filter.pipe';
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
export interface SelectableItem {
item: PaperlessTag | PaperlessDocumentType | PaperlessCorrespondent,
state: SelectableItemState
}
export enum SelectableItemState {
NotSelected = 0,
Selected = 1,
PartiallySelected = 2
}
@Component({
selector: 'app-filterable-dropdown',
templateUrl: './filterable-dropdown.component.html',
@ -13,10 +27,26 @@ export class FilterableDropdownComponent {
constructor(private filterPipe: FilterPipe) { }
@Input()
items: ObjectWithId[]
set items(items: ObjectWithId[]) {
if (items) {
this.selectableItems = items.map(i => {
return {item: i, state: SelectableItemState.NotSelected}
})
}
}
selectableItems: SelectableItem[] = []
@Input()
itemsSelected: ObjectWithId[]
set itemsSelected(itemsSelected: ObjectWithId[]) {
this.selectableItems.forEach(i => {
i.state = (itemsSelected.find(is => is.id == i.item.id)) ? SelectableItemState.Selected : SelectableItemState.NotSelected
})
}
get itemsSelected() :ObjectWithId[] {
return this.selectableItems.filter(si => si.state == SelectableItemState.Selected).map(si => si.item)
}
@Input()
title: string
@ -35,12 +65,8 @@ export class FilterableDropdownComponent {
filterText: string
toggleItem(item: ObjectWithId): void {
this.toggle.emit(item)
}
isItemSelected(item: ObjectWithId): boolean {
return this.itemsSelected?.find(i => i.id == item.id) !== undefined
toggleItem(selectableItem: SelectableItem): void {
this.toggle.emit(selectableItem.item)
}
dropdownOpenChange(open: boolean): void {
@ -50,12 +76,12 @@ export class FilterableDropdownComponent {
}, 0);
} else {
this.filterText = ''
this.close.emit(this.itemsSelected)
this.close.next()
}
}
listFilterEnter(): void {
let filtered = this.filterPipe.transform(this.items, this.filterText)
let filtered = this.filterPipe.transform(this.selectableItems, this.filterText)
if (filtered.length == 1) this.toggleItem(filtered.shift())
this.dropdown.close()
}

View File

@ -1,16 +1,17 @@
import { Pipe, PipeTransform } from '@angular/core';
import { SelectableItem } from 'src/app/components/common/filterable-dropdown/filterable-dropdown.component';
@Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(items: any[], searchText: string): any[] {
if (!items) return [];
if (!searchText) return items;
transform(selectableItems: SelectableItem[], searchText: string): any[] {
if (!selectableItems) return [];
if (!searchText) return selectableItems;
return items.filter(item => {
return Object.keys(item).some(key => {
return String(item[key]).toLowerCase().includes(searchText.toLowerCase());
return selectableItems.filter(selectableItem => {
return Object.keys(selectableItem.item).some(key => {
return String(selectableItem.item[key]).toLowerCase().includes(searchText.toLowerCase());
});
});
}