@true-directive/base
Version:
The set of base classes for the TrueDirective Grid
530 lines (529 loc) • 22.8 kB
JavaScript
/**
* Copyright (c) 2018-2019 Aleksey Melnikov, True Directive Company.
* @link https://truedirective.com/
* @license MIT
*/
import { GridPart } from './enums';
import { Column } from './column.class';
import { ColumnBand } from './column-band.class';
import { GridLayoutSelection } from './grid-layout-selection.class';
/**
* Разметка секции грида.
* Содержит колонки только своей секции
* Пересчитывает их ширину
* Содержит информацию о выделенных областях своей секции
* @return [description]
*/
var GridLayout = /** @class */ (function () {
function GridLayout(place) {
this.place = place;
// Уровни группировки генерируются вместе с первой видимой колонкой, но не
// считая чекбокса.
this.groupLevels = false;
this.columns = [];
this.bands = [];
this.selection = new GridLayoutSelection();
// Вспомогательное поле для идентификации лэйаута
this.tag = '';
this._levelColumns = [];
this._autoWidth = false;
}
Object.defineProperty(GridLayout.prototype, "levelColumns", {
// Список виртуальных колонок для отступов
get: function () {
return this._levelColumns;
},
enumerable: true,
configurable: true
});
Object.defineProperty(GridLayout.prototype, "isLeft", {
// Находится ли эта разметка слева
get: function () {
return this.place === GridPart.LEFT;
},
enumerable: true,
configurable: true
});
Object.defineProperty(GridLayout.prototype, "isCenter", {
// Находится ли эта разметка в основной части
get: function () {
return this.place === GridPart.CENTER;
},
enumerable: true,
configurable: true
});
Object.defineProperty(GridLayout.prototype, "isRight", {
// Находится ли эта разметка справа
get: function () {
return this.place === GridPart.RIGHT;
},
enumerable: true,
configurable: true
});
// Обновить список полей группировки
GridLayout.prototype.updateGroupedColumns = function (groupedColumns) {
var _this = this;
if (groupedColumns === null) {
this._levelColumns = [];
return;
}
var lc = [];
if (groupedColumns.length > 0) {
lc.push(new Column('_', '', this._levelIndent)); // Для стрелки
}
groupedColumns.forEach(function (col, index) {
lc.push(new Column('__group_' + index, '', _this._levelIndent));
});
this._levelColumns = lc;
};
// Обновление отступов для дерева
GridLayout.prototype.updateTreeColumns = function (maxLevel) {
var lc = [];
if (maxLevel > 0) {
// Для стрелки
lc.push(new Column('_', '', this._levelIndent));
}
for (var i = 0; i < maxLevel; i++) {
lc.push(new Column('__group_' + i, '', this._levelIndent));
}
this._levelColumns = lc;
};
Object.defineProperty(GridLayout.prototype, "levelsWidth", {
// Суммарная ширина отступов
get: function () {
return this._levelColumns.length * this._levelIndent;
},
enumerable: true,
configurable: true
});
// Ширина колонки
GridLayout.prototype.columnDataWidth = function (col) {
return col.width;
};
// Ширина колонки в заголовке
GridLayout.prototype.columnHeaderWidth = function (col) {
// Отступы уровней добавляем к первой не чекбоксовой колонке
for (var i = 0; i < this.columns.length; i++) {
var c = this.columns[i];
if (c.isCheckbox) {
continue;
}
// Если это убрать, то нужно сделать синхронно с row.directive
if (c === col) {
return (col.displayedWidth + this.levelsWidth);
}
break;
}
return col.displayedWidth;
};
GridLayout.prototype.displayedHeaderWidth = function (col) {
// Если нет группировок или уровней, то легко
if (this._levelColumns.length === 0) {
return col.displayedWidthU;
}
return this.columnHeaderWidth(col) + this._widthUnit;
};
Object.defineProperty(GridLayout.prototype, "headerWidth", {
// Суммарная ширина всех заголовков
get: function () {
if (this.place === GridPart.CENTER) {
var ww = this.totalWidth + this.levelsWidth;
if (this._widthUnit === 'px') {
ww += 96;
}
else {
ww += 10;
}
return ww + this._widthUnit;
}
return this.totalWidth + this.levelsWidth + this._widthUnit;
},
enumerable: true,
configurable: true
});
Object.defineProperty(GridLayout.prototype, "dataWidth", {
// Суммарная ширина данных
get: function () {
var ww = this.totalWidth + this.levelsWidth;
if (this._autoWidth) {
return ww + 'px';
}
return ww + this._widthUnit;
},
enumerable: true,
configurable: true
});
// Количество видимых колонок с учетом временно вынесенной в панель группировки
GridLayout.prototype.visibleColumnCount = function (groupedTemp) {
var res = 0;
for (var i = 0; i < this.columns.length; i++) {
var col = this.columns[i];
if (groupedTemp && groupedTemp.fieldName === col.fieldName) {
continue;
}
if (col.visible) {
res++;
}
}
return res;
};
// Обновление разметки
GridLayout.prototype.update = function (columns, widthUnit, levelIndent, clientWidth, autoWidth) {
if (widthUnit === void 0) { widthUnit = 'px'; }
if (levelIndent === void 0) { levelIndent = 0; }
if (clientWidth === void 0) { clientWidth = 0; }
if (autoWidth === void 0) { autoWidth = false; }
this._levelIndent = levelIndent;
this._widthUnit = widthUnit;
this.bands = [];
this.columns = [];
var gLevels = false;
if (columns !== undefined) {
var currentBand = '';
var currentBandColumns = [];
var hasBands = false;
var bandWidth = 0;
var isFirstColumn = true;
for (var _i = 0, columns_1 = columns; _i < columns_1.length; _i++) {
var column = columns_1[_i];
if (!column.visible) {
continue;
}
if (!column.isCheckbox && isFirstColumn) {
if (column.fixed === this.place) {
gLevels = true;
}
isFirstColumn = false;
}
if (column.fixed !== this.place && this.place !== GridPart.GROUPED_COLUMN) {
// в сгруппированном всегда показываем
continue;
}
var ww = column.width;
if (column.isCheckbox) {
ww = this._levelIndent;
}
this.columns.push(column);
if (column.band !== currentBand) {
if (currentBand) {
var band = new ColumnBand(currentBand, currentBandColumns, bandWidth);
this.bands.push(band);
}
hasBands = true;
currentBandColumns = [];
currentBand = column.band;
bandWidth = 0;
}
currentBandColumns.push(column);
bandWidth += ww;
}
if (hasBands) {
this.bands.push(new ColumnBand(currentBand, currentBandColumns, bandWidth));
}
}
this.resize(widthUnit, levelIndent, clientWidth, autoWidth);
this.groupLevels = gLevels;
};
// Минимальная ширина с заданным набором колонок
GridLayout.prototype.minWidth = function () {
var res = this.levelsWidth;
this.columns.forEach(function (c) { return res += c.autoWidthFixed ? c.width : (c.autoWidthMin ? c.autoWidthMin : 0); });
return res;
};
// Убираем одну колонку с конца с наименьшим приоритетом
GridLayout.prototype.removeColumnWithLowPriority = function () {
var min = undefined;
this.columns.forEach(function (c) {
if (min === undefined || c.autoWidthPriority < min) {
min = c.autoWidthPriority;
}
});
if (min === undefined) {
return false;
}
var _loop_1 = function (i) {
var c = this_1.columns[i];
if (c.autoWidthPriority === min) {
this_1.columns.splice(i, 1);
// Убираем колонку из бэнда и сам бэнд, если он опустел
var band = this_1.bands.find(function (b) { return b.columns.indexOf(c) >= 0; });
if (band) {
band.removeColumn(c);
if (band.columns.length === 0) {
this_1.bands.splice(this_1.bands.indexOf(band), 1);
}
}
return { value: true };
}
};
var this_1 = this;
for (var i = this.columns.length - 1; i >= 0; i--) {
var state_1 = _loop_1(i);
if (typeof state_1 === "object")
return state_1.value;
}
return false;
};
GridLayout.prototype.fixedSize = function (c) {
// autoWidthMin может быть не задана для чекбокса, например
if (c.autoWidthFixed) {
return c.width;
}
return isNaN(c.autoWidthMin) ? c.width : c.autoWidthMin;
};
//
GridLayout.prototype.calcWidth = function (fixedSizeColumns, clientWidth) {
var _this = this;
var res = [];
var fixedSize = 0;
fixedSizeColumns.forEach(function (c) { return fixedSize += _this.fixedSize(c); });
var remains = clientWidth - fixedSize - this.levelsWidth;
var totalWidth = 0;
this.columns.forEach(function (c) {
if (fixedSizeColumns.indexOf(c) < 0) {
totalWidth += c.width;
}
});
this.columns.forEach(function (c) {
if (fixedSizeColumns.indexOf(c) >= 0) {
res.push({ c: c, width: _this.fixedSize(c) });
}
else {
res.push({ c: c, width: Math.floor(c.width * remains / totalWidth) });
}
});
return res;
};
// Автоматический пересчет ширины колонок
GridLayout.prototype.resize = function (widthUnit, levelIndent, clientWidth, autoWidth) {
this._autoWidth = autoWidth;
this.totalWidth = 0;
if (this.columns.length === 0) {
return;
}
var sizes = [];
if (autoWidth) {
// Удалим колонки, которые никак не влезут
var res = true;
while (autoWidth && this.minWidth() > clientWidth && res) {
res = this.removeColumnWithLowPriority();
}
// Последовательно фиксируем ширину колонок, если расчетная меньше минимальной
var fixedCols_1 = [];
var recalc_1 = true;
while (autoWidth && recalc_1) {
recalc_1 = false;
sizes = this.calcWidth(fixedCols_1, clientWidth);
sizes.some(function (s) {
// Проверяем, устроит ли нас длина
if (fixedCols_1.indexOf(s.c) < 0
&& (s.width < s.c.autoWidthMin || s.c.autoWidthFixed || s.c.isCheckbox)) {
fixedCols_1.push(s.c); // Не устроила
recalc_1 = true;
return true;
}
return false;
});
}
}
// Фиксируем
var newTotalWidth = 0;
var firstCol = true;
var _loop_2 = function (i) {
var c = this_2.columns[i];
c.displayedWidth = c.width;
c.displayedWidthU = c.width + this_2._widthUnit;
if (autoWidth) {
c.displayedWidth = sizes.find(function (s) { return s.c === c; }).width;
c.displayedWidthU = c.displayedWidth + 'px';
}
if (firstCol && !c.isCheckbox) {
c.headerWidth = c.displayedWidth + this_2.levelsWidth;
firstCol = false;
}
else {
c.headerWidth = c.displayedWidth;
}
newTotalWidth += c.displayedWidth;
};
var this_2 = this;
for (var i = 0; i < this.columns.length; i++) {
_loop_2(i);
}
// Отброшенные дробные части плюсуем к первой колонке
if (autoWidth && this.columns.length > 0) {
this.columns[0].displayedWidth += clientWidth - newTotalWidth - this.levelsWidth;
this.columns[0].displayedWidthU = this.columns[0].displayedWidth + 'px';
}
this.totalWidth = newTotalWidth;
};
/**
* Is it possible to reorder the column at the specified coordinates?
* @param mouseAction User action info with mouse coordinates
* @param items List of column headers or bands
* @param r0 Header bounding rectangle
* @param hasL We have part positioned to left of this part
* @param hasR We have part positioned to right of this part
* @param columns Columns collection of the grid
* @return Possibility of reordering, position
*/
GridLayout.prototype.canDrop = function (mouseAction, items, r0, hasL, hasR, columns) {
var result = null;
var tg = mouseAction.target;
if (mouseAction.y < r0.top) {
return result;
}
var isColumn = tg instanceof Column;
var isBand = tg instanceof ColumnBand;
if (mouseAction.y < r0.top) {
return result;
}
if (items.length === 0 && mouseAction.x >= r0.left && mouseAction.x < r0.right) {
return { inColumns: isColumn, item: null, pos: 'left', place: this.place };
}
var mrX = 0;
var cbCol = columns.prevCheckbox(tg);
// Необходимо перебрать колонки и понять, сможем ли мы бросить сюда наш заголовок.
for (var i = 0; i < items.length; i++) {
var isFirst = i === 0;
var isLast = i === items.length - 1;
var rr = items[i].boundingRect;
var item = items[i].item;
if (mouseAction.inItemRect(r0, rr)) {
// проверяем, можно ли вставить колонку сюда..
var canDropLeft = true;
var canDropRight = true;
if (isColumn && tg.fieldName === item.fieldName) {
// Навели на себя же
canDropLeft = false;
canDropRight = false;
}
if (cbCol && cbCol.fieldName === item.fieldName) {
// Навели на чекбокс, который прилеплен к перетаскиваемой колонке
canDropLeft = false;
canDropRight = false;
}
if (item.isCheckbox && i < items.length - 1) {
canDropRight = false; // между чекбоксом и норм столбцом не вклиниваемся
}
// Не самый первый элемент
if (!isFirst) {
var prevItem = items[i - 1].item;
// Простая проверка - для колонки
if (isColumn && prevItem.fieldName === tg.fieldName) {
canDropLeft = false;
}
if (isColumn && prevItem.isCheckbox) {
if (cbCol && cbCol.fieldName === prevItem.fieldName) {
canDropLeft = false;
}
}
if (isBand && tg.columns[tg.columns.length - 1].fieldName === prevItem.columns[prevItem.columns.length - 1].fieldName) {
canDropLeft = false;
}
}
// Бэнд
if (isBand) {
// Нельзя бросить бэнд слева от себя
if (tg.columns[0].fieldName === item.columns[0].fieldName) {
canDropLeft = false;
}
// Нельзя бросить справа от себя
if (tg.columns[tg.columns.length - 1].fieldName === item.columns[item.columns.length - 1].fieldName) {
canDropRight = false;
}
//
if (tg.columns[0].fieldName === item.columns[item.columns.length - 1].fieldName) {
canDropRight = false;
}
if (tg.columns[tg.columns.length - 1].fieldName === item.columns[0].fieldName) {
canDropLeft = false;
}
}
// Не последний элемент
if (!isLast) {
var nextItem = items[i + 1].item;
if (isColumn && nextItem.fieldName === tg.fieldName) {
canDropRight = false;
}
if (isBand && tg.columns[0].fieldName === nextItem.columns[0].fieldName) {
canDropRight = false;
}
if (isColumn && nextItem.isCheckbox) {
if (cbCol && cbCol.fieldName === nextItem.fieldName) {
canDropRight = false;
}
}
}
// Если мы вписываемся в наш компонент, то показываем сразу..
// Иначе нам нужно скрыть и немного проскроллить..
var showMarker = false;
if ((mouseAction.x - rr.left < rr.width / 2 || item.isCheckbox) && canDropLeft) {
if (i > 0 && items[i - 1].item.isCheckbox) {
// Колонка с чекбоксом неразделимы
item = items[i - 1].item;
rr = items[i - 1].boundingRect;
}
mrX = rr.left - 1;
showMarker = true;
result = { inColumns: isColumn, item: item, pos: 'left' };
}
else if (mouseAction.x - rr.left >= rr.width / 2 && canDropRight) {
mrX = rr.right - 1;
showMarker = true;
result = { inColumns: isColumn, item: item, pos: 'right' };
}
return result;
}
}
return result;
};
/**
* Returns the column index in the column list by field name
* @param fieldName Name of the field to be searched
* @return Column index
*/
GridLayout.columnIndex = function (layouts, fieldName) {
var i = 0;
var res = -1;
layouts.forEach(function (l) {
for (var j = 0; j < l.columns.length && res < 0; j++) {
if (l.columns[j].fieldName === fieldName) {
res = i;
break;
}
i++;
}
});
return res;
};
GridLayout.columnByIndex = function (layouts, index) {
var i = 0;
var res = null;
layouts.forEach(function (l) {
for (var j = 0; j < l.columns.length; j++) {
if (i === index) {
res = l.columns[j];
break;
}
i++;
}
});
return res;
};
GridLayout.columnCount = function (layouts) {
var res = 0;
layouts.forEach(function (l) {
res += l.columns.length;
});
return res;
};
GridLayout.firstColumn = function (layouts) {
return GridLayout.columnByIndex(layouts, 0);
};
GridLayout.lastColumn = function (layouts) {
return GridLayout.columnByIndex(layouts, GridLayout.columnCount(layouts) - 1);
};
return GridLayout;
}());
export { GridLayout };