@adaptabletools/adaptable-cjs
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
521 lines (520 loc) • 26 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DualListBoxEditor = void 0;
const tslib_1 = require("tslib");
const React = tslib_1.__importStar(require("react"));
const react_1 = require("react");
const Enums_1 = require("../../../AdaptableState/Common/Enums");
const ListBoxFilterSortComponent_1 = require("./ListBoxFilterSortComponent");
const ArrayExtensions_1 = require("../../../Utilities/Extensions/ArrayExtensions");
const ListGroupItem_1 = tslib_1.__importDefault(require("../../../components/List/ListGroupItem"));
const SimpleButton_1 = tslib_1.__importDefault(require("../../../components/SimpleButton"));
const rebass_1 = require("rebass");
const Panel_1 = tslib_1.__importDefault(require("../../../components/Panel"));
const ListGroup_1 = tslib_1.__importDefault(require("../../../components/List/ListGroup"));
const SelectableList_1 = tslib_1.__importDefault(require("../../../components/SelectableList"));
const useLatest_1 = require("../../../components/utils/useLatest");
const listGroupStyle = {
overflowY: 'auto',
marginBottom: '0px',
};
const listGroupItemStyle = {
fontSize: 'small',
padding: 'var(--ab-space-1)',
};
const ButtonDirection = (props) => (React.createElement(SimpleButton_1.default, { ...props, style: { whiteSpace: 'nowrap', justifyContent: 'center', ...props.style } }));
const DualListBoxEditor = (props) => {
const [placeholder] = (0, react_1.useState)(() => {
let placeholder = document.createElement('button');
placeholder.className = 'placeholder';
placeholder.classList.add('list-group-item');
placeholder.type = 'button';
return placeholder;
});
const refDraggedHTMLElement = (0, react_1.useRef)(null);
const refDraggedElement = (0, react_1.useRef)(null);
const refOverHTMLElement = (0, react_1.useRef)(null);
const refFirstSelected = (0, react_1.useRef)(null);
const createAvailableValuesList = (availableValues, sortOrder) => {
return ArrayExtensions_1.ArrayExtensions.sortArray(availableValues, sortOrder);
};
const [state, updateState] = (0, react_1.useState)(() => {
let availableValues = new Array();
props.AvailableValues.forEach((x) => {
if (props.SelectedValues.indexOf(x) < 0) {
availableValues.push(x);
}
});
return {
SelectedValues: props.SelectedValues,
AvailableValues: createAvailableValuesList(availableValues, Enums_1.SortOrder.Asc),
UiSelectedSelectedValues: [],
UiSelectedAvailableValues: [],
FilterValue: '',
SelectedValuesFilterValue: '',
SortOrder: Enums_1.SortOrder.Asc,
SelectedValuesSortOrder: Enums_1.SortOrder.Asc,
AllValues: props.AvailableValues,
};
});
const callbackRefs = (0, react_1.useRef)([]);
const getProps = (0, useLatest_1.useLatest)(props);
const setState = (newState, callback) => {
updateState({ ...state, ...newState });
if (newState.SelectedValues && props.SelectedValues !== newState.SelectedValues) {
getProps().onChange(newState.SelectedValues);
}
if (callback) {
callbackRefs.current.push(callback);
}
};
(0, react_1.useEffect)(() => {
const callbacks = [...callbackRefs.current];
callbacks.forEach((fn) => fn());
callbackRefs.current = [];
});
(0, react_1.useEffect)(() => {
const availableValues = [];
props.AvailableValues.forEach((x) => {
if (props.SelectedValues.indexOf(x) < 0) {
availableValues.push(x);
}
});
setState({
SelectedValues: props.SelectedValues,
AvailableValues: createAvailableValuesList(availableValues, state.SortOrder),
});
}, [props.SelectedValues, props.AvailableValues]);
const isValueFilteredOut = (item, FilterValue = state.FilterValue) => {
return (FilterValue != '' && item.toLocaleLowerCase().indexOf(FilterValue.toLocaleLowerCase()) < 0);
};
const canGoTopOrUp = () => {
return (state.UiSelectedSelectedValues.length != 0 &&
state.UiSelectedSelectedValues.every((x) => state.SelectedValues.indexOf(x) > 0));
};
const canGoDownOrBottom = () => {
return (state.UiSelectedSelectedValues.length != 0 &&
state.UiSelectedSelectedValues.every((x) => state.SelectedValues.indexOf(x) < state.SelectedValues.length - 1));
};
const ensureFirstSelectedItemVisible = (top) => {
var itemComponentDOMNode = refFirstSelected.current;
if (itemComponentDOMNode) {
itemComponentDOMNode.scrollIntoView(top);
}
};
const Top = () => {
let newSelectedValues = [].concat(state.UiSelectedSelectedValues, state.SelectedValues.filter((x) => state.UiSelectedSelectedValues.indexOf(x) < 0));
setState({
SelectedValues: newSelectedValues,
UiSelectedSelectedValues: [],
}, () => {
ensureFirstSelectedItemVisible(true);
});
};
const Up = () => {
let newSelectedValues = [...state.SelectedValues];
for (let selElement of state.UiSelectedSelectedValues) {
let index = newSelectedValues.indexOf(selElement);
ArrayExtensions_1.ArrayExtensions.moveArray(newSelectedValues, index, index - 1);
}
setState({
SelectedValues: newSelectedValues,
}, () => {
ensureFirstSelectedItemVisible(false);
});
};
const Bottom = () => {
let newSelectedValues = [].concat(state.SelectedValues.filter((x) => state.UiSelectedSelectedValues.indexOf(x) < 0), state.UiSelectedSelectedValues);
setState({
SelectedValues: newSelectedValues,
UiSelectedSelectedValues: [],
}, () => {
ensureFirstSelectedItemVisible(true);
});
};
const Down = () => {
let newSelectedValues = [...state.SelectedValues];
for (var index = state.UiSelectedSelectedValues.length - 1; index >= 0; index--) {
let indexglob = newSelectedValues.indexOf(state.UiSelectedSelectedValues[index]);
ArrayExtensions_1.ArrayExtensions.moveArray(newSelectedValues, indexglob, indexglob + 1);
}
setState({
SelectedValues: newSelectedValues,
}, () => {
ensureFirstSelectedItemVisible(false);
});
};
const Add = () => {
let newSelectedValues = [...state.SelectedValues];
let newAvailableValues = [...state.AvailableValues];
let valuesToAdd = state.UiSelectedAvailableValues;
valuesToAdd.forEach((x) => {
let index = newAvailableValues.indexOf(x);
newAvailableValues.splice(index, 1);
newSelectedValues.push(x);
});
newAvailableValues = createAvailableValuesList(newAvailableValues, state.SortOrder);
setState({
UiSelectedAvailableValues: [],
SelectedValues: newSelectedValues,
AvailableValues: newAvailableValues,
});
};
const AddAll = () => {
let newSelectedValues = [].concat(state.SelectedValues);
let valuesToAdd = state.AvailableValues;
valuesToAdd.forEach((x) => {
newSelectedValues.push(x);
});
setState({
UiSelectedSelectedValues: [],
UiSelectedAvailableValues: [],
SelectedValues: newSelectedValues,
AvailableValues: [],
});
};
const RemoveAll = () => {
let newSelectedValues = [];
let newAvailableValues = [].concat(state.AllValues);
newAvailableValues = createAvailableValuesList(newAvailableValues, state.SortOrder);
setState({
UiSelectedSelectedValues: [],
UiSelectedAvailableValues: [],
SelectedValues: newSelectedValues,
AvailableValues: newAvailableValues,
});
};
const Remove = () => {
let newSelectedValues = [...state.SelectedValues];
let newAvailableValues = [...state.AvailableValues];
state.UiSelectedSelectedValues.forEach((x) => {
let index = newSelectedValues.indexOf(x);
newSelectedValues.splice(index, 1);
let originalItem = state.AllValues.find((y) => y == x);
if (originalItem) {
newAvailableValues.push(originalItem);
}
});
newAvailableValues = createAvailableValuesList(newAvailableValues, state.SortOrder);
setState({
UiSelectedSelectedValues: [],
SelectedValues: newSelectedValues,
AvailableValues: newAvailableValues,
});
};
const DragSelectedStart = (e, listElement) => {
refDraggedHTMLElement.current = e.currentTarget;
refDraggedElement.current = listElement;
};
const DragSelectedEnd = () => {
if (refOverHTMLElement.current && refDraggedElement.current) {
//now we need to check in which drop area we dropped the selected item
let to;
let from = state.SelectedValues.indexOf(refDraggedElement.current);
let newSelectedArray;
let newAvailableValues;
if (refOverHTMLElement.current.classList.contains('Available')) {
to = state.AvailableValues.indexOf(refOverHTMLElement.current.innerText);
newSelectedArray = [...state.SelectedValues];
newSelectedArray.splice(from, 1);
newAvailableValues = [...state.AvailableValues];
let originalItem = state.AllValues.find((y) => y == refDraggedElement.current);
if (originalItem) {
let checkForExistig = newAvailableValues.find((x) => x == originalItem);
if (!checkForExistig) {
newAvailableValues.splice(to, 0, originalItem);
}
}
}
else if (refOverHTMLElement.current.classList.contains('ab-AvailableDropZone')) {
newSelectedArray = [...state.SelectedValues];
newSelectedArray.splice(from, 1);
newAvailableValues = [...state.AvailableValues];
let originalItem = state.AllValues.find((y) => y == refDraggedElement.current);
if (originalItem) {
let checkForExistig = newAvailableValues.find((x) => x == originalItem);
if (!checkForExistig) {
newAvailableValues.push(originalItem);
}
}
}
else if (refOverHTMLElement.current.classList.contains('Selected')) {
to = state.SelectedValues.indexOf(refOverHTMLElement.current.innerText);
newSelectedArray = [...state.SelectedValues];
newSelectedArray.splice(from, 1);
newSelectedArray.splice(to, 0, refDraggedElement.current);
newAvailableValues = [...state.AvailableValues];
}
else if (refOverHTMLElement.current.classList.contains('ab-SelectedDropZone')) {
newSelectedArray = [...state.SelectedValues];
newSelectedArray.splice(from, 1);
newSelectedArray.push(refDraggedElement.current);
newAvailableValues = [...state.AvailableValues];
}
//We remove our awesome placeholder
if (refOverHTMLElement.current.classList.contains('ab-SelectedDropZone') ||
refOverHTMLElement.current.classList.contains('ab-AvailableDropZone')) {
refOverHTMLElement.current.removeChild(placeholder);
}
else {
refOverHTMLElement.current.parentNode.removeChild(placeholder);
}
refOverHTMLElement.current = null;
refDraggedHTMLElement.current = null;
refDraggedElement.current = null;
// Update state
newAvailableValues = createAvailableValuesList(newAvailableValues, state.SortOrder);
setState({
SelectedValues: newSelectedArray,
AvailableValues: newAvailableValues,
UiSelectedSelectedValues: [],
UiSelectedAvailableValues: [],
});
}
};
const DragAvailableStart = (e, listElement) => {
refDraggedHTMLElement.current = e.currentTarget;
refDraggedElement.current = listElement;
};
const DragAvailableEnd = () => {
if (refOverHTMLElement.current && refDraggedElement.current) {
let to;
let from = state.AvailableValues.indexOf(refDraggedElement.current);
let newSelectedArray;
let newAvailableValues;
if (refOverHTMLElement.current.classList.contains('Selected')) {
from = state.AvailableValues.indexOf(refDraggedElement.current);
to = state.SelectedValues.indexOf(refOverHTMLElement.current.innerText);
newSelectedArray = [...state.SelectedValues];
newSelectedArray.splice(to, 0, refDraggedElement.current);
newAvailableValues = [...state.AvailableValues];
newAvailableValues.splice(from, 1);
}
else if (refOverHTMLElement.current.classList.contains('ab-SelectedDropZone')) {
newSelectedArray = [...state.SelectedValues];
newSelectedArray.push(refDraggedElement.current);
newAvailableValues = [...state.AvailableValues];
newAvailableValues.splice(from, 1);
}
//We remove our awesome placeholder
if (refOverHTMLElement.current.classList.contains('ab-SelectedDropZone')) {
refOverHTMLElement.current.removeChild(placeholder);
}
else {
refOverHTMLElement.current.parentNode.removeChild(placeholder);
}
refOverHTMLElement.current = null;
refDraggedHTMLElement.current = null;
refDraggedElement.current = null;
// Update state
setState({
SelectedValues: newSelectedArray,
AvailableValues: newAvailableValues,
UiSelectedSelectedValues: [],
UiSelectedAvailableValues: [],
});
}
};
const DragEnterAvailable = (e) => {
e.preventDefault();
e.stopPropagation();
};
const DragOverAvailable = (e) => {
e.preventDefault();
e.stopPropagation();
//we can only drop selected data into available
if (!refDraggedHTMLElement.current.classList.contains('Selected')) {
e.dataTransfer.dropEffect = 'none';
return;
}
let targetElement = e.target;
//we want to keep the reference of the last intem we were over to
if (targetElement.classList.contains('placeholder')) {
return;
}
refOverHTMLElement.current = targetElement;
if (refOverHTMLElement.current.classList.contains('ab-AvailableDropZone')) {
targetElement.appendChild(placeholder);
}
else {
targetElement.parentNode.insertBefore(placeholder, targetElement);
}
};
const DragLeaveAvailable = (e) => {
e.preventDefault();
e.stopPropagation();
let targetElement = e.target;
if (targetElement.classList.contains('ab-AvailableDropZone') ||
targetElement.classList.contains('placeholder')) {
if (refOverHTMLElement.current) {
if (refOverHTMLElement.current.classList.contains('ab-AvailableDropZone')) {
refOverHTMLElement.current.removeChild(placeholder);
}
else {
refOverHTMLElement.current.parentNode.removeChild(placeholder);
}
refOverHTMLElement.current = null;
}
}
};
const DragEnterSelected = (e) => {
e.preventDefault();
e.stopPropagation();
};
const DragOverSelected = (e) => {
e.preventDefault();
e.stopPropagation();
let targetElement = e.target;
//we want to keep the reference of the last intem we were over to
if (targetElement.classList.contains('placeholder')) {
return;
}
refOverHTMLElement.current = targetElement;
if (refOverHTMLElement.current.classList.contains('ab-SelectedDropZone')) {
targetElement.appendChild(placeholder);
}
else {
targetElement.parentNode.insertBefore(placeholder, targetElement);
}
};
const DragLeaveSelected = (e) => {
e.preventDefault();
e.stopPropagation();
let targetElement = e.target;
if (targetElement.classList.contains('ab-SelectedDropZone') ||
targetElement.classList.contains('placeholder')) {
if (refOverHTMLElement.current) {
if (refOverHTMLElement.current.classList.contains('ab-SelectedDropZone')) {
refOverHTMLElement.current.removeChild(placeholder);
}
else {
refOverHTMLElement.current.parentNode.removeChild(placeholder);
}
refOverHTMLElement.current = null;
}
}
};
const handleChangeFilterValue = (x) => {
setState({
FilterValue: x,
});
};
const handleChangeSelectedValuesFilterValue = (x) => {
setState({
SelectedValuesFilterValue: x,
});
};
const sortColumnValues = () => {
if (state.SortOrder == Enums_1.SortOrder.Asc) {
setState({
AvailableValues: ArrayExtensions_1.ArrayExtensions.sortArray(state.AvailableValues, Enums_1.SortOrder.Desc),
SortOrder: Enums_1.SortOrder.Desc,
});
}
else {
setState({
AvailableValues: ArrayExtensions_1.ArrayExtensions.sortArray(state.AvailableValues, Enums_1.SortOrder.Asc),
SortOrder: Enums_1.SortOrder.Asc,
});
}
};
const sortSelectedColumnValues = () => {
if (state.SelectedValuesSortOrder == Enums_1.SortOrder.Asc) {
setState({
SelectedValues: ArrayExtensions_1.ArrayExtensions.sortArray(state.SelectedValues, Enums_1.SortOrder.Desc),
SelectedValuesSortOrder: Enums_1.SortOrder.Desc,
});
}
else {
setState({
SelectedValues: ArrayExtensions_1.ArrayExtensions.sortArray(state.SelectedValues, Enums_1.SortOrder.Asc),
SelectedValuesSortOrder: Enums_1.SortOrder.Asc,
});
}
};
const getSelectedItemId = (index) => {
const item = state.SelectedValues[index];
if (!item) {
return -1;
}
let display = item;
if (isValueFilteredOut(display, state.SelectedValuesFilterValue)) {
return -1;
}
return item;
};
const onSelectedListSelectionChange = (selection) => {
const UiSelectedSelectedValues = Object.keys(selection);
UiSelectedSelectedValues.sort((a, b) => state.SelectedValues.indexOf(a) - state.SelectedValues.indexOf(b));
setState({ UiSelectedSelectedValues });
};
const getAvailableItemId = (index) => {
const item = state.AvailableValues[index];
if (!item) {
return -1;
}
let display = item;
let value = item;
if (isValueFilteredOut(display)) {
return -1;
}
return value;
};
/**
* @param selection - is a map, values being item keys (their textual representation), while values being true
*/
const onAvailableListSelectionChange = (selection) => {
const UiSelectedAvailableValues = Object.keys(selection);
const availableValues = state.AvailableValues;
UiSelectedAvailableValues.sort((a, b) => availableValues.indexOf(a) - availableValues.indexOf(b));
setState({ UiSelectedAvailableValues });
};
let setRefFirstSelectedSelected = true;
// build selected elements
const selectedElements = state.SelectedValues.map((x, index) => {
let isActive = state.UiSelectedSelectedValues.indexOf(x) >= 0;
if (isValueFilteredOut(x, state.SelectedValuesFilterValue)) {
return null;
}
const result = (React.createElement(ListGroupItem_1.default, { key: `${x}-1`, index: index, className: "Selected", draggable: true, style: listGroupItemStyle, active: isActive, ref: isActive && setRefFirstSelectedSelected ? refFirstSelected : null, onDragStart: (event) => DragSelectedStart(event, x), onDragEnd: () => DragSelectedEnd(), value: x }, x));
if (isActive && setRefFirstSelectedSelected) {
setRefFirstSelectedSelected = false;
}
return result;
});
// build available elements
const availableElements = state.AvailableValues.map((x, index) => {
let isActive = state.UiSelectedAvailableValues.indexOf(x) >= 0;
let value = x;
if (isValueFilteredOut(x)) {
return null;
}
else {
return (React.createElement(ListGroupItem_1.default, { className: "Available", style: listGroupItemStyle, active: isActive, index: index, draggable: true, key: `${value}-item`, onDragStart: (event) => DragAvailableStart(event, x), onDragEnd: () => DragAvailableEnd(), value: value }, x));
}
});
const headerFirstListBox = (React.createElement(ListBoxFilterSortComponent_1.ListBoxFilterSortComponent, { FilterValue: state.FilterValue, sortColumnValues: sortColumnValues, SortOrder: state.SortOrder, handleChangeFilterValue: handleChangeFilterValue }));
const headerSecondListBox = (React.createElement(ListBoxFilterSortComponent_1.ListBoxFilterSortComponent, { FilterValue: state.SelectedValuesFilterValue, sortColumnValues: sortSelectedColumnValues, SortOrder: state.SelectedValuesSortOrder, handleChangeFilterValue: handleChangeSelectedValuesFilterValue }));
return (React.createElement(rebass_1.Flex, { alignItems: "stretch", flexDirection: "row", className: "ab-DualListBoxEditor", style: { ...props.style, maxHeight: '100%', width: '100%' } },
React.createElement(Panel_1.default, { header: props.HeaderAvailable, className: "ab-DualListBoxEditor__source", bodyProps: { padding: 0 }, marginRight: 2, style: { flex: '4 0 0%' }, bodyScroll: true },
headerFirstListBox,
React.createElement(SelectableList_1.default, { getItemId: getAvailableItemId, onSelectedChange: onAvailableListSelectionChange },
React.createElement(ListGroup_1.default, { className: "ab-AvailableDropZone", style: listGroupStyle, onDragEnter: (event) => DragEnterAvailable(event), onDragOver: (event) => DragOverAvailable(event), onDragLeave: (event) => DragLeaveAvailable(event) }, availableElements))),
React.createElement(rebass_1.Flex, { flexDirection: "column", justifyContent: "center", className: "ab-DualListBoxEditor__action-buttons" },
React.createElement(ButtonDirection, { "data-name": "add-all", marginBottom: 2, icon: "fast-forward", iconPosition: "end", disabled: state.AvailableValues.length == 0, onClick: () => AddAll() }, "Add All"),
React.createElement(ButtonDirection, { "data-name": "add", iconPosition: "end", icon: 'arrow-right', marginBottom: 3, disabled: state.UiSelectedAvailableValues.length == 0, onClick: () => Add() }, "Add"),
React.createElement(ButtonDirection, { "data-name": "remove", icon: 'arrow-left', marginBottom: 2, iconPosition: "start", disabled: state.UiSelectedSelectedValues.length == 0, onClick: () => Remove() }, "Remove"),
React.createElement(ButtonDirection, { "data-name": "remove-all", marginBottom: 2, icon: "fast-backward", iconPosition: "start", disabled: state.SelectedValues.length == 0, onClick: () => RemoveAll() }, "Remove All")),
React.createElement(Panel_1.default, { header: props.HeaderSelected, className: "ab-DualListBoxEditor__destination", bodyScroll: true, bodyProps: {
padding: 0,
}, style: { flex: '4 0 0%' }, marginLeft: 2, marginRight: 2 },
headerSecondListBox,
React.createElement(SelectableList_1.default, { getItemId: getSelectedItemId, onSelectedChange: onSelectedListSelectionChange },
React.createElement(ListGroup_1.default, { style: listGroupStyle, className: "ab-SelectedDropZone", onDragEnter: (event) => DragEnterSelected(event), onDragOver: (event) => DragOverSelected(event), onDragLeave: (event) => DragLeaveSelected(event) }, selectedElements))),
React.createElement(rebass_1.Flex, { flexDirection: "column", justifyContent: "center", className: "ab-DualListBoxEditor__order-buttons" },
React.createElement(ButtonDirection, { "data-name": "top", marginBottom: 2, iconPosition: "start", icon: "triangle-up", disabled: !canGoTopOrUp(), onClick: () => Top() }, "Top"),
React.createElement(ButtonDirection, { "data-name": "up", marginBottom: 2, iconPosition: "start", icon: "arrow-up", disabled: !canGoTopOrUp(), onClick: () => Up() }, "Up"),
React.createElement(ButtonDirection, { "data-name": "down", marginBottom: 2, icon: "arrow-down", iconPosition: "start", disabled: !canGoDownOrBottom(), onClick: () => Down() }, "Down"),
React.createElement(ButtonDirection, { "data-name": "bottom", marginBottom: 2, icon: "triangle-down", iconPosition: "start", disabled: !canGoDownOrBottom(), onClick: () => Bottom() }, "Bottom"))));
};
exports.DualListBoxEditor = DualListBoxEditor;