jqGridPHP - таблицы на ajax без головной боли

fandm

Новичок
Боюсь, что единственный метод - реализовать поддержку настроек из colModel самостоятельно конкретно для своей формы.
Уже так и начал делать. :) Только взял за основу штатный метод viewGridRow.

Чтобы была штатная реализация не только LIMIT & OFFSET, но и через window-функцию.
Эмммм.... Не совсем понял, а что такое "window-функция"? :)

Preg_replace в запрос - это несерьезно. :)
Почему? :) Работает же ж. ;)

С наступающим 2012-м! :)
 

~WR~

Новичок
Залил обновление на гитхаб.

Из основного:

1. Добавил адаптер для Oracle:
https://github.com/wildraid/jqGridPHP/blob/master/php/Adapter/Oracle.php
Чтобы им воспользоваться, наследуйте классы таблиц от jqGrid_Adapter_Oracle.

2. Добавил возможно указывать запросы, которые будут выполнены сразу после соединения с базой данных.
Теперь не нужно перегружать драйвер PDO для этого. После настроек загрузчика просто напишите:
PHP:
$loader->addInitQuery("ALTER SESSION SET NLS_DATE_FORMAT = 'DD.MM.YYYY HH24:MI:SS'");
Документация здесь: http://jqgrid-php.net/doku.php?id=wiki:loader-init_query

Работает для любых СУБД, а не только для MySQL.

3. Изменил создание базового html для jqGrid на старый способ.
При нем закрывается тег </script>, далее вставляются простые <table> и <div>, а затем снова открывается <script>.

У обоих способов есть свои плюсы и минусы, но в старом намного проще динамически вставить новый грид на страницу. Например, в диалоговом окне.
Если поломается старый код, который рассчитывает на возврат чистого JS из render, то просто установите $this->render_html = 'js'. Вывод вернется к предыдущему варианту.

4. Теперь, при отсутствии в запросе к серверу параметра sidx, сортировка будет автоматически производиться по primary_key. Дело в том, что при отсутствии ORDER BY порядок вывода может быть непредсказуемым. Это проявлялось редко, но метко. Больше не будет.

Ну и все остальное по-мелочи.
Поставил на тестовый сайт последнюю версию jqGrid 4.3.1. Группировка заголовков работает. Странно. :)
 

fandm

Новичок
Спасибо за крайние изменения. :)
Поставил на тестовый сайт последнюю версию jqGrid 4.3.1. Группировка заголовков работает. Странно. :)
Возможно всё дело в определённых наименованиях групп или же label-ов столбцов или же их длин. Попробуйте создать грид версии 4.3.1 с группировкой с таким colModel (шапка грида, по идее, должна построится без проблем): Отправил colModel в переписку, т.к. здесь ограничение в 10000 символов.
А потом попытайтесь визуализировать хоть один из скрытых столбцов. И вот после этого сразу пойдут тормоза, а когда покажется результат, то он будет весьма странным. :) Да, и это у меня наблюдается в FF 8.0.1, на других не пробовал, но надо будет попробовать.
 

~WR~

Новичок
А можно в интернет куда-нибудь захостить пример? Самое лучшее)
 

fandm

Новичок
Вот.
Попробуйте визуализировать любой столбец. Может так зациклиться, что браузер упадёт, а может просто многократно повториться шапка грида по вертикали, 50 на 50. Проверил, в любом браузере так.
А вот с версией 4.1.2. Всё работает отлично.
 

fandm

Новичок
Может кому-то пригодится, на скорую руку (лишний код особо не убирал, можете заняться этим сами :) ) сваял функцию "myGridToForm" (добавил в jqgrid-ext.js), которая, в отличие от штатной "GridToForm", не требует наличия подготовленных полей формы, т.е. нет необходимости прописывать все ожидаемые input-ы c id равными названиям полей в наборе данных, тем более, когда типы полей разные и имеют ряд специфических настроек на уровне colModel. Достаточно подготовить свой шаблон формы и вложенной таблицы, а тем ячейкам (TD), в которых должны появиться поля из грида, присвоить id равные названиям полей в наборе данных с префиксом "TD_" (TD_имя_поля). При таком подходе все поля будут отформатированы так, как это делает штатный метод editGridRow, т.е. будут восприняты все настройки, такие как: editable, edittype, editoptions, editrules и т.д, но не будет выведен диалог, а все поля появятся в подготовленном шаблоне формы. Пример шаблона формы (в данном примере поля набора данных: PREFIX и SUFIX):
PHP:
<form method="post" name="doc_paramsA" id="doc_paramsA" action="" title="" style="width:100%;"> 
	<table width="99%" cellspacing="2" cellpadding="0"> 
		<tbody> 
			<tr> 
				<td class="ui-widget-content underline" height="16" width="400" valign="top">Префікс | Суфікс</td> 
				<td width="9" background="images/linie.gif"><img src="images/linie.gif" width="9" height="18" border="0"></td>
				<td colspan="3" height="19" valign="top" id="TD_PREFIX"></td>
				<td width="2"><img src="images/spacer.gif" width="2" height="18" border="0"></td>
				<td valign="top" id="TD_SUFIX"></td> 
			</tr>	
		</tbody> 
	</table>
