Javascript динамическая html таблица
Понадобилось мне в одном проекте на днях сделать html таблицу, которую бы выводил серверный php скипт. Но таблицу не простую, а динамическую. Динамическую в том плане, что нужно было предоставить пользователю возможность править данные в ней. Добавлять и удалять строки, а так же отсылать отредактированные данные на сервер. В данной заметке я опишу процесс создания такой html таблицы, а так же приведу полностью рабочий, прокоментированный листинг.
Внимание! По просьбе уважаемого Павла скрипт был доработан. В результате он стал более функционален. Теперь есть возможность размещать и другие элементы html-формы. Скрипт стал меньше размером. Вдобавок немного изменилась структура данных отправляемая на сервер, на мой взгляд она стала более удобна. К сожалению, если приводить описание скрипта на странице, то нужно полностью переписывать статью, а мне делать этого не охота. Поэтому я залил новый скрипт в свой репозиторий: Динамическая html таблица там так же есть полноценный пример и описание как его использовать и пример как обработать данные на сервере.
Получившаяся в итоге html таблица будет примерно следующей:
Порывшись в Javascript доках, я нашёл всего несколько кроссбраузерных функций, отвечающих стандарту W3C. Используя которые, тем не менее, можно творить с html таблицей всё что угодно:
Используя данный инструментарий создадим нашу динамическую html таблицу. Я подумал, что исходная таблица для работы должна быть как можно более естественная. Навроде вот этой:
Поле 1 | Поле 2 | Поле 3 | Поле 4 | |
---|---|---|---|---|
Значение 1 | Значение 2 | Значение 3 | Значение 4 | |
Значение 1 | Значение 2 | Значение 3 | Значение 4 |
В итоге в сжатом виде скрипт динамической html таблицы «весит» 1.15Кб и предоставляет всего одну функцию — конструктор DynamicTable которая принимает три параметра:
- GLOB — глобальный контекст (объект window или this)
- htmlTable — html — элемент таблица, которую будем делать динамичной.
- config — объект конфигурации *
* Объект конфигурации должен содержать свойства в виде целых чисел:
значения которых станут имена подмассивов. Если попробовать объяснить проще, то сама 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"), );
Я думаю найти применение данному скрипту вполне возможно.
Вместо ответа на вопрос от пользователя Яна в одном из комментариев приведу простенький пример занесения данных в базу 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-каркас:
Поле 1 | Поле 2 | Поле 3 | Поле 4 |
---|