Javascript динамическая html таблица

Javascript динамическая html таблица

Понадобилось мне в одном проекте на днях сделать html таблицу, которую бы выводил серверный php скипт. Но таблицу не простую, а динамическую. Динамическую в том плане, что нужно было предоставить пользователю возможность править данные в ней. Добавлять и удалять строки, а так же отсылать отредактированные данные на сервер. В данной заметке я опишу процесс создания такой html таблицы, а так же приведу полностью рабочий, прокоментированный листинг.

Внимание! По просьбе уважаемого Павла скрипт был доработан. В результате он стал более функционален. Теперь есть возможность размещать и другие элементы html-формы. Скрипт стал меньше размером. Вдобавок немного изменилась структура данных отправляемая на сервер, на мой взгляд она стала более удобна. К сожалению, если приводить описание скрипта на странице, то нужно полностью переписывать статью, а мне делать этого не охота. Поэтому я залил новый скрипт в свой репозиторий: Динамическая html таблица там так же есть полноценный пример и описание как его использовать и пример как обработать данные на сервере.

Получившаяся в итоге html таблица будет примерно следующей:

Поле 1 Поле 2 Поле 3 Поле 4  
 

 

Порывшись в Javascript доках, я нашёл всего несколько кроссбраузерных функций, отвечающих стандарту W3C. Используя которые, тем не менее, можно творить с html таблицей всё что угодно:

Метод Описание
createCaption() Создает пустой элемент заголовка и добавляет его в таблицу
createTFoot() Создает пустой элемент TFOOT и добавляет его в таблицу
createTHead() Создает пустой элемент THEAD и добавляет его в таблицу
deleteCaption() Удаляет первый элемент caption
deleteRow() Удаляет строку из таблицы
deleteTFoot() Удаляет элемент TFOOT из таблицы
deleteTHead() Удаляет элемент THEAD из таблицы
insertRow() Создает пустую строку и добавляет её в таблицу

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

 

<form method="post" action="">

  <table id="dynamic" width="650" border="1" cellspacing="0" cellpadding="5">

    <tr>

      <th scope="col">Поле 1</th>

      <th scope="col">Поле 2</th>

      <th scope="col">Поле 3</th>

      <th scope="col">Поле 4</th>

      <th scope="col">&nbsp;</th>

    </tr>

    <tr>

      <td>Значение 1</td>

      <td>Значение 2</td>

      <td>Значение 3</td>

      <td>Значение 4</td>

      <td>&nbsp;</td>

    </tr>

    <tr>

      <td>Значение 1</td>

      <td>Значение 2</td>

      <td>Значение 3</td>

      <td>Значение 4</td>

      <td>&nbsp;</td>

    </tr>

  </table>

  <input name="sub" type="submit" value="SEND">

</form>

 

В итоге в сжатом виде скрипт динамической html таблицы "весит" 1.15Кб и предоставляет всего одну функцию — конструктор DynamicTable которая принимает три параметра:

  1. GLOB — глобальный контекст (объект window или this)

  2. htmlTable — html — элемент таблица, которую будем делать динамичной.

  3. config — объект конфигурации *

* Объект конфигурации должен содержать свойства в виде целых чисел:

{1:"val1", 2:"val2", 3:"val3", 4:"val4"}

значения которых станут имена подмассивов. Если попробовать объяснить проще, то сама html таблица это ассоциативный массив, а столбцы таблицы — это индексные массивы… в общем ниже приведена трассировка результирующего массива. Такой он получится, если 3 параметр конфиг, передать таким, каким он показан в примере. Ниже я приведу полный листинг скрипта с подробными комментариями.

 

Array

(

    [val1] => Array

        (

            [0] => Значение 1

            [1] => Значение 1

        )

 

    [val2] => Array

        (

            [0] => Значение 2

            [1] => Значение 2

        )

 

    [val3] => Array

        (

            [0] => Значение 3

            [1] => Значение 3

        )

    [val4] => Array

        (

            [0] => Значение 4

            [1] => Значение 4

        )

)

Динамическая html таблица

 

