Skip to content

Commit

Permalink
ColumnSorting refactor & changes for MultiColumnSorting (#5411)
Browse files Browse the repository at this point in the history
Refactored `ColumnSorting` plugin and prepared some changes for MultiColumnSorting.
  • Loading branch information
wszymanski committed Sep 26, 2018
1 parent 50cdd82 commit 5ee45d5
Show file tree
Hide file tree
Showing 32 changed files with 2,527 additions and 1,237 deletions.
40 changes: 24 additions & 16 deletions handsontable.d.ts
Expand Up @@ -694,23 +694,16 @@ declare namespace Handsontable {
toggleCollapsibleSection(coords: object, action: string): void;
}

type SortOrderType = 'asc' | 'desc' | 'none';

interface ColumnSortingRowsMapper extends arrayMapper {
columnSorting: ColumnSorting;

createMap(length?: number): void;
destroy(): void;
}
type SortOrderType = 'asc' | 'desc';
type ColumnSortConfig = { column: number, sortOrder: SortOrderType }

interface ColumnSorting extends Base {
sortColumn: undefined | number;
sortEmptyCells: boolean;
sortOrder: SortOrderType;
clearSort(): void;
destroy(): void;
getSortConfig(column?: number): void | ColumnSortConfig | Array<ColumnSortConfig>
isSorted(): boolean;
loadSortingState(): any;
saveSortingState(): void;
sort(column: number, order?: SortOrderType): void;
setSortConfigs(sortConfigs: Array<ColumnSortConfig>): void;
sort(sortConfig?: ColumnSortConfig): void;
}

interface ColumnSummary extends Base {
Expand Down Expand Up @@ -1097,6 +1090,18 @@ declare namespace Handsontable {
moveColumn(from: number, to: number): void;
}

type MultiSortOrderType = 'asc' | 'desc';
type MultiColumnSortConfig = { column: number, sortOrder: MultiSortOrderType }

interface MultiColumnSorting extends Base {
clearSort(): void;
destroy(): void;
getSortConfig(column?: number): void | MultiColumnSortConfig | Array<MultiColumnSortConfig>
isSorted(): boolean;
setSortConfigs(sortConfigs: Array<MultiColumnSortConfig>): void;
sort(sortConfig?: MultiColumnSortConfig | Array<MultiColumnSortConfig>): void;
}

interface TrimRowsMapper extends arrayMapper {
trimRows: TrimRows;

Expand Down Expand Up @@ -1489,6 +1494,7 @@ declare namespace Handsontable {
minRows?: number;
minSpareCols?: number;
minSpareRows?: number;
multiColumnSorting?: boolean | object;
selectionMode?: 'single' | 'range' | 'multiple';
nestedHeaders?: any[]; // pro
noWordWrapClassName?: string;
Expand Down Expand Up @@ -1544,7 +1550,7 @@ declare namespace Handsontable {
afterChangesObserved?: () => void;
afterColumnMove?: (startColumn: number, endColumn: number) => void;
afterColumnResize?: (currentColumn: number, newSize: number, isDoubleClick: boolean) => void;
afterColumnSort?: (column: number, order: plugins.SortOrderType) => void;
afterColumnSort?: (currentSortConfig: object[], destinationSortConfigs: object[]) => void;
afterContextMenuDefaultOptions?: (predefinedItems: any[]) => void;
afterContextMenuHide?: (context: object) => void;
beforeContextMenuShow?: (context: object) => void;
Expand Down Expand Up @@ -1612,7 +1618,7 @@ declare namespace Handsontable {
beforeChangeRender?: (changes: any[], source: string) => void;
beforeColumnMove?: (startColumn: number, endColumn: number) => void;
beforeColumnResize?: (currentColumn: number, newSize: number, isDoubleClick: boolean) => void;
beforeColumnSort?: (column: number, order: plugins.SortOrderType) => void;
beforeColumnSort?: (currentSortConfig: object[], destinationSortConfigs: object[]) => void;
beforeContextMenuSetItems?: (menuItems: any[]) => void;
beforeCopy?: (data: any[], coords: any[]) => any;
beforeCreateCol?: (index: number, amount: number, source?: string) => void;
Expand Down Expand Up @@ -1934,6 +1940,7 @@ declare namespace Handsontable {
ManualRowMove: plugins.ManualRowMove,
ManualRowResize: plugins.ManualRowResize;
MergeCells: plugins.MergeCells;
MultiColumnSorting: plugins.MultiColumnSorting,
MultipleSelectionHandles: plugins.MultipleSelectionHandles,
NestedHeaders: plugins.NestedHeaders,
NestedRows: plugins.NestedRows,
Expand Down Expand Up @@ -1971,6 +1978,7 @@ declare namespace Handsontable {
manualRowMove: plugins.ManualRowMove,
manualRowResize: plugins.ManualRowResize;
mergeCells: plugins.MergeCells;
multiColumnSorting: plugins.MultiColumnSorting,
multipleSelectionHandles: plugins.MultipleSelectionHandles,
nestedHeaders: plugins.NestedHeaders,
nestedRows: plugins.NestedRows,
Expand Down
6 changes: 4 additions & 2 deletions src/core.js
Expand Up @@ -2713,7 +2713,10 @@ export default function Core(rootElement, userSettings, rootInstanceSymbol = fal

const prop = translateVisualIndexToColumns(physicalColumn);

if (priv.settings.columns && isFunction(priv.settings.columns) && priv.settings.columns(prop) && priv.settings.columns(prop).title) {
if (priv.settings.colHeaders === false) {
result = null;

} else if (priv.settings.columns && isFunction(priv.settings.columns) && priv.settings.columns(prop) && priv.settings.columns(prop).title) {
result = priv.settings.columns(prop).title;

} else if (priv.settings.columns && priv.settings.columns[physicalColumn] && priv.settings.columns[physicalColumn].title) {
Expand All @@ -2727,7 +2730,6 @@ export default function Core(rootElement, userSettings, rootInstanceSymbol = fal

} else if (priv.settings.colHeaders && typeof priv.settings.colHeaders !== 'string' && typeof priv.settings.colHeaders !== 'number') {
result = spreadsheetColumnLabel(baseCol); // see #1458

}
}

Expand Down
49 changes: 39 additions & 10 deletions src/css/handsontable.css
Expand Up @@ -233,23 +233,52 @@
position: relative;
}

.handsontable .columnSorting:hover {
.handsontable .columnSorting.sortAction:hover {
text-decoration: underline;
cursor: pointer;
}

.handsontable .columnSorting.ascending::after {
content: '\25B2';
color: #5f5f5f;
position: absolute;
right: -15px;
.handsontable span.colHeader {
display: inline-block;
line-height: 1.1;
}

.handsontable .columnSorting.descending::after {
content: '\25BC';
color: #5f5f5f;
/* Arrow position */
.handsontable span.colHeader.columnSorting::before {
/* Centering start */
top: 50%;
margin-top: -6px; /* One extra pixel for purpose of proper positioning of sorting arrow, when `font-size` set to default */
/* Centering end */

padding-left: 8px; /* For purpose of continuous mouse over experience, when moving between the `span` and the `::before` elements */
position: absolute;
right: -15px;
right: -9px;

content: '';
height: 10px;
width: 5px;
background-size: contain;
background-repeat: no-repeat;
background-position-x: right;
}

.handsontable span.colHeader.columnSorting.ascending::before {
/* arrow up; 20 x 40 px, scaled to 5 x 10 px; base64 size: 0.3kB */
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAMAAADJ7yrpAAAAKlBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKE86IAAAADXRSTlMABBEmRGprlJW72e77tTkTKwAAAFNJREFUeAHtzjkSgCAUBNHPgsoy97+ulGXRqJE5L+xkxoYt2UdsLb5bqFINz+aLuuLn5rIu2RkO3fZpWENimNgiw6iBYRTPMLJjGFxQZ1hxxb/xBI1qC8k39CdKAAAAAElFTkSuQmCC");
}

.handsontable span.colHeader.columnSorting.descending::before {
/* arrow down; 20 x 40 px, scaled to 5 x 10 px; base64 size: 0.3kB */
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAMAAADJ7yrpAAAAKlBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKE86IAAAADXRSTlMABBEmRGprlJW72e77tTkTKwAAAFJJREFUeAHtzjkSgCAQRNFmQYUZ7n9dKUvru0TmvPAn3br0QfgdZ5xx6x+rQn23GqTYnq1FDcnuzZIO2WmedVqIRVxgGKEyjNgYRjKGkZ1hFIZ3I70LyM0VtU8AAAAASUVORK5CYII=");
}

.htGhostTable .htCore span.colHeader.columnSorting:not(.indicatorDisabled)::after {
content: '*';
display: inline-block;
position: relative;
/* The multi-line header and header with longer text need more padding to not hide arrow,
we make header wider in `GhostTable` to make some space for arrow which is positioned absolutely in the main table */
padding-right: 20px;
}

/* Selection */
Expand Down
138 changes: 84 additions & 54 deletions src/defaultSettings.js
Expand Up @@ -1626,30 +1626,49 @@ DefaultSettings.prototype = {
/**
* @description
* Turns on [Column sorting](https://docs.handsontable.com/demo-sorting-data.html). Can be either a boolean (`true` / `false`) or an object with a declared sorting options:
* * `column` - sorted column
* * `sortOrder` - order in which column will be sorted
* * `'asc'` = ascending
* * `'desc'` = descending
* * `'none'` = original order
* * `initialConfig` - Object with predefined keys:
* * `column` - sorted column
* * `sortOrder` - order in which column will be sorted
* * `'asc'` = ascending
* * `'desc'` = descending
* * `indicator` - display status for sorting order indicator (an arrow icon in the column header, specifying the sorting order).
* * `true` = show sort indicator for sorted columns
* * `false` = don't show sort indicator for sorted columns
* * `headerAction` - allow to click on the headers to sort
* * `true` = turn on possibility to click on the headers to sort
* * `false` = turn off possibility to click on the headers to sort
* * `sortEmptyCells` - how empty values should be handled
* * `true` = the table sorts empty cells
* * `false` = the table moves all empty cells to the end of the table
* * `compareFunctionFactory` - curry function returning compare function; compare function should work in the same way as function which is handled by native `Array.sort` method); please take a look at below examples for more information.
*
* @type {Boolean|Object}
* @default undefined
*
* @example
* ```js
* // as a boolean
* columnSorting: true,
* // as boolean
* columnSorting: true
*
* // as a object with initial order (sort ascending column at index 2)
* // as an object with initial sort config (sort ascending for column at index 1)
* columnSorting: {
* column: 2,
* sortOrder: 'asc',
* sortEmptyCells: true
* },
* ```
* initialConfig: {
* column: 1,
* sortOrder: 'asc'
* }
* }
*
* // as an object which define specific sorting options for all columns
* columnSorting: {
* sortEmptyCells: true, // true = the table sorts empty cells, false = the table moves all empty cells to the end of the table
* indicator: true, // true = shows indicator for all columns, false = don't show indicator for columns
* headerAction: false, // true = allow to click on the headers to sort, false = turn off possibility to click on the headers to sort
* compareFunctionFactory: function(sortOrder, columnMeta) {
* return function(value, nextValue) {
* // Some value comparisons which will return -1, 0 or 1...
* }
* }
* }```
*/
columnSorting: void 0,

Expand Down Expand Up @@ -1752,6 +1771,58 @@ DefaultSettings.prototype = {
*/
mergeCells: false,

/**
* @description
* Turns on [Multi-column sorting](https://docs.handsontable.com/demo-sorting-data.html). Can be either a boolean (`true` / `false`) or an object with a declared sorting options:
* * `initialConfig` - Array containing objects, every with predefined keys:
* * `column` - sorted column
* * `sortOrder` - order in which column will be sorted
* * `'asc'` = ascending
* * `'desc'` = descending
* * `indicator` - display status for sorting order indicator (an arrow icon in the column header, specifying the sorting order).
* * `true` = show sort indicator for sorted columns
* * `false` = don't show sort indicator for sorted columns
* * `headerAction` - allow to click on the headers to sort
* * `true` = turn on possibility to click on the headers to sort
* * `false` = turn off possibility to click on the headers to sort
* * `sortEmptyCells` - how empty values should be handled
* * `true` = the table sorts empty cells
* * `false` = the table moves all empty cells to the end of the table
* * `compareFunctionFactory` - curry function returning compare function; compare function should work in the same way as function which is handled by native `Array.sort` method); please take a look at below examples for more information.
*
* @pro
* @type {Boolean|Object}
* @default undefined
*
* @example
* ```js
* // as boolean
* multiColumnSorting: true
*
* // as an object with initial sort config (sort ascending for column at index 1 and then sort descending for column at index 0)
* multiColumnSorting: {
* initialConfig: [{
* column: 1,
* sortOrder: 'asc'
* }, {
* column: 0,
* sortOrder: 'desc'
* }]
* }
*
* // as an object which define specific sorting options for all columns
* multiColumnSorting: {
* sortEmptyCells: true, // true = the table sorts empty cells, false = the table moves all empty cells to the end of the table
* indicator: true, // true = shows indicator for all columns, false = don't show indicator for columns
* headerAction: false, // true = allow to click on the headers to sort, false = turn off possibility to click on the headers to sort
* compareFunctionFactory: function(sortOrder, columnMeta) {
* return function(value, nextValue) {
* // Some value comparisons which will return -1, 0 or 1...
* }
* }
* }```
*/
multiColumnSorting: void 0,
/**
* @description
* Number of rows to be rendered outside of the visible part of the table. By default, it's set to `'auto'`, which
Expand Down Expand Up @@ -1858,21 +1929,6 @@ DefaultSettings.prototype = {
*/
disableVisualSelection: false,

/**
* Set whether to display the current sorting order indicator (a triangle icon in the column header, specifying the sorting
* order).
*
* @type {Boolean}
* @default undefined
*
* @example
* ```js
* // show sort indicator for sorted columns
* sortIndicator: true,
* ```
*/
sortIndicator: void 0,

/**
* Disables or enables {@link ManualColumnFreeze} plugin.
*
Expand Down Expand Up @@ -2613,32 +2669,6 @@ DefaultSettings.prototype = {
*/
observeChanges: void 0,

/**
* @description
* When passed to the `column` property, allows specifying a custom sorting function for the desired column.
*
* @type {Function}
* @default undefined
*
* @example
* ```js
* columns: [
* {
* sortFunction: function(sortOrder) {
* return function(a, b) {
* // sorting function body.
* //
* // Function parameters:
* // sortOrder: If true, the order is ascending, if false - descending. undefined = original order
* // a, b: Two compared elements. These are 2-element arrays, with the first element being the row index, the second - cell value.
* }
* }
* }
* ],
* ```
*/
sortFunction: void 0,

/**
* If defined as `true`, the Autocomplete's suggestion list would be sorted by relevance (the closer to the left the
* match is, the higher the suggestion).
Expand Down
24 changes: 9 additions & 15 deletions src/pluginHooks.js
Expand Up @@ -563,7 +563,7 @@ const REGISTERED_HOOKS = [
* Fired after calling the `updateSettings` method.
*
* @event Hooks#afterUpdateSettings
* @param {Object} settings New settings object.
* @param {Object} newSettings New settings object.
*/
'afterUpdateSettings',

Expand Down Expand Up @@ -1007,30 +1007,24 @@ const REGISTERED_HOOKS = [
'persistentStateSave',

/**
* Fired by {@link ColumnSorting} plugin before sorting the column. If you return `false` value then sorting
* Fired by {@link ColumnSorting} and {@link MultiColumnSorting} plugin before sorting the column. If you return `false` value then sorting
* will be not applied by the Handsontable (useful for server-side sorting).
*
* This hook is fired when {@link Options#columnSorting} option is enabled.
* This hook is fired when {@link Options#columnSorting} or {@link Options#multiColumnSorting} option is enabled.
*
* @event Hooks#beforeColumnSort
* @param {Number} column Sorted visual column index.
* @param {Boolean} order Soring order where:
* * `asc` means ascending order
* * `desc` means descending order
* * `none` means original order
* @param {Array} currentSortConfig Current sort configuration (for all sorted columns).
* @param {Array} destinationSortConfigs Destination sort configuration (for all sorted columns).
*/
'beforeColumnSort',

/**
* Fired by {@link ColumnSorting} plugin after sorting the column. This hook is fired when {@link Options#columnSorting}
* option is enabled.
* Fired by {@link ColumnSorting} and {@link MultiColumnSorting} plugin after sorting the column. This hook is fired when {@link Options#columnSorting}
* or {@link Options#multiColumnSorting} option is enabled.
*
* @event Hooks#afterColumnSort
* @param {Number} column Sorted visual column index.
* @param {String} order Soring order where:
* * `'asc'` means ascending order
* * `'desc'` means descending order
* * `'none'` means original order
* @param {Array} currentSortConfig Current sort configuration (for all sorted columns).
* @param {Array} destinationSortConfigs Destination sort configuration (for all sorted columns).
*/
'afterColumnSort',

Expand Down

0 comments on commit 5ee45d5

Please sign in to comment.