Построение таблицы данных GRID по JSON файлу с возможностью редактирования
Настало время рассмотреть пример приближенный к реальной задаче. Допустим, есть на сервере файл в JSON формате, который нужно форматировать через интерфейс. Для этого может пригодиться компонент в виде редактируемой таблицы GRID.
Итак, создадим компонент GRID для редактирования текстовых файлов в формате JSON
В этом компоненте бизнес- логика будет на PHP, ядровая часть на PHP, JQUERY, HTML, CSS
Проект состоит из файлов:
file.json - исходный файл с данными
file_grid.js - Java Script файл с вспомогательными функциями
file_grid.php - файл с бизнес логикой
lib.php - файл с библиотекой
utils.php - файл с общими вспомогательными функциями
jquery-3.7.1.min.js - файл библиотеки Jquery, который можно взять с jquery.com
Все файлы лежат в одной папке. Точкой входа в проект является файл file_grid.php.
При запуске отображается таблица, которую можно редактировать.

Любое значение таблицы можно поменять и сохранить в исходный файл. Войти в редактирование - двойной щелчок мыши, отменить изменение Ctrl+Z, запостить - сместить мышкой фокус на другую ячейку или область экрана. Сохранит данные на сервере - нажатие кнопки “Сохранить”.

При нажатии на кнопку “Сохранить” вызывается событие в PHP файле, в котором происходит сохранение и выдается сообщение “Сохранено!”.

