Сообщение от MC-XOBAHCK
|
Идея как считать высоту и ширину для объединённой ячейки у меня появилась. Буду пробовать.
|
MC-XOBAHCK,
рони, возможно вы захотите проверить, как работает объединение ячеек при следующих условиях.
1. Пусть дана таблица 5 на 5 ячеек. Некоторые ячейки уже объединены: (Это не сложно)
127.0.0.1_37415_select.html.jpg
Если теперь объединить ячейки A и B, то таблица должна по прежнему остаться прямоугольной таблицей!
2. Дана таблица:
127.0.0.1_37415_select.html (1).jpg
Объединив любую ячейку с соседней, у вас по прежнему должна остаться прямоугольная таблица!
Сообщение от MC-XOBAHCK
|
сделать правильное выделение
|
По сути у вас может быть две области выделения. Одна показывает область выделенную при помощи мыши (исходное выделение), другая вычисляется на основе первой: она показывает какие ячейки таблицы будут объединены (вычисленное выделение). Поскольку ячейка таблицы прямоугольная, то и выделенные ячейки представляют из себя прямоугольную область. Отсюда следует, что вам нужно найти такую область, которая больше, чем исходное выделение и равна наименьшей области среди всевозможных выделении, которые целиком вмещают в себя любые ячейки таблицы (т. е. граница вычисленного выделения не проходит по грани какой-либо ячейки, но только по ребрам ячеек, и содержит в себе исходное выделение)
Сообщение от рони
|
смотря какой нужен результат.
|
Поскольку ячейка таблицы прямоугольная, то и выделенные ячейки (из которых можно получить объединённую ячейку) представляют из себя прямоугольную область.
Вот мой вариант. Я ввёл вспомогательный класс Box, который представляет прямоугольные области исходного или вычисленного выделения. После того, как на основе области исходного выделения была найдена область вычисленного выделения, все ячейки кроме одной удаляются, а у оставшейся устанавливаются соответствующие атрибуты colspan и rowspan таким образом, что она целиком заполняет область вычисленного выделения.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<style type="text/css">
table {
height: 500px;
width: 500px;
border-collapse: collapse;
position: relative;
user-select: none;
cursor: crosshair;
table-layout: fixed;
font: 1em Helvetica Neue, Segoe UI, Roboto, Ubuntu, sans-serif;
text-align: center;
}
td {
border: 1px gray solid;
}
</style>
<script>
class Box {
constructor(x = 0, y = 0, width = 0, height = 0) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
contains({ x, y, width, height }) {
return (
x > this.x &&
y > this.y &&
(x + width) < (this.x + this.width) &&
(y + height) < (this.y + this.height)
);
}
overlaps({ x, y, width, height }) {
return (
1.001 * Math.max(x, this.x) < 0.998 * Math.min(x + width, this.x + this.width) &&
1.001 * Math.max(y, this.y) < 0.998 * Math.min(y + height, this.y + this.height)
);
}
equals({ x, y, width, height }) {
return (
x === this.x &&
y === this.y &&
width === this.width &&
height === this.height
);
}
add({x, y, width, height}) {
return new Box(
Math.min(x, this.x),
Math.min(y, this.y),
Math.max(x + width, this.x + this.width) - Math.min(x, this.x),
Math.max(y + height, this.y + this.height) - Math.min(y, this.y)
);
}
relativeTo({ x, y }) {
return new Box(
this.x - x,
this.y - y,
this.width,
this.height
);
}
relativeToElement(element) {
return this.relativeTo(element.getBoundingClientRect());
}
static from({ x = 0, y = 0, width = 0, height = 0 } = {}) {
return new Box(x, y, width, height);
}
}
class EditableTable extends HTMLTableElement {
constructor() {
super();
const document = this.ownerDocument;
this.selectionElement = document.createElement("div");
this.selectionElement2 = document.createElement("div");
this.selectionElement.style.cssText = `
position: absolute;
background: rgba(111, 168, 220, 0.66);
pointer-events: none;
`;
this.selectionElement2.style.cssText = `
position: absolute;
outline: 1px dashed rgba(0, 0, 0, 0.5);
pointer-events: none;
`;
this.mergeButton = document.createElement("button");
this.mergeButton.textContent = "объединить";
this.mergeButton.addEventListener("click", this.merge.bind(this));
this.addEventListener("mousedown", this.startSelect.bind(this));
document.addEventListener("mousemove", this.moveSelect.bind(this));
document.addEventListener("mouseup", this.endSelect.bind(this));
}
connectedCallback() {
this.parentNode.insertBefore(this.mergeButton, this);
}
startSelect(event) {
this._x = event.pageX;
this._y = event.pageY;
this._pointerIsMoving = true;
this.appendChild(this.selectionElement);
this.appendChild(this.selectionElement2);
this.cellBoxes = [];
for(const cell of this.querySelectorAll("td")) {
const box = Box.from(cell.getBoundingClientRect())
.relativeToElement(this);
this.cellBoxes.push(box);
}
this.moveSelect(event);
}
moveSelect(event) {
if(!this._pointerIsMoving) return;
event.preventDefault();
const { pageX: x, pageY: y } = event;
const selectionBox = new Box(
Math.min(x, this._x), Math.min(y, this._y),
Math.abs(x - this._x), Math.abs(y - this._y)
).relativeToElement(this);
let actualSelectionBox = this.constructor.getActualSelection(selectionBox, this.cellBoxes);
this.constructor.transferBoxToElement(actualSelectionBox, this.selectionElement);
this.constructor.transferBoxToElement(selectionBox, this.selectionElement2);
}
static transferBoxToElement({x, y, width, height }, { style }) {
style.left = `${x}px`;
style.top = `${y}px`;
style.width = `${width}px`;
style.height = `${height}px`;
}
static getActualSelection(selectionBox, cellBoxes) {
const box = getActualSelectionHelper(selectionBox, cellBoxes);
return box.equals(getActualSelectionHelper(box, cellBoxes)) ? box : this.getActualSelection(box, cellBoxes);
function getActualSelectionHelper(selectionBox, cellBoxes) {
let actualSelectionBox = Box.from(selectionBox)
for(const box of cellBoxes) {
if(selectionBox.overlaps(box)) {
actualSelectionBox = actualSelectionBox.add(box);
}
}
return actualSelectionBox;
}
}
merge() {
let actualSelectionBox = this.constructor.getActualSelection(this._selectionBox, this.cellBoxes);
this.constructor.transferBoxToElement(actualSelectionBox, this.selectionElement);
this.constructor.transferBoxToElement(this._selectionBox, this.selectionElement2);
const cellsMap = [];
function addToMap(x, y, r = 0) {
loop: for(var j = r; true; j++) {
if(!cellsMap[j]) cellsMap[j] = [];
for(var i = 0; true; i++) {
if(!cellsMap[j][i]) {
for(var v = 0; v < y; v++) {
for(var u = 0; u < x; u++) {
if(!cellsMap[j + v]) cellsMap[j + v] = [];
cellsMap[j + v][i + u] = 1;
}
}
break loop;
}
}
}
}
let selectedCells = [];
let index;
let isFirstSelectedRowProcessed = false;
for(const row of this.rows) {
let isRowAffectedBySelection = false;
for(const cell of row.cells) {
const cellBox = Box.from(cell.getBoundingClientRect()).relativeToElement(this);
if(actualSelectionBox.overlaps(cellBox)) {
if(!isRowAffectedBySelection) {
isRowAffectedBySelection = true;
}
if(!isFirstSelectedRowProcessed) {
isFirstSelectedRowProcessed = true;
index = 0;
}
selectedCells.push(cell);
addToMap(cell.colSpan, cell.rowSpan, index);
}
}
index++;
}
const span = {
colSpan: "0" in cellsMap ? cellsMap[0].length : 0,
rowSpan: cellsMap.length
};
const selectedCell = selectedCells.shift();
Object.assign(selectedCell || {}, span);
selectedCells.forEach(cell => {
selectedCell.append(...cell.childNodes);
cell.remove()
});
this.selectionElement.remove();
}
split() {
// TODO если нужно
}
endSelect(event) {
if(!this._pointerIsMoving) return;
this._pointerIsMoving = false;
const { pageX: x, pageY: y } = event;
this._selectionBox = new Box(
Math.min(x, this._x), Math.min(y, this._y),
Math.abs(x - this._x), Math.abs(y - this._y)
).relativeToElement(this);
this.selectionElement2.remove();
}
}
customElements.define("editable-table", EditableTable, { extends: "table" });
// firefox + contenteditable + table !== ❤
//document.addEventListener("DOMContentLoaded", event => {
// for(const cell of document.querySelectorAll("td")) {
// cell.contentEditable = true;
// }
//});
</script>
</head>
<body>
<table is="editable-table">
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>
Сообщение от MC-XOBAHCK
|
У меня у таблицы стоит атрибут contenteditable="true", то есть можно ячейки редактировать.
Можно ли как то определить в какой ячейке установлен курсор? Может добавляется к ячейке фокус или ещё что?
|
Когда у таблицы стоит атрибут contenteditable, то всё редактируется, что внутри элемента table. Например, вы можете вписать текст в две соседние ячейки, затем обе выделить, скопировать и вставить в третью ячейку. Если нужно редактировать только содержимое ячеек, то нужно прописать к каждой ячейке contenteditable
for(const cell of document.querySelectorAll("td")) {
cell.contentEditable = true;
}