Javascript динамическая html таблица
Понадобилось мне в одном проекте на днях сделать html таблицу, которую бы выводил серверный php скипт. Но таблицу не простую, а динамическую. Динамическую в том плане, что нужно было предоставить пользователю возможность править данные в ней. Добавлять и удалять строки, а так же отсылать отредактированные данные на сервер. В данной заметке я опишу процесс создания такой html таблицы, а так же приведу полностью рабочий, прокоментированный листинг.
Внимание! По просьбе уважаемого Павла скрипт был доработан. В результате он стал более функционален. Теперь есть возможность размещать и другие элементы html-формы. Скрипт стал меньше размером. Вдобавок немного изменилась структура данных отправляемая на сервер, на мой взгляд она стала более удобна. К сожалению, если приводить описание скрипта на странице, то нужно полностью переписывать статью, а мне делать этого не охота. Поэтому я залил новый скрипт в свой репозиторий: Динамическая html таблица там так же есть полноценный пример и описание как его использовать и пример как обработать данные на сервере.
Получившаяся в итоге html таблица будет примерно следующей:
Порывшись в 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"> </th> </tr> <tr> <td>Значение 1</td> <td>Значение 2</td> <td>Значение 3</td> <td>Значение 4</td> <td> </td> </tr> <tr> <td>Значение 1</td> <td>Значение 2</td> <td>Значение 3</td> <td>Значение 4</td> <td> </td> </tr> </table> <input name="sub" type="submit" value="SEND"> </form>
В итоге в сжатом виде скрипт динамической html таблицы "весит" 1.15Кб и предоставляет всего одну функцию - конструктор DynamicTable которая принимает три параметра:
GLOB - глобальный контекст (объект window или this)
htmlTable - html - элемент таблица, которую будем делать динамичной.
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)); } }; }
Забираем и пользуемся на здоровье
Информация копипастерам
Внимание! Копирование контента с сайта, возможно только с разрешения администратора. Т.е. Меня! Я скорее всего разрешу Вам это сделать, в обмен на живую ссылку, на статью оригинал.