jQuery Grid Plugin – "продвинутое" решение для создания таблиц

22 мая, 2009
jqgrid

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

jQuery Grid Plugin имеет смысл использовать если нужно работать с большими таблицами. Дело в том, что плагин существенно упрощает разбивку данных на страницы. Т.е. автоматически создается панель с кнопками перелистывания страниц, а новые данные подгружаются с помощью AJAX. Кроме того, плагин имеет кучу дополнительных функций и несколько тем оформления.

От вас требуется настроить плагин и передавать ему данные. Звучит не сложно, но как всегда есть нюансы.

Чтобы не ходить вокруг да около, давайте рассмотрим небольшой пример.

Хочу сразу предупредить. Этот пример максимально упрощен и не заменяет документацию. Он поможет понять только общий принцип работы плагина. В реальном приложении вам придется сделать кучу дополнительных настроек.

Кстати, есть демонстрационная страница со множеством интересных примеров.

Сформулируем задачу.

Допустим, у нас есть небольшая таблица (в базе данных) с данными пользователей (фамилия, имя, отчество). Нужно создать html страничку с этими данными, причем передаваться они будут в формате JSON (это не единственный формат, который поддерживает плагин, но, на мой взгляд, наиболее удобный).

Прежде всего, скачиваем плагин и распаковываем в папку с проектом.

Создаём файлы index.html (наша страничка) и getdata.php (серверный скрипт, который будет получать данные из БД и отправлять их браузеру).

Скачиваем библиотеку jQuery.

В результате должна получиться следующая структура папок.

index.html
getdata.php
jquery-1.3.1.min.js
jquery.jqGrid.js
/js
	...
/themes
	...

Рассмотрим структуру нашей страницы.

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  4. <head>
  5.     <title>Использование jqGrid</title>
  6.  
  7.     <meta http-equiv="content-type" content="text/html;charset=utf-8" />
  8.     <meta http-equiv="Content-Style-Type" content="text/css" />
  9.    
  10.     <link rel="stylesheet" type="text/css" media="screen" href="themes/basic/grid.css" />
  11.     <link rel="stylesheet" type="text/css" media="screen" href="themes/jqModal.css" />
  12. </head>
  13. <body>
  14.     <table id="list" class="scroll"></table>
  15.     <div id="pager" class="scroll" style="text-align:center;"></div>
  16.    
  17.     <script type="text/javascript" src="jquery-1.3.1.min.js"></script>
  18.     <script type="text/javascript" src="jquery.jqGrid.js"></script>
  19.     <script type="text/javascript">
  20.     jQuery(document).ready(function(){
  21.         jQuery("#list").jqGrid({
  22.             url:'getdata.php',
  23.             datatype: 'json',
  24.             mtype: 'POST',
  25.             colNames:['#', 'Фамилия', 'Имя', 'Отчество'],
  26.             colModel :[
  27.                 {name:'id', index:'id', width:30}
  28.                 ,{name:'surname', index:'surname', width:80, align:'right'}
  29.                 ,{name:'fname', index:'fname', width:90}
  30.                 ,{name:'lname', index:'lname', width:80, align:'right'}
  31.                 ],
  32.             pager: jQuery('#pager'),
  33.             rowNum:5,
  34.             rowList:[5,10,30],
  35.             sortname: 'id',
  36.             sortorder: "asc",
  37.             viewrecords: true,
  38.             imgpath: 'themes/basic/images',
  39.             caption: 'Данные пользователей'
  40.         });
  41.     });
  42.     </script>
  43. </body>
  44. </html>

Прежде всего, обратите внимание на строки 10 и 11, в которых мы подключили файлы со стилями. В этом примере используется тема basic, но, естественно, её можно изменить.

В теле страницы мы создаем таблицу (строка 14) и панель навигации (тег div, строка 15). На данный момент они пустые, их содержимое будет создано плагином.

После этого, мы подключаем библиотеку jQuery, jQuery Grid Plugin и создаём скрипт с настройками (строки 20-41). На них мы остановимся отдельно.

