P8-Panels/app/components/p8p_table_reducer.js

219 lines
9.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Парус 8 - Панели мониторинга
Компонент: Таблица - редьюсер состояния
*/
//---------
//Константы
//---------
//Типы действий
const P8P_TABLE_AT = {
SET_HEADER: "SET_HEADER", //Установка заголовка таблицы
TOGGLE_HEADER_EXPAND: "TOGGLE_HEADER_EXPAND" //Сворачивание/разворачивание уровня заголовка
};
//Состояние заголовка таблицы по умолчанию
const HEADER_INITIAL_STATE = () => ({
columnsDef: [],
displayLevels: [],
displayLevelsColumns: {},
displayDataColumnsCount: 0,
displayDataColumns: [],
displayFixedColumnsCount: 0
});
//Состояние описания ячейки заголовка таблицы по умолчанию
const HEADER_COLUMN_INITIAL_STATE = ({ columnDef, objectsCopier }) => {
const tmp = objectsCopier(columnDef);
if (!hasValue(tmp.parent)) tmp.parent = "";
if (!hasValue(tmp.expandable)) tmp.expandable = false;
if (!hasValue(tmp.expanded)) tmp.expanded = true;
if (!hasValue(tmp.fixed)) tmp.fixed = false;
if (!hasValue(tmp.fixedLeft)) tmp.fixedLeft = 0;
return tmp;
};
//--------------------------------
//Вспомогательные классы и функции
//--------------------------------
//Проверка существования значения
const hasValue = value => typeof value !== "undefined" && value !== undefined && value !== null && value !== "";
//Определение высоты (в уровнях) ячейки заголовка
const getDisplayColumnRowSpan = (displayTree, maxLevel) => {
displayTree.forEach(columnDef => {
columnDef.rowSpan = columnDef.hasChild ? maxLevel - columnDef.childMaxLevel + 1 : maxLevel - columnDef.level + 1;
if (columnDef.hasChild) getDisplayColumnRowSpan(columnDef.child, maxLevel);
});
};
//Определение ширины (в колонках) ячейки заголовка
const getDisplayColumnColSpan = (displayTree, columnDef) => {
if (columnDef.hasChild) {
let colSpan = 0;
displayTree.forEach(cD => (cD.parent == columnDef.name ? (colSpan += getDisplayColumnColSpan(cD.child, cD)) : null));
return colSpan;
} else return 1;
};
//Определения признака зафиксированности колонки
const getFixedColumns = (displayTree, parentFixed, parentLeft, fixedColumns) => {
if (fixedColumns) {
let left = parentLeft;
displayTree.forEach((columnDef, i) => {
left += columnDef.width;
if ((columnDef.level == 1 && i + 1 <= fixedColumns) || (columnDef.level > 1 && parentFixed)) {
columnDef.fixed = true;
columnDef.fixedLeft = left - columnDef.width;
} else columnDef.fixed = false;
if (columnDef.hasChild) getFixedColumns(columnDef.child, columnDef.fixed, columnDef.fixedLeft, fixedColumns);
});
}
};
//Формирование дерева отображаемых элементов заголовка
const buildDisplayTree = (columnsDef, parent, level, expandable, fixedColumns) => {
const baseBuild = (columnsDef, parent, level) => {
let maxLevel = level - 1;
const res = columnsDef
.filter(columnDef => columnDef.parent == parent && columnDef.visible)
.map(columnDef => {
const [child, childMaxLevel] = columnDef.expanded ? baseBuild(columnsDef, columnDef.name, level + 1) : [[], level];
if (childMaxLevel > maxLevel) maxLevel = childMaxLevel;
const res = {
...columnDef,
child,
hasChild: child.length > 0 ? true : false,
level,
childMaxLevel: child.length > 0 ? childMaxLevel : 0
};
return { ...res, colSpan: getDisplayColumnColSpan(child, res), rowSpan: 1 };
});
return [res, maxLevel];
};
const [displayTree, maxLevel] = baseBuild(columnsDef, parent, level);
getDisplayColumnRowSpan(displayTree, maxLevel);
getFixedColumns(displayTree, false, expandable ? 60 : 0, fixedColumns);
return [displayTree, maxLevel];
};
//Формирование коллекции отображаемых колонок уровня
const buildDisplayLevelsColumns = (displayTree, maxLevel) => {
const extractLevel = (displayTree, level) => {
let res = [];
displayTree.forEach(columnDef => {
if (columnDef.level == level) res.push(columnDef);
if (columnDef.hasChild) res = res.concat(extractLevel(columnDef.child, level));
});
return res;
};
const displayLevels = [...Array(maxLevel).keys()].map(i => i + 1);
const displayLevelsColumns = {};
displayLevels.forEach(level => (displayLevelsColumns[level] = extractLevel(displayTree, level)));
return [displayLevels, displayLevelsColumns];
};
//Формирование коллекции отображаемых колонок данных
const buildDisplayDataColumns = (displayTree, expandable) => {
const displayDataColumns = [];
const traverseTree = displayTree => {
displayTree.forEach(columnDef => (!columnDef.hasChild ? displayDataColumns.push(columnDef) : traverseTree(columnDef.child)));
};
traverseTree(displayTree);
return [displayDataColumns, displayDataColumns.length + (expandable === true ? 1 : 0)];
};
//Подсчёт количества отображаемых фиксированных колонок
const getDisplayFixedColumnsCount = displayTree => {
let res = 0;
const traverseTree = displayTree => {
displayTree.forEach(columnDef => (columnDef.hasChild ? traverseTree(columnDef.child) : columnDef.fixed ? res++ : null));
};
traverseTree(displayTree);
return res;
};
//Формирование описания отображаемых колонок
const buildDisplay = ({ columnsDef, expandable, fixedColumns }) => {
//Сформируем дерево отображаемых колонок заголовка
const [displayTree, maxLevel] = buildDisplayTree(columnsDef, "", 1, expandable, fixedColumns);
//Вытянем дерево в удобные для рендеринга структуры
const [displayLevels, displayLevelsColumns] = buildDisplayLevelsColumns(displayTree, maxLevel);
//Сформируем отображаемые колонки данных
const [displayDataColumns, displayDataColumnsCount] = buildDisplayDataColumns(displayTree, expandable);
//Подсчитаем количество отображаемых фиксированных колонок
const displayFixedColumnsCount = getDisplayFixedColumnsCount(displayTree);
//Вернём результат
return [displayLevels, displayLevelsColumns, displayDataColumns, displayDataColumnsCount, displayFixedColumnsCount];
};
//Формирование описания заголовка
const buildHeaderDef = ({ columnsDef, expandable, fixedColumns, objectsCopier }) => {
//Инициализируем результат
const res = HEADER_INITIAL_STATE();
//Инициализируем внутренне описание колонок и поместим его в результат
columnsDef.forEach(columnDef => res.columnsDef.push(HEADER_COLUMN_INITIAL_STATE({ columnDef, objectsCopier })));
//Добавим в результат сведения об отображаемых данных
[res.displayLevels, res.displayLevelsColumns, res.displayDataColumns, res.displayDataColumnsCount, res.displayFixedColumnsCount] = buildDisplay({
columnsDef: res.columnsDef,
expandable,
fixedColumns
});
//Вернём результат
return res;
};
//-----------
//Тело модуля
//-----------
//Обработчики действий
const handlers = {
//Формирование заголовка
[P8P_TABLE_AT.SET_HEADER]: (state, { payload }) => {
const { columnsDef, expandable, fixedColumns, objectsCopier } = payload;
return {
...state,
...buildHeaderDef({ columnsDef, expandable, fixedColumns, objectsCopier })
};
},
[P8P_TABLE_AT.TOGGLE_HEADER_EXPAND]: (state, { payload }) => {
const { columnName, expandable, fixedColumns, objectsCopier } = payload;
const columnsDef = objectsCopier(state.columnsDef);
columnsDef.forEach(columnDef => (columnDef.name == columnName ? (columnDef.expanded = !columnDef.expanded) : null));
const [displayLevels, displayLevelsColumns, displayDataColumns, displayDataColumnsCount, displayFixedColumnsCount] = buildDisplay({
columnsDef,
expandable,
fixedColumns
});
return {
...state,
columnsDef,
displayLevels,
displayLevelsColumns,
displayDataColumns,
displayDataColumnsCount,
displayFixedColumnsCount
};
},
//Обработчик по умолчанию
DEFAULT: state => state
};
//----------------
//Интерфейс модуля
//----------------
//Константы
export { P8P_TABLE_AT, HEADER_INITIAL_STATE, hasValue };
//Редьюсер состояния
export const p8pTableReducer = (state, action) => {
//Подберём обработчик
const handle = handlers[action.type] || handlers.DEFAULT;
//Исполним его
return handle(state, action);
};