css, html, php, javascript, jQuery, ajax … – решения, примеры, рецепты
23 Май
Введение
Я рад приветствовать вас, дорогие читатели в третьей статье цикла, посвященного плагину jqGrid. Прошу прощения за столь долгое отсутствие, сейчас все свободное время отдаю изучению и освоению другого монстра – ExtJS.
Напомню, что в предыдущей статье (jqGrid Часть II: Базовые возможности) мы перешли от basic к advanced уровню использования этого замечательного плагина. Но прежде чем мы начнем я хотел бы сообщить, что jqGrid обновилась до версии 3.6.5 и соответственно и мы будем использовать новую версию.
Итак план работ на эту статью:
1) Краткие сведения о деревьях
Конечно здесь речь пойдет не о садово-огородных работах, а об представлении древовидных структур данных. В очередной раз обратимся за определением к Википедии:
Древовидная структура является одним из способов представления иерархической структуры в графическом виде. Древовидной структурой называется благодаря тому, что граф выглядит как перевернутое дерево. По этой же причине говорят, что корневой узел (корень) находится на самом верху, а листья — внизу.
В принципе более или менее сносное определение, несмотря на то, что оно навивает академическую скуку я все же настоятельно рекомендую ознакомиться с материалом по древовидным структурам. Поверьте моему не большому опыту, абсолютно каждый программист рано или поздно станет перед необходимостью использовать дерево, и тут одним из помошников может стать jqGrid. Плагин может работать с двумя самыми распространенными в вычислительной технике типами деревьев: на основе «вложенных множеств» (Nested Sets) и «матрицы смежностей» (adjacency matrix). Вообще говоря тема деревьев очень серьезна, это один из разделов теории графов на изучение которых отводится не одна лекция в ВУЗах, но от себя посоветую те материалы которые мне в свое время очень помогли. Столкнувшись впервые с проблемой хранения деревьев я наткнулся на материал по подходу вложенных множеств (Хранение древовидных структур в Базах данных) и сразу же остановился на нем, наверное потому что для работы с таким деревом существует готовый PHP класс. Но в процессе написания статьи я все же прочел статью Работа с «MySQL. Деревья» в которой рассматривается подход матрицы смежностей.
Думаю здесь общие сведения следует закончить и перейти к практическому рассмотрению.
2) jqGrid и SQL деревья
Для того, чтобы jqGrid могла работать с древовидными структурами, она должна быть загружена с модулем Tree Grid. Плагин может работать с обоими типами деревьев, упомянутых в предыдущем пункте. При этом jqGrid может работать с деревьями подгрузив все дерево за один раз или же подгружая ветви и листья по мере необходимости. Собственно загрузку дерева за один раз я рассматривать не буду, не интересно, а вот автоподгрузку мы разберем детально. И на этой ноте давайте перейдем к просмотру ДЕМО3.1, ссылка для скачивания
$(function(){
$('#table_nested').jqGrid({
treeGrid: true,
treeGridModel: 'nested',
ExpandColumn: 'title',
ExpandColClick: true,
url: 'p3e1_n.php',
datatype: "json",
mtype: "POST",
colNames:["id","Раздел"],
colModel:[
{name:'cid',index:'cid', width:1, hidden:true, key:true},
{name:'title',index:'title', width:180}
],
treeIcons: {plus:'ui-icon-folder-collapsed',minus:'ui-icon-folder-open',leaf:'ui-icon-document'},
height: 'auto',
caption: 'вложенныe множества',
hidegrid: false
});
$('#table_adjacency').jqGrid({
treeGrid: true,
treeGridModel: 'adjacency',
ExpandColumn: 'title',
ExpandColClick: true,
url: 'p3e1_a.php',
datatype: "json",
mtype: "POST",
colNames:["cid","Раздел"],
colModel:[
{name:'cid',index:'cid', width:1, hidden:true, key:true},
{name:'title',index:'title', width:180}
],
treeIcons: {plus:'ui-icon-circle-plus',minus:'ui-icon-circle-minus',leaf:'ui-icon-person'},
height: 'auto',
caption:'матрица смежностей',
hidegrid: false
});
});
Настройки обоих таблиц идентичны, но имеется ряд свойств, отличающих эти экземпляры от рассмотренных в предыдущих статьях.
С не описанными свойства мы знакомы из предыдущих статей, а другие свойства и методы, характерные только для работы с jqGrid в качестве дерева можно посмотреть на странице TreeGrid.
Прежде чем перейдем к рассмотрению серверных скриптов давайте немного разберемся как работает плагин. Сразу после инициализации, таблица отправляет запрос к серверу, при этом передает следующие данные и ждет ответа сервера:
В режиме ‘nested’ (на примере метода POST)
- POST['nodeid'] – идентификатор узла (ветви)
- POST['n_left'] – позиция узла «слева»
- POST['n_right'] – позиция узла «справа»
- POST['n_level'] – уровень вложенности узла
В режиме ‘adjacency’ (на примере метода POST)
- POST['nodeid'] – идентификатор узла (ветви)
- POST['parentid'] – идентификатор родительского узла
- POST['n_level'] – уровень вложенности узла
кроме этого, на сервер также передаются стандартные для jqGrid параметры (на примере метода POST)
POST['page'] – номер страницы, для «листалки» POST['rows'] – количество записей, для ограничения при выборке данных POST['sidx'] – значение index: в colModel, имя столбца по которому необходимо отсортировать результат POST['sord'] – направление сортировки
После получения данных, серверный скрипт выполняет запрос данных из БД и сроит и выводит результат в следующих форматах.
В режиме ‘nested’ (на примере XML ответа)
- пользовательские столбцы
- level – уровень узла в иерархии
- lft – позиция узла «слева»
- rgt – позиция узла «справа»
- isLeaf – логический признак (true/false), определяет этот узел является «веткой»(есть подузлы) или «листом»(нет подузлов)
- expanded – логический признак (true/false), нужно ли показывать подузлы если этот узел является веткой, т.е. загрузить «открытым» или «закрытым»
В режиме ‘adjacency’ (на примере XML ответа)
- пользовательские столбцы
- level – уровень узла в иерархии
- parent – идентификатор родительского узла
- isLeaf – логический признак (true/false), определяет этот узел является «веткой»(есть подузлы) или «листом»(нет подузлов)
- expanded – логический признак (true/false), нужно ли показывать подузлы если этот узел является веткой, т.е. загрузить «открытым» или «закрытым»
Ядро (ридеры) jqGrid разбирают ответ и вставляют данные в таблицу. Вот теперь серверные скрипты будут более-менее понятны.
Вложенные множества (p3e1_n.php)(опущены подключение к БД и вывод результата)
// Получаем параметры от таблицы
# ВНИМАНИЕ!!!
# Данный код не имеет проверок запрашиваемых данных
# что может стать причиной взлома! Обязательно проверяйте все данные
# поступающие от клиента
$node = intval($_POST['nodeid']);
$left = intval($_POST['n_left']);
$right = intval($_POST['n_right']);
$level = intval($_POST['n_level']);
// Настраиваем условие для выбора ветки
$WHERE = '';
if($node > 0) {
$level = $level + 1; // нужно выбрать следующий уровень
$WHERE = " AND cleft > ".$left." AND cright < ".$right." AND clevel = ".$level;
}else{
$level = 0;
}
// Выбираем подразделы
$query = "SELECT cid, title, clevel, cleft, cright FROM tree_nested_sets WHERE cleft BETWEEN cleft AND cright ".$WHERE." GROUP BY title ORDER BY cleft;";
$result = mysql_query($query);
// Формируем результат
$response->page = 1;
$response->total = 1;
$response->records = 1;
$i = 0;
while($row = mysql_fetch_assoc($result)) {
//-------------------------------------
// Определяем является ли выбранный узел "листом"
if($row['cright'] == $row['cleft']+1)
$leaf = 'true';
else
$leaf='false';
//-------------------------------------
if($level == $row['clevel']){
$response->rows[$i]['cell'] = array($row['cid'], $row['title'], $row['clevel'], $row['cleft'], $row['cright'], $leaf, 'false');
}
$i++;
}
Матрица смежностей(p3e1_a.php)(опущены подключение к БД и вывод результата)
// Выбираем все разделы без подразделов (листья)
$query_feafs = "SELECT t1.cid FROM tree_adjacency_matrix AS t1 LEFT JOIN tree_adjacency_matrix AS t2 ON t1.cid = t2.id_parent WHERE t2.cid IS NULL";
$sql_leafs = mysql_query($query_feafs);
while($row = mysql_fetch_assoc($sql_leafs)) {
$leafs[] = $row['cid']; // Массив для хранения "листьев"
}
unset($row);
// Получаем параметры от таблицы
# ВНИМАНИЕ!!!
# Данный код не имеет проверок запрашиваемых данных
# что может стать причиной взлома! Обязательно проверяйте все данные
# поступающие от клиента
$level = intval($_POST['n_level']);
$node = intval($_POST['nodeid']);
// Настраиваем условие для выбора ветки
if($node > 0) {
$level = $level + 1; // нужно выбрать следующий уровень
$WHERE = 'id_parent ='.$node;
} else {
$WHERE = 'id_parent IS NULL'; // выбрать корень(ни)
}
// Выбираем подразделы
$query = "SELECT cid, title, id_parent FROM tree_adjacency_matrix WHERE ".$WHERE;
$result = mysql_query($query);
// Формируем результат
$response->page = 1;
$response->total = 1;
$response->records = 1;
$i = 0;
while($row = mysql_fetch_assoc($result)) {
//-------------------------------------
// Определяем ID родителя этого узла
if(!$row['id_parent'])
$parent = 'NULL';
else
$parent = $row['id_parent'];
//-------------------------------------
// Определяем является ли выбранный узел "листом"
if(in_array($row['cid'], $leafs))
$is_leaf = 'true';
else
$is_leaf = 'false';
//--------------------------------------
$response->rows[$i]['cell'] = array($row['cid'], $row['title'], $level, $parent, $is_leaf, FALSE);
$i++;
}
Я считаю комментировать в этих листингах особо нечего, кроме того что данные поступающие с сервера не ограничиваются по кол-ву записей и не сортируются. А практически все остальное является повторением примеров из предыдущих статей или специфическими вещами при работе с деревьями, описанными в статьях приложенных выше. Также обращу ваше внимание на то, что пример работы с Nested Set Model описанный в документации разработчиком не хотел работать, пришлось его доводить, за что мне большое спасибо
.
3)jqGrid и статические деревья
Название статические деревья оносительно, того что мы делали немного ранее. Безусловно данные «загруженные» таким способом могут быть построены и скриптом динамически, но все же бывают такие задачи, когда дерево уже есть необходимо лишь вывести его в дружественной для пользователя форме как в ДЕМО3.2 . Не правда ли что-то напоминает? Да! это меню из демо jqGrid от разработчика.
Листинг клиентской части:
$('#table').jqGrid({
url: "tree.xml",
datatype: "xml",
height: "auto",
pager: false,
loadui: "disable",
colNames: ["id","Items","url"],
colModel: [
{name: "id",width:1,hidden:true, key:true},
{name: "menu", width:150, resizable: false, sortable:false},
{name: "url",width:1,hidden:true}
],
treeGrid: true,
caption: "jqGrid Demos",
ExpandColumn: "menu",
width: 200,
rowNum: 200,
ExpandColClick: true,
treeIcons: {leaf:'ui-icon-document-b'}
});
Полностью идентичные параметры плагина, за исключением того, что я отключил обработчик события onSelectRow.
Из листига должно стать понятно, что таблица запрашивает статичный(оносительно) файл tree.xml, который выглядит так
<?xml version='1.0' encoding="utf-8"?>
<rows>
<page>1</page>
<total>1</total>
<records>1</records>
<row><cell>1</cell><cell>Loading Data</cell><cell></cell><cell>0</cell><cell>1</cell><cell>10</cell><cell>false</cell><cell>false</cell></row>
<row><cell>2</cell><cell>XML Data</cell><cell>xmlex.html</cell><cell>1</cell><cell>2</cell><cell>3</cell><cell>true</cell><cell>true</cell></row>
<row><cell>3</cell><cell>JSON Data</cell><cell>jsonex.html</cell><cell>1</cell><cell>4</cell><cell>5</cell><cell>true</cell><cell>true</cell></row>
...
</rows>
Т.е. jqGrid просто разбирает данные из этого файла и строит TreeGrid.
II. Связывание данных в jqGrid
Иногда при построении какого либо приложения необходимо как то сгруппировать данные для экономии визуального пространства на странице и при этом иметь возможность как можно быстрее и комфортнее получить доступ к более детальной информации или связанной с текущей. Немного сумбурно получилось
, но надеюсь вы меня поняли, а если не поняли то примеры разложат все по полочкам.
1)Простая подтаблица (subgrid)
Это весьма полезная опция у этого плагина. На ДЕМО3.3 обратите внимание на крестик в крайнем правом столбце таблицы. При клике на нем под выбранной строкой распахивается «карман» в котором находится небольшая табличка с названиями городов и прочей «рыбой». Вот пример группировки. Теперь давайте перейдем к листингам.
$('#table').jqGrid({
url:'p3e3.php',
datatype: 'json',
mtype: 'POST',
colNames:['Код страны','Cтрана',''],
colModel :[
{name:'country_code', index:'country_code', width:80, hidden: true},
{name:'country_name', index:'country_name', width:60},
{name:'description', width:200, sortable: false}
],
pager: $('#tablePager'),
rowNum: 100,
scroll: true,
viewrecords: true,
sortname: 'country_name',
sortorder: 'asc',
width: 700,
height: 400,
subGrid: true,
subGridUrl: 'p3e3.php?get=subgrid',
subGridModel:[{
name : [' ','Город', 'Широта', 'Долгота'],
width: [40, 200, 150, 150],
align: ['center','left','right','right'],
params: ['country_code','country_name']
}]
});
Как видите, абсолютно обыкновенный jqGrid, но в «настройках» есть несколько интересных моментов:
subGridModel: – представляет из себя массив с объектом, у которого есть свои параметры. name, width и align уверен их имена говорят сами за себя, это тексты заголовков столбцов, ширины соответствующих столбцов и выравнивание в них соответственно. Более интересным будет свойство params. Оно позволяет передать значения из каких то ячеек родительской таблицы на сервер при запросе подтаблицы. Иными словами, когда вы нажмете на крестик , открывающий подтаблицу, происходит запрос к серверу которому передается клоч (id) записи (в нашем случае это country_code) и плос к этим данным можно передать дополнительные (в нашем случае серверу будет передан еще и country_name). А вот на сервере получив значения нужных параметров (в этом примере серверный скрипт просто игнорирует значение параметра country_name) вы выполняем запрос к БД и выводим JSON объект с данными.
...
$get = $_REQUEST['get']; // Параметр указывающий на то
// что мы запросили информацию для подтаблицы
...
switch ($get){
case 'subgrid':
// Выбрать города запрошенной страны
$id =$_REQUEST['id'];
// Запрос данных
$query = "SELECT id, country_code, region_code, city, latitude, longitude, nbip FROM cities WHERE country_code LIKE '".$id."'";
$result = mysql_query($query);
if(mysql_num_rows($result) >= 1){
// Строки данных для таблицы
$i = 0;
while($row = mysql_fetch_assoc($result)) {
$data->rows[$i]['cell'] = array($i+1,'<strong><em>'.$row['city'].'</em></strong>',$row['latitude'],$row['longitude']);
$i++;
}
}
break;
default:
// Выбрать названия стран и сосчетать города
...
}
// Вывести результат
...
Серверный скрипт особо ничем не отличается от рассмотренных в предыдущих примерах. При этом он разбит на две части оператором switch. Который по умолчанию выполняет «стандартную» обработку и вывод данных для главной таблицы. А вот если этот скрипт запустить передав ему параметр get (я выбрал такое имя только ради наглядности) тогда будет выполнена выборка данных для подтаблицы. Ну и если у вас чешутся руки поковырять пример, вы можете скачать его.
Подтаблицы это замечательный инструмент, который позволяет очень элегантно организавать данные, но при этом имеет один существенный недостаток – подтаблица «не живая». Т.е. данные в подтаблице не могут сортироваться, нет возморжности реализовать поиск и т.д. А это очень критично если в подтаблице будет выводиться много записей. Однако это ограничение отпадает само собой при использовании jqGrid как подтаблицы.
2)jqGrid как subgrid
Итак начнем с промотра ДЕМО3.4. Как видите все тот же «родной» jqGrid с названиями стран, и кол-вом городов в этой стране, но при этом подтаблица теперь представляет из себя самую настоящую jqGrid со всеми свойствами и методами. Согласитесь, что так работать намного приятнее и удобнее.
А вот здесь самое интересное – клиентский код (в листинге опущены все настройки родительской таблицы, кроме одного свойства):
...
subGrid: true,
subGridRowExpanded: function(subgrid_id, row_id) {
var subgrid_table_id;
subgrid_table_id = subgrid_id+'_t';
$('#'+subgrid_id).html('<table id="'+subgrid_table_id+'"></table><div id="'+subgrid_table_id+'_pager"></div>');
$('#'+subgrid_table_id).jqGrid({
url: 'p3e4.php',
datatype: 'json',
mtype: 'POST',
postData: {'get':'subgrid', 'id':row_id},
colNames: ['Город', 'Широта', 'Долгота'],
colModel: [
{name: 'city', index: 'city', width:130},
{name: 'latitude', index: 'latitude', width:80, align: 'right'},
{name: 'longitude', index: 'longitude', width:80, align: 'right'}
],
height: 'auto',
autowidth: true,
rownumbers: true,
rownumWidth: 40,
rowNum: 10,
sortname: 'city',
sortorder: 'asc',
pager: $('#'+subgrid_table_id+'_pager'),
rowNum:10,
rowList:[10,20,50,100]
});
}
...
Настройки таблицы-родителя идентичные предыдущему примеру, но при этом появилось новое свойство subGridRowExpanded. Думаю самые догадливые поняли, а самые любопытные прочли в документации в секции Subgrid, что subGridRowExpanded не просто свойство, а событие.
subGridRowExpanded – это событие возникает когда пользователь кликает на крестик раскрытия подтаблицы и выполняет определенную функцию при распахивании «кармана» для подтаблицы. При этом в функцию обработчик будут переданы 2 параметра:
- pID – уникальный идентификатор «кармана» (контейнера, если хотите) который открывается под выбранной строкой
- id – идентификатор записи под которой открылся «карман»
Безусловно существуют еще события, о которых вы можете узнать из документации. А мы пока пробежимся по листингу. Итак когда пользователь «тыцнул» на крестик, открывается контейнер и выполняется функция. Которая получает 2 параметра subgrid_id и row_id. Далее в строке 5 создаем строковое значение для атрибута id и в 6й строке используем метод jQuery, который вставляет в «карман» (кстати сказать что это обыкновенный DIV) html разметку таблицы и DIV’a. А далее идет бональная инициализация jqGrid для только что созданной разметки. Т.е. фактически мы просто напросто динамически создали разметку и создали jqGrid! В этот момент я воскликнул «Тааак!» и потер руки. Почему? Да потому, что зная id объекта (<div id="someid">…</div>) с помощью jQuery можно сотворить с ним все что угодно. Но прежде чем мы перейдем к следующему примеру я приведу ссылку на раздаточный материал по теме – это пример который только что расмотрели.
А теперь зная id контейнера давайте, еще добавим наглядности нашему примеру со странами и городами, например как на ДЕМО3.5. Как видите все эта же, скучная таблица со странами, но теперь при клике на крестик открывается подробная информация и флаг выбранной строки. Под которой свернутая jqGrid таблица с перечнем городов. Этот пример вы можете скачать перейдя по этой ссылке. Внутри архива все скрипты, но наибольший интерес представляет клиентский код. Я условно разделю его на 2 части, первая (нижняя) часть:
$('#table').jqGrid({
...
subGrid: true,
subGridRowExpanded: function(subgrid_id, row_id) {
getCountryDetails(subgrid_id, row_id);
}
});
Это таблица из предыдущего примера, но теперь при возникновении события subGridRowExpanded будет выполнена пользовательская функция getCountryDetails(), которая и является 2 половиной клиентского скрипта:
function getCountryDetails(container, countryCode){
$.ajax({
type: 'post',
url: 'p3e5.php',
data: ({'id': countryCode, 'get': 'details'}),
dataType: 'json',
success: function(server){
$('#'+container).html('<div class="c_detail"><img src="files/images/'+server.flag+'"><ul><li><strong>Столица:</strong> '+server.capital+'</li><li><strong>Дата независимости:</strong> '+server.independence_date+'</li><li><strong>Официальные языки:</strong> '+server.off_lng+'</li><li><strong>Форма правления:</strong> '+server.government+'</li><li><strong>Глава правительства:</strong> '+server.president+'</li><li><strong>Валюта:</strong> '+server.currency+'</li><li><strong>Интернет-домены:</strong> '+server.domains+'</li><li><strong>Телефонный код:</strong> '+server.dialing_code+'</li><li><strong>Часовой пояс:</strong> '+server.time_zone+'</li></ul></div>');
var subgrid_table_id = container+'_t';
$('#'+container).append('<table id="'+subgrid_table_id+'"></table><div id="'+subgrid_table_id+'_pager"></div>');
$('#'+subgrid_table_id).jqGrid({
caption: 'Города',
hiddengrid: true,
url: 'p3e4.php',
datatype: 'json',
mtype: 'POST',
postData: {'get':'subgrid', 'id':countryCode},
colNames: ['Город', 'Широта', 'Долгота'],
colModel: [
{name: 'city', index: 'city', width:130},
{name: 'latitude', index: 'latitude', width:80, align: 'right'},
{name: 'longitude', index: 'longitude', width:80, align: 'right'}
],
height: 'auto',
autowidth: true,
rownumbers: true,
rownumWidth: 40,
rowNum: 10,
sortname: 'city',
sortorder: 'asc',
pager: $('#'+subgrid_table_id+'_pager'),
rowNum:10,
rowList:[10,20,50,100]
});
}
});
}
В теле этой функции выполняется AJAX запрос, передающий серверному скрипту 2 параметра методом POST:
После того как запрос будет успешно выполнен (см. строку 8), в открывшийся под выбранной строкой контейнер будет помещена какая-то HTML разметка с полученными с сервера данными. В данном примере такая разметка представлена не нумированным списком в картинкой – флагом страны. После этого в этот же контейнер помещается разметка для дочерней таблицы и производится инициализация дочерней таблицы.
В принципе логика работы достаточно проста, главное не забывать что работать с полученными от сервера данными можно только после окончания AJAX запроса или производя синхронные запросы.
Как вы понимаете в контейнер для подтаблицы можно помещать не только какую-то примитивную разметку, но и формы, флеш, другие «вкусные» и полезные вещи.
Однако не всегда можно сгруппировать данные и засунуть их в подтаблицу. Иногда данные могут быть полностью самостоятельными и при этом иметь некую связь. Например однажды передо мной стала задача вывести пользователю перечень счетов и платежей по этим счетам, и при этом обеспечить возможность просмотра счетов, платежей и платежей по выбранному счету. И тут то мне очень помогла методика «ведущий-ведомый». Работа которой проиллюстрирована в ДЕМО5.6.
Имеется две jqGrid, отображающие страны и города, которые можно менять местами благодаря плагину sortable. Также в верхней части документа есть checkbox который включает функцию фильтрации содержимого ведомой (нижней) таблицы, на основе выбранной записи в ведущей (верхней) таблице. При этом вся фильтровка осуществляется на стороне сервера, с этого и начнем разбор примера. Для удобства две таблицы запрашивают данные у двух отдельных серверных скриптов, один для городов, другой для стран.
Скрипт для таблицы «Города» (т.к. основная структура скрипта «стандартна» я привожу лишь основные моменты):
// Получение параметров page, rows и т.д.
...
if($_REQUEST['filterBy'] != 'null') // Фильтр
$WHERE = " WHERE country_code = '".$_REQUEST['filterBy']."' ";
else
$WHERE = '';
...
// Запрос подсчета суммарного кол-ва записей
$result = mysql_query("SELECT COUNT(*)AS count FROM cities".$WHERE);
...
// Запрос выборки данных
$result = mysql_query("SELECT city, latitude, longitude FROM cities ".$WHERE." ORDER BY ".$sidx." ".$sord." LIMIT ".$start.", ".$limit);
В данном случае клиентский код при запросе данных с сервера помимо «стандартных» параметров, отправляет еще и дополнительный параметр filterBy, который по сути и определяет критерий по которому нужно выбрать записи из БД и вернуть клиенту. Что и проиллюстрировано в последующих запросах.
Скрипт для таблицы «Страны»:
// Получение параметров page, rows и т.д.
...
if($_REQUEST['filterBy'] != 'null'){
$country = @mysql_fetch_array(mysql_query("SELECT country_code FROM cities WHERE id = ".$_REQUEST['filterBy']));
$WHERE = " WHERE country_code = '".$country['country_code']."' ";
}else{
$WHERE = '';
}
...
// Запрос подсчета суммарного кол-ва записей
$result = mysql_query("SELECT COUNT(*)AS count FROM countries ".$WHERE);
...
// Запрос выборки данных
$result = mysql_query("SELECT * FROM countries ".$WHERE." ORDER BY ".$sidx." ".$sord." LIMIT ".$start.", ".$limit);
Как видите полная аналогия предыдущего листинга, но теперь если передано определенное значение фильтра, происходит дополнительный запрос для уточнения кода страны.
На этом все премудрости серверной стороны заканчиваются и начинается клиентский код, но прежде чем перейти к его рассмотрению пара слов о разметке страницы:
<input type="checkbox" id="link">
<label for="link">Связать таблицы</label>
<div id="interfaceBody" style="margin: 10px 10px 10px 10px; padding: 0px 10px 10px 10px; border: 2px solid gray">
<div class="grid" id="countries" style="padding: 10px 10px 10px 10px; margin-top: 10px; border: 2px solid red">
<table id="table_countries"></table>
<div id="table_countriesPager"></div>
</div>
<div class="grid" id="cities" style="padding: 10px 10px 10px 10px; margin-top: 10px; border: 2px solid green">
<table id="table_cities"></table>
<div id="table_citiesPager"></div>
</div>
</div>
Если не обращать внимание на ужасно длинные inline-стили, то разметка достаточно проста. Сначала идет код checkbox’a с меткой, а следом контейнер всего интерфейса, внутри которого два дочерних контейнера, по одному для каждой таблицы.
И наконец клиентский скрипт:
//-------------------------------------------------------------------
// Функция-обработчик
function stickTogether(){
if($('#link').is(':checked')){
// Флаг "Связать таблицы" утановлен
var masterId = $('#interfaceBody > DIV:first').attr('id'); //Определяем ID контейнера "ведущей таблицы"
var masterPostData = $('#table_'+masterId).jqGrid('getGridParam','postData'); //Получаем весь объект postData "ведущей таблицы"
var selId = $('#table_'+masterId).jqGrid('getGridParam','selrow'); //Получаем id записи в выделенной строке
//не путайте с rowIndex
// Проверяем какое текущее значение фильтра ведущей таблицы, он должен быть сброшен (например null)
if(masterPostData.filterBy){
$('#table_'+masterId).jqGrid('setGridParam',{'postData':{'filterBy':null}}); //Сбрасываем значение фильтра
$('#table_'+masterId).trigger('reloadGrid'); //Перезагружаем данные в таблицу
}
var slaveId = $('#interfaceBody > DIV:last').attr('id'); //Определяем ID контейнера "ведомой таблицы"
$('#table_'+slaveId).jqGrid('setGridParam',{'postData':{'filterBy':selId}}); //Применяем в качестве фильтра ведомой таблицы
//id выбранной записи в ведущей таблице
$('#table_'+slaveId).trigger('reloadGrid'); //Перезагружаем данные в таблицу
}else{
// Флаг "Связать таблицы" не утановлен
var coutriesT = $('#table_countries');
var countriesPost = coutriesT.jqGrid('getGridParam','postData');
if(countriesPost.filterBy){ //Сбрасываем фильтр т.к. ничего не нужно фильтровать
coutriesT.jqGrid('setGridParam',{'postData':{'filterBy':null}})
coutriesT.trigger('reloadGrid');
}
//-----------------------
var citiesT = $('#table_cities');
var citiesPost = citiesT.jqGrid('getGridParam','postData');
if(citiesPost.filterBy){ //Сбрасываем фильтр т.к. ничего не нужно фильтровать
citiesT.jqGrid('setGridParam',{'postData':{'filterBy':null}})
citiesT.trigger('reloadGrid');
}
}
}
//-------------------------------------------------------------------
// Настройка плагинов
var tcountries = $('#table_countries').jqGrid({
caption: 'Страны',
hidegrid: false,
url:'p3e6_countries.php',
postData: {'filterBy':null},
datatype: 'json',
mtype: 'POST',
colNames:['Код страны','Cтрана','Столица','Домены'],
colModel :[
{name:'country_code', index:'country_code', width:80, hidden: true},
{name:'country_name', index:'country_name', width:60},
{name:'capital', index:'capital', width:60},
{name:'domains', index:'domains', width:60}
],
pager: $('#table_countriesPager'),
rowNum: 100,
scroll: true,
viewrecords: true,
sortname: 'country_name',
sortorder: 'asc',
height: 200,
autowidth: true,
onSelectRow: stickTogether
});
var tcities = $('#table_cities').jqGrid({
caption: 'Города',
hidegrid: false,
url: 'p3e6_cities.php',
postData: {'filterBy':null},
datatype: 'json',
mtype: 'POST',
colNames: ['Город', 'Широта', 'Долгота'],
colModel: [
{name: 'city', index: 'city', width:130},
{name: 'latitude', index: 'latitude', width:80, align: 'right'},
{name: 'longitude', index: 'longitude', width:80, align: 'right'}
],
height: 200,
autowidth: true,
rownumbers: true,
rownumWidth: 40,
rowNum: 100,
sortname: 'city',
sortorder: 'asc',
pager: $('#table_citiesPager'),
scroll: true,
viewrecords: true,
onSelectRow: stickTogether
});
$("#interfaceBody").sortable({
connectWith: '.grid',
placeholder: 'ui-state-error',
opacity: 0.7,
stop: stickTogether
});
//-------------------------------------------------------------------
// Назначаем обработчик на событие
$('#link').change(stickTogether);
При всем своем объеме код достаточно груб и прост. Начнем пожалуй с назначения обработчика на изменение checkbox’a, самая последняя строка в листинге. Как видите при изменении checkbox’a будет вызвана функция stickTogether. В теле которой происходит проверка состояния флажка и если он не установлен, то произойдет сброс всех «фильтров» таблиц и перезагрузка данных в таблице.
Но самое интересное это код в «положительной» ветке if’а, когда флажок установлен. Мы знаем, что ведущая таблица расположена вверху, значит для начала узнаем какая же таблица в данный момент находится там. Для этого получаем значение атрибута id первого контейнера (строка 6). Теперь зная какая таблица ведущая – получаем объект postData, который, напомню, содержит все параметры и их значения для отправки серверу. В 7й строке получим Id записи в выделенной строке ведущей таблицы. Далее проверив значение фильтра ведущей таблицы, при необходимости сбросим его. Это необходимо если пользователь поменял таблицы местами.
В строке 16 узнаем id контейнера ведомой таблицы. И применяем фильтр, записав id выделенной строки в ведущей таблице, и перезагружаем данные.
У данного скрипта есть один недостаток – расплата за универсальность. Из-за того что на событие onSelectRow: в обоих таблицах определен один и тот же обработчик stickTogether, при выделении строки в ведомой таблице, происходит постоянное обновление ее записей. Но уверен что те кому потребуется такой функционал jqGrid без труда смогут это исправить.
Заключение
Подведя итоги проделанной работы можно сказать, что в данной статье мы рассмотрели практически все варианты расширенного использования плагина. Научились работать с древовидными данными и подтаблицами, а также попробовали управлять содержимым ведомой таблицы посредством данных из ведущей. В следующей части я рассмотрю еще одну экзотическую интересную опцию jqGrid – «drag’n'drop записей между jqGrid’ами».
Ну и как всегда если у вас возникли вопросы задавайте их на форуме ну а комментарии используйте для предложений и пожеланий.
Итак приблизительный план работ на следующий выпуск:
Отзывов (34) на «jqGrid Часть III: Расширенные возможности»
Спасибо за серию статей про jqGrid. В рунете мало информации по этой библиотеке.
Есть одна просьба. Не могли бы вы сделать чтобы в rss-ленту попадала не все статья, а только тизер, а то не очень удобно получается.
Я в WP дилетант, и не знаю как это можно сделать. К тому же у меня права автора, а не админа
Жду с нетерпением продолжения.
Жаль подзно увидел пост. Мучился с сабгридами на jq, но в итоге перешёл на ExtJS
Спасибо большое за оч грамотные статьи.
Подскажите новичку: какое событие происходит после вызова editrow? с Cell всё понятно.
Геннадий, спасибо за статьи! Очень полезно!
Я бы только хотел выразить желание сделать колонку контента более широкой, если не на всю ширину, то пикселей на 200.
Когда будет продолжение?
Уже пишу, но т.к. в данный момент занят заказом, то времени статье уделяю очень мало. Однако стараюсь как могу…
Пропал совсем автор где-то.. объявись!
Уже который раз в комментариях к постам пишу – все вопросы на форум jQuery.
Все-таки ExtJs для таблиц как-то поудобнее получается…на мой взгляд.
Кстати что мне в jqgrid не понравилось, так это реализация поиска. Пришлось написать свою реализацию http://web-linux.ru/?p=895 . Планирую развивать.
Еще раз: технические вопросы – на форум jQuery.
Спасибо за статью, некоторые моменты очень помогли, жду часть IV
Можно ли чтобы после добавления строки, эта строка выделялась, и скролл перемещался к выбранной строке. А то ситуация сейчас такая. СТрока добавляется вниз, а скролл остается наверху и не видна последняя добавленная строка. Спасибо
Приходится повторять буквально через два комментария, на третий – технические вопросы задавайте на форуме jQuery. Тем более, что по jqGrid там информации очень и очень много.
Спасибо вам, TRAHOMOTO, за последний пример!
Полдня сегодня промучился с «master/detail tables»
Все делал по документации с оф сайта, ни чего не пахало…
Взял вашу «Функцию-обработчик», оставил в ней 3 строчки кода – ЗАРАБОТАЛО!
Видать разработчики уже с головой ушли в платную версию и забили на бесплатную. Печально
Еще раз спасибо вам!
PS. Кстати вычисление ID строки можно было не делать. ID передается как параметр:
function stickTogether(currentRow){
// …
$(‘#table_’+slaveId).jqGrid(’setGridParam’,
{‘postData’:
{‘filterBy’:currentRow}
}
);
$(‘#table_’+slaveId).trigger(‘reloadGrid’);
TO: rame0
Только что прикрутил у себя в проге master/detail tables
С оф. сайта http://www.trirand.com/blog/jqgrid/jqgrid.html
все норм заработало. Без какого-либо гемора. Может просто внимательнее надо читать/писать?
огромное спасибо за статью, сделал дерево по аналогии с пунктом 2 «jqGrid и SQL деревья»
раскрытие узлов сделал по +, а на строки повесил ссылки.
Проблема в том что не могу понять как после перехода по ссылке сделать активным пункт меню с которого по ней перешли, уважаемые гуру помогите пожалуйста
Пожалуйста, адресуйте свои вопросы на форум jQuery.
Оно и правда спасибо!
Как раз думал над тем, стоит ли использовать дерево jqGrid.
Были сомнения, но толково описано, таки да, теперь использую деревья на jqGrid !
Добрый день, скажите возможно ли что бы при добавлении/редактировании ячейки пустые поля были заполнены заранее заданными значениями.
Не очень понятен смысл, но возможно на решение Вас наведет пример на http://www.trirand.com/blog/jqgrid/jqgrid.html
Откройте Row Editing -> Input types и посмотрите в ColModel опцию editoptions…
Документация в помощь:
http://www.trirand.com/jqgridwiki/doku.php?id=wiki:colmodel_options
http://www.trirand.com/jqgridwiki/doku.php?id=wiki:common_rules#editable
А вообще, вопросы такого плана обсуждаются на форуме jQuery.
Здравствуйте!
Подскажите пожалуйста, мне нужно редактировать и добавлять данные внутри одной таблицы mysql со справочниками. Чтобы было понятнее приведу упрощенный SQL код
SELECT vexels_id, staff.staff_fio from transactions
inner join staff on transactions.staff_id = staff.staff_id
Т.е. в таблице transactions есть поле staff_id, которое является id ключом в таблице staff и мне нужно чтобы вместо id выводилась staff_fio.
Sql запрос работает, все правильно выводится, но как быть с добавлением и редактированием внутри главной таблицы? Когда я нажимаю – добавить запись – скрипт пытается внести изменения в таблицу staff, а не в transactions. Можно ли это как-то обойти?
Спасибо!
1. Еще раз: такие вопросы обсуждаются на форуме.
2. Вы привели пример SELECT-запроса, а вопрос задаете о добавлении данных в таблицу. Вы понимаете разницу между SELECT, INSERT, UPDATE?
TRAHOMOTO, подскажи, пожалуйста.
Я создаю таблицу jqGrid из данных в файле, потом собираюсь данные эти, после проверки, положить в mysql db, но пере этим, мне следует:
1. удалить лишние строки в таблице
2. изменить несоответствия полей
Методы editGridRow и delGridRow при сохранении требуют указать url куда летят данные, но они у меня пока никуда не должны уходить. Мне подставлять какой то фейковый url или можно это решить как то без костылей?
Опять таки проблему решил и если кому интересно, то для редактирования яйчеек по клику нужно указать параметры:
cellEdit: true,
cellsubmit: ‘clientArray’,
А для удаления строки, без отправки данных на сервер стоит использовать $(‘#table’).jqGrid(‘editData’,rowid);
Упс, ошибочка вышла… для удаления используем XXXX.jqGrid(‘delRowData’,rowid);
Завам еще один вопрос (практика показывает, что после этого у меня быстро получается найти ответ) – можно ли ширину столбца установить в авто режим какой нидь – автоподбор ширины по данным яйчеек?
А вот такого трюка грид не знает. Разве что повесить css свойство white-space для переноса строк
Автор большое спасибо за прекрасный урок !!!
Здравствуйте, Спасибо за офигенную статью, Пожалуйста подскажите, когда формируются ссылки при помощи функции formatter:’showlink’ возможно как-то загружать документ по ссылке в модальном окне, а не в новом ? Уже второй день бьюсь с этой задачей нечего не выходит, пытался прирезать плагин “prettyPhoto” но не знаю как ссылкам добавить параметр rel=»prettyPhoto[iframes]«
1. Сделать свой custom форматтер, наподобие showlink
2. На событие afterInsertRow подвесить функцию, которая добавит в ссылку необходимые атрибуты (но нужно проверить очередность срабатывания событий, т.е. afterInsertRow может происходить раньше чем форматтер вернет отформатированное значение ячейке)
3. (как сделал бы я) На событие ondblClickRow или onCellSelect повесил бы обработчик, который бы и открывал $.prettyPhoto.open(…) имейдж
Оставьте отзыв