Skip to content

Commit

Permalink
refactor: move all picker common code into a seperate file
Browse files Browse the repository at this point in the history
- ColumnPicker/GridMenu share common code related to the column picker list, we can move this common code into a separate and shared file instead of keeping duplicate code (DRY)
  • Loading branch information
ghiscoding committed Aug 22, 2021
1 parent a977914 commit 18d74ba
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 359 deletions.
190 changes: 12 additions & 178 deletions packages/common/src/controls/columnPicker.control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { ExtensionUtility } from '../extensions/extensionUtility';
import { BindingEventService } from '../services/bindingEvent.service';
import { PubSubService } from '../services/pubSub.service';
import { SharedService } from '../services/shared.service';
import { emptyElement, sanitizeTextByAvailableSanitizer } from '../services';
import { emptyElement } from '../services';
import { handleColumnPickerItemClick, populateColumnPicker, updateColumnPickerOrder } from '../extensions/extensionCommonUtils';

// using external SlickGrid JS libraries
declare const Slick: SlickNamespace;
Expand All @@ -23,14 +24,15 @@ declare const Slick: SlickNamespace;
* @constructor
*/
export class ColumnPickerControl {
protected _areVisibleColumnDifferent = false;
protected _bindEventService: BindingEventService;
protected _columns: Column[] = [];
protected _columnTitleElm!: HTMLDivElement;
protected _eventHandler!: SlickEventHandler;
protected _gridUid = '';
protected _listElm!: HTMLSpanElement;
protected _menuElm!: HTMLDivElement;
protected columnCheckboxes: HTMLInputElement[] = [];
protected _columnCheckboxes: HTMLInputElement[] = [];

protected _defaults = {
// the last 2 checkboxes titles
Expand Down Expand Up @@ -91,7 +93,7 @@ export class ColumnPickerControl {
const onHeaderContextMenuHandler = this.grid.onHeaderContextMenu;
const onColumnsReorderedHandler = this.grid.onColumnsReordered;
(this._eventHandler as SlickEventHandler<GetSlickEventType<typeof onHeaderContextMenuHandler>>).subscribe(onHeaderContextMenuHandler, this.handleHeaderContextMenu.bind(this) as EventListener);
(this._eventHandler as SlickEventHandler<GetSlickEventType<typeof onColumnsReorderedHandler>>).subscribe(onColumnsReorderedHandler, this.updateColumnOrder.bind(this) as EventListener);
(this._eventHandler as SlickEventHandler<GetSlickEventType<typeof onColumnsReorderedHandler>>).subscribe(onColumnsReorderedHandler, updateColumnPickerOrder.bind(this) as EventListener);

this._menuElm = document.createElement('div');
this._menuElm.className = `slick-columnpicker ${this._gridUid}`;
Expand Down Expand Up @@ -119,7 +121,7 @@ export class ColumnPickerControl {
this._menuElm.appendChild(this._columnTitleElm);
}

this._bindEventService.bind(this._menuElm, 'click', this.updateColumn.bind(this) as EventListener);
this._bindEventService.bind(this._menuElm, 'click', handleColumnPickerItemClick.bind(this) as EventListener);

this._listElm = document.createElement('span');
this._listElm.className = 'slick-columnpicker-list';
Expand Down Expand Up @@ -168,196 +170,28 @@ export class ColumnPickerControl {
handleHeaderContextMenu(e: DOMEvent<HTMLDivElement>) {
e.preventDefault();
emptyElement(this._listElm);
this.updateColumnOrder();
this.columnCheckboxes = [];

let liElm;
let inputElm;
let columnId;
let columnLabel;
let excludeCssClass;
for (const column of this.columns) {
columnId = column.id;
excludeCssClass = column.excludeFromColumnPicker ? 'hidden' : '';
liElm = document.createElement('li');
liElm.className = excludeCssClass;
this._listElm.appendChild(liElm);

inputElm = document.createElement('input');
inputElm.type = 'checkbox';
inputElm.id = `${this._gridUid}-colpicker-${columnId}`;
inputElm.dataset.columnid = `${columnId}`;
const colIndex = this.grid.getColumnIndex(columnId);
if (colIndex >= 0) {
inputElm.checked = true;
}
liElm.appendChild(inputElm);
this.columnCheckboxes.push(inputElm);

if (this.controlOptions?.headerColumnValueExtractor) {
columnLabel = this.controlOptions.headerColumnValueExtractor(column, this.gridOptions);
} else {
columnLabel = this._defaults.headerColumnValueExtractor!(column, this.gridOptions);
}

const labelElm = document.createElement('label');
labelElm.htmlFor = `${this._gridUid}-colpicker-${columnId}`;
labelElm.innerHTML = sanitizeTextByAvailableSanitizer(this.gridOptions, columnLabel);
liElm.appendChild(labelElm);
}

if (!this.controlOptions.hideForceFitButton || !this.controlOptions.hideSyncResizeButton) {
this._listElm.appendChild(document.createElement('hr'));
}

if (!(this.controlOptions?.hideForceFitButton)) {
const forceFitTitle = this.controlOptions?.forceFitTitle;

liElm = document.createElement('li');
this._listElm.appendChild(liElm);
inputElm = document.createElement('input');
inputElm.type = 'checkbox';
inputElm.id = `${this._gridUid}-colpicker-forcefit`;
inputElm.dataset.option = 'autoresize';
liElm.appendChild(inputElm);

const labelElm = document.createElement('label');
labelElm.htmlFor = `${this._gridUid}-colpicker-forcefit`;
labelElm.textContent = `${forceFitTitle ?? ''}`;
liElm.appendChild(labelElm);
if (this.grid.getOptions().forceFitColumns) {
inputElm.checked = true;
}
}
updateColumnPickerOrder.call(this);
this._columnCheckboxes = [];

if (!(this.controlOptions?.hideSyncResizeButton)) {
const syncResizeTitle = (this.controlOptions?.syncResizeTitle) || this.controlOptions.syncResizeTitle;
liElm = document.createElement('li');
this._listElm.appendChild(liElm);

inputElm = document.createElement('input');
inputElm.type = 'checkbox';
inputElm.id = `${this._gridUid}-colpicker-syncresize`;
inputElm.dataset.option = 'syncresize';
liElm.appendChild(inputElm);

const labelElm = document.createElement('label');
labelElm.htmlFor = `${this._gridUid}-colpicker-syncresize`;
labelElm.textContent = `${syncResizeTitle ?? ''}`;
liElm.appendChild(labelElm);
if (this.grid.getOptions().syncColumnCellResize) {
inputElm.checked = true;
}
}
populateColumnPicker.call(this, this.controlOptions);
this.repositionMenu(e);
}

repositionMenu(e: DOMEvent<HTMLDivElement>) {
this._menuElm.style.top = `${(e as any).pageY - 10}px`;
this._menuElm.style.left = `${(e as any).pageX - 10}px`;
this._menuElm.style.maxHeight = `${document.body.clientHeight - (e as any).pageY - 10}px`;
this._menuElm.style.visibility = 'visible';
this._menuElm.appendChild(this._listElm);
}

updateColumnOrder() {
// Because columns can be reordered, we have to update the `columns` to reflect the new order, however we can't just take `grid.getColumns()`,
// as it does not include columns currently hidden by the picker. We create a new `columns` structure by leaving currently-hidden
// columns in their original ordinal position and interleaving the results of the current column sort.
const current = this.grid.getColumns().slice(0);
const ordered = new Array(this.columns.length);

for (let i = 0; i < ordered.length; i++) {
const columnIdx = this.grid.getColumnIndex(this.columns[i].id);
if (columnIdx === undefined) {
// if the column doesn't return a value from getColumnIndex, it is hidden. Leave it in this position.
ordered[i] = this.columns[i];
} else {
// otherwise, grab the next visible column.
ordered[i] = current.shift();
}
}

// the new set of ordered columns becomes the new set of column picker columns
this._columns = ordered;
}

/** Update the Titles of each sections (command, customTitle, ...) */
updateAllTitles(options: ColumnPickerOption) {
if (this._columnTitleElm?.textContent && options.columnTitle) {
this._columnTitleElm.textContent = options.columnTitle;
}
}

updateColumn(e: DOMEvent<HTMLInputElement>) {
if (e.target.dataset.option === 'autoresize') {
// when calling setOptions, it will resize with ALL Columns (even the hidden ones)
// we can avoid this problem by keeping a reference to the visibleColumns before setOptions and then setColumns after
const previousVisibleColumns = this.getVisibleColumns();
const isChecked = e.target.checked;
this.grid.setOptions({ forceFitColumns: isChecked });
this.grid.setColumns(previousVisibleColumns);
return;
}

if (e.target.dataset.option === 'syncresize') {
this.grid.setOptions({ syncColumnCellResize: !!(e.target.checked) });
return;
}

if (e.target.type === 'checkbox') {
const isChecked = e.target.checked;
const columnId = e.target.dataset.columnid || '';
const visibleColumns: Column[] = [];
this.columnCheckboxes.forEach((columnCheckbox: HTMLInputElement, idx: number) => {
if (columnCheckbox.checked) {
visibleColumns.push(this.columns[idx]);
}
});

if (!visibleColumns.length) {
e.target.checked = true;
return;
}

this.grid.setColumns(visibleColumns);

// keep reference to the updated visible columns list
if (Array.isArray(visibleColumns) && visibleColumns.length !== this.sharedService.visibleColumns.length) {
this.sharedService.visibleColumns = visibleColumns;
}

// when using row selection, SlickGrid will only apply the "selected" CSS class on the visible columns only
// and if the row selection was done prior to the column being shown then that column that was previously hidden (at the time of the row selection)
// will not have the "selected" CSS class because it wasn't visible at the time.
// To bypass this problem we can simply recall the row selection with the same selection and that will trigger a re-apply of the CSS class
// on all columns including the column we just made visible
if (this.gridOptions.enableRowSelection && isChecked) {
const rowSelection = this.grid.getSelectedRows();
this.grid.setSelectedRows(rowSelection);
}

// if we're using frozen columns, we need to readjust pinning when the new hidden column becomes visible again on the left pinning container
// we need to readjust frozenColumn index because SlickGrid freezes by index and has no knowledge of the columns themselves
const frozenColumnIndex = this.gridOptions.frozenColumn ?? -1;
if (frozenColumnIndex >= 0) {
this.extensionUtility.readjustFrozenColumnIndexWhenNeeded(frozenColumnIndex, this.columns, visibleColumns);
}

const callbackArgs = {
columnId,
showing: isChecked,
allColumns: this.columns,
visibleColumns,
columns: visibleColumns,
grid: this.grid
};

// execute user callback when defined
this.pubSubService.publish('columnPicker:onColumnsChanged', callbackArgs);
if (typeof this.controlOptions?.onColumnsChanged === 'function') {
this.controlOptions.onColumnsChanged(e, callbackArgs);
}
}
}

/** Translate the Column Picker headers and also the last 2 checkboxes */
translateColumnPicker() {
// update the properties by pointers, that is the only way to get Column Picker Control to see the new values
Expand Down
Loading

0 comments on commit 18d74ba

Please sign in to comment.