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

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

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

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

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

Поле 1 Поле 2 Поле 3 Поле 4  
Значение 1 Значение 2 Значение 3 Значение 4  
Значение 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));
            }
        };
    }

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

Добавить комментарий


Защитный код
Обновить



Кто на сайте
Сейчас 68 гостей онлайн