url – адрес скрипта, формирующего массив с данными;
datatype – формат данных;
mtype – метод отправки запроса;
colNames – названия столбцов (используются в качестве заголовков таблицы);
colModel – параметры полей (значения и количество этих элементов должны соответствовать формату передаваемых данных);
pager – id блока в котором будет размещена навигационная панель;
rowNum – количество строк в таблице;
rowList – этот массив используется для настройки выпадающего списка, который пользователь может использовать для изменения количества строк в таблице;
sortname – поле, которое по-умолчанию используется для сортировки;
sortorder – порядок сортировки (в данном случае возрастающий);
viewrecords – указывает, нужно ли показывать в панели управления общее количество записей;
imgpath – размещение папки с изображениями;
caption – заголовок таблицы.

Настройки достаточно простые, но очень важно их внимательно заполнить.

Теперь создаем базу данных и таблицу в ней (вы можете импортировать файл users.sql из архива с этим примером, ссылка внизу страницы).

После этого займемся PHP скриптом.

  1. <?php
  2. try {
  3.     //читаем параметры
  4.     $curPage = $_POST['page'];
  5.     $rowsPerPage = $_POST['rows'];
  6.     $sortingField = $_POST['sidx'];
  7.     $sortingOrder = $_POST['sord'];
  8.    
  9.     //подключаемся к базе
  10.     $dbh = new PDO('mysql:host=localhost;dbname=name', 'user', 'pass');
  11.     //указываем, мы хотим использовать utf8
  12.     $dbh->exec('SET CHARACTER SET utf8');
  13.  
  14.     //определяем количество записей в таблице
  15.     $rows = $dbh->query('SELECT COUNT(id) AS count FROM users');
  16.     $totalRows = $rows->fetch(PDO::FETCH_ASSOC);
  17.  
  18.     $firstRowIndex = $curPage * $rowsPerPage$rowsPerPage;
  19.     //получаем список пользователей из базы
  20.     $res = $dbh->query('SELECT * FROM users ORDER BY '.$sortingField.' '.$sortingOrder.' LIMIT '.$firstRowIndex.', '.$rowsPerPage);
  21.    
  22.     //сохраняем номер текущей страницы, общее количество страниц и общее количество записей
  23.     $response->page = $curPage;
  24.     $response->total = ceil($totalRows['count'] / $rowsPerPage);
  25.     $response->records = $totalRows['count'];
  26.  
  27.     $i=0;
  28.     while($row = $res->fetch(PDO::FETCH_ASSOC)) {
  29.         $response->rows[$i]['id']=$row['id'];
  30.         $response->rows[$i]['cell']=array($row['id'], $row['surname'], $row['fname'], $row['lname']);
  31.         $i++;
  32.     }
  33.     echo json_encode($response);
  34. }
  35. catch (PDOException $e) {
  36.     echo 'Database error: '.$e->getMessage();
  37. }
  38.  
  39. // end of getdata.php

Прежде всего, обратите внимание на параметры, которые передаются в запросе (строки 4-7). С их помощью плагин указывает, какую страницу данных он хочет получить, а также по какому полю и в каком порядке она должна быть отсортирована.

После этого, мы получаем данные. В этом примере для работы с базой мы использовали библиотеку PDO (параметры подключения к базе вам, конечно, нужно будет указать свои, строка 10).

Обратите внимание. Скрипт максимально упрощен, т.е. убрана проверку параметров, которые передает плагин. В реальном приложении так делать НЕЛЬЗЯ.

Затем мы формируем объект с данными ($response).

В параметрах этого объекта нужно передать:

- номер текущей страницы ($response->page);
- количество страниц ($response->total);
- общее количество записей ($response->records);
- сами данные ($response->rows).

Формат, в котором нужно сохранять данные, можно изменить. Для этого придется изменить настройки плагина (в документации раздел Retrieving Data -> JSON Data).

В конце скрипта мы преобразуем данные в JSON формат и отправляем браузеру (строка 33).

Все, задача решена. Если хотите поэкспериментировать, качайте архив с примером

архив с исходным кодом

Заключение

Этот пример не раскрывает и 10% возможностей плагина. Например, вы можете встраивать inline редакторы для строк, изменять оформление, использовать другие форматы данных (тот же xml), создавать вложенные таблицы и т.п.

