DevGang
Авторизоваться

Как управлять DOM на чистом JavaScript?

Управление DOM с помощью JavaScript в современных браузерах и IE 11+

Добавить или удалить класс из элемента Разрешить ввод только определенных символов Добавить к элементу Подписаться на событие Вычислить положение мыши относительно элемента Рассчитать размер полосы прокрутки Изменить favicon Проверить элемент по селектору Проверка наличия класса у элемента Проверьте, является ли элемент потомком другого Проверьте, находится ли элемент в области просмотра Проверка, является ли элемент прокручиваемым Проверка, поддерживается ли ввод даты Клонировать элемент Связь между iframe и его родительским окном Скопировать текст в буфер обмена Подсчитайте количество символов текстовой области Создать элемент Создать слайдер сравнения изображений Создать одноразовый обработчик событий Создание изменяемых размеров разделенных видов Обнаружение кликов за пределами элемента Определить, находится ли элемент в фокусе Определите, включен ли caps-lock Как определить Mac OS браузер Определить высоту и ширину элемента Определение левый и правый клик мыши Скачать файл Перетаскивание элементов в списоке Перетаскивание столбецов таблицы Перетаскивание строки таблицы Выполнить код, когда документ готов Экспорт таблицы в CSV Получить CSS-стили элемента Получить или установить заголовок документа Получить или установить HTML-элемент Получить, установить и удалить атрибуты Получить установить и удалить атрибуты data Получить братьев и сестер элемента Получить размер выбранного файла Получить ближайший элемент по данному селектору Получить значение по умолчанию для свойства css Получиту высоту и ширину документа Получить первый прокручиваемый родительский элемент Получить родительский элемент Получить положение элемента относительно другого Получить позицию элемента относительно документа Получить размер изображения Получить текстовое содержимое элемента Вернуться на предыдущую страницу Выделите элемент при перетаскивании файла поверх него Вставить элемент после или перед другим элементом Вставить данный HTML после или перед элементом Загрузить файл CSS динамически Загрузить файл JavaScript динамически Цикл по нодлисту Сделать изменяемый размер элемента Вставить изображение из буфера обмена Placeholder для contenteditable Нажмите Shift и введите новую строку Предварительный просмотр изображения перед его загрузкой Поместить курсор в конец ввода Перенаправить на другую страницу Перезагрузить текущую страницу Удалить все дочерние узлы Удалить элемент Заменить элемент Заменить элемент Заменить сломанные изображения Измените размер фрейма, чтобы он соответствовал его содержанию Изменить размер изображения Изменить размер столбцов таблицы Измените ширину текстового поля, чтобы оно автоматически соответствовало его содержимому Масштабировать текст, чтобы он поместился внутри элемента Прокрутить до верхней части страницы Выберите элемент или список элементов Выберите дочерние элементы Сериализация данных формы в строку запроса Показывать фальшивый элемент при перетаскивании элемента Сортировка таблицы, нажав на ее заголовки Поменять местами два узла Скрыть / показать элемент Переключить видимость пароля Запустить событие Развернуть элемент Загрузить файлы с помощью AJAX Обернуть элемент вокруг данного элемента

Прежде чем взглянуть на этот пример, рекомендуем посетить этот пост, чтобы узнать, как мы можем перетаскивать элементы в списке.

Тот же метод может быть применен к столбцам таблицы. Основная идея

  • Когда пользователь начинает перемещать столбец таблицы, мы создаем список элементов. Каждый элемент клонируется из каждого столбца таблицы.
  • Мы показываем список в той же позиции, что и таблица, и скрываем таблицу.
  • На этом этапе перемещение столбца фактически перемещает элемент списка.
  • Когда пользователь перетаскивает элемент, мы определяем индекс целевого элемента в списке. И поменяйте местами столбцы, связанные с индексами перетаскивания и окончания.

Давайте начнем с основной разметки таблицы:

<table id="table">
    ...
</table>

Базовая настройка

Как упомянуто в элементе Drag and drop в примере списка, нам нужно обработать три события:

  • mousedown для всех ячеек заголовка, поэтому пользователь может нажать и перетащить первую ячейку в каждом столбце
  • mousemove для document: это событие срабатывает, когда пользователь перемещает столбец, и мы создадим и вставим столбец-заполнитель в зависимости от направления (влево или вправо)
  • mouseup для document: это событие происходит, когда пользователь перетаскивает столбец.

Вот скелет этих обработчиков событий:

// Query the table
const table = document.getElementById('table');

const mouseDownHandler = function(e) {
    ...

    // Attach the listeners to `document`
    document.addEventListener('mousemove', mouseMoveHandler);
    document.addEventListener('mouseup', mouseUpHandler);
};

const mouseMoveHandler = function(e) {
    ...
};

const mouseUpHandler = function() {
    ...
    // Remove the handlers of `mousemove` and `mouseup`
    document.removeEventListener('mousemove', mouseMoveHandler);
    document.removeEventListener('mouseup', mouseUpHandler);
};