Данные в исходном файле file.json:
[
{
"name": "Samsung",
"price": "51201.62"
},
{
"name": "Lg",
"price": "5400.6"
},
{
"name": "Alcatel",
"price": "4503.6"
}
]
Формат исходных данных представляет из себя набор записей с одинаковым количеством столбцов в каждой из них.
Состав файла file_grid.js:
<?php
$(document) . ready(
function() {
$("body") . delegate(
'[change_cell]',
"change",
function(event) {
id = $(this) . attr('id');
parentid = $(this) . attr('parentid');
val = event . target . value;
v = $('JSN') . text();
jsn = JSON . parse(v);
if (jsn) {
jsn[parentid][id] = val;
};
s = JSON . stringify(jsn);
$('JSN') . text(s);
}
);
function getparams($attr)
{
req = '[';
if ($attr) {
jsn = JSON . parse($attr);
var name;
var param;
var attrname;
var value;
for (var i = 0; i < jsn . length; i++) {
name = jsn[i] . name;
id = jsn[i] . id;
param = jsn[i] . param;
attrname = jsn[i] . attrname;
var type = jsn[i] . type;
if (type === "value") {
if (name) value = $('[name='+name + ']') . val();
if (id) value = $('[id='+id + ']') . val();
}
if (type === "attr") {
if (name) value = $('[name='+name + ']') . attr(attrname);
if (id) value = $('[id='+id + ']') . attr(attrname);
}
if (type === "text") {
if (name) value = $('[name='+name + ']') . text();
if (id) value = $('[id='+id + ']') . text();
}
if (type === "html") {
if (name) value = $('[name='+name + ']') . html();
if (id) value = $('[id='+id + ']') . html();
}
if (req !== '[') req = req + ',';
req = req + '{"'+param + '":"'+encodeURI(value) + '"}';
}
}
req = req + ']';
return req;
}
$("body") . delegate(
'[click]',
"click",
function() {
req = getparams($(this) . attr('request'));
$. post(
'file_grid.php', {
type:"event",
name:$(this
) . attr('click'), request:req
},
onSuccess
);
}
);
function onSuccess(data)
{
jsn = JSON . parse(data);
if (!jsn . exec == '') {
eval(jsn . exec);
}
if (!jsn . message == '') {
alert(jsn . message);
}
}
});
В java-script файле file_grid.js реализовано делегирование события изменения ячейки change, при котором записывает значение ячейки в JSON объект HTML формы. JSON объект хранится в теге JSN. Так же реализовано делегирование события click кнопки сохранения таблицы, из него вызывается событие в PHP файле, где и происходит это изменение. Вспомогательная функция getparams формирует JSON для передачи из JS скрипта в PHP событие.
Состав файла file_grid.php:
<?php
include('utils.php');
function event_click($data)
{
$mes = 'Сохранено!';
$text = urldecode(getval($data, 'jsn'));
$file = urldecode(getval($data, 'file'));
file_put_contents($file, $text);
$jsn = json_encode(['result' => 'OK', 'message' => $mes]);
return $jsn;
}
if (isset($_POST['type'])) {
if ($_POST['type'] == 'event') {
if (isset($_POST['name'])) {
$request = array();
if (isset($_POST['request'])) {
$request = $_POST['request'];
}
echo call_user_func($_POST['name'], $request);
}
}
} else {
include('lib.php');
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>edited grid</title>
<script src="jquery-3.7.1.min.js"></script>
<script src="file_grid.js"></script>
</head>
<body>';
$e = new file_grid;
$e->id = 'file_grid';
$e->file = 'file.json';
echo $e->print();
echo '
</body>
</html>';
}
Файл file_grid.php служит для первоначальной отрисовки таблицы, а так же для обработки событий, которые вызываются из браузера. event_click - событие сохранения таблицы. call_user_func - стандартная функция PHP для CALLBACK вызова event_click. Переменная $e содержит экземпляр класса, который описан в файле lib.php. $e->print() - вывод на экран таблицы данных.
Состав файла lib.php:
<?php
define(
'STYLE',
'<style>
grid{
border-top: 1px solid black;
border-left: 1px solid black;
display: table;
}
row{
display: table-row;
}
cell{
display: table-cell;
border-right: 1px solid black;
border-bottom: 1px solid black;
}
hrow{
display: table-row;
}
hcell{
background-color: grey;
color: white;
display: table-cell;
border-right: 1px solid black;
border-bottom: 1px solid black;
}
JSN{
display: none;
}
</style>'
);
define('CONTENT', '%content%');
define('ID', '%id%');
define('PARENTID', '%parentid%');
define('ELEMENT', '<div id=' . ID . ' >' . CONTENT . '</div>');
define(
'CELL',
'<cell name=cell_' . PARENTID . '_' . ID . ' '
. 'id=' . ID . ' parentid=' . PARENTID . ' '
. 'change_cell="" '
. 'ondblclick=" '
. "elem1 = $('[name=cell_" . PARENTID . "_" . ID . "]'); "
. 'cont = elem1.text(); '
. 'elem1.empty(); '
. 'elem1.append("<textarea '
. 'name=textarea_' . PARENTID . '_' . ID . ' '
. "onfocusout=\" "
. "elem1 = $('[name=cell_" . PARENTID . "_" . ID . "]'); "
. "elem2 = $('[name=textarea_" . PARENTID . "_" . ID . "]'); "
. 'cont = elem2.val(); '
. 'elem1.empty(); '
. 'elem2.remove(); '
. 'elem1.append(cont); '
. '\">"+cont+"</textarea>"); '
. 'elem2 = $("[name=textarea_' . PARENTID . '_' . ID . ']"); '
. 'elem2.focus(); '
. '" '
. '>' . CONTENT . '</cell>'
);
define('ROW', '<row id=' . ID . ' >' . CONTENT . '</row>');
define('HCELL', '<hcell>' . CONTENT . '</hcell>');
define('HROW', '<hrow id=' . ID . ' >' . CONTENT . '</hrow>');
define('GRID', STYLE . '<grid id=' . ID . ' >' . CONTENT . '</grid>');
define('SAVE_CONTENT', 'Сохранить');
define('JSN', '%jsn%');
define('JSN_NAME', 'JSN_NAME');
define('JSN_CONT', '<JSN name=' . JSN_NAME . '>' . JSN . '</JSN>');
define(
'BUTTON_SAVE',
'<button name=button_save click=event_click '
. 'request=[{"type":"text","name":"' . JSN_NAME . '","param":"jsn"},'
. '{"type":"attr","attrname":"file","id":"' . ID . '","param":"file"}] '
. '>' . SAVE_CONTENT . '</button>'
);
define('FILE_NAME', '%file_name%');
define('FILE_GRID', STYLE . '<grid id=' . ID . ' file= ' . FILE_NAME . ' >' . CONTENT . JSN_CONT . BUTTON_SAVE . '</grid>');
class element
{
public $template;
public $content;
public $child;
public $id;
public $hasparent;
public $parentid;
public function __construct()
{
if (!isset($this->template)) {
$this->template = ELEMENT;
}
$this->content = array();
}
function getclass()
{
if (isset($this->child)) {
return new $this->child;
} else {
return new element;
}
}
public function print($content = '') {
if (is_array($this->content)) {
//$content = '';
foreach ($this->content as $k => $value) {
$e = $this->getclass();
$e->content = $value;
$e->id = $k;
if ($e->hasparent) {
$e->parentid = $this->id;
}
$content .= $e->print();
}
} else {
$content = $content . $this->content;
}
$ret = str_replace(
array(ID, CONTENT, PARENTID),
array($this->id, $content, $this->parentid),
$this->template
);
return $ret;
}
}
class cell extends element
{
public function __construct()
{
$this->template = CELL;
$this->hasparent = true;
parent::__construct('');
}
}
class row extends element
{
public function __construct()
{
$this->template = ROW;
$this->child = 'cell';
parent::__construct();
}
}
class hcell extends element
{
public function __construct()
{
$this->template = HCELL;
$this->hasparent = true;
parent::__construct('');
}
}
class hrow extends element
{
public function __construct()
{
$this->template = HROW;
$this->child = 'hcell';
parent::__construct();
}
}
class grid extends element
{
public $header;
public function __construct()
{
$this->template = GRID;
$this->child = 'row';
parent::__construct();
}
public function print($content = '') {
$e = new hrow;
$e->content = $this->header;
$content = $e->print();
return parent::print($content);
}
}
class file_grid extends grid
{
public $file;
public $json;
public function __construct()
{
parent::__construct();
$this->template = FILE_GRID;
}
public function print($content = '') {
//$this->header = array('1'=>'column1','2'=>'column2');
$this->json = file_get_contents($this->file);
$arr = json_decode($this->json, true);
foreach ($arr as $key => $value) {
foreach ($value as $vkey => $vvalue) {
$this->header[] = $vkey;
}
break;
}
$this->content = $arr;
$ret = parent::print('');
$ret = str_replace(
array(JSN, FILE_NAME),
array($this->json, $this->file),
$ret
);
return $ret;
}
}
В файле lib.php описаны базовые константы через стандартную функцию defile, а так же описана иерархия классов. Базовым классом является ELEMENT. Далее от него наследуется ROW, CELL, GRID. От GRID наследуется FILE_GRID. В каждом классе прописана своя специфическая логика. В константах задана как HTML, CSS разметка, так и события JAVA SCRIPT, такие как DBLCLICK и FOCUSOUT. Классы используют эти константы чтоб отрисовывать в статике и динамике HTML разметку.
Состав файла utils.php:
<?php
function getval($data, $name){
$rjsn = json_decode($data, true);
foreach ($rjsn as $vjsn){
foreach ($vjsn as $key => $value) {
if($name == $key){
return $value;
}
}
}
return 'undefined';
}
Функция getval используется для получения значения параметра по имени внутри PHP события.
Таким образом реализована редактируемая табличная форма данных, получаемая из JSON файла.