if(typeof window.DynamicTable !== 'function') {

 

    function DynamicTable(GLOB, htmlTable, config) {   

        // Так как эта функция является конструктором,

        // подразумевается, что ключевое слово this - будет

        // указывать на экземнпляр созданного объекта. Т.е.

        // вызывать её нужно с оператором "new".

        // Проверка ниже является страховкой:

        // если эта функция была вызвана без оператора "new",

        // то здесь эта досадная ситуация исправляется:

        if ( !(this instanceof DynamicTable) ) {

            return new DynamicTable(GLOB, htmlTable, config);  

        }

        // Зависимость:

        var DOC       = GLOB.document,

            // Ссылка на массив строк таблицы:

            tableRows = htmlTable.rows,

            // Кол-во строк таблицы:

            RLength   = tableRows.length,

            // Кол-во ячеек в таблице:

            CLength   = tableRows[0].cells.length,

            // Контейнер для работы в циклах ниже:

            inElement = null,

            // Контейнер кнопки

            button    = null,

            // Контейнер текстового узла кнопки

            butText   = null,

            // В одном из методов ниже, потребуется

            // сохранить контекст:

            self      = this,

            // Счётчики итераций:

            i,j;   

                 

        // Метод "Вставить кнопки".

        // Создаёт/добавляет две кнопки "удалить" и "добавить"

        // завёрнутые в элемент "P". Используются DOM - методы создания

        // и добавления элементов.

        this.insertButtons = function() {

            // Создаём первую кнопку:

            inElement = DOC.createElement("P");

            inElement.className = "d-butts";

             

            button = DOC.createElement("BUTTON");                  

            button.onclick = this.delRow;

                                         

            butText = DOC.createTextNode("-");

            button.appendChild(butText);   

            // Добавляем её в DOM:     

            inElement.appendChild(button);

            // Создаём вторую кнопку:

            button = DOC.createElement("BUTTON");                  

            button.onclick = this.addRow;

                                         

            butText = DOC.createTextNode("+");

            button.appendChild(butText);

            // Добавляем её в DOM:     

            inElement.appendChild(button);

            // Обнуляем переменную, т.к.

            // она используется и в других методах.

            return inElement;

        };

        // Метод "Добавить строку"

        this.addRow = function(ev) {

            // Кросс бр. получаем событие и цель (кнопку)

            var e         = ev||GLOB.event,

                target    = e.target||e.srcElement,

                // Получаем ссылку на строку, в которой была кнопка:

                row       = target.parentNode.parentNode.parentNode,

                // Получаем кол-во ячеек в строке:

                cellCount = row.cells.length,

                // Получаем индекс строки в которой была кнопка + 1,

                // что бы добавить строку сразу после той, в которой

                // была нажата кнопка:

                index     = row.rowIndex + 1,

                i;

            // Вставляем строку:

            htmlTable.insertRow(index);        

            // В этом цикле, вставляем ячейки.

            for(i=0; i < cellCount; i += 1) {   

                         

                htmlTable.rows[index].insertCell(i);               

                // Если ячейка последняя...

                if(i == cellCount-1) {

                    // Получаем в переменную кнопки, используя метод, описанный выше:

                    inElement = self.insertButtons();              

                } else {           

                    // Иначе получаем в переменную текстовое поле:     

                    inElement = DOC.createElement("INPUT");

                    // ... и задаём ему имя, типа name[] - которое

                    // впоследствии станет массивом.

                    inElement.name  = config[i+1]+"[]";                

                }                  

                // Добавляем в DOM, то что получили в переменную:

                htmlTable.rows[index].cells[i].appendChild(inElement);                     

            }

            // Обнуляем переменную, т.к.

            // она используется и в других методах.

            inElement = null;

            // Во избежании ненужных действий, при нажатии на кнопку

            // возвращаем false:

            return false;

        };

         

        // Метод "Удалить строку"

        // Удаляем строку, на  кнопку, которой нажали:

        this.delRow = function(ev) {

            // Страховка: не даёт удалить строку, если она осталась

            // последней. Цифра 2 здесь потому, что мы считаем так же

            // строку с заголовками TH. Итого получается, что 1 строка

            // с заголовками и 1 строка - с содержимым.

            if(tableRows.length > 2) {

                htmlTable.deleteRow(this.parentNode.parentNode.parentNode.rowIndex);

            } else {

                return false;  

            }          

        };         

         

        // Фактически, ниже это инициализация таблицы:

        // Содержимое ячеек помещается внутрь текстовых

        // полей, а в последнюю ячейку кажой строки, помещаются

        // нопки "удалить" и "добавить" Функция является

        // "вызываемой немедленно"

        return (function() {

            // Мы имеем дело с двумерным массивом:

            // table.rows[...].cells[...]

            // Поэетому сдесь вложенный цикл.

            // Внешний цикл "шагает" по строкам...

            for( i = 1; i < RLength; i += 1 ) { 

                // Внутренний цикл "шагает" по ячейкам:

                for( j = 0; j < CLength; j += 1 ) {

                    // Если ячейка последняя...

                    if( j + 1 == CLength ) {

                        // Помещаем в переменную кнопки:

                        inElement = self.insertButtons();                                      

                    } else {                   

                        // Иначе создаем текстовый элемент,

                        inElement = DOC.createElement("INPUT");

                        // Помещаем в него данные ячейки,

                        inElement.value = tableRows[i].cells[j].firstChild.data;

                        // Присваиваем имя - массив,

                        inElement.name  = config[j+1]+"[]";

                        // Удаляем, уже не нужный экземпляр данных непосредственно

                        // из самой ячейки, потому что теперь данные у нас внутри

                        // текстового поля:

                        tableRows[i].cells[j].firstChild.data = "";

                    }  

                    // Вставляем в ячейку содержимое переменной - это

                    // либо текстовое поле, либо кнопки:

                    tableRows[i].cells[j].appendChild(inElement);

                    // Обнуляем переменную, т.к.

                    // она используется и в других методах.

                    inElement = null;

                }      

            }

       

        }());

     

    }// end function DynamicTable

   

}

