Skip to content

Commit

Permalink
Ability to hide rows/cols with UI toggle (#480)
Browse files Browse the repository at this point in the history
* Fix the behavior of the conext menu item used to hide column

* Add new attributes and styles for hide contiguous columns/rows

* Update context menu to support hiding multiple columns

* Update event handlers for unhide indicator

* Rename `strokeLines` to `drawLines`

* Fix unresolved merge conflict

* Add methods and properties for the feature of unhide indicator

* Implement the render of the unhide indicator

* Update the render of unhide indicators on row headers

* Add util function `mergeHiddenRowRanges` and unit tests.

* Update methods and properties for hide/unhide feature

* Add `hiddenRowRanges`

* Update `getFilteredAndSortedViewData` to support hidden rows

* Add public methods `hideRows` and `unhideRows`

* Update context menu for hiding rows

* Add method `getSelectedContiguousRows`

* Update click event handler to support unhide rows

* Update `draw.js` to support hidden rows

* Add `currentRowIndexOffset` and `rowIndexOffsetByHiddenRows` to make the current row index model is compatible with hidden rows.

* Hide the row gap indicator(a bold black line) for the hidden rows.

* Update `draw.js` for unhide indicator

* Fix jsDoc

* Extract a new method `refreshScrollCacheX` from the method `resize`

* And call this method in the `stopDragMove` to fix incorrect values in `scrollCache.x`

* Fix `Cannot read 'context' of undefined`

* Add method `hideColumns`

* Fix the relationship between unhide indicator and dragging

* Fix generating codes of`visibleUnhideIndicators`

* Fix incorrect indexes in row headers when the first row is hidden

* Add tests for unhide indicator

* Add util functions for tests

* Fix tests for unhide indicator
  • Loading branch information
hangxingliu authored Feb 17, 2022
1 parent 7465002 commit 50c3f0e
Show file tree
Hide file tree
Showing 13 changed files with 963 additions and 93 deletions.
2 changes: 1 addition & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = function (config) {
served: true,
},
{
pattern: 'lib/events/util.js',
pattern: 'lib/**/util.js',
type: 'module',
included: true,
served: true,
Expand Down
223 changes: 176 additions & 47 deletions lib/contextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -538,8 +538,114 @@ export default function (self) {
});
}
}

/**
* Return a tuple if the user selected contiguous columns, otherwise `null`.
* Info: Because the user may reorder the columns,
* the schemaIndex of the first item may be greater than the schemaIndex of the second item,
* but the columnIndex of the firs item must less than the columnIndex of the second item.
* @param {any[]} schema from `self.getSchema()`
* @returns {any[]} column schemas tuple (each schema has an additional field `schemaIndex`)
*/
function getSelectedContiguousColumns(ev, schema) {
const memoKey = '__contiguousColumns';
if (Array.isArray(ev[memoKey]) || ev[memoKey] === null) return ev[memoKey];
ev[memoKey] = null;

if (!Array.isArray(self.selections) || self.selections.length === 0) return;
const selection = self.selections[0];
if (!selection || selection.length === 0) return;
for (let rowIndex = 0; rowIndex < self.viewData.length; rowIndex++) {
const row = self.viewData[rowIndex];
if (!row) continue;
const compare = self.selections[rowIndex];
if (!compare) return;
if (compare.length !== selection.length) return;
for (let i = 0; i < selection.length; i++)
if (selection[i] !== compare[i]) return;
}
selection.sort((a, b) => a - b);

/** @type {number[][]} */
const ranges = [];
let begin = selection[0];
let end = selection[0];
for (let i = 1; i < selection.length; i++) {
const orderIndex = selection[i];
if (orderIndex === end + 1) {
end = orderIndex;
continue;
}
ranges.push([begin, end]);
begin = orderIndex;
end = orderIndex;
}
ranges.push([begin, end]);

const currentOrderIndex = ev.cell.columnIndex;
const matchedRange = ranges.find(
(range) =>
currentOrderIndex >= range[0] &&
currentOrderIndex <= range[1] &&
range[0] !== range[1],
);
if (!matchedRange) return;

/** @type {number[]} orders[index] => columnIndex */
const orders = self.orders.columns;
if (!Array.isArray(orders)) return;

const matchedSchema = matchedRange.map((orderIndex) => {
const schemaIndex = orders[orderIndex];
const thisSchema = schema[schemaIndex];
return Object.assign({}, thisSchema, { orderIndex });
});
if (matchedSchema.findIndex((it) => !it) >= 0) return;
return (ev[memoKey] = matchedSchema);
}
/**
* @param {boolean} [allowOnlyOneRow]
* @returns {number[]} a rowIndex tuple. It can contains one row index or two row indexes.
*/
function getSelectedContiguousRows(allowOnlyOneRow) {
let range = [];
let prev = -2;
let ok = true;
self.selections.forEach(function (row, index) {
if (!ok) return;
if (prev < -1) {
prev = index;
range[0] = index;
return;
}
if (index !== prev + 1 || !row || row.length === 0) {
ok = false;
return;
}
prev = index;
range[1] = index;
});
if (ok) {
if (range.length === 1) return allowOnlyOneRow ? range : null;
return range;
}
}
function addDefaultContextMenuItem(e) {
var isNormalCell =
const schema = self.getSchema();
/**
* A map between columnIndex and column data
* @type {Map<string,any>}
*/
let columns;
const getColumnsMap = () => {
if (!columns)
columns = new Map(schema.map((_col) => [_col.columnIndex, _col]));
return columns;
};
const isSorting =
self.orderings.columns && self.orderings.columns.length > 0;

const isNormalCell =
!(
e.cell.isBackground ||
e.cell.isColumnHeaderCellCap ||
Expand Down Expand Up @@ -614,20 +720,30 @@ export default function (self) {
const columnOrderIndex = e.cell.columnIndex;
const columnIndex = self.orders.columns[columnOrderIndex];

const contiguousColumns = getSelectedContiguousColumns(e, schema);
let title = '';
if (contiguousColumns) {
title = contiguousColumns
.map((col) => col.title || col.name)
.join('-');
} else {
const column = schema[columnIndex];
if (column) title = column.title || column.name;
}
e.items.push({
title: self.attributes.hideColumnText.replace(
/%s/gi,
e.cell.header.title || e.cell.header.name,
),
title: self.attributes.hideColumnText.replace(/%s/gi, title),
click: function (ev) {
self.getSchema()[columnIndex].hidden = true;
ev.preventDefault();
self.stopPropagation(ev);
self.disposeContextMenu();
self.setStorageData();
setTimeout(function () {
self.resize(true);
}, 10);
if (contiguousColumns) {
self.hideColumns(
contiguousColumns[0].orderIndex,
contiguousColumns[1].orderIndex,
);
} else {
self.hideColumns(columnOrderIndex);
}
},
});
}
Expand Down Expand Up @@ -689,6 +805,51 @@ export default function (self) {
},
});
}

