lp1778311 On The Fly Resizing of Grids WIP user/khuckins/lp1778311-web-column-on-the-fly-resizing
authorKyle Huckins <khuckins@catalyte.io>
Sat, 26 Feb 2022 22:20:00 +0000 (22:20 +0000)
committerKyle Huckins <khuckins@catalyte.io>
Sat, 26 Feb 2022 22:20:00 +0000 (22:20 +0000)
- Introduce resizing component
- Refactor grid into html table

Signed-off-by: Kyle Huckins <khuckins@catalyte.io>
 Changes to be committed:
modified:   Open-ILS/src/eg2/src/app/share/grid/grid-body.component.html
modified:   Open-ILS/src/eg2/src/app/share/grid/grid-body.component.ts
modified:   Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.html
modified:   Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.ts
modified:   Open-ILS/src/eg2/src/app/share/grid/grid-header.component.html
modified:   Open-ILS/src/eg2/src/app/share/grid/grid-print.component.ts
modified:   Open-ILS/src/eg2/src/app/share/grid/grid.component.css
modified:   Open-ILS/src/eg2/src/app/share/grid/grid.component.html
modified:   Open-ILS/src/eg2/src/app/share/grid/grid.module.ts
new file:   Open-ILS/src/eg2/src/app/share/grid/resizable.component.html
new file:   Open-ILS/src/eg2/src/app/share/grid/resizable.component.ts
new file:   Open-ILS/src/eg2/src/app/share/grid/resizable.css
new file:   Open-ILS/src/eg2/src/app/share/grid/resizable.directive.ts
new file:   Open-ILS/src/eg2/src/app/share/grid/resizable.style.less

14 files changed:
Open-ILS/src/eg2/src/app/share/grid/grid-body.component.html
Open-ILS/src/eg2/src/app/share/grid/grid-body.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.html
Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid-header.component.html
Open-ILS/src/eg2/src/app/share/grid/grid-print.component.ts
Open-ILS/src/eg2/src/app/share/grid/grid.component.css
Open-ILS/src/eg2/src/app/share/grid/grid.component.html
Open-ILS/src/eg2/src/app/share/grid/grid.module.ts
Open-ILS/src/eg2/src/app/share/grid/resizable.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/grid/resizable.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/grid/resizable.css [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/grid/resizable.directive.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/grid/resizable.style.less [new file with mode: 0644]

index fed2276..597144f 100644 (file)
@@ -7,13 +7,11 @@
 <!--
   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)}}"
+<div 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">
-
+    *ngFor="let row of context.dataSource.getPageOfRows(context.pager); let idx = index"(keydown)="onGridKeyDown($event)" >
     <ng-container *ngIf="!context.disableSelect">
-      <div class="eg-grid-cell eg-grid-checkbox-cell eg-grid-cell-skinny">
+      <td class="eg-grid-cell eg-grid-checkbox-cell eg-grid-cell-skinny">
         <input type='checkbox' [(ngModel)]="context.rowSelector.indexes[context.getRowIndex(row)]"
           i18n-aria-label="e.g. Row 13" attr.aria-label="Row {{context.pager.rowNumber(idx)}}"
           #rowContextMenu="ngbPopover"
           [ngbPopover]="contextMenu"
           placement="right"
           triggers="manual">
-      </div>
+      </td>
     </ng-container>
-    <div class="eg-grid-cell eg-grid-number-cell eg-grid-cell-skinny-2">
+    <td 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>
+    <td *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">
           </span>
         </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"
+    <td role="gridcell" class="eg-grid-cell eg-grid-body-cell"
       [ngStyle]="{flex:col.flex}"
       [ngClass]="{'eg-grid-cell-overflow': context.overflowCells}"
       (dblclick)="onRowDblClick(row)"
@@ -58,7 +56,5 @@
 
       <eg-grid-body-cell [context]="context" [row]="row" [column]="col">
       </eg-grid-body-cell>
-    </div>
-  </div>
-</div>
-
+    </td>
+    </div>
\ No newline at end of file
index 14c6b4c..e99636e 100644 (file)
@@ -5,7 +5,7 @@ import {GridComponent} from './grid.component';
 import {NgbPopover} from '@ng-bootstrap/ng-bootstrap';
 
 @Component({
-  selector: 'eg-grid-body',
+  selector: 'eg-grid-body, [eg-grid-body]',
   templateUrl: './grid-body.component.html'
 })
 