</form>
В данном случае результатом будет (для столбцов PREFIX и SUFIX на уровне colModel указано 'edittype' => 'text'):
PHP:
<form method="post" name="doc_paramsA" id="doc_paramsA" action="" title="" style="width:100%;"> 
	<table width="99%" cellspacing="2" cellpadding="0"> 
		<tbody> 
			<tr> 
				<td class="ui-widget-content underline" height="16" width="400" valign="top">Префікс | Суфікс</td> 
				<td width="9" background="images/linie.gif"><img src="images/linie.gif" width="9" height="18" border="0"></td>
				<td height="19" valign="top" id="TD_PREFIX" colspan="3"><input type="text" style="height: 11px; width: 98%;" id="PREFIX" name="PREFIX" role="textbox" class="FormElement ui-widget-content ui-corner-all" readonly="readonly"/></td>
				<td width="2"><img height="18" border="0" width="2" src="images/spacer.gif"/></td>
				<td valign="top" id="TD_SUFIX"><input type="text" style="height: 11px; width: 98%;" id="SUFIX" name="SUFIX" role="textbox" class="FormElement ui-widget-content ui-corner-all" readonly="readonly"/></td> 
			</tr>	
		</tbody> 
	</table>
</form>
Пример вызова:
PHP:
	$grid.jqGrid('myGridToForm', $grid.jqGrid('getGridParam','selrow'), "#doc_paramsA", {
		'readonly' : true
	});
Последним параметром передаются стандартные опции, которые обычно передаются в метод editGridRow (за исключением событий и ряда некоторых опций, присущих исключительно диалогу). Дополнительная опция "readonly" - сделает все поля формы нередактируемыми.
 

mamboz

Новичок
Доброго времени суток!

У меня появилась маленькая проблема. При некоторых условиях необходимо необходимо одному полю присваивать formatoptions как baseLinkUrl, а при некоторых выводить просто как текст. Попробовал через parseRow, но как я понял там только обработка самих данных. Пока что на ум ничего не приходит.
Надоумте пожалуйста..
P.S. Всех с наступившем новым :)
 

~WR~

Новичок
Здесь два варианта.

1. Правильный.
Сделать свой formatter, который будет выводить либо ссылку, либо текст в зависимости от внешних условий. Там в аргументы форматтеров передается просто куча всего, поэтому очень многие условия можно проверять на client-side. Почитать об этом можно в документации jqGrid, раздел Custom Formatters.

Это особенно важно, если вы хотите потом редактировать такую хитрую колонку. Через formatter'ы это возможно.

2. Быстрый.
Прямо в parseRow формировать html. При этом колонке нужно указать параметр 'encode' => false. Например:
PHP:
protected function parseRow($r)
{
    $r['name'] = $r['is_active'] ? "<a href='/user/{$r['id']}/' target='_blank'>{$r['name']}</a>" : $r['name'];
    return $r;
}

Способ подходит, если грид нужен только для вывода, и нет желания копаться с javascript.

fandm, пока не понял, в чем там дело. Немного разгребу текущие дела и посмотрю.
 

mamboz

Новичок
Здесь два варианта.

1. Правильный.
Сделать свой formatter, который будет выводить либо ссылку, либо текст в зависимости от внешних условий. Там в аргументы форматтеров передается просто куча всего, поэтому очень многие условия можно проверять на client-side. Почитать об этом можно в документации jqGrid, раздел Custom Formatters.

Это особенно важно, если вы хотите потом редактировать такую хитрую колонку. Через formatter'ы это возможно.

2. Быстрый.
Прямо в parseRow формировать html. При этом колонке нужно указать параметр 'encode' => false. Например:
PHP:
protected function parseRow($r)
{
    $r['name'] = $r['is_active'] ? "<a href='/user/{$r['id']}/' target='_blank'>{$r['name']}</a>" : $r['name'];
    return $r;
}

Способ подходит, если грид нужен только для вывода, и нет желания копаться с javascript.

fandm, пока не понял, в чем там дело. Немного разгребу текущие дела и посмотрю.
Сенк за быстрый ответ.... сейчас попробую...
 

~WR~

Новичок
Разобрался. Проблема была в коллизиях между моей версией groupHeader и версией, принятой в в основную ветку jqGrid.

