<!-- drop-down toggle link -->
-<ng-template #dropdownToggle let-col="col">
+<ng-template #dropdownToggle>
<span i18n>Filter</span>
<ng-container *ngIf="!col.isFiltered">
<span class="material-icons mat-icon-in-button">filter_list</span>
</ng-template>
<!-- apply/clear actions are the same for all filter types -->
-<ng-template #actionsTemplate let-col="col">
+<ng-template #actionsTemplate>
<div class="pt-2">
- <button class="btn btn-sm btn-outline-dark"
- (click)="closeDropdown(); applyFilter(col)" i18n>Apply filter</button>
+ <button class="btn btn-sm btn-outline-dark" (click)="applyFilterCommon(col)" i18n>Apply filter</button>
<span class="pl-2"></span>
- <button class="btn btn-sm btn-outline-dark"
- (click)="closeDropdown(); clearFilter(col)" i18n>Clear filter</button>
+ <button class="btn btn-sm btn-outline-dark" (click)="clearFilter(col)" i18n>Clear filter</button>
+ <span class="pl-2"></span>
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown()" i18n>Close</button>
</div>
</ng-template>
<!-- various number filters all use the same operators -->
-<ng-template #numericOperators let-col="col">
+<ng-template #numericOperators>
<select id="eg-filter-op-select-{{col.name}}" class="form-control"
[(ngModel)]="col.filterOperator" (change)="operatorChanged(col)">
<option value="=" i18n>Is exactly</option>
<div [ngSwitch]="col.datatype">
<div *ngSwitchCase="'link'">
<div class="input-group">
- <div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
+ <div ngbDropdown container="body" class="d-inline-block p-1" [autoClose]="false" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
<a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
- <ng-container *ngTemplateOutlet="dropdownToggle; context:{col:col}"></ng-container>
+ <ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
</a>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
- <div class="pt-2">
+ <div>
<eg-combobox [asyncSupportsEmptyTermClick]="col.asyncSupportsEmptyTermClick"
- [idlClass]="col.idlFieldDef.class" (onChange)="applyLinkFilter($event, col)"
+ [(ngModel)]="linkFilterEntry" [idlClass]="col.idlFieldDef.class"
+ (ngModelChange)="applyFilterCommon(col)"
[disabled]="col.filterInputDisabled || context.dataSource.requestingData"
i18n-placeholder placeholder="Enter value to filter by"></eg-combobox>
</div>
- <ng-container *ngTemplateOutlet="actionsTemplate; context:{col: col}"></ng-container>
+ <ng-container *ngTemplateOutlet="actionsTemplate"></ng-container>
</div>
</div>
</div>
<div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
<a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
- <ng-container *ngTemplateOutlet="dropdownToggle; context:{col:col}"></ng-container>
+ <ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
</a>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
- <div class="pt-2">
- <select class="custom-select" [(ngModel)]="col.filterValue" (change)="applyBooleanFilter(col)"
- [disabled]="col.filterInputDisabled || context.dataSource.requestingData">
+ <div>
+ <select class="custom-select" [(ngModel)]="col.filterValue"
+ (change)="applyFilterCommon(col)"
+ [disabled]="col.filterInputDisabled || context.dataSource.requestingData">
<option value="" i18n>Any</option>
<option value="t" i18n>True</option>
<option value="f" i18n>False</option>
</select>
</div>
- <ng-container *ngTemplateOutlet="actionsTemplate; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="actionsTemplate"></ng-container>
</div>
</div>
</div>
<div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
<a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
- <ng-container *ngTemplateOutlet="dropdownToggle; context:{col:col}"></ng-container>
+ <ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
</a>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
- <div class="pt-2">
+ <div>
<label for="eg-filter-op-select-{{col.name}}" i18n>Operator</label>
<select id="eg-filter-op-select-{{col.name}}" class="form-control"
[(ngModel)]="col.filterOperator" (change)="operatorChanged(col)">
</div>
<div class="pt-2">
<input type="text" class="form-control"
- [(ngModel)]="col.filterValue" (keyup.enter)="applyFilter(col); closeDropdown()"
+ [(ngModel)]="col.filterValue" (change)="applyFilterCommon(col)"
[disabled]="col.filterInputDisabled || context.dataSource.requestingData"
i18n-placeholder placeholder="Enter value to filter by">
</div>
- <ng-container *ngTemplateOutlet="actionsTemplate; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="actionsTemplate"></ng-container>
</div>
</div>
</div>
<div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
<a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
- <ng-container *ngTemplateOutlet="dropdownToggle; context:{col:col}"></ng-container>
+ <ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
</a>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
- <div class="pt-2">
+ <div>
<label for="eg-filter-op-select-{{col.name}}" i18n>Operator</label>
- <ng-container *ngTemplateOutlet="numericOperators; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="numericOperators"></ng-container>
</div>
<div class="pt-2">
<input type="number" min="0" step="1" class="form-control"
- [(ngModel)]="col.filterValue" (keyup.enter)="applyFilter(col); closeDropdown()"
+ [(ngModel)]="col.filterValue" (change)="applyFilterCommon(col)"
[disabled]="col.filterInputDisabled || context.dataSource.requestingData"/>
</div>
- <ng-container *ngTemplateOutlet="actionsTemplate; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="actionsTemplate"></ng-container>
</div>
</div>
</div>
<div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
<a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
- <ng-container *ngTemplateOutlet="dropdownToggle; context:{col:col}"></ng-container>
+ <ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
</a>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
- <div class="pt-2">
+ <div>
<label for="eg-filter-op-select-{{col.name}}" i18n>Operator</label>
- <ng-container *ngTemplateOutlet="numericOperators; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="numericOperators"></ng-container>
</div>
<div class="pt-2">
<input type="number" min="0" step="1" class="form-control"
- [(ngModel)]="col.filterValue" (keyup.enter)="applyFilter(col); closeDropdown()"
+ [(ngModel)]="col.filterValue" (change)="applyFilterCommon(col)"
[disabled]="col.filterInputDisabled || context.dataSource.requestingData">
</div>
- <ng-container *ngTemplateOutlet="actionsTemplate; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="actionsTemplate"></ng-container>
</div>
</div>
</div>
<div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
<a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
- <ng-container *ngTemplateOutlet="dropdownToggle; context:{col:col}"></ng-container>
+ <ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
</a>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
- <div class="pt-2">
+ <div>
<label for="eg-filter-op-select-{{col.name}}" i18n>Operator</label>
- <ng-container *ngTemplateOutlet="numericOperators; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="numericOperators"></ng-container>
</div>
<div class="pt-2">
- <input type="number" class="form-control" [(ngModel)]="col.filterValue"
- (keyup.enter)="applyFilter(col); closeDropdown()"
+ <input type="number" class="form-control"
+ [(ngModel)]="col.filterValue" (change)="applyFilterCommon(col)"
[disabled]="col.filterInputDisabled || context.dataSource.requestingData">
</div>
- <ng-container *ngTemplateOutlet="actionsTemplate; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="actionsTemplate"></ng-container>
</div>
</div>
</div>
<div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
<a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
- <ng-container *ngTemplateOutlet="dropdownToggle; context:{col:col}"></ng-container>
+ <ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
</a>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
- <div class="pt-2">
+ <div>
<label for="eg-filter-op-select-{{col.name}}" i18n>Operator</label>
- <ng-container *ngTemplateOutlet="numericOperators; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="numericOperators"></ng-container>
</div>
<div class="pt-2">
<input type="number" step="0.01" class="form-control"
- [(ngModel)]="col.filterValue" (keyup.enter)="applyFilter(col); closeDropdown()"
+ [(ngModel)]="col.filterValue" (change)="applyFilterCommon(col)"
[disabled]="col.filterInputDisabled || context.dataSource.requestingData"/>
</div>
- <ng-container *ngTemplateOutlet="actionsTemplate; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="actionsTemplate"></ng-container>
</div>
</div>
</div>
<div ngbDropdown container="body" class="d-inline-block p-1" [autoClose]="false" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
<a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
- <ng-container *ngTemplateOutlet="dropdownToggle; context:{col:col}"></ng-container>
+ <ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
</a>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
- <div class="pt-2">
+ <div>
<label for="eg-filter-op-select-{{col.name}}" i18n>Operator</label>
<select id="eg-filter-op-select-{{col.name}}" class="form-control"
[(ngModel)]="col.filterOperator" (change)="operatorChanged(col)">
</select>
</div>
<div class="pt-2">
- <eg-date-select [initialYmd]="col.filterValue"
- (onChangeAsYmd)="applyDateFilter($event, col, dateendsel.currentAsYmd())" (onCleared)="clearDateFilter(col)"
- [disabled]="col.filterInputDisabled || context.dataSource.requestingData" #datesel></eg-date-select>
+ <eg-date-select [initialYmd]="col.filterValue" #dateSelectOne
+ (onChangeAsYmd)="applyFilterCommon(col)"
+ [disabled]="col.filterInputDisabled || context.dataSource.requestingData"></eg-date-select>
<div [hidden]="col.filterOperator !== 'between'" class="form-inline form-group">
<label for="eg-filter-end-date-select-{{col.name}}" style="width: 3em;" i18n>and</label>
- <eg-date-select [hidden]="col.filterOperator !== 'between'"
- (onChangeAsYmd)="applyDateFilter(datesel.currentAsYmd(), col, $event)"
+ <eg-date-select [hidden]="col.filterOperator !== 'between'" #dateSelectTwo
+ (onChangeAsYmd)="applyFilterCommon(col)"
[disabled]="col.filterInputDisabled || context.dataSource.requestingData"
- [required]="col.filterOperator == 'between'" #dateendsel></eg-date-select>
+ [required]="col.filterOperator == 'between'"></eg-date-select>
</div>
</div>
- <ng-container *ngTemplateOutlet="actionsTemplate; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="actionsTemplate"></ng-container>
</div>
</div>
</div>
</div>
<div *ngSwitchCase="'org_unit'">
<div class="input-group">
- <div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
+ <div ngbDropdown container="body" class="d-inline-block p-1" [autoClose]="false" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
<a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
- <ng-container *ngTemplateOutlet="dropdownToggle; context:{col:col}"></ng-container>
+ <ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
</a>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
- <div class="pt-2">
+ <div>
<label for="eg-filter-op-select-{{col.name}}" i18n>Operator</label>
- <select id="eg-filter-op-select-{{col.name}}" class="form-control" [(ngModel)]="col.filterOperator" (change)="operatorChanged(col)">
+ <select id="eg-filter-op-select-{{col.name}}" class="form-control"
+ [(ngModel)]="col.filterOperator" (change)="operatorChanged(col)">
<option value="=" i18n>Is (or includes)</option>
<option value="!=" i18n>Is not (or excludes)</option>
</select>
</div>
</div>
<div class="pt-2">
- <eg-org-select [applyOrgId]="col.filterValue" (onChange)="applyOrgFilter($event, col)"
+ <eg-org-select [applyOrgId]="col.filterValue"
+ (onChange)="col.filterValue = $event; applyFilterCommon(col)"
[disabled]="col.filterInputDisabled || context.dataSource.requestingData"
i18n-placeholder placeholder="Enter library to filter by" #ousel></eg-org-select>
</div>
- <ng-container *ngTemplateOutlet="actionsTemplate; context:{'col': col}"></ng-container>
+ <ng-container *ngTemplateOutlet="actionsTemplate"></ng-container>
</div>
</div>
</div>
-import {Component, Input, OnInit, QueryList, ViewChildren} from '@angular/core';
+import {Component, Input, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {GridContext, GridColumn} from './grid';
import {IdlObject} from '@eg/core/idl.service';
import {ComboboxComponent} from '@eg/share/combobox/combobox.component';
import {OrgSelectComponent} from '@eg/share/org-select/org-select.component';
import {OrgService} from '@eg/core/org.service';
import {NgbDropdown} from '@ng-bootstrap/ng-bootstrap';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
@Component({
selector: 'eg-grid-filter-control',
@ViewChildren(ComboboxComponent) filterComboboxes: QueryList<ComboboxComponent>;
- @ViewChildren(DateSelectComponent) dateSelects: QueryList<DateSelectComponent>;
@ViewChildren(OrgSelectComponent) orgSelects: QueryList<OrgSelectComponent>;
@ViewChildren(NgbDropdown) dropdowns: QueryList<NgbDropdown>;
+ @ViewChild('dateSelectOne') dateSelectOne: DateSelectComponent;
+ @ViewChild('dateSelectTwo') dateSelectTwo: DateSelectComponent;
+
+ // So we can use (ngModelChange) on the link combobox
+ linkFilterEntry: ComboboxEntry = null;
+
constructor(
private org: OrgService
) {}
}
}
- applyOrgFilter(org: IdlObject, col: GridColumn) {
+ applyOrgFilter(col: GridColumn) {
+ const org: IdlObject = (col.filterValue as unknown) as IdlObject;
+
if (org == null) {
this.clearFilter(col);
return;
this.context.dataSource.filters[col.name] = [ filt ];
col.isFiltered = true;
this.context.reload();
+
+ this.closeDropdown();
}
- applyLinkFilter($event, col: GridColumn) {
- if ($event) {
- col.filterValue = $event.id;
+ applyLinkFilter(col: GridColumn) {
+ if (col.filterValue) {
this.applyFilter(col);
} else {
return new Date(
Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
}
- applyDateFilter(dateStr: string, col: GridColumn, endDateStr: string) {
+
+ applyDateFilter(col: GridColumn) {
+ const dateStr = this.dateSelectOne.currentAsYmd();
+ const endDateStr =
+ this.dateSelectTwo ? this.dateSelectTwo.currentAsYmd() : null;
+
+ if (endDateStr && !dateStr) {
+ // User has applied a second date (e.g. between) but cleared
+ // the first date. Avoid applying the filter until
+ // dateStr gets a value or endDateStr is cleared or the
+ // operator changes.
+ return;
+ }
+
if (col.filterOperator === 'null' || col.filterOperator === 'not null') {
this.applyFilter(col);
} else {
this.context.dataSource.filters[col.name] = filters;
col.isFiltered = true;
this.context.reload();
+ this.closeDropdown();
}
-
- // The date filter has autoClose=false so that interacting with
- // date selectors won't result in closing the dropdown. Once
- // we've successfully applied a filter, force it closed.
- this.closeDropdown();
}
+
clearDateFilter(col: GridColumn) {
delete this.context.dataSource.filters[col.name];
col.isFiltered = false;
this.context.reload();
+ this.closeDropdown();
}
applyBooleanFilter(col: GridColumn) {
if (!col.filterValue || col.filterValue === '') {
col.isFiltered = true;
this.context.reload();
}
+
+ this.closeDropdown();
}
+
+ applyFilterCommon(col: GridColumn) {
+
+ switch (col.datatype) {
+ case 'link':
+ col.filterValue =
+ this.linkFilterEntry ? this.linkFilterEntry.id : null;
+ return this.applyLinkFilter(col);
+ case 'bool':
+ return this.applyBooleanFilter(col);
+ case 'timestamp':
+ return this.applyDateFilter(col);
+ case 'org_unit':
+ return this.applyOrgFilter(col);
+ default:
+ return this.applyFilter(col);
+ }
+ }
+
applyFilter(col: GridColumn) {
// fallback if the operator somehow was not set yet
if (col.filterOperator === undefined) { col.filterOperator = '='; }
}
}
this.context.reload();
+ this.closeDropdown();
}
clearFilter(col: GridColumn) {
// clear filter values...
col.isFiltered = false;
this.reset();
this.context.reload();
+ this.closeDropdown();
}
closeDropdown() {
- this.dropdowns.forEach(drp => { drp.close(); });
+ // Timeout allows actions to occur before closing (some) dropdows
+ // clears the values (e.g. link selector)
+ setTimeout(() => this.dropdowns.forEach(drp => { drp.close(); }));
}
reset() {
this.filterComboboxes.forEach(ctl => { ctl.applyEntryId(null); });
- this.dateSelects.forEach(ctl => { ctl.reset(); });
this.orgSelects.forEach(ctl => { ctl.reset(); });
+ if (this.dateSelectOne) { this.dateSelectOne.reset(); }
+ if (this.dateSelectTwo) { this.dateSelectTwo.reset(); }
}
}