index ca24c00..5451d2f 100644 (file)
@@ -1,20 +1,19 @@
-<div *ngIf="isVisible" class="eg-grid-column-width-config">
-  <div class="eg-grid-row">
-    <div class="eg-grid-column-width-header" i18n>Expand</div>
-    <div *ngFor="let col of columnSet.displayColumns()" 
+
+  <tr class="eg-grid-row" *ngIf="isVisible" class="eg-grid-column-width-config">
+    <td class="eg-grid-column-width-header" i18n>Expand</td>
+    <td *ngFor="let col of columnSet.displayColumns()" 
       class="eg-grid-cell text-center" [ngStyle]="{flex:col.flex}">
       <a (click)="expandColumn(col)" title="Expand Column" i18n-title>
         <span class="material-icons eg-grid-column-width-icon">call_made</span>
       </a>
-    </div>
-  </div>
-  <div class="eg-grid-row">
-    <div class="eg-grid-column-width-header" i18n>Shrink</div>
-    <div *ngFor="let col of columnSet.displayColumns()" 
+    </td>
+  </tr>
+  <tr class="eg-grid-row" *ngIf="isVisible" class="eg-grid-column-width-config">
+    <td class="eg-grid-column-width-header" i18n>Shrink</td>
+    <td *ngFor="let col of columnSet.displayColumns()" 
       class="eg-grid-cell text-center" [ngStyle]="{flex:col.flex}">
       <a (click)="shrinkColumn(col)" title="Shrink Column" i18n-title>
         <span class="material-icons eg-grid-column-width-icon">call_received</span>
       </a>
-    </div>
-  </div>
-</div>
+    </td>
+  </tr>
index bb597cd..3c8b9cb 100644 (file)
@@ -2,7 +2,7 @@ import {Component, Input, OnInit} from '@angular/core';
 import {GridContext, GridColumn, GridColumnSet} from './grid';
 
 @Component({
-  selector: 'eg-grid-column-width',
+  selector: 'eg-grid-column-width, [eg-grid-column-width]',
   templateUrl: './grid-column-width.component.html'
 })
 
index a680765..cc7d302 100644 (file)
@@ -1,23 +1,23 @@
 
-<div row="row" class="eg-grid-row eg-grid-header-row">
+
   <ng-container *ngIf="!context.disableSelect">
-    <div role="columnheader"
+    <th resizable role="columnheader" scope="col"
       class="eg-grid-cell eg-grid-header-cell eg-grid-checkbox-cell eg-grid-cell-skinny">
       <input type='checkbox' (click)="handleBatchSelect($event)"
         i18n-aria-label aria-label="All rows"
         [(ngModel)]="batchRowCheckbox">
-    </div>
+    </th>
   </ng-container>
-  <div role="columnheader"
+  <th resizable role="columnheader" scope="col"
     class="eg-grid-cell eg-grid-header-cell eg-grid-number-cell eg-grid-cell-skinny">
     <span i18n="number|Row Number Header">#</span>
-  </div>
-  <div *ngIf="context.rowFlairIsEnabled"
+  </th>
+  <th resizable *ngIf="context.rowFlairIsEnabled" scope="col"
     role="columnheader"
     class="eg-grid-cell eg-grid-header-cell eg-grid-flair-cell">
     <span class="material-icons">notifications</span>
-  </div>
-  <div role="columnheader"
+  </th>
+  <th resizable role="columnheader" scope="col"
     *ngFor="let col of context.columnSet.displayColumns()"
     draggable="true"
     (dragstart)="dragColumn = col"
         *ngIf="isColumnSorting(col, 'DESC')">arrow_downwards</span>
     </a>
     <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>
+  </th>
index f7c857a..6a73382 100644 (file)
@@ -4,7 +4,7 @@ import {PrintService} from '@eg/share/print/print.service';
 import {GridContext} from '@eg/share/grid/grid';
 
 @Component({
-  selector: 'eg-grid-print',
+  selector: 'eg-grid-print, [eg-grid-print]',
   templateUrl: './grid-print.component.html'
 })
 
index 83a907c..77b7ab4 100644 (file)
@@ -1,11 +1,9 @@
 
 .eg-grid {
-    width: 100%;
-    color: rgba(0,0,0,.87); 
+    color: rgba(0,0,0,.87);
 }
     
 .eg-grid-row {
-    display: flex;
     border-bottom: 1px solid rgba(0,0,0,.12);
     padding-left: 10px;
     padding-right: 10px;
     white-space: normal;
 }
 