Они налепили вызовы updateGroupHeader в showHideCol. И теперь, когда отрабатывает columnChooser, у них заголовок пересчитывается столько раз, сколько колонок в colModel. А там еще и функция пересчета заметно тяжелее моей. Не знаю, о чем люди думали, когда принимали этот commit. :)

Если хотите использовать мою версию, то нужно скачать новый jqgrid-ext.js
https://github.com/wildraid/jqGridPHP/blob/master/client/jqgrid-ext.js

И в коде везде заменить опцию groupHeader на groupHeaderWR
Заменить функцию updateGroupHeader на updateGroupHeaderWR
Заменить функцию destroyGroupHeader на destroyGroupHeaderWR

Кстати, буквально сегодня в jqGrid сделали классный pull request, в котором добавляются нормальные события. Наконец-то можно будет без проблем повесить несколько функций на событие gridComplete. И можно будет использовать delegate. Мурр :)
 

fandm

Новичок
Если хотите использовать мою версию, то нужно скачать новый jqgrid-ext.js
https://github.com/wildraid/jqGridPHP/blob/master/client/jqgrid-ext.js
Конечно, только Вашу! :) Огромное спасибо, теперь всё работает как надо! :)

Кстати, буквально сегодня в jqGrid сделали классный pull request, в котором добавляются нормальные события. Наконец-то можно будет без проблем повесить несколько функций на событие gridComplete. И можно будет использовать delegate.
Не совсем понял в чём вкусняшка, но очевидно что-то очень полезное. :)))
 

fandm

Новичок
Борюсь тут с выводом дерева... Зашёл на http://jqgrid-php.net/examples/?render=jqOutTree, чтоб подсмотреть, а там ошибку выводит (ред. от 07.01.2012, уже работает :)).
Не могу понять как вывести дерево и сразу сделать ExpandAll или же сразу вывести все узлы раскрытыми. Если 'treedatatype'=> 'local', то всё понятно, работает, а если не локальные данные, тогда как?
Вероятно ответ кроется здесь:
Разумеется, вы можете использовать любые другие модели treegrid, а также загружать все дерево сразу целиком.
Но как это сделать? Как сначала загрузить все узлы через Ajax, а затем указать, что данные являются локальными и при раскрытии узлов уже не нужно делать Ajax-запросы?
 

~WR~

Новичок
Обновил старый пример. Добавил новый:
http://jqgrid-php.net/examples/?render=jqOutTreeFull

Тут сразу всё дерево целиком.
В зависимости от СУБД, структуры таблиц и количества данных меняются и способы выборки всего дерева. В этом примере просто рекурсивные запросы.

Если использовать не MySQL, то можно сделать в один запрос.
В любом случае, очень не рекомендую загружать большие деревья целиком. На слабых компах браузер начинает буксовать из-за размеров DOM-дерева.
 

~WR~

Новичок
Чтобы узлы были сразу открыты - измените expanded на true.

jqGrid обращается к серверу в тот момент, когда пытается открыть узел, у которого isLeaf = false, но при этом нет уже загруженных детей.
Если дети есть, то просто их показывает.
 

fandm

Новичок
Спасибо за пример, буду разбираться. :)

В любом случае, очень не рекомендую загружать большие деревья целиком.
Согласен, но у меня как раз тот случай, когда ожидаемое дерево небольшое.

Чтобы узлы были сразу открыты - измените expanded на true.
В parseRow? Пробовал, влияет в итоге только на иконку, а узел на самом деле остаётся свёрнутым. Тут вообще какая-то загадка... Строю дерево (Oracle), вроде бы всё ок, кликаю на свёрнутом узле - он раскрывается, кликаю на нём снова - меняется только иконка, но узел остаётся раскрытым. Не понимаю, с чего бы это? Пробовал и на версии 4.1.2 - то же самое. Что характерно, на портированном в Oracle примере с tbl_tree всё работает чудно, узлы раскрываются и снова сворачиваются, без проблем. Явно причина в специфическом query, который я использую. Где прописывается реакция на клик по ExpandColumn, куда копать?
Кстати, в примерах указывается: $this->table = 'tbl_tree'; Это обязательно? Необходимо указывать таблицу, независимо от того, что позже указывается $this->query? Просто у меня дерево строится не на основании выборки из одной конкретной таблицы, а на основании выборки, представляющей собой union выборок из двух и трёх таблиц, соответственно.

И ещё одна странность, с которой сейчас разбираюсь. Не работают фильтры в дереве, ни в одном из столбцов. Даже явное указание search_op не помогает, даже не заходит в searchOpLike... {where} в запросе присутствует, но туда упорно подставляется $where_empty... Или в деревьях фильтр игнорируется?

Да, кстати, вспомнил! Для Oracle не катит $where_empty = 'null = null', т.к. в Oracle результатом такой операции сравнения будет false. Я использую $where_empty = '1 = 1'. Это я к тому, что для работы с Oracle этот нюанс тоже необходимо учесть.
 

