Показать сообщение отдельно
  #13 (permalink)  
Старый 02.11.2018, 11:37
Аватар для Malleys
Профессор
Отправить личное сообщение для Malleys Посмотреть профиль Найти все сообщения от Malleys
 
Регистрация: 20.12.2009
Сообщений: 1,714

Сообщение от 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;
}

Последний раз редактировалось Malleys, 02.11.2018 в 16:06. Причина: Ура!!! Оказывается можно уменьшить коробку для сравнения на 0.2%
Ответить с цитированием