-<span *ngIf="!column.cellTemplate"
- [ngbTooltip]="column.disableTooltip ? null : context.getRowColumnValue(row, column)"
+<div *ngIf="!column.cellTemplate"
+ [ngbTooltip]="column.enableTooltip ? context.getRowColumnValue(row, column) : null"
placement="top-left"
- class="{{context.cellClassCallback(row, column)}}"
triggers="mouseenter:mouseleave">
<ng-container *ngIf="column.datatype === 'bool'">
<eg-bool [value]="context.getRowColumnValue(row, column)"
<ng-container *ngIf="column.datatype !== 'bool'">
{{context.getRowColumnValue(row, column)}}
</ng-container>
-</span>
-<span *ngIf="column.cellTemplate"
- class="{{context.cellClassCallback(row, column)}}"
- [ngbTooltip]="column.disableTooltip ? null : column.cellTemplate"
+</div>
+<div *ngIf="column.cellTemplate"
+ [ngbTooltip]="column.enableTooltip ? column.cellTemplate : null"
placement="top-left"
#tooltip="ngbTooltip"
(mouseenter)="tooltip.open(column.getCellContext(row))"
<ng-container #templateContainer
*ngTemplateOutlet="column.cellTemplate; context: column.getCellContext(row)">
</ng-container>
-</span>
+</div>
-<!-- uses dropdown menu CSS for easy stying, but it's not a dropdown -->
-<ng-template #contextMenu let-gridContext="gridContext">
- <eg-grid-toolbar-actions-menu [gridContext]="gridContext" [viaContextMenu]="true">
- </eg-grid-toolbar-actions-menu>
-</ng-template>
+
<!--
tabindex=1 so the grid body can capture keyboard events.
-->
-<div class="eg-grid-body" tabindex="1" (keydown)="onGridKeyDown($event)">
- <div role="row" class="eg-grid-row eg-grid-body-row {{context.rowClassCallback(row)}}"
+
+ <tr scope="row" role="row" class="eg-grid-row eg-grid-body-row {{context.rowClassCallback(row)}}"
[ngClass]="{'selected': context.rowSelector.contains(context.getRowIndex(row))}"
*ngFor="let row of context.dataSource.getPageOfRows(context.pager); let idx = index">
<ng-container *ngIf="!context.disableSelect">
- <div class="eg-grid-cell eg-grid-checkbox-cell eg-grid-cell-skinny">
+ <td role="cell" class="eg-grid-cell eg-grid-checkbox-cell">
+ <div class="eg-grid-cell-contents">
<input type='checkbox'
[ngModel]="context.rowSelector.indexes[context.getRowIndex(row)]"
(ngModelChange)="context.rowSelector.toggle(context.getRowIndex(row))"
[ngbPopover]="contextMenu"
placement="right"
triggers="manual">
- </div>
+ </div>
+ </td>
</ng-container>
- <div class="eg-grid-cell eg-grid-number-cell eg-grid-cell-skinny-2">
- {{context.pager.rowNumber(idx)}}
- </div>
- <div *ngIf="context.rowFlairIsEnabled" class="eg-grid-cell eg-grid-flair-cell">
+ <td role="cell" class="eg-grid-cell eg-grid-number-cell numeric">
+ <div class="eg-grid-cell-contents">{{context.pager.rowNumber(idx)}}</div>
+ </td>
+ <td role="cell" *ngIf="context.rowFlairIsEnabled" class="eg-grid-cell eg-grid-flair-cell">
<!-- using *ngIf allows us to assign the flair callback to a value,
obviating the need for multiple calls of the same function -->
<ng-container *ngIf="context.rowFlairCallback(row); let flair">
<ng-container *ngIf="flair.icon">
+ <div class="eg-grid-cell-contents">
<!-- tooltip is disabled when no title is set -->
<span class="material-icons"
ngbTooltip="{{flair.title || ''}}" triggers="mouseenter:mouseleave">
{{flair.icon}}
</span>
+ </div>
</ng-container>
</ng-container>
- </div>
+ </td>
<!-- contextMenu applied to cells instead of rows so the position
of the popover is close to the mouse. As of writing, no way
- to position the popover at the mouse -->
- <div role="gridcell" class="eg-grid-cell eg-grid-body-cell"
- [ngStyle]="{flex:col.flex}"
- [ngClass]="{'eg-grid-cell-overflow': context.overflowCells}"
- (dblclick)="onRowDblClick(row)"
- (click)="onRowClick($event, row, idx)"
- #rowContextMenu="ngbPopover"
- popoverTitle="Actions for Selected Rows" i18n-popoverTitle
- (contextmenu)="onRowContextClick($event, row, rowContextMenu)"
- [ngbPopover]="contextMenu"
- placement="bottom"
- triggers="manual"
- *ngFor="let col of context.columnSet.displayColumns()">
+ to position the popover at the mouse. -->
+ <!-- aria-describedby is removed to prevent the entire context menu
+ from being read after the cell contents -->
+ <td role="cell" *ngFor="let col of context.columnSet.displayColumns()"
+ class="eg-grid-cell eg-grid-body-cell {{context.setClassNames(row, col)}}">
+ <div class="eg-grid-cell-contents" (dblclick)="onRowDblClick(row)"
+ (click)="onRowClick($event, row, idx)"
+ #rowContextMenu="ngbPopover"
+ popoverTitle="Actions for Selected Rows" i18n-popoverTitle
+ (contextmenu)="onRowContextClick($event, row, rowContextMenu)"
+ [ngbPopover]="contextMenu" [attr.aria-describedby]="null"
+ placement="bottom"
+ triggers="manual">
+ <eg-grid-body-cell [context]="context" [row]="row" [column]="col">
+ </eg-grid-body-cell>
+ </div>
+ </td>
- <eg-grid-body-cell [context]="context" [row]="row" [column]="col">
- </eg-grid-body-cell>
- </div>
- </div>
-</div>
+ <!-- This is set to visually-hidden so its presence in a <tr> doesn't disrupt table columns -->
+ <ng-template class="visually-hidden" #contextMenu let-gridContext="gridContext">
+ <eg-grid-toolbar-actions-menu [gridContext]="gridContext" [viaContextMenu]="true">
+ </eg-grid-toolbar-actions-menu>
+ </ng-template>
+ </tr>
import {NgbPopover} from '@ng-bootstrap/ng-bootstrap';
@Component({
- selector: 'eg-grid-body',
+ selector: 'tbody.eg-grid-body',
templateUrl: './grid-body.component.html'
})
@Input() cellContext: any;
@Input() cellTemplate: TemplateRef<any>;
- @Input() disableTooltip: boolean;
+ @Input() enableTooltip: boolean;
@Input() asyncSupportsEmptyTermClick: boolean;
// Required columns are those that must be present in any auto-generated
col.isIndex = this.index === true;
col.cellTemplate = this.cellTemplate;
col.cellContext = this.cellContext;
- col.disableTooltip = this.disableTooltip;
+ col.enableTooltip = this.enableTooltip;
col.isSortable = this.sortable;
col.isFilterable = this.filterable;
col.filterOperator = this.initialFilterOperator;
<!-- drop-down toggle link -->
<ng-template #dropdownToggle>
- <span i18n>Filter</span>
<ng-container *ngIf="!col.isFiltered">
- <span class="material-icons mat-icon-in-button">filter_list</span>
+ <span id="filter_toggle_{{col.name}}" i18n hidden>Filter by {{col.headerLabel}}</span>
+ <span class="material-icons mat-icon-in-button" attr.aria-hidden="true" title="Filter by {{col.headerLabel}}" i18n-title>filter_alt</span>
</ng-container>
<ng-container *ngIf="col.isFiltered">
- <span class="material-icons mat-icon-in-button">create</span>
+ <span id="filter_toggle_{{col.name}}" i18n hidden>Edit {{col.headerLabel}} filter</span>
+ <span class="material-icons mat-icon-in-button" attr.aria-hidden="true" title="Edit {{col.headerLabel}} filter" i18n-title>create</span>
</ng-container>
</ng-template>
</select>
</ng-template>
-<div *ngIf="col.isFilterable" class="eg-grid-filter-control">
- <div [ngSwitch]="col.datatype">
- <div *ngSwitchCase="'link'">
- <div class="input-group">
- <div ngbDropdown container="body" class="d-inline-block p-1" [autoClose]="false" placement="bottom-left"
+
+ <ng-container *ngIf="col.isFilterable" [ngSwitch]="col.datatype">
+ <div *ngSwitchCase="'link'" class="eg-grid-filter-control">
+
+ <div ngbDropdown container="body" [autoClose]="false" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
- <a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
+ <button ngbDropdownToggle class="btn no-dropdown-caret ps-1" attr.aria-labelledby="filter_toggle_{{col.name}}">
<ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
- </a>
+ </button>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
<div>
</div>
</div>
</div>
- </div>
+
</div>
- <div *ngSwitchCase="'bool'">
- <div class="input-group">
- <div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
+ <div *ngSwitchCase="'bool'" class="eg-grid-filter-control">
+
+ <div ngbDropdown container="body" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
- <a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
+ <button ngbDropdownToggle class="btn no-dropdown-caret ps-1" attr.aria-labelledby="filter_toggle_{{col.name}}">
<ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
- </a>
+ </button>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
<div>
</div>
</div>
</div>
- </div>
+
</div>
- <div *ngSwitchCase="'text'">
- <div class="input-group">
- <div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
+ <div *ngSwitchCase="'text'" class="eg-grid-filter-control">
+
+ <div ngbDropdown container="body" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
- <a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
+ <button ngbDropdownToggle class="btn no-dropdown-caret ps-1" attr.aria-labelledby="filter_toggle_{{col.name}}">
<ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
- </a>
+ </button>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
<div>
</div>
</div>
</div>
- </div>
+
</div>
- <div *ngSwitchCase="'int'">
- <div class="input-group">
- <div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
+ <div *ngSwitchCase="'int'" class="eg-grid-filter-control">
+
+ <div ngbDropdown container="body" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
- <a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
+ <button ngbDropdownToggle class="btn no-dropdown-caret ps-1" attr.aria-labelledby="filter_toggle_{{col.name}}">
<ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
- </a>
+ </button>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
<div>
</div>
</div>
</div>
- </div>
+
</div>
- <div *ngSwitchCase="'id'">
- <div class="input-group">
- <div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
+ <div *ngSwitchCase="'id'" class="eg-grid-filter-control">
+
+ <div ngbDropdown container="body" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
- <a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
+ <button ngbDropdownToggle class="btn no-dropdown-caret ps-1" attr.aria-labelledby="filter_toggle_{{col.name}}">
<ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
- </a>
+ </button>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
<div>
</div>
</div>
</div>
- </div>
+
</div>
- <div *ngSwitchCase="'float'">
- <div class="input-group">
- <div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
+ <div *ngSwitchCase="'float'" class="eg-grid-filter-control">
+
+ <div ngbDropdown container="body" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
- <a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
+ <button ngbDropdownToggle class="btn no-dropdown-caret ps-1" attr.aria-labelledby="filter_toggle_{{col.name}}">
<ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
- </a>
+ </button>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
<div>
</div>
</div>
</div>
- </div>
+
</div>
- <div *ngSwitchCase="'money'">
- <div class="input-group">
- <div ngbDropdown container="body" class="d-inline-block p-1" autoClose="outside" placement="bottom-left"
+ <div *ngSwitchCase="'money'" class="eg-grid-filter-control">
+
+ <div ngbDropdown container="body" autoClose="outside" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
- <a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
+ <button ngbDropdownToggle class="btn no-dropdown-caret ps-1" attr.aria-labelledby="filter_toggle_{{col.name}}">
<ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
- </a>
+ </button>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
<div>
</div>
</div>
</div>
- </div>
+
</div>
- <div *ngSwitchCase="'timestamp'">
- <div class="input-group">
+ <div *ngSwitchCase="'timestamp'" class="eg-grid-filter-control">
+
<!-- [autoClose]="false" because editing the date widgets, which open
their open popups, registers to the dropdown as clicking
outside the dropdown -->
- <div ngbDropdown container="body" class="d-inline-block p-1" [autoClose]="false" placement="bottom-left"
+ <div ngbDropdown container="body" [autoClose]="false" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
- <a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
+ <button ngbDropdownToggle class="btn no-dropdown-caret ps-1" attr.aria-labelledby="filter_toggle_{{col.name}}">
<ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
- </a>
+ </button>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
<div>
</div>
</div>
</div>
- </div>
+
</div>
- <div *ngSwitchCase="'org_unit'">
- <div class="input-group">
- <div ngbDropdown container="body" class="d-inline-block p-1" [autoClose]="false" placement="bottom-left"
+ <div *ngSwitchCase="'org_unit'" class="eg-grid-filter-control">
+
+ <div ngbDropdown container="body" [autoClose]="false" placement="bottom-left"
[ngClass]="{'border rounded border-secondary eg-grid-col-is-filtered' : col.isFiltered}">
- <a ngbDropdownToggle class="no-dropdown-caret text-dark" href="javascript:;">
+ <button ngbDropdownToggle class="btn no-dropdown-caret ps-1" attr.aria-labelledby="filter_toggle_{{col.name}}">
<ng-container *ngTemplateOutlet="dropdownToggle"></ng-container>
- </a>
+ </button>
<div ngbDropdownMenu class="eg-grid-filter-menu">
<div class="dropdown-item">
<div>
</div>
</div>
</div>
- </div>
+
</div>
- <div *ngSwitchCase="'interval'">
+ <div *ngSwitchCase="'interval'" class="eg-grid-filter-control">
<!-- this is a short-term fix to prevent *ngSwitchDefault from displaying -->
</div>
<div *ngSwitchDefault>I don't know how to filter {{col.name}} - {{col.datatype}}</div>
- </div>
+ </ng-container>
<!--
<span *ngIf="col.datatype !== 'org_unit'" class="eg-grid-filter-operator"><ng-container i18n>Operator:</ng-container>
<span [ngSwitch]="col.filterOperator">
</span>
</span>
-->
-</div>
+
+<!-- note that colgroups are set up in grid.component.html -->
-<div row="row" class="eg-grid-row eg-grid-header-row">
+<tr row="row" role="row" class="eg-grid-row eg-grid-header-row">
<ng-container *ngIf="!context.disableSelect">
- <div role="columnheader"
- class="eg-grid-cell eg-grid-header-cell eg-grid-checkbox-cell eg-grid-cell-skinny">
+ <th scope="col" role="columnheader"
+ class="eg-grid-cell eg-grid-header-cell eg-grid-checkbox-cell">
<input type='checkbox' (click)="handleBatchSelect($event)"
i18n-aria-label aria-label="All rows"
[(ngModel)]="batchRowCheckbox">
- </div>
+ </th>
</ng-container>
- <div role="columnheader"
- class="eg-grid-cell eg-grid-header-cell eg-grid-number-cell eg-grid-cell-skinny">
+ <th scope="col" role="columnheader" class="eg-grid-cell eg-grid-header-cell eg-grid-number-cell numeric">
<span i18n="number|Row Number Header">#</span>
- </div>
- <div *ngIf="context.rowFlairIsEnabled"
- role="columnheader"
+ </th>
+ <th *ngIf="context.rowFlairIsEnabled" scope="col"
class="eg-grid-cell eg-grid-header-cell eg-grid-flair-cell">
<span class="material-icons">notifications</span>
- </div>
- <div role="columnheader"
- *ngFor="let col of context.columnSet.displayColumns()"
+ </th>
+ <th *ngFor="let col of context.columnSet.displayColumns()"
+ scope="col" role="columnheader"
draggable="true"
(dragstart)="dragColumn = col"
(drop)="onColumnDrop(col)"
(dragover)="onColumnDragEnter($event, col)"
(dragleave)="onColumnDragLeave($event, col)"
[ngClass]="{'dragover' : col.isDragTarget}"
- class="eg-grid-cell eg-grid-header-cell" [ngStyle]="{flex:col.flex}">
- <a class="sortable label-with-material-icon" *ngIf="col.isSortable"
+ [attr.aria-sort]="ariaSortDirection(col)"
+ class="eg-grid-cell eg-grid-header-cell {{context.setClassNames(row, col)}}">
+ <button class="sortable btn d-inline p-0" *ngIf="col.isSortable"
(click)="sortOneColumn(col)">
- <span class="eg-grid-header-cell-sort-label">{{col.headerLabel}}</span>
+ <span class="eg-grid-header-cell-sort-label" title="Sort by {{col.headerLabel}}" i18n-title>{{col.headerLabel}}</span>
<span class="material-icons eg-grid-header-cell-sort-arrow"
*ngIf="isColumnSorting(col, 'ASC')">arrow_upwards</span>
<span class="material-icons eg-grid-header-cell-sort-arrow"
*ngIf="isColumnSorting(col, 'DESC')">arrow_downwards</span>
- </a>
+ </button>
<span *ngIf="!col.isSortable">{{col.headerLabel}}</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>
+ <ng-container *ngIf="context.isFilterable">
+ <eg-grid-filter-control [context]="context" [col]="col"></eg-grid-filter-control>
+ </ng-container>
+ </th>
+</tr>
\ No newline at end of file
import {GridFilterControlComponent} from './grid-filter-control.component';
@Component({
- selector: 'eg-grid-header',
+ selector: '.eg-grid-header',
templateUrl: './grid-header.component.html'
})
return sort && sort.dir === dir;
}
+ // Returns sorting direction in ARIA's required format
+ ariaSortDirection(col: GridColumn): string {
+ const sort = this.context.dataSource.sort.filter(c => c.name === col.name)[0];
+
+ if (sort && sort.dir === 'ASC')
+ return 'ascending';
+ if (sort && sort.dir === 'DESC')
+ return 'descending';
+
+ return null;
+ }
+
handleBatchSelect($event) {
if ($event.target.checked) {
if (this.context.rowSelector.isEmpty() || !this.allRowsAreSelected()) {
+@media (max-width: 960px) {
+ .eg-grid-wrapper {
+ overflow-x: auto;
+ }
+}
+
+.eg-grid-wrapper {
+ width: fit-content;
+ overflow-x: auto;
+}
+
+.eg-grid-wrapper:focus-visible {
+ outline: 2px solid #0A58CA;
+ outline-offset: 5px;
+ width: auto;
+}
+
+/* Undo Bootstrap table width */
+table.table.eg-grid {
+ width: revert;
+}
.eg-grid {
- width: 100%;
- color: rgba(0,0,0,.87);
+ caption-side: top;
+ color: rgba(0,0,0,.87);
+ table-layout: auto;
+ white-space: normal;
+}
+
+.eg-grid > :not(:first-child) {
+ border-top: revert;
+}
+
+.eg-grid caption {
+ color: #000;
}
.eg-grid-row {
- display: flex;
- border-bottom: 1px solid rgba(0,0,0,.12);
- padding-left: 10px;
- padding-right: 10px;
+ border-bottom: 1px solid rgba(0,0,0,.12);
+ padding-left: 10px;
+ padding-right: 10px;
}
.eg-grid-header-row {
}
.eg-grid-body {
- outline: none; /* for keyboard events */
-}
-
-.eg-grid-body-row {
+ outline: none; /* for keyboard events */
}
.eg-grid-body-row.selected,
border-color: #b8daff;
}
-.eg-grid-cell {
- flex: 2; /* applied per column */
- padding: 6px;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
-}
-
/* allow tooltips to be wider than the default 200px */
.eg-grid-cell .tooltip-inner {
max-width: 400px;
* inconsistent grid column widths
*/
.eg-grid-cell-overflow {
- white-space: normal;
+ white-space: normal;
}
-.eg-grid-body-cell {
+.eg-grid-header-cell {
+ font-weight: 600;
+ white-space: normal;
}
-.eg-grid-body-cell a[href] {
- text-decoration: underline !important;
+.eg-grid-header-cell .btn.sortable {
+ color: #255a88;
+ font-weight: 600;
+ text-align: inherit;
}
-.eg-grid-header-cell {
- font-weight: bold;
- white-space: normal;
+.eg-grid-header-cell[draggable=true] {
+ cursor: grab;
}
.eg-grid-header-cell.dragover {
- background-color: #cce5ff;
- border-color: #b8daff;
-}
-
-.eg-grid-header-cell-sort-label {
- cursor: pointer;
- text-decoration: underline;
+ background-color: #cce5ff;
+ border-color: #b8daff;
}
.eg-grid-header-cell-sort-arrow {
font-size: 14px;
}
+.col-resize {
+ display: block;
+ background: transparent;
+ border: 0;
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 5px;
+ height: 100%;
+ cursor: col-resize;
+}
+
+.col-resize:hover,
+.col-resize:active,
+.col-resize:focus,
+.resizing {
+ background-color: rgba(0,0,225,0.05);
+ border-right: 2px solid blue;
+ cursor: col-resize;
+ outline: 2px solid transparent;
+}
+
.eg-grid-toolbar {
display: flex;
}
padding-bottom: 11px;
}
-.eg-grid-cell-skinny {
- width: 2em;
- text-align: center;
- flex: none;
-}
-
-.eg-grid-cell-skinny-2 {
- width: 2.6em;
- flex: none;
-}
-
-.eg-grid-flair-cell {
- /* mat icons currently 22px, unclear why it needs this much space */
- width: 34px;
- text-align: center;
- flex: none;
-}
-
-/* depends on width of .eg-grid-cell-skinny */
-.eg-grid-column-width-header {
- width: 4.6em;
- text-align: center;
- flex: none;
- display: inline-flex;
- vertical-align: middle;
- align-items: center;
-}
-
.eg-grid-column-width-config .eg-grid-cell {
- border-left: 2px dashed grey;
+ border-left: 2px dashed grey;
}
.eg-grid-column-width-icon {
cursor: pointer;
font-size: 18px;
- color: #007bff;
+ color: #0A58CA;
}
.eg-grid-column-config-dialog {
box-shadow: none;
}
-.eg-grid-filter-control-cell {
- white-space: normal;
+.eg-grid-filter-control,
+.eg-grid-filter-control .dropdown {
+ display: inline;
}
+
+.eg-grid-filter-control .material-icons {
+ font-size: 18px;
+}
+
+.eg-grid-filter-control .dropdown-toggle.btn {
+ border-width: 0;
+ color: #255a88; /* match link color */
+ line-height: 1;
+ padding: 0;
+}
+
.eg-grid-col-is-filtered {
- background: lightblue;
+ background: lightblue;
}
.eg-grid-filter-menu {
min-width: 17rem;
.popover {
max-width: initial;
}
+
+/* Popovers' container needs to be inside <td> and should be the same size */
+.eg-grid-cell-contents {
+ height: 100%;
+ width: 100%;
+}
+
+/* Firefox td > div height fix */
+@-moz-document url-prefix() {
+ .eg-grid tr,
+ .eg-grid th,
+ .eg-grid td {
+ height: 100%;
+ }
+}
+
+/* GRID PADDING */
+/* Sets up basic padding for table headers and cells. See the widths and
+alignment section below for some overrides for specific data types and formats,
+such as numeric cells that should be right-aligned.
+
+Padding should be set on .eg-grid-cell-contents rather than its parent <td>.
+This div exists to house the popovers, and should take up the full unpadded
+height and width of the cell.
+/**/
+
+/* override Bootstrap's table settings */
+.table > :not(caption) > * > * {
+ padding: revert;
+}
+
+.eg-grid th,
+.eg-grid td .eg-grid-cell-contents {
+ padding: .2rem .5rem;
+}
+
+.eg-grid.compact th,
+.eg-grid.compact td .eg-grid-cell-contents {
+ line-height: 1.4;
+ padding: .1rem .5rem .1rem .1rem;
+}
+
+.eg-grid.wide th,
+.eg-grid.wide td .eg-grid-cell-contents {
+ padding: .5rem;
+}
+
+/* GRID COLUMN WIDTH AND ALIGNMENT */
+/*
+Sets up column widths and text alignment for eg-grid tables according to
+datatype, column name, and IDL class. Use more specific combinations in
+components' CSS to override the default width.
+/**/
+
+.eg-grid th {
+ vertical-align: bottom;
+}
+
+.eg-grid td {
+ vertical-align: top;
+ white-space: normal;
+ word-break: break-all;
+}
+
+.eg-grid-col-utilities col {
+ min-width: 4ch;
+}
+
+.eg-grid-checkbox-cell,
+.eg-grid-flair-cell {
+ text-align: center;
+}
+
+.eg-grid .numeric {
+ font-variant-numeric: tabular-nums lining-nums;
+ text-align: right;
+}
+
+.compact td.numeric .eg-grid-cell-contents {
+ padding: .1rem .25rem .1rem .4rem;
+}
+
+/* These contain numbers we might want to compare vertically, but should not be aligned right */
+.eg-grid .alphanumeric,
+.eg-grid-type-timestamp {
+ font-variant-numeric: tabular-nums lining-nums;
+}
+
+.eg-grid td.eg-grid-number-cell {
+ white-space: nowrap;
+}
+
+.eg-grid-col-name {
+ width: 30ch;
+ hyphens: none !important;
+ /* use with <wbr> before periods: */
+ /*
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ -ms-word-break: break-all;
+ word-break: break-all;
+ word-break: break-word;
+ /**/
+}
+
+.eg-grid-col-barcode {
+ width: 16ch;
+}
+
+.eg-grid-col-description,
+.eg-grid-col-label {
+ width: 20ch;
+}
+
+/* <td> only, not the headers */
+td.eg-grid-col-datatype {
+ font-family: monospace;
+ font-size: .85rem;
+}
+
+td.eg-grid-col-url,
+td.eg-grid-col-email {
+ hyphens: none !important;
+ overflow-wrap: anywhere;
+ word-break: break-all;
+}
\ No newline at end of file
-
-<div class="eg-grid" role="grid">
+<div id="{{gridDomId}}" class="eg-grid-wrapper" role="region" aria-labelledby="eg-grid-caption" tabindex="0">
+
+ <eg-grid-print #gridPrinter [gridContext]="context">
+ </eg-grid-print>
<eg-grid-toolbar #toolbar
[gridContext]="context"
[gridPrinter]="gridPrinter"
- [colWidthConfig]="colWidthConfig"
[disableSaveSettings]="!persistKey || ('disabled' === persistKey)">
</eg-grid-toolbar>
- <div #egGridStickyHeader [ngClass]="{'eg-grid-sticky-header' : context.stickyGridHeader}">
- <eg-grid-header [context]="context"></eg-grid-header>
- </div>
+<!-- uses dropdown menu CSS for easy stying, but it's not a dropdown -->
+<ng-template #contextMenu let-gridContext="gridContext">
+ <eg-grid-toolbar-actions-menu [gridContext]="gridContext" [viaContextMenu]="true">
+ </eg-grid-toolbar-actions-menu>
+</ng-template>
- <eg-grid-column-width #colWidthConfig [gridContext]="context">
- </eg-grid-column-width>
-
- <eg-grid-print #gridPrinter [gridContext]="context">
- </eg-grid-print>
-
- <ng-container *ngIf="dataSource.data.length === 0">
- <div class="row">
+<table #egGrid class="eg-grid table" role="table" [ngClass]="context.persistKey.replaceAll('.', '_')">
+ <caption id="eg-grid-caption">
+ <ng-container *ngIf="dataSource.data.length === 0">
<ng-container *ngIf="dataSource.requestingData">
- <div class="col-lg-6 offset-lg-3 text-center mt-3">
+ <span>
<eg-progress-inline></eg-progress-inline>
- </div>
+ </span>
</ng-container>
<ng-container *ngIf="!dataSource.requestingData">
- <div class="col-lg-12 text-center alert alert-danger fst-italic fw-bold" *ngIf="dataSource.retrievalError">
- <span i18n>Error Retrieving Results</span>
- </div>
- <div class="col-lg-12 text-center alert fst-italic fw-bold" *ngIf="!dataSource.retrievalError">
- <span i18n>Nothing to Display</span>
- </div>
+ <span i18n *ngIf="dataSource.retrievalError">
+ Error Retrieving Results
+ </span>
+ <span i18n *ngIf="!dataSource.retrievalError">
+ Nothing to Display
+ </span>
</ng-container>
- </div>
- </ng-container>
-
- <eg-grid-body [context]="context"></eg-grid-body>
-</div>
-
+ </ng-container>
+ </caption>
+
+ <colgroup class="eg-grid-col-utilities">
+ <col *ngIf="!context.disableSelect" class="eg-grid-col eg-grid-checkbox-col">
+ <col class="eg-grid-col eg-grid-number-col">
+ <col *ngIf="context.rowFlairIsEnabled" class="eg-grid-col eg-grid-flair-col">
+ </colgroup>
+ <colgroup class="eg-grid-col-data">
+ <col *ngFor="let col of context.columnSet.displayColumns()"
+ class="eg-grid-col {{context.setClassNames(row, col)}}">
+ </colgroup>
+
+ <thead class="eg-grid-header" #egGridStickyHeader [context]="context" [ngClass]="{'eg-grid-sticky-header' : context.stickyGridHeader}" role="rowgroup"> </thead>
+ <tbody class="eg-grid-body" [context]="context" role="rowgroup"></tbody>
+</table>
+
+</div>
\ No newline at end of file
@Input() reloadOnColumnChange = false;
context: GridContext;
+ private static id: number = 0;
+ gridDomId: string = 'eg-grid-';
// These events are emitted from our grid-body component.
// They are defined here for ease of access to the caller.
ngOnInit() {
if (!this.dataSource) {
- throw new Error('<eg-grid/> requires a [dataSource]');
+ throw new Error('[egGrid] requires a [dataSource]');
}
+ this.gridDomId += GridComponent.id++;
+
this.context.idlClass = this.idlClass;
this.context.dataSource = this.dataSource;
this.context.persistKey = this.persistKey;
if (this.pageSize) {
this.context.pager.limit = this.pageSize;
}
-
+
// TS doesn't seem to like: let foo = bar || () => '';
this.context.rowClassCallback =
this.rowClassCallback || function () { return ''; };
this.context.cellClassCallback =
- this.cellClassCallback ||
- function (row: any, col: GridColumn) {
- if (col.datatype === 'money') {
- // get raw value
- let val;
- if (col.path) {
- val = this.nestedItemFieldValue(row, col);
- } else if (col.name in row) {
- val = this.getObjectFieldValue(row, col.name);
- }
- if (Number(val) < 0) {
- return 'negative-money-amount';
- }
- }
- return '';
- };
+ this.cellClassCallback || function () { return ''; };
this.context.rowSelector.selectionChange.subscribe(
keys => this.rowSelectionChange.emit(keys)
isFilterable: boolean;
isFiltered: boolean;
isMultiSortable: boolean;
- disableTooltip: boolean;
+ enableTooltip: boolean;
asyncSupportsEmptyTermClick: boolean;
comparator: (valueA: any, valueB: any) => number;
required = false;
col.isIndex = this.isIndex;
col.cellTemplate = this.cellTemplate;
col.cellContext = this.cellContext;
- col.disableTooltip = this.disableTooltip;
+ col.enableTooltip = this.enableTooltip;
col.isSortable = this.isSortable;
col.isFilterable = this.isFilterable;
col.isMultiSortable = this.isMultiSortable;
reset() {
this.columns.forEach(col => {
- col.flex = 2;
col.sort = 0;
- col.align = 'left';
+ col.align = '';
col.visible = this.stockVisible.includes(col.name);
});
}
if (!col.name) { col.name = col.path; }
if (!col.flex) { col.flex = 2; }
- if (!col.align) { col.align = 'left'; }
+ if (!col.align) { col.align = ''; }
if (!col.label) { col.label = col.name; }
if (!col.datatype) { col.datatype = 'text'; }
if (!col.isAuto) { col.headerLabel = col.label; }
// scrunch the data down to just the needed info.
return this.displayColumns().map(col => {
const c: GridColumnPersistConf = {name : col.name};
- if (col.align !== 'left') { c.align = col.align; }
- if (col.flex !== 2) { c.flex = Number(col.flex); }
+ if (col.align !== '') { c.align = col.align; }
if (Number(col.sort)) { c.sort = Number(col.sort); }
return c;
});
col.visible = true;
if (colConf.align) { col.align = colConf.align; }
- if (colConf.flex) { col.flex = Number(colConf.flex); }
+ if (colConf.flex) { col.flex = colConf.flex; }
if (colConf.sort) { col.sort = Number(colConf.sort); }
// Add to new columns array, avoid dupes.
columnHasTextGenerator(col: GridColumn): boolean {
return this.cellTextGenerator && col.name in this.cellTextGenerator;
}
+
+ setClassNames(row: any, col: GridColumn): string {
+ /* set initial classes from specific grids' callbacks */
+ const classes = [this.cellClassCallback(row, col)];
+ /* preserve alignmnet, if set */
+ switch (col.align) {
+ case 'left':
+ classes.push('text-start');
+ break;
+ case 'right':
+ classes.push('text-end');
+ break;
+ case 'center':
+ classes.push('text-center');
+ break;
+ default:
+ break;
+ }
+
+ /* Base classes */
+ if (col.datatype)
+ classes.push('eg-grid-type-' + col.datatype);
+ if (col.name)
+ classes.push('eg-grid-col-' + col.name);
+ if (col.idlClass)
+ classes.push('eg-grid-class-' + col.idlClass);
+ if (col.path)
+ classes.push('eg-grid-path-' + col.path);
+ if (this.overflowCells)
+ classes.push('eg-grid-cell-overflow');
+
+ /* Name-based formats */
+ if (col.name.endsWith('count'))
+ classes.push('numeric');
+
+ switch (col.name) {
+ case 'callnumber':
+ case 'barcode':
+ classes.push('alphanumeric');
+ break;
+ default:
+ break;
+ }
+
+ /* Type-based formats */
+ switch (col.datatype) {
+ case 'money':
+ // get raw value
+ let val;
+ if (col.path) {
+ val = this.nestedItemFieldValue(row, col);
+ } else if (col.name in row) {
+ val = this.getObjectFieldValue(row, col.name);
+ }
+ if (Number(val) < 0) {
+ classes.push('negative-money-amount');
+ }
+ // don't break
+ case 'id':
+ case 'int':
+ case 'float':
+ case 'money':
+ case 'number':
+ classes.push('numeric');
+ break;
+ default:
+ break;
+ }
+
+ // smush into string and replace dots in name and path
+ return classes.join(' ').replaceAll('.', '');
+ }
}
</eg-grid-toolbar-action>
- <eg-grid-column path="email" [cellTemplate]="emailTmpl" [disableTooltip]="true"></eg-grid-column>
- <eg-grid-column path="phone" [cellTemplate]="phoneTmpl" [disableTooltip]="true"></eg-grid-column>
+ <eg-grid-column path="email" [cellTemplate]="emailTmpl"></eg-grid-column>
+ <eg-grid-column path="phone" [cellTemplate]="phoneTmpl"></eg-grid-column>
<eg-grid-column [filterable]="false" [sortable]="false" i18n-label label="Is Primary?" path="_is_primary" datatype="bool"></eg-grid-column>
</eg-grid>
<eg-grid-column path="creator" [hidden]="true"></eg-grid-column>
<eg-grid-column path="editor" [hidden]="true"></eg-grid-column>
<eg-grid-column path="owner" [hidden]="true"></eg-grid-column>
- <eg-grid-column [asyncSupportsEmptyTermClick]="true" i18n-label label="Status" path="state" [disableTooltip]="true"></eg-grid-column>
+ <eg-grid-column [asyncSupportsEmptyTermClick]="true" i18n-label label="Status" path="state"></eg-grid-column>
<eg-grid-column [asyncSupportsEmptyTermClick]="true" path="cancel_reason"></eg-grid-column>
<eg-grid-column path="prepayment_required" [sortable]="false"></eg-grid-column>
<eg-grid-toolbar-action label="Retrieve Provider" i18n-label (onClick)="retrieveRow($event)"></eg-grid-toolbar-action>
<eg-grid-column [asyncSupportsEmptyTermClick]="true" path="currency_type" [hidden]="true"></eg-grid-column>
<eg-grid-column [asyncSupportsEmptyTermClick]="true" path="default_claim_policy" [hidden]="true"></eg-grid-column>
- <eg-grid-column path="contacts" [cellTemplate]="contactTmpl" [filterable]="false" [sortable]="false" [hidden]="true" [disableTooltip]="true"></eg-grid-column>
+ <eg-grid-column path="contacts" [cellTemplate]="contactTmpl" [filterable]="false" [sortable]="false" [hidden]="true"></eg-grid-column>
</eg-grid>
(onClick)="exportSingleAttributeList($event)" [disableOnRows]="noSelectedRows">
</eg-grid-toolbar-action>
- <eg-grid-column path="id" [cellTemplate]="idTmpl" [disableTooltip]="true"></eg-grid-column>
+ <eg-grid-column path="id" [cellTemplate]="idTmpl"></eg-grid-column>
<eg-grid-column i18n-label label="Title" path="title" [cellTemplate]="liAttrTmpl"></eg-grid-column>
<eg-grid-column i18n-label label="Author" path="author" [cellTemplate]="liAttrTmpl"></eg-grid-column>
<eg-grid-column path="provider" [cellTemplate]="providerTmpl"></eg-grid-column>
- <eg-grid-column i18n-label label="Links" path="_links" [cellTemplate]="liLinksTmpl" [disableTooltip]="true" [filterable]="false" [sortable]="false"></eg-grid-column>
+ <eg-grid-column i18n-label label="Links" path="_links" [cellTemplate]="liLinksTmpl" [filterable]="false" [sortable]="false"></eg-grid-column>
<eg-grid-column i18n-label label="Item Count" path="item_count"></eg-grid-column>
<eg-grid-column [asyncSupportsEmptyTermClick]="true" path="claim_policy" [sortable]="false"></eg-grid-column>
- <eg-grid-column [asyncSupportsEmptyTermClick]="true" i18n-label label="Status" path="state" [disableTooltip]="true"></eg-grid-column>
- <eg-grid-column path="estimated_unit_price" [disableTooltip]="true"></eg-grid-column>
- <eg-grid-column i18n-label label="PO ID" path="purchase_order.id" [disableTooltip]="true"></eg-grid-column>
- <eg-grid-column i18n-label label="PO Name" path="purchase_order" [cellTemplate]="poTmpl" [disableTooltip]="true" [hidden]="true"></eg-grid-column>
- <eg-grid-column path="picklist" [cellTemplate]="plTmpl" [disableTooltip]="true" [hidden]="true"></eg-grid-column>
+ <eg-grid-column [asyncSupportsEmptyTermClick]="true" i18n-label label="Status" path="state"></eg-grid-column>
+ <eg-grid-column path="estimated_unit_price"></eg-grid-column>
+ <eg-grid-column i18n-label label="PO ID" path="purchase_order.id"></eg-grid-column>
+ <eg-grid-column i18n-label label="PO Name" path="purchase_order" [cellTemplate]="poTmpl" [hidden]="true"></eg-grid-column>
+ <eg-grid-column path="picklist" [cellTemplate]="plTmpl" [hidden]="true"></eg-grid-column>
<eg-grid-column [asyncSupportsEmptyTermClick]="true" path="cancel_reason" [hidden]="true"></eg-grid-column>
<eg-grid-column path="create_time" [datePlusTime]="true"></eg-grid-column>
<eg-grid-column path="edit_time" [datePlusTime]="true"></eg-grid-column>
<eg-grid-column path="creator" [hidden]="true"></eg-grid-column>
<eg-grid-column path="editor" [hidden]="true"></eg-grid-column>
<eg-grid-column path="owner" [hidden]="true"></eg-grid-column>
- <eg-grid-column [asyncSupportsEmptyTermClick]="true" i18n-label label="Status" path="state" [disableTooltip]="true"></eg-grid-column>
+ <eg-grid-column [asyncSupportsEmptyTermClick]="true" i18n-label label="Status" path="state"></eg-grid-column>
<eg-grid-column [asyncSupportsEmptyTermClick]="true" path="cancel_reason"></eg-grid-column>
<eg-grid-column path="prepayment_required" [sortable]="false"></eg-grid-column>
<button class="btn btn-outline-dark" (click)="openExchangeRatesDialog(currency.code())" i18n>Manage Exchange Rates</button>
</ng-template>
<eg-grid-column i18n-label label="Exchange Rates" name="exchange_rates"
- [sortable]="false" [filterable]="false" [cellTemplate]="exchangeRatesTmpl" [disableTooltip]="true"></eg-grid-column>
+ [sortable]="false" [filterable]="false" [cellTemplate]="exchangeRatesTmpl"></eg-grid-column>
</eg-grid>
<button class="btn btn-outline-dark" (click)="openEdiAttrSetProvidersDialog(attrSet.id())" [disabled]="attrSet.num_providers < 1" i18n>View Providers</button>
</ng-template>
<eg-grid-column i18n-label label="View Providers" name="view_providers"
- [sortable]="false" [filterable]="false" [cellTemplate]="ediAttrSetProvidersTmpl" [disableTooltip]="true"></eg-grid-column>
+ [sortable]="false" [filterable]="false" [cellTemplate]="ediAttrSetProvidersTmpl"></eg-grid-column>
<eg-grid-column path="id" [hidden]="true"></eg-grid-column>
<eg-grid-column path="original_value_str" label="Original Value" i18n-label></eg-grid-column>
<eg-grid-column path="new_value_str" label="New Value" i18n-label></eg-grid-column>
<eg-grid-column i18n-label label="Revert?" name="revert"
- [disableTooltip]="true" [cellTemplate]="revertTemplate"></eg-grid-column>
+ [cellTemplate]="revertTemplate"></eg-grid-column>
</eg-grid>
</div>
</div>
persistKey="admin.actor.org_unit_settings">
<eg-grid-column i18n-label label="Edit" name="edit" [flex]="1" [sortable]="false" [multiSortable]="false"
- [disableTooltip]="true" [cellTemplate]="editCellTemplate"></eg-grid-column>
+ [cellTemplate]="editCellTemplate"></eg-grid-column>
<eg-grid-column i18n-label label="History" name="history" [flex]="1" [sortable]="false" [multiSortable]="false"
- [disableTooltip]="true" [cellTemplate]="historyCellTemplate"></eg-grid-column>
+ [cellTemplate]="historyCellTemplate"></eg-grid-column>
<eg-grid-column path="name" label="Name" i18n-label [hidden]="true" [flex]="3"></eg-grid-column>
<eg-grid-column path="grp" label="Group" i18n-label [flex]="1"></eg-grid-column>
<eg-grid-column path="label" label="Setting" [index]="true" i18n-label [flex]="4"></eg-grid-column>
this.cspRowFlairCallback = (row: any): GridRowFlairEntry => {
const flair = {icon: null, title: null};
- if (row.id() < 100) {
+ if (row && (row.id() < 100)) {
flair.icon = 'not_interested';
flair.title = this.cspFlairTooltip.text;
}
}
cspGridCellClassCallback = (row: any, col: GridColumn): string => {
- if (col.name === 'id' && row.a[0] < 100) {
+ if (row && col.name === 'id' && row.a[0] < 100) {
return 'text-danger';
}
return '';
<eg-grid-toolbar-button label="Create Reservation" i18n-label (onClick)="openTheDialog($event)"></eg-grid-toolbar-button>
<eg-grid-toolbar-action label="Create Reservation" i18n-label (onClick)="openTheDialog($event)"></eg-grid-toolbar-action>
<eg-grid-column path="time" [index]="true" name="Time" i18n-name [cellTemplate]="timeTemplate" ></eg-grid-column>
- <eg-grid-column *ngFor="let resource of resources" path="{{resource.barcode()}}" [cellTemplate]="reservationsTemplate" [name]="resource.barcode()" [disableTooltip]="true"></eg-grid-column>
+ <eg-grid-column *ngFor="let resource of resources" path="{{resource.barcode()}}" [cellTemplate]="reservationsTemplate" [name]="resource.barcode()"></eg-grid-column>
</eg-grid>
</ng-container>
<div class="text-sm-center" *ngIf="this.resourceType.value && !resources.length" i18n>
{{copy.call_number_suffix_label}}
<div>
<a routerLink="/staff/cat/volcopy/holdings/callnumber/{{copy.call_number}}"
- class="" target="_blank"i18n>Edit</a>
+ target="_blank" i18n attr.aria-describedby="copy-callnumber-{{copy.call_number}}">Edit</a>
</div>
</ng-template>
<ng-template #barcodeTemplate let-copy="row" let-context="userContext">
<div>{{copy.barcode}}</div>
<div>
- <a class="ps-1" target="_blank"
+ <a class="ps-1" target="_blank" attr.
href="/eg/staff/cat/item/{{copy.id}}" i18n>View</a>
<ng-container *ngIf="context.editable(copy)">
- | <a class="ps-1" target="_blank"
+ | <a target="_blank" attr.aria-describedby="copy-barcode-{{copy.id}}"
routerLink="/staff/cat/volcopy/attrs/item/{{copy.id}}" i18n>Edit</a>
</ng-container>
</div>
<div class="border-bottom">
<a routerLink="/staff/catalog/hold/C"
[queryParams]="{target: copy.id}"
- queryParamsHandling="merge" i18n>
+ queryParamsHandling="merge" i18n
+ attr.aria-describedby="copy-barcode-{{copy.id}}">
Item Hold
</a>
</div>
<div>
<a routerLink="/staff/catalog/hold/V"
[queryParams]="{target: copy.call_number}"
- queryParamsHandling="merge" i18n>
+ queryParamsHandling="merge" i18n
+ attr.aria-describedby="copy-callnumber-{{copy.call_number}}">
Call Number Hold
</a>
</div>
<ng-template #courseTemplate let-copy="row">
<div *ngFor="let course of copy._courses">
- <a routerLink="/staff/admin/local/asset/course_list/{{course.id()}}">
+ <div class="visually-hidden" id="course-name-{{course.id()}}">{{course.name()}}</div>
+ <a routerLink="/staff/admin/local/asset/course_list/{{course.id()}}"
+ attr.aria-describedby="course-name-{{course.id()}}">
{{course.course_number()}}
</a>
</div>
</eg-grid-column>
<eg-grid-column name="owner_label" [flex]="4"
[cellTemplate]="locationTemplate" [cellContext]="gridTemplateContext"
- label="Location/Barcode" [disableTooltip]="true" i18n-label>
+ label="Location / Barcode" i18n-label>
</eg-grid-column>
<eg-grid-column path="callNumCount" datatype="number" label="Call Numbers" i18n-label>
</eg-grid-column>
import {ServerStoreService} from '@eg/core/server-store.service';
import {PatronService} from '@eg/staff/share/patron/patron.service';
import {PatronContextService} from './patron.service';
-import {GridDataSource, GridColumn, GridCellTextGenerator, GridRowFlairEntry} from '@eg/share/grid/grid';
+import {GridContext, GridDataSource, GridColumn, GridCellTextGenerator, GridRowFlairEntry} from '@eg/share/grid/grid';
import {GridComponent} from '@eg/share/grid/grid.component';
import {Pager} from '@eg/share/util/pager';
import {CircService, CircDisplayInfo} from '@eg/staff/share/circ/circ.service';
<eg-grid-column path="id" [hidden]=true [index]="true" i18n-label label="Run ID" [filterable]="false" [sortable]="false"></eg-grid-column>
<eg-grid-column path="template_name" i18n-label label="Report"></eg-grid-column>
<eg-grid-column path="complete_time" i18n-label label="Finish Time" datatype="timestamp" [datePlusTime]="true"></eg-grid-column>
- <eg-grid-column path="_output" [cellTemplate]="outputTmpl" i18n-label label="Output" [sortable]="false" [filterable]="false" [disableTooltip]="true"></eg-grid-column>
+ <eg-grid-column path="_output" [cellTemplate]="outputTmpl" i18n-label label="Output" [sortable]="false" [filterable]="false"></eg-grid-column>
<eg-grid-column path="error_text"[hidden]=true i18n-label label="Error Text"></eg-grid-column>
</eg-grid>
"node_modules/@types"
],
"lib": [
- "es2018",
+ "ESNext",
"dom"
]
}