// If true, boolean fields support 3 values: true, false, null (unset)
@Input() ternaryBool: boolean;
+ // result filtering
+ @Input() filterable: boolean;
+
// Display date and time when datatype = timestamp
@Input() datePlusTime: boolean;
col.cellContext = this.cellContext;
col.disableTooltip = this.disableTooltip;
col.isSortable = this.sortable;
+ col.isFilterable = this.filterable;
col.isMultiSortable = this.multiSortable;
col.datatype = this.datatype;
col.datePlusTime = this.datePlusTime;
--- /dev/null
+<div *ngIf="col.isFilterable" class="eg-grid-filter-control">
+ <div [ngSwitch]="col.datatype">
+ <div *ngSwitchCase="'link'">
+ <div class="input-group">
+ <div ngbDropdown class="d-inline-block" autoClose="outside" placement="bottom-left" [ngClass]="{'eg-grid-col-is-filtered' : col.isFiltered}">
+ <button ngbDropdownToggle class="form-control btn btn-sm btn-outline-dark text-button"><span class="material-icons mat-icon-in-button">filter_list</span></button>
+ <div ngbDropdownMenu class="eg-grid-filter-menu">
+ <div class="dropdown-item">
+ <div style="padding-top: 2px;">
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); applyFilter(col)" i18n>Apply filter</button>
+ <span style="padding-left: 2px;"></span>
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); clearFilter(col)" i18n>Clear filter</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <eg-combobox [idlClass]="col.idlFieldDef.class" (onChange)="applyLinkFilter($event, col)" placeholder="Enter value to filter by"></eg-combobox>
+ </div>
+ </div>
+ <div *ngSwitchCase="'bool'">
+ <div class="input-group">
+ <div ngbDropdown class="d-inline-block" autoClose="outside" placement="bottom-left" [ngClass]="{'eg-grid-col-is-filtered' : col.isFiltered}">
+ <button ngbDropdownToggle class="form-control btn btn-sm btn-outline-dark text-button"><span class="material-icons mat-icon-in-button">filter_list</span></button>
+ <div ngbDropdownMenu class="eg-grid-filter-menu">
+ <div class="dropdown-item">
+ <div style="padding-top: 2px;">
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); applyBooleanFilter(col)" i18n>Apply filter</button>
+ <span style="padding-left: 2px;"></span>
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); clearFilter(col)" i18n>Clear filter</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <select class="custom-select" [(ngModel)]="col.filterValue" (change)="applyBooleanFilter(col)">
+ <option value="" i18n>Any</option>
+ <option value="t" i18n>True</option>
+ <option value="f" i18n>False</option>
+ </select>
+ </div>
+ </div>
+ <div *ngSwitchCase="'text'">
+ <div class="input-group">
+ <div ngbDropdown class="d-inline-block" autoClose="outside" placement="bottom-left" [ngClass]="{'eg-grid-col-is-filtered' : col.isFiltered}">
+ <button ngbDropdownToggle class="form-control btn btn-sm btn-outline-dark text-button"><span class="material-icons mat-icon-in-button">filter_list</span></button>
+ <div ngbDropdownMenu class="eg-grid-filter-menu">
+ <div class="dropdown-item">
+ <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)">
+ <option value="=" i18n>Is exactly</option>
+ <option value="!=" i18n>Is not</option>
+ <option value="like" i18n>Contains</option>
+ <option value="not like" i18n>Does not contain</option>
+ <option value="startswith" i18n>Starts with</option>
+ <option value="endswith" i18n>Ends with</option>
+ <option value="not null" i18n>Exists</option>
+ <option value="null" i18n>Does not exist</option>
+ <option value="<" i18n>Is less than</option>
+ <option value=">" i18n>Is greater than</option>
+ <option value="<=" i18n>Is less than or equal to</option>
+ <option value=">=" i18n>Is greater than or equal to</option>
+ </select>
+ <div style="padding-top: 2px;">
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); applyFilter(col)" i18n>Apply filter</button>
+ <span style="padding-left: 2px;"></span>
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); clearFilter(col)" i18n>Clear filter</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <input type="text" class="form-control" [(ngModel)]="col.filterValue" (keyup.enter)="applyFilter(col)" [disabled]="col.filterInputDisabled" placeholder="Enter value to filter by">
+ </div>
+ </div>
+ <div *ngSwitchCase="'int'">
+ <div class="input-group">
+ <div ngbDropdown class="d-inline-block" autoClose="outside" placement="bottom-left" [ngClass]="{'eg-grid-col-is-filtered' : col.isFiltered}">
+ <button ngbDropdownToggle class="form-control btn btn-sm btn-outline-dark text-button"><span class="material-icons mat-icon-in-button">filter_list</span></button>
+ <div ngbDropdownMenu class="eg-grid-filter-menu">
+ <div class="dropdown-item">
+ <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)">
+ <option value="=" i18n>Is exactly</option>
+ <option value="!=" i18n>Is not</option>
+ <option value="not null" i18n>Exists</option>
+ <option value="null" i18n>Does not exist</option>
+ <option value="<" i18n>Is less than</option>
+ <option value=">" i18n>Is greater than</option>
+ <option value="<=" i18n>Is less than or equal to</option>
+ <option value=">=" i18n>Is greater than or equal to</option>
+ </select>
+ <div style="padding-top: 2px;">
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); applyFilter(col)" i18n>Apply filter</button>
+ <span style="padding-left: 2px;"></span>
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); clearFilter(col)" i18n>Clear filter</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <input type="number" min="0" step="1" class="form-control" [(ngModel)]="col.filterValue" (keyup.enter)="applyFilter(col)" [disabled]="col.filterInputDisabled">
+ </div>
+ </div>
+ <div *ngSwitchCase="'id'">
+ <div class="input-group">
+ <div ngbDropdown class="d-inline-block" autoClose="outside" placement="bottom-left" [ngClass]="{'eg-grid-col-is-filtered' : col.isFiltered}">
+ <button ngbDropdownToggle class="form-control btn btn-sm btn-outline-dark text-button"><span class="material-icons mat-icon-in-button">filter_list</span></button>
+ <div ngbDropdownMenu class="eg-grid-filter-menu">
+ <div class="dropdown-item">
+ <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)">
+ <option value="=" i18n>Is exactly</option>
+ <option value="!=" i18n>Is not</option>
+ <option value="not null" i18n>Exists</option>
+ <option value="null" i18n>Does not exist</option>
+ <option value="<" i18n>Is less than</option>
+ <option value=">" i18n>Is greater than</option>
+ <option value="<=" i18n>Is less than or equal to</option>
+ <option value=">=" i18n>Is greater than or equal to</option>
+ </select>
+ <div style="padding-top: 2px;">
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); applyFilter(col)" i18n>Apply filter</button>
+ <span style="padding-left: 2px;"></span>
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); clearFilter(col)" i18n>Clear filter</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <input type="number" min="0" step="1" class="form-control" [(ngModel)]="col.filterValue" (keyup.enter)="applyFilter(col)" [disabled]="col.filterInputDisabled">
+ </div>
+ </div>
+ <div *ngSwitchCase="'float'">
+ <div class="input-group">
+ <div ngbDropdown class="d-inline-block" autoClose="outside" placement="bottom-left" [ngClass]="{'eg-grid-col-is-filtered' : col.isFiltered}">
+ <button ngbDropdownToggle class="form-control btn btn-sm btn-outline-dark text-button"><span class="material-icons mat-icon-in-button">filter_list</span></button>
+ <div ngbDropdownMenu class="eg-grid-filter-menu">
+ <div class="dropdown-item">
+ <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)">
+ <option value="=" i18n>Is exactly</option>
+ <option value="!=" i18n>Is not</option>
+ <option value="not null" i18n>Exists</option>
+ <option value="null" i18n>Does not exist</option>
+ <option value="<" i18n>Is less than</option>
+ <option value=">" i18n>Is greater than</option>
+ <option value="<=" i18n>Is less than or equal to</option>
+ <option value=">=" i18n>Is greater than or equal to</option>
+ </select>
+ <div style="padding-top: 2px;">
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); applyFilter(col)" i18n>Apply filter</button>
+ <span style="padding-left: 2px;"></span>
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); clearFilter(col)" i18n>Clear filter</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <input type="number" class="form-control" [(ngModel)]="col.filterValue" (keyup.enter)="applyFilter(col)" [disabled]="col.filterInputDisabled">
+ </div>
+ </div>
+ <div *ngSwitchCase="'money'">
+ <div class="input-group">
+ <div ngbDropdown class="d-inline-block" autoClose="outside" placement="bottom-left" [ngClass]="{'eg-grid-col-is-filtered' : col.isFiltered}">
+ <button ngbDropdownToggle class="form-control btn btn-sm btn-outline-dark text-button"><span class="material-icons mat-icon-in-button">filter_list</span></button>
+ <div ngbDropdownMenu class="eg-grid-filter-menu">
+ <div class="dropdown-item">
+ <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)">
+ <option value="=" i18n>Is exactly</option>
+ <option value="!=" i18n>Is not</option>
+ <option value="not null" i18n>Exists</option>
+ <option value="null" i18n>Does not exist</option>
+ <option value="<" i18n>Is less than</option>
+ <option value=">" i18n>Is greater than</option>
+ <option value="<=" i18n>Is less than or equal to</option>
+ <option value=">=" i18n>Is greater than or equal to</option>
+ </select>
+ <div style="padding-top: 2px;">
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); applyFilter(col)" i18n>Apply filter</button>
+ <span style="padding-left: 2px;"></span>
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); clearFilter(col)" i18n>Clear filter</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <input type="number" step="0.01" class="form-control" [(ngModel)]="col.filterValue" (keyup.enter)="applyFilter(col)" [disabled]="col.filterInputDisabled">
+ </div>
+ </div>
+ <div *ngSwitchCase="'timestamp'">
+ <div class="input-group">
+ <div ngbDropdown class="d-inline-block" autoClose="outside" placement="bottom-left" [ngClass]="{'eg-grid-col-is-filtered' : col.isFiltered}">
+ <button ngbDropdownToggle class="form-control btn btn-sm btn-outline-dark text-button"><span class="material-icons mat-icon-in-button">filter_list</span></button>
+ <div ngbDropdownMenu class="eg-grid-filter-menu">
+ <div class="dropdown-item">
+ <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)">
+ <option value="=" i18n>Is exactly</option>
+ <option value="!=" i18n>Is not</option>
+ <option value="not null" i18n>Exists</option>
+ <option value="null" i18n>Does not exist</option>
+ <option value="<" i18n>Is less than</option>
+ <option value=">" i18n>Is greater than</option>
+ <option value="<=" i18n>Is less than or equal to</option>
+ <option value=">=" i18n>Is greater than or equal to</option>
+ <option value="between" i18n>Between</option>
+ </select>
+ <div style="padding-top: 2px;">
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); applyDateFilter(datesel.currentAsYmd(), col, dateendsel.currentAsYmd())" i18n>Apply filter</button>
+ <span style="padding-left: 2px;"></span>
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); clearFilter(col)" i18n>Clear filter</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <eg-date-select [initialYmd]="col.filterValue" (onChangeAsYmd)="applyDateFilter($event, col, dateendsel.currentAsYmd())" (onCleared)="clearDateFilter(col)"
+ [disabled]="col.filterInputDisabled" #datesel></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)"
+ [required]="col.filterOperator == 'between'" #dateendsel></eg-date-select>
+ </div>
+ </div>
+ </div>
+ <div *ngSwitchCase="'org_unit'">
+ <div class="input-group">
+ <div ngbDropdown class="d-inline-block" autoClose="outside" placement="bottom-left" [ngClass]="{'eg-grid-col-is-filtered' : col.isFiltered}">
+ <button ngbDropdownToggle class="form-control btn btn-sm btn-outline-dark text-button"><span class="material-icons mat-icon-in-button">filter_list</span></button>
+ <div ngbDropdownMenu class="eg-grid-filter-menu">
+ <div class="dropdown-item">
+ <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)">
+ <option value="=" i18n>Is (or includes)</option>
+ <option value="!=" i18n>Is not (or excludes)</option>
+ </select>
+ </div>
+ <div class="dropdown-item">
+ <div class="form-check">
+ <input type="checkbox"
+ [(ngModel)]="col.filterIncludeOrgAncestors"
+ class="form-check-input" id="include-ancestors">
+ <label class="form-check-label" for="include-ancestors" i18n>+ Ancestors</label>
+ </div>
+ <div class="form-check">
+ <input type="checkbox"
+ [(ngModel)]="col.filterIncludeOrgDescendants"
+ class="form-check-input" id="include-descendants">
+ <label class="form-check-label" for="include-descendants" i18n>+ Descendants</label>
+ </div>
+ <div style="padding-top: 2px;">
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); applyOrgFilter(ousel.selectedOrg(), col)" i18n>Apply filter</button>
+ <span style="padding-left: 2px;"></span>
+ <button class="btn btn-sm btn-outline-dark" (click)="closeDropdown(); clearFilter(col)" i18n>Clear filter</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <eg-org-select [applyOrgId]="col.filterValue" (onChange)="applyOrgFilter($event, col)" placeholder="Enter library to filter by" #ousel></eg-org-select>
+ </div>
+ </div>
+ <div *ngSwitchDefault>I don't know how to filter {{col.name}} - {{col.datatype}}</div>
+ </div>
+ <span *ngIf="col.datatype !== 'org_unit'" i18n class="eg-grid-filter-operator">Operator:
+ <span [ngSwitch]="col.filterOperator">
+ <span *ngSwitchCase="'='" i18n>Is exactly</span>
+ <span *ngSwitchCase="'!='" i18n>Is not</span>
+ <span *ngSwitchCase="'>'" i18n>Is greater than</span>
+ <span *ngSwitchCase="'>='" i18n>Is greater than or equal to</span>
+ <span *ngSwitchCase="'<'" i18n>Is less than</span>
+ <span *ngSwitchCase="'<='" i18n>Is less than or equal to</span>
+ <span *ngSwitchCase="'like'" i18n>Contains</span>
+ <span *ngSwitchCase="'not like'" i18n>Does not contain</span>
+ <span *ngSwitchCase="'startswith'" i18n>Starts with</span>
+ <span *ngSwitchCase="'endswith'" i18n>Ends with</span>
+ <span *ngSwitchCase="'null'" i18n>Does not exist</span>
+ <span *ngSwitchCase="'not null'" i18n>Exists</span>
+ <span *ngSwitchCase="'between'" i18n>Between</span>
+ </span>
+ </span>
+ <span *ngIf="col.datatype == 'org_unit'" i18n class="eg-grid-filter-operator">Operator:
+ <span [ngSwitch]="col.filterOperator">
+ <span *ngSwitchCase="'='" i18n>Is (or includes)</span>
+ <span *ngSwitchCase="'!='" i18n>Is not (or excludes)</span>
+ </span>
+ </span>
+</div>
--- /dev/null
+import {Component, Input, OnInit, QueryList, ViewChildren} from '@angular/core';
+import {GridContext, GridColumn, GridRowSelector,
+ GridColumnSet, GridDataSource} from './grid';
+import {IdlObject} from '@eg/core/idl.service';
+import {ComboboxComponent,
+ ComboboxEntry} from '@eg/share/combobox/combobox.component';
+import {DateSelectComponent} from '@eg/share/date-select/date-select.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';
+
+@Component({
+ selector: 'eg-grid-filter-control',
+ templateUrl: './grid-filter-control.component.html'
+})
+
+export class GridFilterControlComponent implements OnInit {
+
+ @Input() context: GridContext;
+ @Input() col: GridColumn;
+
+
+ @ViewChildren(ComboboxComponent) filterComboboxes: QueryList<ComboboxComponent>;
+ @ViewChildren(DateSelectComponent) dateSelects: QueryList<DateSelectComponent>;
+ @ViewChildren(OrgSelectComponent) orgSelects: QueryList<OrgSelectComponent>;
+ @ViewChildren(NgbDropdown) dropdowns: QueryList<NgbDropdown>;
+
+ constructor(
+ private org: OrgService
+ ) {}
+
+ ngOnInit() { }
+
+ operatorChanged(col: GridColumn) {
+ if (col.filterOperator === 'null' || col.filterOperator === 'not null') {
+ col.filterInputDisabled = true;
+ col.filterValue = undefined;
+ } else {
+ col.filterInputDisabled = false;
+ }
+ }
+
+ applyOrgFilter(org: IdlObject, col: GridColumn) {
+ if (org == null) {
+ this.clearFilter(col);
+ return;
+ }
+ const ous: any[] = new Array();
+ if (col.filterIncludeOrgDescendants || col.filterIncludeOrgAncestors) {
+ if (col.filterIncludeOrgAncestors) {
+ ous.push(...this.org.ancestors(org, true));
+ }
+ if (col.filterIncludeOrgDescendants) {
+ ous.push(...this.org.descendants(org, true));
+ }
+ } else {
+ ous.push(org.id());
+ }
+ const filt: any = {};
+ filt[col.name] = {};
+ const op: string = (col.filterOperator === '=' ? 'in' : 'not in');
+ filt[col.name][op] = ous;
+ this.context.dataSource.filters[col.name] = [ filt ];
+ col.isFiltered = true;
+ this.context.reload();
+ }
+ applyLinkFilter($event, col: GridColumn) {
+ col.filterValue = $event.id;
+ this.applyFilter(col);
+ }
+
+ // TODO: this was copied from date-select and
+ // really belongs in a date service
+ localDateFromYmd(ymd: string): Date {
+ const parts = ymd.split('-');
+ return new Date(
+ Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
+ }
+ applyDateFilter(dateStr: string, col: GridColumn, endDateStr: string) {
+ if (col.filterOperator === 'null' || col.filterOperator === 'not null') {
+ this.applyFilter(col);
+ } else {
+ if (dateStr == null) {
+ this.clearFilter(col);
+ return;
+ }
+ const date: Date = this.localDateFromYmd(dateStr);
+ let date1 = new Date();
+ let date2 = new Date();
+ const op: string = col.filterOperator;
+ const filt: Object = {};
+ const filt2: Object = {};
+ const filters = new Array();
+ if (col.filterOperator === '>') {
+ date1 = date;
+ date1.setHours(23);
+ date1.setMinutes(59);
+ date1.setSeconds(59);
+ filt[op] = date1.toISOString();
+ if (col.name === 'dob') { filt[op] = dateStr; } // special case
+ filt2[col.name] = filt;
+ filters.push(filt2);
+ } else if (col.filterOperator === '>=') {
+ date1 = date;
+ filt[op] = date1.toISOString();
+ if (col.name === 'dob') { filt[op] = dateStr; } // special case
+ filt2[col.name] = filt;
+ filters.push(filt2);
+ } else if (col.filterOperator === '<') {
+ date1 = date;
+ filt[op] = date1.toISOString();
+ if (col.name === 'dob') { filt[op] = dateStr; } // special case
+ filt2[col.name] = filt;
+ filters.push(filt2);
+ } else if (col.filterOperator === '<=') {
+ date1 = date;
+ date1.setHours(23);
+ date1.setMinutes(59);
+ date1.setSeconds(59);
+ filt[op] = date1.toISOString();
+ if (col.name === 'dob') { filt[op] = dateStr; } // special case
+ filt2[col.name] = filt;
+ filters.push(filt2);
+ } else if (col.filterOperator === '=') {
+ date1 = new Date(date.valueOf());
+ filt['>='] = date1.toISOString();
+ if (col.name === 'dob') { filt['>='] = dateStr; } // special case
+ filt2[col.name] = filt;
+ filters.push(filt2);
+
+ date2 = new Date(date.valueOf());
+ date2.setHours(23);
+ date2.setMinutes(59);
+ date2.setSeconds(59);
+ const filt_a: Object = {};
+ const filt2_a: Object = {};
+ filt_a['<='] = date2.toISOString();
+ if (col.name === 'dob') { filt_a['<='] = dateStr; } // special case
+ filt2_a[col.name] = filt_a;
+ filters.push(filt2_a);
+ } else if (col.filterOperator === '!=') {
+ date1 = new Date(date.valueOf());
+ filt['<'] = date1.toISOString();
+ if (col.name === 'dob') { filt['<'] = dateStr; } // special case
+ filt2[col.name] = filt;
+
+ date2 = new Date(date.valueOf());
+ date2.setHours(23);
+ date2.setMinutes(59);
+ date2.setSeconds(59);
+ const filt_a: Object = {};
+ const filt2_a: Object = {};
+ filt_a['>'] = date2.toISOString();
+ if (col.name === 'dob') { filt_a['>'] = dateStr; } // special case
+ filt2_a[col.name] = filt_a;
+
+ const date_filt: any = { '-or': [] };
+ date_filt['-or'].push(filt2);
+ date_filt['-or'].push(filt2_a);
+ filters.push(date_filt);
+ } else if (col.filterOperator === 'between') {
+ date1 = date;
+ date2 = this.localDateFromYmd(endDateStr);
+
+ let date1op = '>=';
+ let date2op = '<=';
+ if (date1 > date2) {
+ // don't make user care about the order
+ // they enter the dates in
+ date1op = '<=';
+ date2op = '>=';
+ }
+ filt[date1op] = date1.toISOString();
+ if (col.name === 'dob') { filt['>='] = dateStr; } // special case
+ filt2[col.name] = filt;
+ filters.push(filt2);
+
+ date2.setHours(23);
+ date2.setMinutes(59);
+ date2.setSeconds(59);
+ const filt_a: Object = {};
+ const filt2_a: Object = {};
+ filt_a[date2op] = date2.toISOString();
+ if (col.name === 'dob') { filt_a['<='] = endDateStr; } // special case
+ filt2_a[col.name] = filt_a;
+ filters.push(filt2_a);
+ }
+ this.context.dataSource.filters[col.name] = filters;
+ col.isFiltered = true;
+ this.context.reload();
+ }
+ }
+ clearDateFilter(col: GridColumn) {
+ delete this.context.dataSource.filters[col.name];
+ col.isFiltered = false;
+ this.context.reload();
+ }
+ applyBooleanFilter(col: GridColumn) {
+ if (!col.filterValue || col.filterValue === '') {
+ delete this.context.dataSource.filters[col.name];
+ col.isFiltered = false;
+ this.context.reload();
+ } else {
+ const val: string = col.filterValue;
+ const op = '=';
+ const filt: Object = {};
+ filt[op] = val;
+ const filt2: Object = {};
+ filt2[col.name] = filt;
+ this.context.dataSource.filters[col.name] = [ filt2 ];
+ col.isFiltered = true;
+ this.context.reload();
+ }
+ }
+ applyFilter(col: GridColumn) {
+ // fallback if the operator somehow was not set yet
+ if (col.filterOperator === undefined) { col.filterOperator = '='; }
+
+ if ( (col.filterOperator !== 'null') && (col.filterOperator !== 'not null') &&
+ (!col.filterValue || col.filterValue === '') ) {
+ // if value is empty and we're _not_ checking for null/not null, clear
+ // the filter
+ delete this.context.dataSource.filters[col.name];
+ col.isFiltered = false;
+ } else {
+ let op: string = col.filterOperator;
+ let val: string = col.filterValue;
+ const name: string = col.name;
+ if (col.filterOperator === 'null') {
+ op = '=';
+ val = null;
+ } else if (col.filterOperator === 'not null') {
+ op = '!=';
+ val = null;
+ } else if (col.filterOperator === 'like' || col.filterOperator === 'not like') {
+ val = '%' + val + '%';
+ } else if (col.filterOperator === 'startswith') {
+ op = 'like';
+ val = val + '%';
+ } else if (col.filterOperator === 'endswith') {
+ op = 'like';
+ val = '%' + val;
+ }
+ const filt: any = {};
+ if (col.filterOperator === 'not like') {
+ filt['-not'] = {};
+ filt['-not'][col.name] = {};
+ filt['-not'][col.name]['like'] = val;
+ this.context.dataSource.filters[col.name] = [ filt ];
+ col.isFiltered = true;
+ } else {
+ filt[col.name] = {};
+ filt[col.name][op] = val;
+ this.context.dataSource.filters[col.name] = [ filt ];
+ col.isFiltered = true;
+ }
+ }
+ this.context.reload();
+ }
+ clearFilter(col: GridColumn) {
+ // clear filter values...
+ col.removeFilter();
+ // ... and inform the data source
+ delete this.context.dataSource.filters[col.name];
+ col.isFiltered = false;
+ this.reset();
+ this.context.reload();
+ }
+
+ closeDropdown() {
+ 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(); });
+ }
+}
+
<span *ngIf="!col.isSortable">{{col.label}}</span>
</div>
</div>
+<div *ngIf="context.isFilterable"
+ class="eg-grid-row eg-grid-filter-controls-row">
+ <ng-container *ngIf="!context.disableSelect">
+ <div class="eg-grid-cell eg-grid-header-cell eg-grid-cell-skinny"></div>
+ </ng-container>
+ <div class="eg-grid-cell eg-grid-header-cell eg-grid-cell-skinny"></div>
+ <div *ngIf="context.rowFlairIsEnabled"
+ class="eg-grid-cell eg-grid-header-cell"></div>
+ <div *ngFor="let col of context.columnSet.displayColumns()"
+ class="eg-grid-cell eg-grid-filter-control-cell" [ngStyle]="{flex:col.flex}">
+ <eg-grid-filter-control [context]="context" [col]="col"></eg-grid-filter-control>
+ </div>
+</div>
-import {Component, Input, OnInit} from '@angular/core';
+import {Component, Input, OnInit, AfterViewInit, QueryList, ViewChildren} from '@angular/core';
import {GridContext, GridColumn, GridRowSelector,
GridColumnSet, GridDataSource} from './grid';
+import {GridFilterControlComponent} from './grid-filter-control.component';
@Component({
selector: 'eg-grid-header',
templateUrl: './grid-header.component.html'
})
-export class GridHeaderComponent implements OnInit {
+export class GridHeaderComponent implements OnInit, AfterViewInit {
@Input() context: GridContext;
batchRowCheckbox: boolean;
+ @ViewChildren(GridFilterControlComponent) filterControls: QueryList<GridFilterControlComponent>;
+
constructor() {}
ngOnInit() {
);
}
+ ngAfterViewInit() {
+ this.context.filterControls = this.filterControls;
+ }
+
onColumnDragEnter($event: any, col: any) {
if (this.dragColumn && this.dragColumn.name !== col.name) {
col.isDragTarget = true;
<div class="btn-toolbar">
<!-- buttons -->
- <div class="btn-grp" *ngIf="gridContext.toolbarButtons.length">
+ <div class="btn-grp" *ngIf="gridContext.toolbarButtons.length || gridContext.isFilterable">
+ <!-- special case for remove filters button -->
+ <button *ngIf="gridContext.isFilterable"
+ class="btn btn-outline-dark mr-1" (click)="gridContext.removeFilters()"
+ [disabled]="!gridContext.filtersSet()" i18n>
+ Remove Filters
+ </button>
<button *ngFor="let btn of gridContext.toolbarButtons"
[disabled]="btn.disabled"
class="btn btn-outline-dark mr-1" (click)="performButtonAction(btn)">
box-shadow: none;
}
+.eg-grid-filter-control-cell {
+ overflow: visible !important;
+}
+.eg-grid-col-is-filtered {
+ background: lightblue;
+}
+.eg-grid-filter-menu {
+ min-width: 17rem;
+}
+
+.eg-grid-sticky-header {
+ position: sticky;
+ top: 50px;
+ background: white;
+ z-index: 1;
+}
+
+.eg-grid-filter-operator {
+ font-style: italic;
+}
+
+/* override the dropdown menu effects for the filter menus */
+.eg-grid-filter-menu .dropdown-item:active {
+ color: #212529;
+ background-color: transparent;
+}
+.eg-grid-filter-menu .dropdown-item:hover {
+ background-color: transparent;
+}
[disableSaveSettings]="!persistKey || ('disabled' === persistKey)">
</eg-grid-toolbar>
- <eg-grid-header [context]="context"></eg-grid-header>
+ <div #egGridStickyHeader [ngClass]="{'eg-grid-sticky-header' : context.stickyGridHeader}">
+ <eg-grid-header [context]="context"></eg-grid-header>
+ </div>
<eg-grid-column-width #colWidthConfig [gridContext]="context">
</eg-grid-column-width>
import {Component, Input, Output, OnInit, AfterViewInit, EventEmitter,
- OnDestroy, HostListener, ViewEncapsulation} from '@angular/core';
+ OnDestroy, HostListener, ViewEncapsulation, QueryList, ViewChildren} from '@angular/core';
import {Subscription} from 'rxjs';
import {IdlService} from '@eg/core/idl.service';
import {OrgService} from '@eg/core/org.service';
import {ServerStoreService} from '@eg/core/server-store.service';
import {FormatService} from '@eg/core/format.service';
import {GridContext, GridColumn, GridDataSource, GridRowFlairEntry} from './grid';
+import {GridFilterControlComponent} from './grid-filter-control.component';
/**
* Main grid entry point.
@Input() disablePaging: boolean;
+ // result filtering
+ //
+ // filterable: true if the result filtering controls
+ // should be displayed
+ @Input() filterable: boolean;
+
+ // sticky grid header
+ //
+ // stickyHeader: true of the grid header should be
+ // "sticky", i.e., remain visible if if the table is long
+ // and the user has scrolled far enough that the header
+ // would go out of view
+ @Input() stickyHeader: boolean;
+
context: GridContext;
// These events are emitted from our grid-body component.
this.context.dataSource = this.dataSource;
this.context.persistKey = this.persistKey;
this.context.isSortable = this.sortable === true;
+ this.context.isFilterable = this.filterable === true;
+ this.context.stickyGridHeader = this.stickyHeader === true;
this.context.isMultiSortable = this.multiSortable === true;
this.context.useLocalSort = this.useLocalSort === true;
this.context.disableSelect = this.disableSelect === true;
reload() {
this.context.reload();
}
+ reloadSansPagerReset() {
+ this.context.reloadSansPagerReset();
+ }
+
+
}
import {GridColumnConfigComponent} from './grid-column-config.component';
import {GridColumnWidthComponent} from './grid-column-width.component';
import {GridPrintComponent} from './grid-print.component';
+import {GridFilterControlComponent} from './grid-filter-control.component';
@NgModule({
GridToolbarActionsMenuComponent,
GridColumnConfigComponent,
GridColumnWidthComponent,
- GridPrintComponent
+ GridPrintComponent,
+ GridFilterControlComponent
],
imports: [
EgCommonModule,
/**
* Collection of grid related classses and interfaces.
*/
-import {TemplateRef, EventEmitter} from '@angular/core';
+import {TemplateRef, EventEmitter, QueryList} from '@angular/core';
import {Observable, Subscription} from 'rxjs';
import {IdlService, IdlObject} from '@eg/core/idl.service';
import {OrgService} from '@eg/core/org.service';
import {ServerStoreService} from '@eg/core/server-store.service';
import {FormatService} from '@eg/core/format.service';
import {Pager} from '@eg/share/util/pager';
+import {GridFilterControlComponent} from './grid-filter-control.component';
const MAX_ALL_ROW_COUNT = 10000;
isIndex: boolean;
isDragTarget: boolean;
isSortable: boolean;
+ isFilterable: boolean;
+ isFiltered: boolean;
isMultiSortable: boolean;
disableTooltip: boolean;
comparator: (valueA: any, valueB: any) => number;
// True if the column was automatically generated.
isAuto: boolean;
+ // for filters
+ filterValue: string;
+ filterOperator: string;
+ filterInputDisabled: boolean;
+ filterIncludeOrgAncestors: boolean;
+ filterIncludeOrgDescendants: boolean;
+
flesher: (obj: any, col: GridColumn, item: any) => any;
getCellContext(row: any) {
userContext: this.cellContext
};
}
+
+ constructor() {
+ this.removeFilter();
+ }
+
+ removeFilter() {
+ this.isFiltered = false;
+ this.filterValue = undefined;
+ this.filterOperator = '=';
+ this.filterInputDisabled = false;
+ this.filterIncludeOrgAncestors = false;
+ this.filterIncludeOrgDescendants = false;
+ }
}
export class GridColumnSet {
idlClass: string;
indexColumn: GridColumn;
isSortable: boolean;
+ isFilterable: boolean;
isMultiSortable: boolean;
stockVisible: string[];
idl: IdlService;
}
this.applyColumnSortability(col);
+ this.applyColumnFilterability(col);
}
// Returns true if the new column was inserted, false otherwise.
col.isSortable = true;
}
}
+ applyColumnFilterability(col: GridColumn) {
+ // column filterability defaults to the afilterability of the column set.
+ if (col.isFilterable === undefined && this.isFilterable) {
+ col.isFilterable = true;
+ }
+ }
displayColumns(): GridColumn[] {
return this.columns.filter(c => c.visible);
pager: Pager;
idlClass: string;
isSortable: boolean;
+ isFilterable: boolean;
+ stickyGridHeader: boolean;
isMultiSortable: boolean;
useLocalSort: boolean;
persistKey: string;
// action has occurred.
selectRowsInPageEmitter: EventEmitter<void>;
+ filterControls: QueryList<GridFilterControlComponent>;
+
// Services injected by our grid component
idl: IdlService;
org: OrgService;
this.selectRowsInPageEmitter = new EventEmitter<void>();
this.columnSet = new GridColumnSet(this.idl, this.idlClass);
this.columnSet.isSortable = this.isSortable === true;
+ this.columnSet.isFilterable = this.isFilterable === true;
this.columnSet.isMultiSortable = this.isMultiSortable === true;
this.columnSet.defaultHiddenFields = this.defaultHiddenFields;
this.columnSet.defaultVisibleFields = this.defaultVisibleFields;
});
}
+ reloadSansPagerReset() {
+ setTimeout(() => {
+ this.dataSource.reset();
+ this.dataSource.requestPage(this.pager);
+ });
+ }
+
// Sort the existing data source instead of requesting sorted
// data from the client. Reset pager to page 1. As with reload(),
// give the client a chance to setting before redisplaying.
});
}
+ removeFilters(): void {
+ this.dataSource.filters = {};
+ this.columnSet.displayColumns().forEach(col => { col.removeFilter(); });
+ this.filterControls.forEach(ctl => ctl.reset());
+ this.reload();
+ }
+ filtersSet(): boolean {
+ return Object.keys(this.dataSource.filters).length > 0;
+ }
+
gridToCsv(): Promise<string> {
let csvStr = '';
data: any[];
sort: any[];
+ filters: Object;
allRowsRetrieved: boolean;
requestingData: boolean;
getRows: (pager: Pager, sort: any[]) => Observable<any>;
constructor() {
this.sort = [];
+ this.filters = {};
this.reset();
}