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));
}
};
}
Забираем и пользуемся на здоровье
Информация копипастерам
Внимание! Копирование контента с сайта, возможно только с разрешения администратора. Т.е. Меня! Я скорее всего разрешу Вам это сделать, в обмен на живую ссылку, на статью оригинал.
Javascript динамическая html таблица