// Query all header cells
table.querySelectorAll('th').forEach(function(headerCell) {
    // Attach event handler
    headerCell.addEventListener('mousedown', mouseDownHandler);
});

Клонировать таблицу, когда пользователь перемещает столбец

Поскольку эта задача выполняется один раз, нам нужен флаг для отслеживания ее выполнения:

let isDraggingStarted = false;

const mouseMoveHandler = function(e) {
    if (!isDraggingStarted) {
        isDraggingStarted = true;

        cloneTable();
    }
    ...
};

cloneTable создает элемент, который имеет ту же позицию, что и таблица, и отображается прямо перед таблицей:

let list;

const cloneTable = function() {
    // Get the bounding rectangle of table
    const rect = table.getBoundingClientRect();

    // Create new element
    list = document.createElement('div');

    // Set the same position as table
    list.style.position = 'absolute';
    list.style.left = `${rect.left}px`;
    list.style.top = `${rect.top}px`;

    // Insert it before the table
    table.parentNode.insertBefore(list, table);

    // Hide the table
    table.style.visibility = 'hidden';
};

Представьте, что list состоит из элементов, клонированных из столбцов таблицы:

const cloneTable = function() {
    ...

    // Get all cells
    const originalCells = [].slice.call(table.querySelectorAll('tbody td'));

    const originalHeaderCells = [].slice.call(table.querySelectorAll('th'));
    const numColumns = originalHeaderCells.length;

    // Loop through the header cells
    originalHeaderCells.forEach(function(headerCell, headerIndex) {
        const width = parseInt(window.getComputedStyle(headerCell).width);

        // Create a new table from given row
        const item = document.createElement('div');
        item.classList.add('draggable');

        const newTable = document.createElement('table');

        // Header
        const th = headerCell.cloneNode(true);
        let newRow = document.createElement('tr');
        newRow.appendChild(th);
        newTable.appendChild(newRow);

        const cells = originalCells.filter(function(c, idx) {
            return (idx - headerIndex) % numColumns === 0;
        });
        cells.forEach(function(cell) {
            const newCell = cell.cloneNode(true);
            newRow = document.createElement('tr');
            newRow.appendChild(newCell);
            newTable.appendChild(newRow);
        });

        item.appendChild(newTable);
        list.appendChild(item);
    });
};

После этого шага мы имеем следующее list:

<!-- The list -->
<div>
    <!-- First item -->
    <div>
        <table>
            <!-- The first column of original table -->
            <tr>...</tr>
            <tr>...</tr>
            ...
        </table>
    </div>

    <!-- Second item -->
    <div>
        <table>
            <!-- The second column of original table -->
            <tr>...</tr>
            <tr>...</tr>
            ...
        </table>
    </div>

    <!-- ... -->
</div>

<!-- The original table -->
<table>
    ...
</table>

Стоит отметить, что при клонировании ячеек в каждом элементе мы должны установить ширину ячейки, равную исходной ячейке. Таким образом, элемент выглядит полностью как исходный столбец:

originalHeaderCells.forEach(function(headerCell, headerIndex) {
    // Get the width of original cell
    const width = parseInt(window.getComputedStyle(headerCell).width);

    newTable.style.width = `${width}px`;

    cells.forEach(function(cell) {
        const newCell = cell.cloneNode(true);
        newCell.style.width = `${width}px`;
        ...
    });
});

Определить индексы перетаскивания и целевые столбцы

let draggingEle;        // The dragging element
let draggingRowIndex;   // The index of dragging column

const mouseDownHandler = function(e) {
    // Get the index of dragging column
    draggingColumnIndex = [].slice.call(table.querySelectorAll('th')).indexOf(e.target);
};

const mouseMoveHandler = function(e) {
    if (!isDraggingStarted) {
        cloneTable();

        // Query the dragging element
        draggingEle = [].slice.call(list.children)[draggingColumnIndex];
    }
};

const mouseUpHandler = function() {
    // Get the end index
    const endColumnIndex = [].slice.call(list.children).indexOf(draggingEle);
};

У нас есть draggingColumnIndex и endColumnIndex, теперь легко проверить, если пользователь отпускает элемент слева или справа от таблицы. И мы можем решить, как переместить целевой столбец до или после перетаскиваемого столбца:

const mouseUpHandler = function() {
    // Move the dragged column to `endColumnIndex`
    table.querySelectorAll('tr').forEach(function(row) {
        const cells = [].slice.call(row.querySelectorAll('th, td'));
        draggingColumnIndex > endColumnIndex
            ? cells[endColumnIndex].parentNode.insertBefore(cells[draggingColumnIndex], cells[endColumnIndex])
            : cells[endColumnIndex].parentNode.insertBefore(cells[draggingColumnIndex], cells[endColumnIndex].nextSibling);
    });
};

Вот полный код примера

Комментарии
Чтобы оставить комментарий, необходимо авторизоваться