[open-ils-commits] [GIT] Evergreen ILS branch master updated. 2c0df3989352f96e3a7edc5c1a99d570dfb9b610
Evergreen Git
git at git.evergreen-ils.org
Thu Aug 1 09:53:57 EDT 2019
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".
The branch, master has been updated
via 2c0df3989352f96e3a7edc5c1a99d570dfb9b610 (commit)
via d0ab509dab731f1f03cd32d05c49256f9cb30ba2 (commit)
via bb8b2321b16cded0ed6abc2143503776854b64c9 (commit)
via 56163b124f7b2321b867324c68a7637d23ea8492 (commit)
via aef077e88ad73d7e9381f13d9cb378d29e399521 (commit)
via 59a69b150f13a82c72de5ebbdde918ef18e696dc (commit)
via 28bf803625dd359cfd327ad94aa18f4d625ad8e4 (commit)
via bd4c8c4669397bbd3b9cad6a686c3f004577d6bb (commit)
via fb3925bd56dc98d35d62822c918b5bc7add9b258 (commit)
via 58a5fbc2c6fc2ed878daf324c243ecbed168aa62 (commit)
from 18277d2154ada19acddfd8ef294f41084a8b87f2 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 2c0df3989352f96e3a7edc5c1a99d570dfb9b610
Author: Jane Sandberg <sandbej at linnbenton.edu>
Date: Wed Jul 31 14:19:08 2019 -0700
LP1831788: (follow-up) removing small linting errors, unused imports
Signed-off-by: Jane Sandberg <sandbej at linnbenton.edu>
Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.ts
index f9bacf4dad..bb597cd86e 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.ts
@@ -1,6 +1,5 @@
-import {Component, Input, OnInit, Host} from '@angular/core';
-import {GridContext, GridColumn, GridColumnSet,
- GridDataSource} from './grid';
+import {Component, Input, OnInit} from '@angular/core';
+import {GridContext, GridColumn, GridColumnSet} from './grid';
@Component({
selector: 'eg-grid-column-width',
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts
index 95af28d8bf..b616b82480 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts
@@ -1,5 +1,5 @@
import {Component, Input, OnInit, Host, TemplateRef} from '@angular/core';
-import {GridColumn, GridColumnSet} from './grid';
+import {GridColumn} from './grid';
import {GridComponent} from './grid.component';
@Component({
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts
index e6a36523dc..f5931b4059 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts
@@ -1,9 +1,7 @@
import {Component, Input, OnInit, QueryList, ViewChildren} from '@angular/core';
-import {GridContext, GridColumn, GridRowSelector,
- GridColumnSet, GridDataSource} from './grid';
+import {GridContext, GridColumn} from './grid';
import {IdlObject} from '@eg/core/idl.service';
-import {ComboboxComponent,
- ComboboxEntry} from '@eg/share/combobox/combobox.component';
+import {ComboboxComponent} 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';
@@ -218,7 +216,7 @@ export class GridFilterControlComponent implements OnInit {
if ( (col.filterOperator !== 'null') && (col.filterOperator !== 'not null') &&
(!col.filterValue || col.filterValue === '') &&
- (col.filterValue != '0') ) {
+ (col.filterValue !== '0') ) {
// if value is empty and we're _not_ checking for null/not null, clear
// the filter
delete this.context.dataSource.filters[col.name];
@@ -226,7 +224,6 @@ export class GridFilterControlComponent implements OnInit {
} else {
let op: string = col.filterOperator;
let val: string = col.filterValue;
- const name: string = col.name;
if (col.filterOperator === 'null') {
op = '=';
val = null;
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts
index fbc826c062..ae55c56f32 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts
@@ -1,8 +1,6 @@
-import {Component, Input, OnInit, Host} from '@angular/core';
+import {Component, Input, OnInit} from '@angular/core';
import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
-import {Pager} from '@eg/share/util/pager';
-import {GridColumn, GridColumnSet, GridToolbarButton,
- GridToolbarAction, GridContext, GridDataSource} from '@eg/share/grid/grid';
+import {GridToolbarButton, GridToolbarAction, GridContext} from '@eg/share/grid/grid';
import {GridColumnWidthComponent} from './grid-column-width.component';
import {GridPrintComponent} from './grid-print.component';
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
index b043a418be..69edbf3130 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
@@ -1,12 +1,10 @@
import {Component, Input, Output, OnInit, AfterViewInit, EventEmitter,
- OnDestroy, HostListener, ViewEncapsulation, QueryList, ViewChildren} from '@angular/core';
-import {Subscription} from 'rxjs';
+ OnDestroy, ViewEncapsulation} from '@angular/core';
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.
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.ts
index 18d4f7adbe..600f815564 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid.ts
@@ -417,7 +417,7 @@ export class GridRowSelector {
// In some contexts (template checkboxes) the value for an index is
// set to false to deselect instead of having it removed (via deselect()).
// NOTE GridRowSelector has no knowledge of when a row is no longer
- // present in the grid. Use GridContext.getSelectedRows() to get
+ // present in the grid. Use GridContext.getSelectedRows() to get
// list of selected rows that are still present in the grid.
selected() {
return Object.keys(this.indexes).filter(
commit d0ab509dab731f1f03cd32d05c49256f9cb30ba2
Author: Galen Charlton <gmc at equinoxinitiative.org>
Date: Wed Jul 31 16:15:21 2019 -0400
LP#1831788: (follow-up) properly handle case where filter value is 0
To test
-------
[1] In the sandbox's copy grid, display the status column and filter
by the 'Available' status (ccs.id = 0).
[2] Verify that the correct rows are returned.
Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
Signed-off-by: Jane Sandberg <sandbej at linnbenton.edu>
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts
index fbc59186c2..e6a36523dc 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts
@@ -217,7 +217,8 @@ export class GridFilterControlComponent implements OnInit {
if (col.filterOperator === undefined) { col.filterOperator = '='; }
if ( (col.filterOperator !== 'null') && (col.filterOperator !== 'not null') &&
- (!col.filterValue || col.filterValue === '') ) {
+ (!col.filterValue || col.filterValue === '') &&
+ (col.filterValue != '0') ) {
// if value is empty and we're _not_ checking for null/not null, clear
// the filter
delete this.context.dataSource.filters[col.name];
diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
index 163a1cb723..a816caffb8 100644
--- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
+++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
@@ -186,7 +186,7 @@ export class SandboxComponent implements OnInit {
return this.pcrud.search('acp',
query, {
flesh: 1,
- flesh_fields: {acp: ['location']},
+ flesh_fields: {acp: ['location','status']},
offset: pager.offset,
limit: pager.limit,
order_by: orderBy
commit bb8b2321b16cded0ed6abc2143503776854b64c9
Author: Bill Erickson <berickxx at gmail.com>
Date: Wed Jul 31 14:21:18 2019 -0400
LP1831788 Add EgCoreModule for CommonWidgetsModule, etc.
Collect core objects into their own module so they may be imported
without requiring task-specific modules to import EgCommonModule, which
provides a lot more than most sub-modules need.
In the case of CommonWidgetsModule, it required access to the
FormatPipe, which is a core object, originally exported from
EgCommonModule. However, EgCommonModule was overkill for
CommonWidgetsModule and importing it would likely have created other
dependency problems.
Signed-off-by: Bill Erickson <berickxx at gmail.com>
Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
Signed-off-by: Jane Sandberg <sandbej at linnbenton.edu>
diff --git a/Open-ILS/src/eg2/src/app/common.module.ts b/Open-ILS/src/eg2/src/app/common.module.ts
index a4e402680d..76394c3924 100644
--- a/Open-ILS/src/eg2/src/app/common.module.ts
+++ b/Open-ILS/src/eg2/src/app/common.module.ts
@@ -1,11 +1,12 @@
/**
* Modules, services, and components used by all apps.
*/
-import {CommonModule, DatePipe, CurrencyPipe} from '@angular/common';
+import {CommonModule} from '@angular/common';
import {NgModule, ModuleWithProviders} from '@angular/core';
import {RouterModule} from '@angular/router';
import {FormsModule} from '@angular/forms';
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
+import {EgCoreModule} from '@eg/core/core.module';
/*
Note core services are injected into 'root'.
@@ -13,7 +14,6 @@ They do not have to be added to the providers list.
*/
// consider moving these to core...
-import {FormatService, FormatValuePipe} from '@eg/core/format.service';
import {HatchService} from '@eg/share/print/hatch.service';
import {PrintService} from '@eg/share/print/print.service';
@@ -36,20 +36,21 @@ import {BoolDisplayComponent} from '@eg/share/util/bool.component';
PromptDialogComponent,
ProgressInlineComponent,
ProgressDialogComponent,
- BoolDisplayComponent,
- FormatValuePipe
+ BoolDisplayComponent
],
imports: [
CommonModule,
FormsModule,
RouterModule,
- NgbModule
+ NgbModule,
+ EgCoreModule
],
exports: [
CommonModule,
RouterModule,
NgbModule,
FormsModule,
+ EgCoreModule,
PrintComponent,
DialogComponent,
AlertDialogComponent,
@@ -58,7 +59,6 @@ import {BoolDisplayComponent} from '@eg/share/util/bool.component';
ProgressInlineComponent,
ProgressDialogComponent,
BoolDisplayComponent,
- FormatValuePipe
]
})
@@ -69,11 +69,8 @@ export class EgCommonModule {
return {
ngModule: EgCommonModule,
providers: [
- DatePipe,
- CurrencyPipe,
HatchService,
- PrintService,
- FormatService
+ PrintService
]
};
}
diff --git a/Open-ILS/src/eg2/src/app/core/core.module.ts b/Open-ILS/src/eg2/src/app/core/core.module.ts
new file mode 100644
index 0000000000..82052f591d
--- /dev/null
+++ b/Open-ILS/src/eg2/src/app/core/core.module.ts
@@ -0,0 +1,29 @@
+/**
+ * Core objects.
+ * Note that core services are generally defined with
+ * @Injectable({providedIn: 'root'}) so they are globally available
+ * and do not require entry in our 'providers' array.
+ */
+import {NgModule} from '@angular/core';
+import {CommonModule, DatePipe, CurrencyPipe} from '@angular/common';
+import {FormatService, FormatValuePipe} from './format.service';
+
+ at NgModule({
+ declarations: [
+ FormatValuePipe
+ ],
+ imports: [
+ CommonModule
+ ],
+ exports: [
+ CommonModule,
+ FormatValuePipe
+ ],
+ providers: [
+ DatePipe,
+ CurrencyPipe
+ ]
+})
+
+export class EgCoreModule {}
+
diff --git a/Open-ILS/src/eg2/src/app/share/common-widgets.module.ts b/Open-ILS/src/eg2/src/app/share/common-widgets.module.ts
index 0f15c98b67..71c5d3023c 100644
--- a/Open-ILS/src/eg2/src/app/share/common-widgets.module.ts
+++ b/Open-ILS/src/eg2/src/app/share/common-widgets.module.ts
@@ -7,6 +7,7 @@ import {NgModule, ModuleWithProviders} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
+import {EgCoreModule} from '@eg/core/core.module';
import {ComboboxComponent} from '@eg/share/combobox/combobox.component';
import {ComboboxEntryComponent} from '@eg/share/combobox/combobox-entry.component';
import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
@@ -22,12 +23,14 @@ import {OrgSelectComponent} from '@eg/share/org-select/org-select.component';
imports: [
CommonModule,
FormsModule,
- NgbModule
+ NgbModule,
+ EgCoreModule
],
exports: [
CommonModule,
FormsModule,
NgbModule,
+ EgCoreModule,
ComboboxComponent,
ComboboxEntryComponent,
DateSelectComponent,
commit 56163b124f7b2321b867324c68a7637d23ea8492
Author: Bill Erickson <berickxx at gmail.com>
Date: Wed Jul 31 14:56:43 2019 -0400
LP1831788 dialog dismissal and i18n repairs
Update sandbox dialog error handling to treat all error conditions as
errors. Dialogs no longer produce an error on dismissal, they just
complete the obvservable.
Add i18n-placeholder attributes to some grid filter placeholder text.
Signed-off-by: Bill Erickson <berickxx at gmail.com>
Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
Signed-off-by: Jane Sandberg <sandbej at linnbenton.edu>
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.html
index 6367fb6e83..742cdca481 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.html
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.html
@@ -14,7 +14,8 @@
</div>
</div>
</div>
- <eg-combobox [idlClass]="col.idlFieldDef.class" (onChange)="applyLinkFilter($event, col)" placeholder="Enter value to filter by"></eg-combobox>
+ <eg-combobox [idlClass]="col.idlFieldDef.class" (onChange)="applyLinkFilter($event, col)"
+ i18n-placeholder placeholder="Enter value to filter by"></eg-combobox>
</div>
</div>
<div *ngSwitchCase="'bool'">
@@ -67,7 +68,8 @@
</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">
+ <input type="text" class="form-control" [(ngModel)]="col.filterValue" (keyup.enter)="applyFilter(col)"
+ [disabled]="col.filterInputDisabled" i18n-placeholder placeholder="Enter value to filter by">
</div>
</div>
<div *ngSwitchCase="'int'">
@@ -250,7 +252,8 @@
</div>
</div>
</div>
- <eg-org-select [applyOrgId]="col.filterValue" (onChange)="applyOrgFilter($event, col)" placeholder="Enter library to filter by" #ousel></eg-org-select>
+ <eg-org-select [applyOrgId]="col.filterValue" (onChange)="applyOrgFilter($event, col)"
+ i18n-placeholder 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>
diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
index 1d47a070db..163a1cb723 100644
--- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
+++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
@@ -351,13 +351,11 @@ export class SandboxComponent implements OnInit {
resolve(ok);
},
rejection => {
- if (!rejection.dismissed) {
- this.updateFailedString.current()
- .then(str => this.toast.danger(str));
- reject(rejection);
- }
+ this.updateFailedString.current()
+ .then(str => this.toast.danger(str));
+ reject(rejection);
}
- )
+ );
});
}
}
commit aef077e88ad73d7e9381f13d9cb378d29e399521
Author: Galen Charlton <gmc at equinoxinitiative.org>
Date: Tue Jul 30 16:05:27 2019 -0400
LP#1831788: (follow-up) rename grid method
Specifically, reloadSansPagerReset() => reloadWithoutPagerReset()
Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
Signed-off-by: Bill Erickson <berickxx at gmail.com>
Signed-off-by: Jane Sandberg <sandbej at linnbenton.edu>
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
index b1b29895f3..b043a418be 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
@@ -195,8 +195,8 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
reload() {
this.context.reload();
}
- reloadSansPagerReset() {
- this.context.reloadSansPagerReset();
+ reloadWithoutPagerReset() {
+ this.context.reloadWithoutPagerReset();
}
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.ts
index 7740bddbc1..18d4f7adbe 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid.ts
@@ -564,7 +564,7 @@ export class GridContext {
});
}
- reloadSansPagerReset() {
+ reloadWithoutPagerReset() {
setTimeout(() => {
this.dataSource.reset();
this.dataSource.requestPage(this.pager);
diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
index 0c637b5a7e..1d47a070db 100644
--- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
+++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
@@ -347,7 +347,7 @@ export class SandboxComponent implements OnInit {
ok => {
this.successString.current()
.then(str => this.toast.success(str));
- this.acpGrid.reloadSansPagerReset();
+ this.acpGrid.reloadWithoutPagerReset();
resolve(ok);
},
rejection => {
commit 59a69b150f13a82c72de5ebbdde918ef18e696dc
Author: Galen Charlton <gmc at equinoxinitiative.org>
Date: Tue Jul 30 16:02:49 2019 -0400
LP#1831788: (follow-up) update sandbox example
- dialog adjusted for LP#1823041 changes
- typo fixed
Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
Signed-off-by: Bill Erickson <berickxx at gmail.com>
Signed-off-by: Jane Sandberg <sandbej at linnbenton.edu>
diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
index 92c0e84b79..37e46d9c86 100644
--- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
+++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
@@ -223,7 +223,7 @@
<eg-fm-record-editor #acpEditDialog idlClass="acp" hiddenFields="call_number,creator,create_date,editor,edit_time,loan_duration,fine_level,dummy_author,dummy_isbn,ref,floating,holdable,circ_as_type,active_date,mint_condition,cost,deleted,deposit,deposit_amount,circulate,status_changed_time,copy_number">
</eg-fm-record-editor>
<eg-string #successString text="Updated succeeded!" i18n-text></eg-string>
-<eg-string #updatedFailedString text="Updated failed!" i18n-text></eg-string>
+<eg-string #updateFailedString text="Updated failed!" i18n-text></eg-string>
<h4>PCRUD auto flesh and FormatService detection</h4>
<div *ngIf="aMetarecord">Fingerprint: {{aMetarecord}}</div>
diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
index 0fd1664d98..0c637b5a7e 100644
--- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
+++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
@@ -339,22 +339,26 @@ export class SandboxComponent implements OnInit {
this.numConfirmDialog.open();
}
- showEditDialog(idlThing: IdlObject) {
+ showEditDialog(idlThing: IdlObject): Promise<any> {
this.editDialog.mode = 'update';
this.editDialog.recId = idlThing['id']();
- return this.editDialog.open({size: 'lg'}).then(
- ok => {
- this.successString.current()
- .then(str => this.toast.success(str));
- this.acpGrid.reloadSansPagerReset();
- },
- rejection => {
- if (!rejection.dismissed) {
- this.updateFailedString.current()
- .then(str => this.toast.danger(str));
+ return new Promise((resolve, reject) => {
+ this.editDialog.open({size: 'lg'}).subscribe(
+ ok => {
+ this.successString.current()
+ .then(str => this.toast.success(str));
+ this.acpGrid.reloadSansPagerReset();
+ resolve(ok);
+ },
+ rejection => {
+ if (!rejection.dismissed) {
+ this.updateFailedString.current()
+ .then(str => this.toast.danger(str));
+ reject(rejection);
+ }
}
- }
- );
+ )
+ });
}
}
commit 28bf803625dd359cfd327ad94aa18f4d625ad8e4
Author: Galen Charlton <gmc at equinoxinitiative.org>
Date: Wed Jun 5 13:27:43 2019 -0400
LP#1831788: add user-level release notes
Sponsored-by: MassLNC
Sponsored-by: Georgia Public Library Service
Sponsored-by: Indiana State Library
Sponsored-by: CW MARS
Sponsored-by: King County Library System
Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
Signed-off-by: Bill Erickson <berickxx at gmail.com>
Signed-off-by: Jane Sandberg <sandbej at linnbenton.edu>
diff --git a/docs/RELEASE_NOTES_NEXT/Architecture/Angular_Grid_Improvements.adoc b/docs/RELEASE_NOTES_NEXT/Architecture/Angular_Grid_Improvements.adoc
new file mode 100644
index 0000000000..92927c9235
--- /dev/null
+++ b/docs/RELEASE_NOTES_NEXT/Architecture/Angular_Grid_Improvements.adoc
@@ -0,0 +1,10 @@
+Angular Grid Improvements
+^^^^^^^^^^^^^^^^^^^^^^^^^
+Grids in new Angular staff interfaces now have options to
+
+* allow users to filter results per-column
+* make the grid header in tall/long grids sticky (i.e., the
+ grid header continues to be displayed while the user
+ scrolls through the grid
+* allow users to edit a record in a grid and save the results
+ without losing one's place in grid paging.
commit bd4c8c4669397bbd3b9cad6a686c3f004577d6bb
Author: Galen Charlton <gmc at equinoxinitiative.org>
Date: Wed Jun 5 11:18:14 2019 -0400
LP#1813788: add example of grid improvements to sandbox
This patch adds a new grid to the Angular sandbox page
that demonstrates the following features introduced
in the previous patch:
- grid filtering
- stick grid headers
- grid reloads without losing one's current paging
location
Sponsored-by: MassLNC
Sponsored-by: Georgia Public Library Service
Sponsored-by: Indiana State Library
Sponsored-by: CW MARS
Sponsored-by: King County Library System
Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
Signed-off-by: Bill Erickson <berickxx at gmail.com>
Signed-off-by: Jane Sandberg <sandbej at linnbenton.edu>
diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
index f6df154702..92c0e84b79 100644
--- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
+++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
@@ -182,6 +182,7 @@
[rowFlairIsEnabled]="true"
[rowFlairCallback]="btGridRowFlairCallback"
[cellClassCallback]="btGridCellClassCallback"
+ [stickyHeader]="true"
[sortable]="true">
<eg-grid-toolbar-action label="Action that needs a single row" i18n-label
(onClick)="complimentEvergreen($event)" [disableOnRows]="notOneSelectedRow">
@@ -201,6 +202,29 @@
<br/><br/>
+<h4>Grid with filtering</h4>
+<eg-grid #acpGrid idlClass="acp"
+ [dataSource]="acpSource"
+ [filterable]="true"
+ [sortable]="true"
+ [showLinkSelectors]="true"
+ [stickyHeader]="true"
+ showFields="barcode,location,circ_lib,price,dummy_title,create_date"
+>
+ <eg-grid-toolbar-action label="Edit Selected" i18n-label [action]="editSelected">
+ </eg-grid-toolbar-action>
+ <eg-grid-column [sortable]="true" [filterable]="false" path="barcode"></eg-grid-column>
+ <eg-grid-column [sortable]="true" path="circ_lib"></eg-grid-column>
+ <eg-grid-column [sortable]="true" path="price"></eg-grid-column>
+ <eg-grid-column [sortable]="true" path="dummy_title"></eg-grid-column>
+ <eg-grid-column [sortable]="true" path="create_date"></eg-grid-column>
+</eg-grid>
+
+<eg-fm-record-editor #acpEditDialog idlClass="acp" hiddenFields="call_number,creator,create_date,editor,edit_time,loan_duration,fine_level,dummy_author,dummy_isbn,ref,floating,holdable,circ_as_type,active_date,mint_condition,cost,deleted,deposit,deposit_amount,circulate,status_changed_time,copy_number">
+</eg-fm-record-editor>
+<eg-string #successString text="Updated succeeded!" i18n-text></eg-string>
+<eg-string #updatedFailedString text="Updated failed!" i18n-text></eg-string>
+
<h4>PCRUD auto flesh and FormatService detection</h4>
<div *ngIf="aMetarecord">Fingerprint: {{aMetarecord}}</div>
diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
index d3410b6d05..0fd1664d98 100644
--- a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
+++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
@@ -13,10 +13,12 @@ import {Pager} from '@eg/share/util/pager';
import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
import {PrintService} from '@eg/share/print/print.service';
import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
-import {FormatService} from '@eg/core/format.service';
import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
import {FormGroup, FormControl} from '@angular/forms';
import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+import {FormatService} from '@eg/core/format.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {GridComponent} from '@eg/share/grid/grid.component';
@Component({
templateUrl: 'sandbox.component.html'
@@ -53,6 +55,12 @@ export class SandboxComponent implements OnInit {
cbAsyncSource: (term: string) => Observable<ComboboxEntry>;
btSource: GridDataSource = new GridDataSource();
+ acpSource: GridDataSource = new GridDataSource();
+ editSelected: (rows: IdlObject[]) => void;
+ @ViewChild('acpGrid') acpGrid: GridComponent;
+ @ViewChild('acpEditDialog') editDialog: FmRecordEditorComponent;
+ @ViewChild('successString') successString: StringComponent;
+ @ViewChild('updateFailedString') updateFailedString: StringComponent;
world = 'world'; // for local template version
btGridTestContext: any = {hello : this.world};
@@ -157,6 +165,50 @@ export class SandboxComponent implements OnInit {
}));
};
+ this.acpSource.getRows = (pager: Pager, sort: any[]) => {
+ const orderBy: any = {acp: 'id'};
+ if (sort.length) {
+ orderBy.acp = sort[0].name + ' ' + sort[0].dir;
+ }
+
+ // base query to grab everything
+ const base: Object = {};
+ base[this.idl.classes['acp'].pkey] = {'!=' : null};
+ const query: any = new Array();
+ query.push(base);
+
+ // and add any filters
+ Object.keys(this.acpSource.filters).forEach(key => {
+ Object.keys(this.acpSource.filters[key]).forEach(key2 => {
+ query.push(this.acpSource.filters[key][key2]);
+ });
+ });
+ return this.pcrud.search('acp',
+ query, {
+ flesh: 1,
+ flesh_fields: {acp: ['location']},
+ offset: pager.offset,
+ limit: pager.limit,
+ order_by: orderBy
+ });
+ };
+
+ this.editSelected = (idlThings: IdlObject[]) => {
+
+ // Edit each IDL thing one at a time
+ const editOneThing = (thing: IdlObject) => {
+ if (!thing) { return; }
+
+ this.showEditDialog(thing).then(
+ () => editOneThing(idlThings.shift()));
+ };
+
+ editOneThing(idlThings.shift());
+ };
+ this.acpGrid.onRowActivate.subscribe(
+ (acpRec: IdlObject) => { this.showEditDialog(acpRec); }
+ );
+
this.complimentEvergreen = (rows: IdlObject[]) => alert('Evergreen is great!');
this.notOneSelectedRow = (rows: IdlObject[]) => (rows.length !== 1);
@@ -287,6 +339,23 @@ export class SandboxComponent implements OnInit {
this.numConfirmDialog.open();
}
+ showEditDialog(idlThing: IdlObject) {
+ this.editDialog.mode = 'update';
+ this.editDialog.recId = idlThing['id']();
+ return this.editDialog.open({size: 'lg'}).then(
+ ok => {
+ this.successString.current()
+ .then(str => this.toast.success(str));
+ this.acpGrid.reloadSansPagerReset();
+ },
+ rejection => {
+ if (!rejection.dismissed) {
+ this.updateFailedString.current()
+ .then(str => this.toast.danger(str));
+ }
+ }
+ );
+ }
}
commit fb3925bd56dc98d35d62822c918b5bc7add9b258
Author: Galen Charlton <gmc at equinoxinitiative.org>
Date: Wed Jun 5 11:19:36 2019 -0400
LP#1831788: add result filtering and other improvements to the Angular eg-grid
This patch enables users to filter results in Angular eg-grids that
use PCRUD-based data sources.
Filtering can be enabled in an eg-grid defintion by adding the following
attribute to <eg-grid>:
[filterable]="true"
If, for some reason, a particular column should not be filterable by the
user, filtering can be disabled by passing false to the [filterable]
attribute of an <eg-grid-column> element like this:
<eg-grid-column [sortable]="true" [filterable]="false" path="barcode"></eg-grid-column>
When filtering is enabled, a new section of the grid header is displayed that
includes, for each filterable column:
* A drop-down menu letting the user specify an operator such as
"is exactly", "exists" (i.e., is not null), "is greater than", and so
forth. The drop-down also allows the user to clear a filter for a
specific column or re-apply it after changing the operator.
* An input widget for setting the value to filter on. The type of input
displayed depend on the IDL type of the column. For example, a text field
will use a normal text <input>; an OU field will use an eg-org-select,
a link to another IDL class will use a combobox, a timestamp field
will use an eg-date-select, and so forth.
* A separate display of the current operator.
When filtering is enabled, the grid will also display a "Remove Filters" button
in the action bar.
Under the hood, the widgets for entering filtering parameters expect
the data source to have a "filters" key that in turn contains a
dictionary of PCRUD-style filtering conditions indexed by column name.
Consequently, a grid data source that wants to use filtering should
look something like this:
this.acpSource.getRows = (pager: Pager, sort: any[]) => {
const orderBy: any = {acp: 'id'};
if (sort.length) {
orderBy.acp = sort[0].name + ' ' + sort[0].dir;
}
// base query to grab everything
let base: Object = {};
base[this.idl.classes['acp'].pkey] = {'!=' : null};
var query: any = new Array();
query.push(base);
// and add any filters
Object.keys(this.acpSource.filters).forEach(key => {
Object.keys(this.acpSource.filters[key]).forEach(key2 => {
query.push(this.acpSource.filters[key][key2]);
});
});
return this.pcrud.search('acp',
query, {
flesh: 1,
flesh_fields: {acp: ['location']},
offset: pager.offset,
limit: pager.limit,
order_by: orderBy
});
};
This patch also adds two related grid options, sticky headers and the ability
to reload the data source without losing one's current place in page.
Sticky headers are enabled by adding the following attribute to the
<eg-grid> element:
[stickyHeader]="true"
When this is enabled, as the user scrolls the grid from top to bottom, the
header row, including the filter controls, will continue to remain visible
at the top of the viewport until the user scrolls past the end of the
grid entirely.
Reloading grids without losing the current paging settings can now be
done by a caller (such as code that opens an edit modal) invoking a new
reloadSansPagerReset() method.
Implementation Notes
--------------------
[1] This patch adds special-case logic for handling the "dob" column,
which is the sole date column in the Evergreen schema. Longer-term,
it would be better to define a new "date" IDL field type that's
distinct from "timestamp".
[2] stickyHeader currently makes only the grid header sticky, not both
the header and the action bar. This outcome is a result of z-index
messiness with the ng-bootstrap dropdown menu which I couldn't get
past. However, the forthcoming grid context menus hopefully will
be a reasonable amelioration.
[3] During testing it became evident that it would be handy to add
support for open-ils.fielder as a grid data source at some
point in the near future.
To test
-------
General testing can be done using the new second grid in the
Angular sandbox page added by the following test. Things to check
include:
- grid filter operators are displayed
- hitting enter in text inputs activates the filter
- the grid-level Remove Filters button works
- per-column filter clearing works
- operators have the expected results
- The header of both grids on the sandbox page is sticky. This can
be tested by increasing the row count in the second grid and
scrolling.
Sponsored-by: MassLNC
Sponsored-by: Georgia Public Library Service
Sponsored-by: Indiana State Library
Sponsored-by: CW MARS
Sponsored-by: King County Library System
Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
Signed-off-by: Bill Erickson <berickxx at gmail.com>
Signed-off-by: Jane Sandberg <sandbej at linnbenton.edu>
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts
index fc18fc7258..95af28d8bf 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts
@@ -27,6 +27,9 @@ export class GridColumnComponent implements OnInit {
// 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;
@@ -57,6 +60,7 @@ export class GridColumnComponent implements OnInit {
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;
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.html
new file mode 100644
index 0000000000..6367fb6e83
--- /dev/null
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.html
@@ -0,0 +1,281 @@
+<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>
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts
new file mode 100644
index 0000000000..fbc59186c2
--- /dev/null
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts
@@ -0,0 +1,280 @@
+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';
+
+ at 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(); });
+ }
+}
+
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.html
index 96811a32aa..571d0740ea 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.html
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.html
@@ -36,4 +36,17 @@
<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>
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.ts
index 591fc66c2b..cc53b26130 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.ts
@@ -1,13 +1,14 @@
-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;
@@ -15,6 +16,8 @@ export class GridHeaderComponent implements OnInit {
batchRowCheckbox: boolean;
+ @ViewChildren(GridFilterControlComponent) filterControls: QueryList<GridFilterControlComponent>;
+
constructor() {}
ngOnInit() {
@@ -23,6 +26,10 @@ export class GridHeaderComponent implements OnInit {
);
}
+ ngAfterViewInit() {
+ this.context.filterControls = this.filterControls;
+ }
+
onColumnDragEnter($event: any, col: any) {
if (this.dragColumn && this.dragColumn.name !== col.name) {
col.isDragTarget = true;
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html
index d75ef88b60..35781a5014 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html
@@ -4,7 +4,13 @@
<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)">
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.css b/Open-ILS/src/eg2/src/app/share/grid/grid.component.css
index 9748c0c4b1..de4894d3fe 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid.component.css
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.css
@@ -140,3 +140,32 @@
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;
+}
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid.component.html
index 20015cab19..e29eb67e63 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid.component.html
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.html
@@ -8,7 +8,9 @@
[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>
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
index e4938cc739..b1b29895f3 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts
@@ -1,11 +1,12 @@
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.
@@ -105,6 +106,20 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
@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.
@@ -134,6 +149,8 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
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;
@@ -178,6 +195,11 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
reload() {
this.context.reload();
}
+ reloadSansPagerReset() {
+ this.context.reloadSansPagerReset();
+ }
+
+
}
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.module.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.module.ts
index a6eb093dc0..b738ac6302 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid.module.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid.module.ts
@@ -14,6 +14,7 @@ import {GridToolbarActionsMenuComponent} from './grid-toolbar-actions-menu.compo
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({
@@ -31,7 +32,8 @@ import {GridPrintComponent} from './grid-print.component';
GridToolbarActionsMenuComponent,
GridColumnConfigComponent,
GridColumnWidthComponent,
- GridPrintComponent
+ GridPrintComponent,
+ GridFilterControlComponent
],
imports: [
EgCommonModule,
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.ts
index e7c7f711b6..7740bddbc1 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid.ts
@@ -1,13 +1,14 @@
/**
* 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;
@@ -32,6 +33,8 @@ export class GridColumn {
isIndex: boolean;
isDragTarget: boolean;
isSortable: boolean;
+ isFilterable: boolean;
+ isFiltered: boolean;
isMultiSortable: boolean;
disableTooltip: boolean;
comparator: (valueA: any, valueB: any) => number;
@@ -39,6 +42,13 @@ export class GridColumn {
// 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) {
@@ -48,6 +58,19 @@ export class GridColumn {
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 {
@@ -55,6 +78,7 @@ export class GridColumnSet {
idlClass: string;
indexColumn: GridColumn;
isSortable: boolean;
+ isFilterable: boolean;
isMultiSortable: boolean;
stockVisible: string[];
idl: IdlService;
@@ -85,6 +109,7 @@ export class GridColumnSet {
}
this.applyColumnSortability(col);
+ this.applyColumnFilterability(col);
}
// Returns true if the new column was inserted, false otherwise.
@@ -224,6 +249,12 @@ export class GridColumnSet {
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);
@@ -425,6 +456,8 @@ export class GridContext {
pager: Pager;
idlClass: string;
isSortable: boolean;
+ isFilterable: boolean;
+ stickyGridHeader: boolean;
isMultiSortable: boolean;
useLocalSort: boolean;
persistKey: string;
@@ -453,6 +486,8 @@ export class GridContext {
// action has occurred.
selectRowsInPageEmitter: EventEmitter<void>;
+ filterControls: QueryList<GridFilterControlComponent>;
+
// Services injected by our grid component
idl: IdlService;
org: OrgService;
@@ -480,6 +515,7 @@ export class GridContext {
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;
@@ -528,6 +564,13 @@ export class GridContext {
});
}
+ 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.
@@ -942,6 +985,16 @@ export class GridContext {
});
}
+ 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 = '';
@@ -1069,12 +1122,14 @@ export class GridDataSource {
data: any[];
sort: any[];
+ filters: Object;
allRowsRetrieved: boolean;
requestingData: boolean;
getRows: (pager: Pager, sort: any[]) => Observable<any>;
constructor() {
this.sort = [];
+ this.filters = {};
this.reset();
}
commit 58a5fbc2c6fc2ed878daf324c243ecbed168aa62
Author: Galen Charlton <gmc at equinoxinitiative.org>
Date: Mon Mar 25 15:27:05 2019 -0400
LP#1831788: create CommonWidgetsModule
This patch moves some commonly-shared components off to a separate
module. The immediate motivation is to avoid circular dependencies
when adding filtering widgets to eg-grid.
Components included in CommonWidgetsModule should be "core" in the
sense that they are unlikely to ever need to embed one another.
Sponsored-by: MassLNC
Sponsored-by: Georgia Public Library Service
Sponsored-by: Indiana State Library
Sponsored-by: CW MARS
Sponsored-by: King County Library System
Signed-off-by: Galen Charlton <gmc at equinoxinitiative.org>
Signed-off-by: Bill Erickson <berickxx at gmail.com>
Signed-off-by: Jane Sandberg <sandbej at linnbenton.edu>
diff --git a/Open-ILS/src/eg2/src/app/share/common-widgets.module.ts b/Open-ILS/src/eg2/src/app/share/common-widgets.module.ts
new file mode 100644
index 0000000000..0f15c98b67
--- /dev/null
+++ b/Open-ILS/src/eg2/src/app/share/common-widgets.module.ts
@@ -0,0 +1,38 @@
+/*
+ Module for grouping commonly used widgets that might be embedded
+ in other shared components. Components included here should be
+ unlikely to ever need to embed one another.
+*/
+import {NgModule, ModuleWithProviders} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {FormsModule} from '@angular/forms';
+import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
+import {ComboboxComponent} from '@eg/share/combobox/combobox.component';
+import {ComboboxEntryComponent} from '@eg/share/combobox/combobox-entry.component';
+import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
+import {OrgSelectComponent} from '@eg/share/org-select/org-select.component';
+
+ at NgModule({
+ declarations: [
+ ComboboxComponent,
+ ComboboxEntryComponent,
+ DateSelectComponent,
+ OrgSelectComponent
+ ],
+ imports: [
+ CommonModule,
+ FormsModule,
+ NgbModule
+ ],
+ exports: [
+ CommonModule,
+ FormsModule,
+ NgbModule,
+ ComboboxComponent,
+ ComboboxEntryComponent,
+ DateSelectComponent,
+ OrgSelectComponent
+ ],
+})
+
+export class CommonWidgetsModule { }
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.module.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.module.ts
index 454dcfb0ed..a6eb093dc0 100644
--- a/Open-ILS/src/eg2/src/app/share/grid/grid.module.ts
+++ b/Open-ILS/src/eg2/src/app/share/grid/grid.module.ts
@@ -1,5 +1,6 @@
import {NgModule} from '@angular/core';
import {EgCommonModule} from '@eg/common.module';
+import {CommonWidgetsModule} from '@eg/share/common-widgets.module';
import {GridComponent} from './grid.component';
import {GridColumnComponent} from './grid-column.component';
import {GridHeaderComponent} from './grid-header.component';
@@ -33,7 +34,8 @@ import {GridPrintComponent} from './grid-print.component';
GridPrintComponent
],
imports: [
- EgCommonModule
+ EgCommonModule,
+ CommonWidgetsModule
],
exports: [
// public components
diff --git a/Open-ILS/src/eg2/src/app/staff/common.module.ts b/Open-ILS/src/eg2/src/app/staff/common.module.ts
index bbf959c98c..66c62c32eb 100644
--- a/Open-ILS/src/eg2/src/app/staff/common.module.ts
+++ b/Open-ILS/src/eg2/src/app/staff/common.module.ts
@@ -1,11 +1,9 @@
import {NgModule, ModuleWithProviders} from '@angular/core';
import {EgCommonModule} from '@eg/common.module';
+import {CommonWidgetsModule} from '@eg/share/common-widgets.module';
import {AudioService} from '@eg/share/util/audio.service';
import {GridModule} from '@eg/share/grid/grid.module';
import {StaffBannerComponent} from './share/staff-banner.component';
-import {ComboboxComponent} from '@eg/share/combobox/combobox.component';
-import {ComboboxEntryComponent} from '@eg/share/combobox/combobox-entry.component';
-import {OrgSelectComponent} from '@eg/share/org-select/org-select.component';
import {OrgFamilySelectComponent} from '@eg/share/org-family-select/org-family-select.component';
import {AccessKeyDirective} from '@eg/share/accesskey/accesskey.directive';
import {AccessKeyService} from '@eg/share/accesskey/accesskey.service';
@@ -17,7 +15,6 @@ import {StringComponent} from '@eg/share/string/string.component';
import {StringService} from '@eg/share/string/string.service';
import {TitleComponent} from '@eg/share/title/title.component';
import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
-import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
import {BucketDialogComponent} from '@eg/staff/share/buckets/bucket-dialog.component';
import {BibSummaryComponent} from '@eg/staff/share/bib-summary/bib-summary.component';
import {TranslateComponent} from '@eg/staff/share/translate/translate.component';
@@ -32,9 +29,6 @@ import {ReactiveFormsModule} from '@angular/forms';
@NgModule({
declarations: [
StaffBannerComponent,
- ComboboxComponent,
- ComboboxEntryComponent,
- OrgSelectComponent,
OrgFamilySelectComponent,
AccessKeyDirective,
AccessKeyInfoComponent,
@@ -43,7 +37,6 @@ import {ReactiveFormsModule} from '@angular/forms';
TitleComponent,
OpChangeComponent,
FmRecordEditorComponent,
- DateSelectComponent,
BucketDialogComponent,
BibSummaryComponent,
TranslateComponent,
@@ -52,16 +45,15 @@ import {ReactiveFormsModule} from '@angular/forms';
],
imports: [
EgCommonModule,
- GridModule,
- ReactiveFormsModule
+ ReactiveFormsModule,
+ CommonWidgetsModule,
+ GridModule
],
exports: [
EgCommonModule,
+ CommonWidgetsModule,
GridModule,
StaffBannerComponent,
- ComboboxComponent,
- ComboboxEntryComponent,
- OrgSelectComponent,
OrgFamilySelectComponent,
AccessKeyDirective,
AccessKeyInfoComponent,
@@ -70,7 +62,6 @@ import {ReactiveFormsModule} from '@angular/forms';
TitleComponent,
OpChangeComponent,
FmRecordEditorComponent,
- DateSelectComponent,
BucketDialogComponent,
BibSummaryComponent,
TranslateComponent,
-----------------------------------------------------------------------
Summary of changes:
Open-ILS/src/eg2/src/app/common.module.ts | 17 +-
Open-ILS/src/eg2/src/app/core/core.module.ts | 29 +++
.../src/eg2/src/app/share/common-widgets.module.ts | 41 +++
.../app/share/grid/grid-column-width.component.ts | 5 +-
.../src/app/share/grid/grid-column.component.ts | 6 +-
.../share/grid/grid-filter-control.component.html | 284 +++++++++++++++++++++
.../share/grid/grid-filter-control.component.ts | 278 ++++++++++++++++++++
.../src/app/share/grid/grid-header.component.html | 13 +
.../src/app/share/grid/grid-header.component.ts | 11 +-
.../src/app/share/grid/grid-toolbar.component.html | 8 +-
.../src/app/share/grid/grid-toolbar.component.ts | 6 +-
.../src/eg2/src/app/share/grid/grid.component.css | 29 +++
.../src/eg2/src/app/share/grid/grid.component.html | 4 +-
.../src/eg2/src/app/share/grid/grid.component.ts | 24 +-
Open-ILS/src/eg2/src/app/share/grid/grid.module.ts | 8 +-
Open-ILS/src/eg2/src/app/share/grid/grid.ts | 59 ++++-
Open-ILS/src/eg2/src/app/staff/common.module.ts | 19 +-
.../src/app/staff/sandbox/sandbox.component.html | 24 ++
.../eg2/src/app/staff/sandbox/sandbox.component.ts | 73 +++++-
.../Architecture/Angular_Grid_Improvements.adoc | 10 +
20 files changed, 905 insertions(+), 43 deletions(-)
create mode 100644 Open-ILS/src/eg2/src/app/core/core.module.ts
create mode 100644 Open-ILS/src/eg2/src/app/share/common-widgets.module.ts
create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.html
create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-filter-control.component.ts
create mode 100644 docs/RELEASE_NOTES_NEXT/Architecture/Angular_Grid_Improvements.adoc
hooks/post-receive
--
Evergreen ILS
More information about the open-ils-commits
mailing list