Как всегда, если убрать комментарии — он не такой уж и страшный Но для срабатывания нужно вызвать функцию DynamicTable как конструктор (в принципе можно и просто как функцию, у нас есть страховка) следующим образом:

 

new DynamicTable( window,

                  document.getElementById("dynamic"),

                  {1:"val1", 2:"val2", 3:"val3", 4:"val4"} );

Я думаю найти применение данному скрипту вполне возможно.

Вместо ответа на вопрос от пользователя Яна в одном из комментариев приведу простенький пример занесения данных в базу MySQL

После того, как форма "ушла" на сервер в массиве $_POST мы можем наблюдать примерно следующее:

 

Array

(

    [val1] => Array

        (

            [0] => Значение 1

            [1] => Значение 1

        )

 

    [val2] => Array

        (

            [0] => Значение 2

            [1] => Значение 2

        )

 

    [val3] => Array

        (

            [0] => Значение 3

            [1] => Значение 3

        )

 

    [val4] => Array

        (

            [0] => Значение 4

            [1] => Значение 4

        )

 

    [sub] => SEND

)

Обработать этого монстра мы можем следующим образом:

// ... Возможно еще какой то другой код ...

if ($_POST) {

 

    // Внимание, это пример! - Все очень кратко и без проверок.

    $mysqli = new mysqli("example.com", "user", "password", "database");

    $mysqli->set_charset("utf8");

 

    // Строим начало запроса.

    // Допустим у нас есть столбцы из формы : val1,val2,val3,val4

    // Ваш способ организации хранения данных может и отличаться. Это лишь пример!

    // В этом примере таблица без ухищрений заносится в таблицу БД. Т.е. столбцы

    // формы становятся столбцами таблицы БД, а строки формы становятся строками

    // (записями) таблицы БД.

    $sql = 'INSERT INTO `YOUR_TABLE_NAME` (`val1`,`val2`,`val3`,`val4`) VALUES ';

 

    // Подразумевается, что в подмассивах val1,val2,val3,val4 - будет одинаковое

    // кол-во элементов.

    for ($i = 0; $i < count($_POST['val1']); $i++) {

        // "Накачиваем" запрос данными.

        // $DBCONN->escape(...) - предполагается, что это функция в Вашем объекте

        // для экранирования нежелательных символов, для избежания SQL-нъекций

        $sql .= ' ("'.

            $mysqli->mysqli_real_escape_string($_POST['val1'][$i]) .'","'.

            $mysqli->mysqli_real_escape_string($_POST['val2'][$i]) .'","'.

            $mysqli->mysqli_real_escape_string($_POST['val3'][$i]) .'","'.

            $mysqli->mysqli_real_escape_string($_POST['val4'][$i]) .'"),';

    }

 

    // обрезаем последнюю запятую

    $sql = rtrim($sql, ',');

 

    // Исполняем запрос.

    $mysqli->query($sql);

}

// ... Возможно еще какой то другой код ...

По просьбам трудящихся выкладываю скрипт таблицы с заранее проставляемыми строками:

