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 rows
table.querySelectorAll('tr').forEach(function(row, index) {
    // Ignore the header
    // We don't want user to change the order of header
    if (index === 0) {
        return;
    }

    // Get the first cell of row
    const firstCell = row.firstElementChild;
    firstCell.classList.add('draggable');

    // Attach event handler
    firstCell.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();

    // Get the width of table
    const width = parseInt(window.getComputedStyle(table).width);

    // 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() {
    ...

    // Loop over the rows
    table.querySelectorAll('tr').forEach(function(row) {
        const item = document.createElement('div');

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

        // Query the cells of row
        const cells = [].slice.call(row.children);
        cells.forEach(function(cell) {
            const newCell = cell.cloneNode(true);
            newRow.appendChild(newCell);
        });

        newTable.appendChild(newRow);
        item.appendChild(newTable);

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

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

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

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

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

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

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

cells.forEach(function(cell) {
    const newCell = cell.cloneNode(true);
    // Set the width as the original cell
    newCell.style.width = `${parseInt(window.getComputedStyle(cell).width)}px`;
    newRow.appendChild(newCell);
});

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

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

const mouseDownHandler = function(e) {
    // Get the original row
    const originalRow = e.target.parentNode;
    draggingRowIndex = [].slice.call(table.querySelectorAll('tr')).indexOf(originalRow);
};

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

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

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

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

const mouseUpHandler = function() {
    // Move the dragged row to `endRowIndex`
    let rows = [].slice.call(table.querySelectorAll('tr'));
    draggingRowIndex > endRowIndex
        // User drops to the top
        ? rows[endRowIndex].parentNode.insertBefore(rows[draggingRowIndex], rows[endRowIndex])
        // User drops to the bottom
        : rows[endRowIndex].parentNode.insertBefore(rows[draggingRowIndex], rows[endRowIndex].nextSibling);
};

Полный код

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