+.eg-grid-resizable th {
+    resize: horizontal;
+    overflow: auto;
+}
+
 .eg-grid-body-cell {
 }
 
index 2ab6be0..ebcf24f 100644 (file)
@@ -1,18 +1,18 @@
-
-<div class="eg-grid" role="grid">
-
-  <eg-grid-toolbar #toolbar
+<eg-grid-toolbar #toolbar
     [gridContext]="context"
     [gridPrinter]="gridPrinter"
     [colWidthConfig]="colWidthConfig"
     [disableSaveSettings]="!persistKey || ('disabled' === persistKey)">
-  </eg-grid-toolbar>
+</eg-grid-toolbar>
+<table class="eg-grid" role="grid">
 
-  <div #egGridStickyHeader [ngClass]="{'eg-grid-sticky-header' : context.stickyGridHeader}">
-    <eg-grid-header [context]="context"></eg-grid-header>
-  </div>
+  <thead #egGridStickyHeader class="eg-grid-resizable" [ngClass]="{'eg-grid-sticky-header' : context.stickyGridHeader}">
+    <tr row="row" class="eg-grid-row eg-grid-header-row"><eg-grid-header [context]="context"></eg-grid-header></tr>
+  </thead>
 
-  <eg-grid-column-width #colWidthConfig [gridContext]="context">
+  
+
+<eg-grid-column-width #colWidthConfig [gridContext]="context">
   </eg-grid-column-width>
 
   <eg-grid-print #gridPrinter [gridContext]="context">
@@ -35,7 +35,8 @@
       </ng-container>
     </div>
   </ng-container>
-
-  <eg-grid-body [context]="context"></eg-grid-body>
-</div>
+  <tbody class="eg-grid-body" tabindex="1">
+  <tr role="row" eg-grid-body [context]="context"></tr>
+  </tbody>
+</table>
 
index 0757fab..9e285b3 100644 (file)
@@ -16,6 +16,8 @@ import {GridColumnWidthComponent} from './grid-column-width.component';
 import {GridPrintComponent} from './grid-print.component';
 import {GridFilterControlComponent} from './grid-filter-control.component';
 import {GridToolbarActionsEditorComponent} from './grid-toolbar-actions-editor.component';
