Skip to content

Commit

Permalink
Fix several bugs related to multiple selection
Browse files Browse the repository at this point in the history
 - Add support to the Walkontable Selection for marking intersection
   with CSS class names in continuous manner ('area-1', 'area-2'...)
 - Optimize clearing CSS classes belongs to Selection
 - Add CSS classes to support multiple selections and normalized their
   colors
 - Adjust ContextMenu actions to make them work in multiple selections
 - Add support for enabling/disabling multiple selection via new
   property 'selectionMode' (posible values: 'single', 'contiguous',
'multiple')
 - Remove 'multiSelect' option. Move to "selectionMode:
   'single'/'contiguous'"
 - Fix keyStateObserver which holds key states after window blur
 - Extend '.alter' method for supporting new arguments which allows
   removing rows and columns in batches

Issue: #4708
  • Loading branch information
budnix committed Feb 9, 2018
1 parent 92d2d81 commit 7532b79
Show file tree
Hide file tree
Showing 21 changed files with 505 additions and 171 deletions.
54 changes: 51 additions & 3 deletions src/3rdparty/walkontable/src/selection.js
@@ -1,4 +1,4 @@
import {addClass} from './../../../helpers/dom/element';
import {addClass, hasClass} from './../../../helpers/dom/element';
import Border from './border';
import CellCoords from './cell/coords';
import CellRange from './cell/range';
Expand All @@ -15,6 +15,8 @@ class Selection {
this.settings = settings;
this.cellRange = cellRange || null;
this.instanceBorders = {};
this.classNames = [this.settings.className];
this.classNameGenerator = this.linearClassNameGenerator(this.settings.className, this.settings.layerLevel);
}

/**
Expand Down Expand Up @@ -115,17 +117,63 @@ class Selection {
* @param {Number} sourceRow Cell row coord
* @param {Number} sourceColumn Cell column coord
* @param {String} className Class name
* @param {Boolean} [markIntersections=false] If `true`, linear className generator will be used to add CSS classes
* in a continuous manner.
*/
addClassAtCoords(wotInstance, sourceRow, sourceColumn, className) {
addClassAtCoords(wotInstance, sourceRow, sourceColumn, className, markIntersections = false) {
let TD = wotInstance.wtTable.getCell(new CellCoords(sourceRow, sourceColumn));

if (typeof TD === 'object') {
if (markIntersections) {
className = this.classNameGenerator(TD);

if (!this.classNames.includes(className)) {
this.classNames.push(className);
}
}
addClass(TD, className);
}

return this;
}

/**
* Generate helper for calculating classNames based on previously added base className.
* The generated className is always generated as a continuation of the previous className. For example, when
* the currently checked element has 'area-2' className the generated new className will be 'area-3'. When
* the element doesn't have any classNames than the base className will be returned ('area');
*
* @param {String} baseClassName Base className to be used.
* @param {Number} layerLevelOwner Layer level which the instance of the Selection belongs to.
* @return {Function}
*/
linearClassNameGenerator(baseClassName, layerLevelOwner) {
// TODO(budnix): Make this recursive function Proper Tail Calls (TCO/PTC) friendly.
return function calcClassName(element, previousIndex = -1) {
if (layerLevelOwner === 0 || previousIndex === 0) {
return baseClassName;
}

let index = previousIndex >= 0 ? previousIndex : layerLevelOwner;
let className = baseClassName;

index--;

const previousClassName = index === 0 ? baseClassName : `${baseClassName}-${index}`;

if (hasClass(element, previousClassName)) {
const currentLayer = index + 1;

className = `${baseClassName}-${currentLayer}`;

} else {
className = calcClassName(element, index);
}

return className;
};
}

/**
* @param wotInstance
*/
Expand Down Expand Up @@ -193,7 +241,7 @@ class Selection {
if (sourceRow >= corners[0] && sourceRow <= corners[2] && sourceCol >= corners[1] && sourceCol <= corners[3]) {
// selected cell
if (this.settings.className) {
this.addClassAtCoords(wotInstance, sourceRow, sourceCol, this.settings.className);
this.addClassAtCoords(wotInstance, sourceRow, sourceCol, this.settings.className, this.settings.markIntersections);
}
} else if (sourceRow >= corners[0] && sourceRow <= corners[2]) {
// selection is in this row
Expand Down
45 changes: 34 additions & 11 deletions src/3rdparty/walkontable/src/table.js
Expand Up @@ -302,6 +302,11 @@ class Table {
}
}

/**
* Refresh the table selection by re-rendering Selection instances connected with that instance.
*
* @param {Boolean} fastDraw If fast drawing is enabled than additionally className clearing is applied.
*/
refreshSelections(fastDraw) {
if (!this.wot.selections) {
return;
Expand All @@ -310,24 +315,42 @@ class Table {
const len = highlights.length;

if (fastDraw) {
// TODO(budnix): Non-consecutive cells feature doubles iteration while removing classNames from the DOM elements.
// This line should be optmized to remove specyfied className only once.
const classesToRemove = [];

for (let i = 0; i < len; i++) {
// there was no rerender, so we need to remove classNames by ourselves
if (highlights[i].settings.className) {
this.removeClassFromCells(highlights[i].settings.className);
const {
highlightHeaderClassName,
highlightRowClassName,
highlightColumnClassName,
} = highlights[i].settings;
const classNames = highlights[i].classNames;
const classNamesLength = classNames.length;

for (let j = 0; j < classNamesLength; j++) {
if (!classesToRemove.includes(classNames[j])) {
classesToRemove.push(classNames[j]);
}
}
if (highlights[i].settings.highlightHeaderClassName) {
this.removeClassFromCells(highlights[i].settings.highlightHeaderClassName);

if (highlightHeaderClassName && !classesToRemove.includes(highlightHeaderClassName)) {
classesToRemove.push(highlightHeaderClassName);
}
if (highlights[i].settings.highlightRowClassName) {
this.removeClassFromCells(highlights[i].settings.highlightRowClassName);
if (highlightRowClassName && !classesToRemove.includes(highlightRowClassName)) {
classesToRemove.push(highlightRowClassName);
}
if (highlights[i].settings.highlightColumnClassName) {
this.removeClassFromCells(highlights[i].settings.highlightColumnClassName);
if (highlightColumnClassName && !classesToRemove.includes(highlightColumnClassName)) {
classesToRemove.push(highlightColumnClassName);
}
}

const classesToRemoveLength = classesToRemove.length;

for (let i = 0; i < classesToRemoveLength; i++) {
// there was no rerender, so we need to remove classNames by ourselves
this.removeClassFromCells(classesToRemove[i]);
}
}

for (let i = 0; i < len; i++) {
highlights[i].draw(this.wot, fastDraw);
}
Expand Down

0 comments on commit 7532b79

Please sign in to comment.