Поле 1 Поле 2 Поле 3 Поле 4

Логика серверной стороны может быть такой же как и у таблицы сверху

Нам понадобится вот такой html-каркас:

 

<!doctype html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <title>Dynamic table</title>

</head>

<body>

 

<form method="post" action="">

    <table width="650" border="1" cellspacing="0" cellpadding="5">

        <caption>

            <!-- Кол-во элементов option и их значения value можно задать произвольно //-->

            <select id="rows_setup">

                <option value="0">--Установить кол-во строк--</option>

                <option value="0">0</option>

                <option value="1">1</option>

                <option value="2">2</option>

                <option value="4">4</option>

                <option value="6">6</option>

                <option value="10">10</option>

            </select>

        </caption>

        <!-- Заголовки //-->

        <thead>

        <tr>

            <th scope="col">Поле 1</th>

            <th scope="col">Поле 2</th>

            <th scope="col">Поле 3</th>

            <th scope="col">Поле 4</th>

        </tr>

        </thead>

        <!-- Сюда будем добавлять строки //-->

        <tbody id="dynamic"></tbody>

    </table>

    <input name="sub" type="submit" value="SEND">

</form>

<script>

    /* Навешиваем логику: */

    setupTable(

        "dynamic", /* ID элемента <tbody> таблицы */

        "rows_setup", /* ID элемента <select> для установки кол-ва строк */

        {1:"val1", 2:"val2", 3:"val3", 4:"val4"}, /* Конфигурация строки таблицы */

 

        function (row, fieldName) { /* Ф-ция должна возвращать HTMLElement тип - поле формы */

            var ELEMENT = document.createElement("INPUT");

 

            ELEMENT.name = fieldName + "[]";

            ELEMENT.type = 'number';

            ELEMENT.max = 100;

            ELEMENT.min = 0;

 

            return ELEMENT;

        }

    );

</script>

</body>

</html>

А вот собственно реализация функции setupTable

 

/**

 * @param {String} tableId ID элемента tbody таблицы

 * @param {String} selectId ID элемента select для установки кол-ва строк

 * @param {Object} fields Конфигурация строки таблицы: {1:"field_name1", 2:"field_name2", ... и т.д. }

 * @param {Function} creatorCallback - ф-ция обратного вызова принимающая два параметра:

 *      {Number} - номер строки таблицы,

 *      {String} - имя поля из объекта fields

 */

function setupTable(tableId, selectId, fields, creatorCallback) {

    var htmlTBody   = document.getElementById(tableId),

        htmlSelect  = document.getElementById(selectId),

        rowTpl      = document.createElement("TR"),

        rowNum      = 0,

        ELEMENT, TD;

 

    /* Строим шаблон строки таблицы один раз, в дальнейшем будем просто его клонировать */

    for(var field in fields) {

        if (false === fields.hasOwnProperty(field)) { continue; }

        TD = document.createElement("TD");

 

        if (creatorCallback) {

            ELEMENT = creatorCallback(rowNum, fields[field])

        } else {

            ELEMENT = document.createElement("INPUT");

            ELEMENT.name = fields[field] + "[]";

        }

 

        TD.appendChild(ELEMENT);

        rowTpl.appendChild(TD);

 

        rowNum += 1;

    }

    // Вешаем обработчик на элемент управления кол-вом строк

    htmlSelect.onchange = function (e) {

        var numRows = htmlSelect.options[htmlSelect.selectedIndex].value;

        /* Отслеживаем отрицательные значения а то мало ли какие там значения в option понаставят */

        numRows = numRows < 0 ? 0 : numRows;

        /* Удаляем те строки которые есть. */

        while(htmlTBody.firstChild) {

            htmlTBody.removeChild(htmlTBody.firstChild);

        }

        for (var i = 0; i < numRows; i++) {

            htmlTBody.appendChild(rowTpl.cloneNode(true));

        }

    };

}

Забираем и пользуемся на здоровье

 

Возможно Вас заинтересуют эти материалы

Javascript выпадающий список

Часто на форумах втречаются вопросы как сделать динамический Javascript выпадающий список? Да ещё с возможностью

Свои события или observer на Javascript

В этой статье, я по сути "убил двух зайцев" - Написал интересный и полезный Javascript

Ajax кроссдоменно

Всем привет. Тема сегодняшней заметки: ajax кроссдоменно, без Jquery. По соображениям безопасности, объект XMLHttpRequest имеет неприятное