В общем, очень удобный инструмент.

Если у вас возникли вопросы, есть советы или замечания, не стесняйтесь, пишите их в комментариях. Буду рад ответить или обсудить ;)

Интересно почитать

Хрупкая красота программного кода:
10 приемов, способных ее разрушить

Понравилась статья? Подписывайтесь на продолжение rss link !

Или на мой твиттер twitter link

]]>

Добавьте эту страницу в google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru

]]>

Опубликовано в Ajax, HTML, JavaScript, PHP, Web разработка View Comments

]]>
  • Ништяк пример - мне для "затравки" хватило на 100% !
    буду пробовать реальную БД перетращить на это дело
  • Макс
    все) разобрался, вопрос снят!
  • Макс
    Доброго времени суток!
    Подскажите, пожалуйста, как сделать 2 несвязаные таблицы jqGrid на одной странице? и можно ли из одного поля ввода сделать для обеих таблиц автозаполнение?
  • Макс
    Добрый вечер!
    Такой вот вопрос возник: а если есть необходимость передавать комплексный ключ,то что делать тогда?
    и еще, если вместо id использовать как ключ другое поле, например:
    $response->rows[$i]['id']=$row['name'];
    $response->rows[$i]['cell']=array($row['id'], $row['surname'], $row['fname'], $row['lname']);
    то при нажатии на строку информация выбираться будет правильно, а вот выделение не снимается если нажать на последующую строку, как с этим бороться?
  • 1) Можно использовать extraparam в котором перечислить нужные поля. А на сервере выбирать запись используя их.

    2) Не сталкивался.
  • Илья
    Обращаюсь с огромным уважением к автору!

    Всё вроде супер, Дамп БД импортировал, настройки подключения к БД прописал, при подключении ошбику вроде не выдаёт, но в строке 28, конкретно на отрезке $row = $res->fetch(PDO::FETCH_ASSOC)
    у меня php спотыкается...
  • Напишите, пожалуйста, какую конкретно ошибку выдает php.
  • Илья
    Я уже нашёл и исправил сам, проблема была из-за первого вызова fetch(), при подсчёте кол-ва значений, при повторной попытке SQL стал слёзно просить либо использовать буферизацию, либо пользоваться fetchAll(), т.к. ещё что-то осталось невыбранным. Как потом оказалось, там был пустой элемент массива. Странная какая-то версия sql у моего провайдера...
    Поставил fetchAll в первом вхождении, всё заработало. Потом ещё правда долго парился с json_encode(), но тоже победил :)
    Кстати интересную ветку нашёл: http://www.linkexchanger.su/2008/41.html, там в комментариях мне функция понравился json_encode_cyr()
  • Илья
    поправка не "провайдера", а "хостинга"
    и функция "понравилась"
    Спешил просто :)
  • Все-таки интересно, какая именно версия MySQL?
  • Дмитрий
    то же тема актуальная но мне кажется в стандартном Import/Export много лишней информации. если реализовать через param = jQuery("#grid").getGridParam(); - получаем нужные данные и сохраняем в mysql а param = jQuery("#grid").setGridParam(....); - установим сохраненые данные или что то типа этого
  • Дмитрий
    jQuery("#treegrid1").hideCol(['CODE','COMMENT'])
  • user1231
    возник вопрос...
    как с помощью начальных установок (в самом теле яваскрипта)
    (когда он только загружается пользователем и инициализируется)
    задать настройки поиска
    ----
    нужно для опции сохранения параметров для сайта

    (допустим у нас есть таблица пользователь зашел, нажал поиск,
    выбрал данные, они сохранились на сервер (тут проблеем нет они просто передаются скрипту))

    _search true
    filters {"groupOp":"OR","rules":[{"field":"ID_vis","op":"eq","data":"2"},{"field":"ID_user","op":"eq","data":"5"}]}

    а вот как эти данные пихнуть в скрипт при ПЕРВОЙ загрузке кто знает ?
  • Кажется я понял :)
    Параметры _search и filters не нужно задавать в настройках jqgrid, их плагин формирует сам при отправке запроса.

    Нужно указать в colModel перечень доступных операций сравнения:
    {name:'fieldname', ... searchoptions:{sopt:['eq','ne','bw','cn']}}


    а в общих настройках - поле по которому выполняется сортировка и направление сортировки.
    sortname: 'id',
    sortorder: "asc",

    Кстати, у меня есть готовый пример поиска.
  • user1231
    user1231
    ----------------
    хехе
    так У МЕНЯ ВОПРОС И СОСТОИТ В ТОМ,
    что он сам формирует, когда ты заполняешь форму поиска так ?
    а мне нужно
    КАК УКАЗАТЬ В НАСТРОЙКАХ
    чтобы плагин СРАЗУ при ПЕРВОЙ загрузке/инициализации
    уже отправил запрос с фильтром
    чтобы значения, введенные при предыдущем посещении сайта,
    сохранились и были введены автоматически через предустановки
    и первым загрузился уже отфильтрованный список с данными настройками поиска
  • А зачем тогда вообще с поиском заморачиваться?
    1) Пользователь загружает страницу.
    2) jqGrid отправляет обычный запрос на получение всех данных.
    3) Сервер получает данные о пользователе (из сессии или из БД), с сохраненными настройками фильтра.
    4) Добавляет эти настройки в запрос и отправляет результат посетителю.
    5) Посетитель видит отфильтрованные данные.
  • user1231
    а теперь представь,
    пользователь хочет изменить запрос, который он сделал
    нажимает поиск а там пусто )
    нет ...
    твой способ самый простой, но у него минус в том что я не могу изменить запрос...

    а мне нужно как раз его именно вбить во все поля, которые были указаны...
  • Значит нужно вешать свой обработчик на кнопку поиска и из него отправлять запрос на получение данных о предыдущем запросе. Тут, правда, будет возникать задержка. Полученные данные вставляем в форму.
  • user1231
    вот так не работает

    _search:true,
    filters: [{groupOp:"OR",rules:[ {field:"ID_vis",op:"eq",data:"2"},{field:"ID_vis",op:"eq",data:"36"}]}],
    sortorder: "asc",
    viewrecords: true,


    параметры sortorder и viewrecords применяются все норм
    а вот параметры _search и filters нет...
    как понимаю они не существуют
    так вот я и хочу узнать а какие существуют чтобы это все заработало =)
  • user1231
    как описать стандартные настройки я знаю...
    я не могу описать в ЭТОМ КОДЕ
    данные по поиску
    тоесть в таблице есть кнопка ПОИСК
    пользователь ее нажал сделал запрос
    и получил данные отфильтрованные...

    после этого закрыл страницу (а полученные данные на серваке сохранились)

    после этого залогинился
    и нужно чтобы сразу проинициализированная таблица
    получила уже отфильтрованные данные ...

    самая проблема как раз менно в том, чтобы были заполнены поля поиска те которые были до выхода...
  • user1231
    да, не понимаете =))
    как отправить получить установить я знаю...
    я не знаю как ОПИСАТЬ!!!!

    jQuery("#list").jqGrid({
    url:'getdata.php',
    datatype: 'json',
    mtype: 'POST',
    colNames:['ID', 'Фамилия', 'Имя', 'Отчество'],
    colModel :[
    {name:'id', index:'id', width:50}
    ,{name:'surname', index:'surname', width:150}
    ,{name:'fname', index:'fname', width:150}
    ,{name:'lname', index:'lname', width:150}],
    pager: jQuery('#pager'),
    rowNum:5,
    rowList:[5,10,30],
    sortname: 'id',
    sortorder: "asc",
    viewrecords: true,
    caption: 'Данные пользователей',


    выше был пример кода...
    там инициализация и прочее
    вот...
    мне надо туда вставить код для поиска (как вставлять и какие данные я умею этого не требуется)

    мне нужно узнать ЧТО вставлять туда

    полагаю нечто вроде:

    sortorder: "asc",
    viewrecords: true,
    поиск: true,
    фильтр: [{"field":"ID_vis","op":$.searchConfig.op1,"data":$.searchConfig.val1}],



    так вот мне нужно знать возможно ли так ?
    и если да,
    то какие слова вставлять вместо слов "поиск" и "фильтр"
  • У меня такое ощущение что я чего-то не понимаю и только путаю вас :)

    Конфиг формируется на сервере и находится в js файле. Правильно?

    Вам нужно изменять этот конфиг в зависимости от предпочтений пользователя, которые хранятся в БД. Так?

    Т.е. вместо
    sortname: 'id',
    выводить, например,
    sortname: 'price',
    где price название столбца и его нужно получить из базы.
    Этот момент я правильно понял?

    Если да, то нужно переименовать js файл в php и вставлять новые значения с помощью обычного php кода.
    Возможно в начале этого файла нужно будет отправить заголовок
    header('Content-type: application/x-javascript')
  • user1231
    я имею ввиду

    rowList:[5,10,30],
    sortname: 'id',

    вот так вставляются варианты кол-ва записей на странице и колонка сортировки,

    так вот меня интересует а как вставить параметры поиска,
    ЧТО НАДО УКАЗАТЬ в конфиге ?
    как вставить сами парачетры в ПХП я знаю...
  • user1231
    так вот как втавить то ? =)
  • Параметры вам известны?

    Если да, то вставляете их так, чтобы в результате получилась строка.

    Например

    [{"field":"ID_vis","op":"eq","data":"<?php echo $value; ?>"},
  • user1231
    я имел ввиду что
    есть код

    jQuery("#list").jqGrid({
    url:'getdata.php',
    datatype: 'json',
    mtype: 'POST',
    colNames:['ID', 'Фамилия', 'Имя', 'Отчество'],
    colModel :[
    {name:'id', index:'id', width:50}
    ,{name:'surname', index:'surname', width:150}
    ,{name:'fname', index:'fname', width:150}
    ,{name:'lname', index:'lname', width:150}],
    pager: jQuery('#pager'),
    rowNum:5,
    rowList:[5,10,30],
    sortname: 'id',
    sortorder: "asc",
    viewrecords: true,
    caption: 'Данные пользователей',


    который генерируется сервером
    и нужно В ЭТОТ код вставить параметры поиска

    _search true
    filters {"groupOp":"OR","rules":[{"field":"ID_vis","op":"eq","data":"2"},{"field":"ID_user","op":"eq","data":"5"}]}

    так вот как это сделать ?
    как передать тут не имеет значения., имеет значение как это описать в коде
  • Не уверен, что правильно понял вопрос. Но посмотрите статью Передача параметров в JavaScript файлы. Там описаны варианты передачи данных от сервера в JS файлы, и они будут доступны при первой загрузке страницы.
    Т.е. при настройке поиска можно будет использовать что-то вроде

    [{"field":"ID_vis","op":$.searchConfig.op1,"data":$.searchConfig.val1},
  • user1231
    это то что надо но есть одно НО!..
    кто бы подсказал функцию как в PHP аналог serialize/unserialize
    которая этот массив с данными переведет в строку и обратно...
    но в JS???
  • Дмитрий
    PHP
    $responce->h=150;
    echo json_encode($responce);//{h:150}

    Javascript
    var oResults = eval("(" + o.responseText + ")");
  • Понятно, я тоже ничего конкретного не нашел.
  • user1231
    помучавшись дня 3 понял что использование
    getGridParam и setGridParam
    оправдано только в случае изменения одного параметра (не массивного) а их оч мало.., из чего следует что эти 2 функции просто БЕСПОЛЕЗНЫ, так как они ПРАВИЛЬНО ничего толком поменять не могут...
    я джаже не сумел передать
    вот эту опцию через эти параметры:
    rowList:[10,15,20,25]

    не то чтообы говорить о чем-то другом...
  • @user1231 Точно сказать не могу, сам не ковырялся. Может getGridParam сохраняет не все параметры, которые вы изменили?
  • Дмитрий
    а как происходит скрытие столбцов в Демо?? там ведь все нормально работает
  • user1231
    что самое интересное,
    res=jQuery("#list").getGridParam('colModel');
    получил
    потом поменял местами столбцы к примеру, размер поменял скрыл половину...
    теперь пытаюсь применить сохраненные настройки
    jQuery("#list").setGridParam({colModel:res}).trigger("reloadGrid");
    и таблицу перекореживает... шапка таблицы вообще остается такой как была...
    а строки перемешиваются... размеры меняются.. глюки

    кто обьяснит что не так делаю ? =)
    _____
    З.Ы.
    все это не перегружая таблицу (даже такой вариант не прокатил)
  • user1231
    пасиб..,
    возник следущий вопрос
    получаю параметры
    jQuery("#list").getGridParam('colModel')
    тут впринципе все ясно.., но..
    а если мне не нужно брать ВСЕ параметры полученные таким способом...
    как мне получить параметры всех столбцов (например ширины)
    как в примере павла. (у него это осуществлено методом перебора)
    у меня же другой вопрос можно ли задать нечто вроде:
    jQuery("#list").getGridParam('colModel','width')
    представленный выше код естессно не работает =)
  • Можно преобразовать в JSON формат. У меня есть статья на эту тему. Отправка данных в формате JSON с помощью JavaScript и jQuery
  • Павел
    да, я действительно заново изобрел велосипед... до getGridParam додумался, а вот до setGridParam как-то не успел :) Спасибо, что глаза открыли.
  • Спасибо, хороший вариант.
  • user1231
    меня сейчас начал волновать несколько другой вопрос...
    кто-нибудь сталкивался с вопросом..., сохранения настроек сделанных пользователем с таблицей (тип сортировки , выбранные столбцы, порядок столбцов, размеры и прочее...)

    интересует какая нить кнопка, которая буудет передавать эти настройки в ПХП и обратно...
    думаю ясно зачем...,
    вот только возможно ли такое реализовать кто в курсе ?
  • Павел
    Я с этим сталкивался (заказчик попросил только ширину и видимость колонок таблицы сохранять). Сохраняю в виде JSON в куку (при желании не проблема отдавать эти данные аяксом серверу и сохранять там в сессии или на что фантазии хватит).

    saveSettings = function(mode){
    var i, cnt, settings = {},
    colModel = $('#list').jqGrid('getGridParam', 'colModel')
    settings.columns = {}
    for( i in colModel ){
    settings.columns[colModel[i].name] = {width:colModel[i].width, hidden:colModel[i].hidden}
    }
    settings['postData'] = $('#list').jqGrid('getPostData');
    delete settings.postData.nd;
    //return settings;
    $.cookies.set('jqgrid_settings_'+mode, $.JSON.encode(settings), {expiresAt: new Date( 2012, 1, 1 )})
    }

    mode - специфичная для моей задачи переменная. Используются плагины cookies и json (гугл в помощь). Плюс при инициализации таблицы приходится обращаться к этой куке для считывания нужных параметров столбцов. Наверное, я изобрел велосипед :)
  • Большое спасибо за решение!
    Велосипед это, или нет, но раз оно работает, то вполне может кому-то сэкономить время.
    Правда, решение Дмитрия выглядит компактнее ;)
  • По-идее, реализовать можно, но я не пробовал.
    Существует модуль Import/Export, он позволяет импортировать и экспортировать настройки таблицы.
    Вам остается только сохранять эти настройки в БД.
  • Андрей
    Подскажите, может кто знает, можно сделать чтобы filterToolbar работал корректно при локальной загрузки данных через массив. Подключаю через jQuery("#table").jqGrid('filterToolbar');, сортировать сортирует только неправильно. Заранее спасибо :)
  • user1231
    он правильно сортирует...
    другой вопрос твои данные какого формата.., и по какому принципу ты их сортируешь...

    пример:
    у тебя данные вида 21,520,534,53,543
    и ты сортируешь их как текст, то у тебя в итоге получится
    21,520,53,534,543
    смотри в коде тип сортировки...
  • А что именно неправильно в сортировке? Если можно, покажите пример.
  • FBI
    А пробовал кто-то убирать заглавие таблицы и название полей(шапку), и выводить только саму сетку с данными?
  • user1231
    а что пробовать, в примерах на сайте в демках есть такие таблицы смотри лучше..,
  • FBI
    Пересмотрел все примеры - где ты там нарыл, хз
  • FBI
    user1231 огромный респект и уважуха :)
  • Спасибо! Правда, я все-таки надеялся, что есть встроенный метод.
blog comments powered by Disqus ]]>