+import {ResizableComponent} from './resizable.component';
+import {ResizableDirective} from './resizable.directive';
 
 
 @NgModule({
@@ -35,7 +37,9 @@ import {GridToolbarActionsEditorComponent} from './grid-toolbar-actions-editor.c
         GridColumnWidthComponent,
         GridPrintComponent,
         GridFilterControlComponent,
-        GridToolbarActionsEditorComponent
+        GridToolbarActionsEditorComponent,
+        ResizableComponent,
+        ResizableDirective
     ],
     imports: [
         EgCommonModule,
@@ -47,7 +51,8 @@ import {GridToolbarActionsEditorComponent} from './grid-toolbar-actions-editor.c
         GridColumnComponent,
         GridToolbarButtonComponent,
         GridToolbarCheckboxComponent,
-        GridToolbarActionComponent
+        GridToolbarActionComponent,
+        ResizableComponent
     ],
     providers: [
     ]
diff --git a/Open-ILS/src/eg2/src/app/share/grid/resizable.component.html b/Open-ILS/src/eg2/src/app/share/grid/resizable.component.html
new file mode 100644 (file)
index 0000000..0795e06
--- /dev/null
@@ -0,0 +1,6 @@
+<div class="resizable-wrapper">\r
+       <div class="resizable-content">\r
+               <ng-content></ng-content>\r
+       </div>\r
+       <div class="resizable-bar" (resizable)="onResize($event)"></div>\r
+</div>
\ No newline at end of file
diff --git a/Open-ILS/src/eg2/src/app/share/grid/resizable.component.ts b/Open-ILS/src/eg2/src/app/share/grid/resizable.component.ts
new file mode 100644 (file)
index 0000000..44cfeb4
--- /dev/null
@@ -0,0 +1,15 @@
+import { Component, ElementRef, HostBinding } from "@angular/core";\r
+\r
+@Component({\r
+  selector: "th[resizable]",\r
+  templateUrl: "./resizable.component.html",\r
+  styleUrls: ["./resizable.style.less"]\r
+})\r
+export class ResizableComponent {\r
+  @HostBinding("style.width.px")\r
+  width: number | null = null;\r
+\r
+  onResize(width: number) {\r
+    this.width = width;\r
+  }\r
+}\r
diff --git a/Open-ILS/src/eg2/src/app/share/grid/resizable.css b/Open-ILS/src/eg2/src/app/share/grid/resizable.css
new file mode 100644 (file)
index 0000000..6f5d127
--- /dev/null
@@ -0,0 +1,35 @@
+:host {\r
+  &:last-child .bar {\r
+    display: none;\r
+  }\r
+}\r
+\r
+.resizable-wrapper {\r
+  display: flex;\r
+  justify-content: flex-end;\r
+}\r
+\r
+.resizable-content {\r
+  flex: 2;\r
+}\r
+\r
+.resizable-bar {\r
+  position: absolute;\r
+  top: 0;\r
+  bottom: 0;\r
+  width: 2px;\r
+  margin: 0 -16px 0 16px;\r
+  justify-self: flex-end;\r
+  border-left: 2px solid transparent;\r
+  border-right: 2px solid transparent;\r
+  background: blueviolet;\r
+  background-clip: content-box;\r
+  cursor: ew-resize;\r
+  opacity: 0;\r
+  transition: opacity .3s;\r
+\r
+  &:hover,\r
+  &:active {\r
+    opacity: 1;\r
+  }\r
+}
\ No newline at end of file
diff --git a/Open-ILS/src/eg2/src/app/share/grid/resizable.directive.ts b/Open-ILS/src/eg2/src/app/share/grid/resizable.directive.ts
new file mode 100644 (file)
index 0000000..06fad98
--- /dev/null
@@ -0,0 +1,36 @@
+import { DOCUMENT } from "@angular/common";\r
+import { Directive, ElementRef, Inject, Output } from "@angular/core";\r
+import {\r
+  distinctUntilChanged, map, switchMap, takeUntil, tap\r
+} from "rxjs/operators";\r
+import { fromEvent } from "rxjs";\r
+\r
+@Directive({\r
+  selector: "[resizable]"\r
+})\r
+export class ResizableDirective {\r
+  @Output()\r
+  readonly resizable = fromEvent<MouseEvent>(\r
+    this.elementRef.nativeElement,\r
+    "mousedown"\r
+  ).pipe(\r
+    tap(e => e.preventDefault()),\r
+    switchMap(() => {\r
+      const { width, right } = this.elementRef.nativeElement\r
+        .closest("th")\r
+        .getBoundingClientRect();\r
+\r
+      return fromEvent<MouseEvent>(this.documentRef, "mousemove").pipe(\r
+        map(({ clientX }) => width + clientX - right),\r
+        distinctUntilChanged(),\r
+        takeUntil(fromEvent(this.documentRef, "mouseup"))\r
+      );\r
+    })\r
+  );\r
+\r
+  constructor(\r
+    @Inject(DOCUMENT) private readonly documentRef: Document,\r
+    @Inject(ElementRef)\r
+    private readonly elementRef: ElementRef<HTMLElement>\r
+  ) {}\r
+}\r
diff --git a/Open-ILS/src/eg2/src/app/share/grid/resizable.style.less b/Open-ILS/src/eg2/src/app/share/grid/resizable.style.less
new file mode 100644 (file)
index 0000000..997ad05
--- /dev/null
@@ -0,0 +1,35 @@
+:host {\r
+  &:last-child .bar {\r
+    display: none;\r
+  }\r
+}\r
+\r
+.resizable-wrapper {\r
+  display: flex;\r
+  justify-content: flex-end;\r
+}\r
+\r
+.resizable-content {\r
+  flex: 1;\r
+}\r
+\r
+.resizable-bar {\r
+  position: absolute;\r
+  top: 0;\r
+  bottom: 0;\r
+  width: 2px;\r
+  margin: 0 -16px 0 16px;\r
+  justify-self: flex-end;\r
+  border-left: 2px solid transparent;\r
+  border-right: 2px solid transparent;\r
+  background: blueviolet;\r
+  background-clip: content-box;\r
+  cursor: ew-resize;\r
+  opacity: 0;\r
+  transition: opacity .3s;\r
+\r
+  &:hover,\r
+  &:active {\r
+    opacity: 1;\r
+  }\r
+}
\ No newline at end of file