~WR~

Новичок
В parseRow? Пробовал, влияет в итоге только на иконку, а узел на самом деле остаётся свёрнутым. Тут вообще какая-то загадка... Строю дерево (Oracle), вроде бы всё ок, кликаю на свёрнутом узле - он раскрывается, кликаю на нём снова - меняется только иконка, но узел остаётся раскрытым. Не понимаю, с чего бы это?
Скорее всего, какие-то ошибки в структуре дерева. Например, некорректный level или parent_id. Сравните свой вывод с примерами.
Кстати, вывод вроде должен быть упорядоченный. Посмотрите еще сортировку нод.

И ещё одна странность, с которой сейчас разбираюсь
Там проблема в том, что использование treeGrid изменяет datatype грида на local. Из-за этого в модуле filterToolbar фильтры упаковываются в json строку вместо того, чтобы добавляться в запрос обычными парами key:value.

Тут есть два варианта.
1. Распарсить filters и достать из него пришедшие значения.
Стандартной процедуры для этого формата я не делал, т.к. он проигрывает в удобстве обычному.

2. В исходниках jqGrid найти строку:
PHP:
if(p.stringResult === true || $t.p.datatype == "local") {
- и убрать условие на datatype. Может поломать фильтрацию на стороне клиента.

Это я к тому, что для работы с Oracle этот нюанс тоже необходимо учесть.
В адаптере для Оракла уже учтено.
 

fandm

Новичок
Например, некорректный level или parent_id.
Вот тут-то как раз и проблема. Я не понимаю на основании чего регулируется этот самый level. Как он зависит от исходного query? В parseRow он просто инкриментируется. $this->input('nodeid') - принимает значение поля id или это что-то другое?
Сравните свой вывод с примерами.
Пока не понимаю как эти выводы можно сравнить и в каком ключе их вообще сравнивать...

Я тут отредактировал немного свой предыдущий пост. Доп. вопрос:
Кстати, в примерах указывается: $this->table = 'tbl_tree'; Это обязательно? Необходимо указывать таблицу, независимо от того, что позже указывается $this->query? Просто у меня дерево строится не на основании выборки из одной конкретной таблицы, а на основании выборки, представляющей собой union выборок из двух и трёх таблиц, соответственно.
С фильтрами понятно. Спасибо, буду разбираться.
 

fandm

Новичок
1. Распарсить filters и достать из него пришедшие значения.
Можно уточнить в какой именно процедуре это лучше сделать в jqGrid.php? Какую процедуру перегрузить? Спасибо.
 

~WR~

Новичок
Я не понимаю на основании чего регулируется этот самый level.
Level - это уровень узла в дереве. Самый верхний - 1. Один раз развернули - 2. Еще глубже развернули - 3.
В первом примере уровень приходит из запроса к серверу. Во втором он рассчитывается в коде в процессе рекурсии.

Кстати, в примерах указывается: $this->table = 'tbl_tree'; Это обязательно?
В данном случае - не обязательно. $this->table используется в стандартных операция (insert,update,delete). Если их нет, то можно не указывать.
Я просто на автомате его вбиваю, чтобы потом вообще не думать, указывал его или нет.

Можно уточнить в какой именно процедуре это лучше сделать в jqGrid.php? Какую процедуру перегрузить? Спасибо.
Как-то так:
PHP:
protected function getInput()
{
    $input = parent::getInput();

    if(isset($input['filters']))
    {
        $filters = json_decode($input['filters'], true);
    
        foreach($filters['rules'] as $r)
        {
            $input[$r['field']] = $r['data'];
        }
    }

    return $input;
}
Специальный хук, чтобы как угодно изменить входящие переменные на уровне грида. Суперглобальные массивы не затрагиваются.
 

fandm

Новичок
Level - это уровень узла в дереве. Самый верхний - 1. Один раз развернули - 2. Еще глубже развернули - 3.
В первом примере уровень приходит из запроса к серверу. Во втором он рассчитывается в коде в процессе рекурсии.
Я понимаю физический смысл и назначение level, проблема в другом. Я пока не усёк может ли его неверное значение в какой-то момент влиять на несворачиваемость узла и как вычислить, что в какой-то момент он может принимать неверное значение, поскольку визуально узлы раскрываются корректно со сдвигом вправо с учётом level. А вот сворачивание уже не происходит. При сворачивании узла разве перепроверяется level или parent_id? Ведь обращения к серверу уже нет в этот момент. Вот если бы как-то отследить клик по узлу и выяснить чего там не хватает, чтобы узел свернулся... Ведь js ошибок-то FireBug не показывает... Ладно, буду копать... В любом случае, спасибо за помощь. :)

По поводу хука огромное спасибо! :) Суть теперь ясна.
 
Сверху