//#region hide rows
const canHideRows = !isSorting && e.cell.isRowHeader && e.cell.header;
if (canHideRows) {
const range = getSelectedContiguousRows(true);
if (range) {
const boundRowIndexes = range.map((viewRowIndex) =>
self.getBoundRowIndexFromViewRowIndex(viewRowIndex),
);
let title;
if (boundRowIndexes.length === 1) {
if (typeof boundRowIndexes[0] === 'number')
title = boundRowIndexes[0] + 1;
else title = range[0] + 1;

title = self.attributes.showHideRow.replace('%s', title);
// hide one row
e.items.push({
title,
click: function (ev) {
ev.preventDefault();
self.hideRows(boundRowIndexes[0], boundRowIndexes[0]);
},
});
} else if (boundRowIndexes[0] <= boundRowIndexes[1]) {
title = boundRowIndexes
.map((it, index) => {
if (typeof it === 'number') return it + 1;
return range[index] + 1;
})
.join('-');
title = self.attributes.showHideRows.replace('%s', title);
// hide rows
e.items.push({
title,
click: function (ev) {
ev.preventDefault();
self.hideRows(boundRowIndexes[0], boundRowIndexes[1]);
},
});
}
}
}
//#endregion hide rows

//#region group/ungroup columns
const groupAreaHeight = self.getColumnGroupAreaHeight();
const groupAreaWidth = self.getRowGroupAreaWidth();
Expand Down Expand Up @@ -760,28 +921,13 @@ export default function (self) {
const canUngroupColumns =
self.attributes.allowGroupingColumns && e.cell.isColumnHeader;
const canGroupByRows =
self.attributes.allowGroupingRows && e.cell.isRowHeader && e.cell.header;
!isSorting &&
self.attributes.allowGroupingRows &&
e.cell.isRowHeader &&
e.cell.header;
const canUngroupRows =
self.attributes.allowGroupingRows && e.cell.isRowHeader;

/**
* The value for storing the return value from `self.getSchema()`
* @type {any[]}
*/
let schema;
/**
* A map between columnIndex and column data
* @type {Map<string,any>}
*/
let columns;
const getColumnsMap = () => {
if (!columns) {
if (!schema) schema = self.getSchema();
columns = new Map(schema.map((_col) => [_col.columnIndex, _col]));
}
return columns;
};

if (canGroupByColumns) {
/** @type {number[]} */
const groupIndexes = [];
Expand Down Expand Up @@ -863,23 +1009,7 @@ export default function (self) {
}
}
if (canGroupByRows) {
let range = [];
let prev = -2;
let ok = true;
self.selections.forEach(function (row, index) {
if (!ok) return;
if (prev < -1) {
prev = index;
range[0] = index;
return;
}
if (index !== prev + 1 || !row || row.length === 0) {
ok = false;
return;
}
prev = index;
range[1] = index;
});
const range = getSelectedContiguousRows(false) || [];
const rangeTitle = range
.map((rowIndex) => {
const index = self.getBoundRowIndexFromViewRowIndex(rowIndex);
Expand All @@ -888,7 +1018,6 @@ export default function (self) {
})
.join('-');
if (
ok &&
range.length === 2 &&
self.isNewGroupRangeValid(self.groupedRows, range[0], range[1])
) {
Expand Down
8 changes: 8 additions & 0 deletions lib/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export default function (self) {
['filterFrozenRows', true],
['globalRowResize', false],
['hideColumnText', 'Hide %s'],
['showUnhideColumnsIndicator', false],
['showUnhideRowsIndicator', false],
['showHideRow', 'Hide row %s'],
['showHideRows', 'Hide rows %s'],
['hoverMode', 'cell'],
['keepFocusOnMouseOut', false],
['maxAutoCompleteItems', 200],
Expand Down Expand Up @@ -377,6 +381,10 @@ export default function (self) {
['treeArrowMarginTop', 6],
['treeArrowWidth', 13],
['treeGridHeight', 250],
['unhideIndicatorColor', 'rgba(0, 0, 0, 1)'],
['unhideIndicatorBackgroundColor', 'rgba(255, 255, 255, 1)'],
['unhideIndicatorBorderColor', 'rgba(174, 193, 232, 1)'],
['unhideIndicatorSize', 16],
['width', 'auto'],
],
};
Expand Down
Loading

0 comments on commit 50c3f0e

Please sign in to comment.