ExtJS: компонент выбора местонахождения

Автор: http://web-dev.info/

Многие проекты на данный момент используют информацию о местонахождении своих клиентов. К таким относятся интернет-магазины, сайты знакомств, банковские операционные ресурсы и прочее. Именно об элементе указания такого рода информации и будет данная статья: Ext.ux.locationSelect реализованный в поле фреймворка ExtJS 2.

Маленькая демка поможет ответить на вопрос о необходимости вчитываться в дальнейшее.

Synopsis

Так сложилось исторически, что управляющим элементом по выбору локации (будем пользоваться этим словом для определения месторасположения, местонахождения и иного) является некоторое количество взаимосвязанных списков <select>, позволяющих последовательно уточнять локацию часть за частью. Выглядеть это может примерно следующим образом. Контрол в сумме удобен, малопротиворечив, но несколько устарел. Вот первые, бросающиеся в глаза, минусы решения:

  • popup окно для донесения до посетителя всего контрола;
  • отсутствие кеширования данных селектов;
  • слабая расширяемость и гибкость — любой функционал необходимо реализовывать самостоятельно.

Что необходимо получить

В одном из проектов мне понадобилось обойти всё вышеперечисленное и ко всему прочему соблюсти следующее:

  • window based дизайн — ресурс интенсивно редактируется и back end было решено выполнять в виде оконного интерфейса;
  • т. к. страница могла не перезагружаться при работе на ней часами, то вопрос кеширования данных стоит очень остро;
  • необходимо не только позволять выбирать локации, но и верно отображать их, в такой, например, ситуации как редактирование, когда все селекты уже означены, а соответствующие списки в них уже загружены.

Инструментарий

Нам потребуется:

Использование ExtJS обусловлено требованием №1 — интерфейс window based. Только этот фреймворк способен был справиться со всеми требованиями, которые были предъявлены к процессу работы с данными.

ZF и базирующийся на его составляющих Application_Db_Table_Nestedset это ответ на вопрос стандартизации кода, ответ в ряде мест противоречивый, но все же ответ.

кстати:

данные ZF-элементы дизайна контрола можно исключить и заменить на что-либо более привычное буквально в течении получаса.

Контрол в действии

Использование, благодаря мастерству и прозорливости разработчиков ExtJS, практически ничем не отличается от использования стандартных компонент этого пакета — создать и применить Ext.ux.locationSelect также легко как и создать обычную панель.

Подключение

Подключение реализуется в обычном порядке. Если ExtJS уже используется, то необходимо подключить только само расширение:

  1. <head>  
  2.     <script type="text/javascript" src="/lib/ext/adapter/ext/ext-base.js"></script>  
  3.     <script type="text/javascript" src="/lib/ext/ext-all.js"></script>  
  4.     <script type="text/javascript" src="/lib/ext/ux/locationSelect.js"></script>  
  5.     <link href="/lib/ext/resources/css/ext-all.css" rel="stylesheet" type="text/css" />  
  6. </head>  
для галочки:

используйте правильный DOCTYPE документа, часто многие нетривиальности можно решить раз и навсегда только начав работать в правильном режиме.

Конфигурирование и создание

Ввиду того, что контрол расширяет Ext.form.FieldSet, то видеть себя он предполагает в поле Ext.FormPanel, однако это необязательное требование.

важно:

подробнейшее описание API, конфигураций и немного примеров по каждому из контролов можно обнаружить в ExtJS API Documentation в соответствующей части дерева компонент, пакетов и классов.

Таким образом включить контрол в форму можно простым добавлением его конфигурационного объекта в items формы:

  1. var myForm = new Ext.form.FormPanel({  
  2.     items: [{  
  3.         xtype: 'locationselect',  
  4.         url: '/someURL',  
  5.         prefix: 'some_location_',  
  6.         title: 'someFieldSetTitle',  
  7.         valueNotFoundText: 'Не важно',  
  8.         validator: function(){/*some js-code*/}  
  9.         autoHeight: true  
  10.     }]  
  11. });  

Нестандартными конфигурационными полями являются:

  • url — адрес куда хранилища контрола будут обращаться за списками частей локаций (страны, регионы и города), по этому же адресу будет отправляться запрос на полную единовременную загрузку локации целиком. Например, Россия | Рязанская обл. | Рязань;
  • prefix — префикс имен переменных в которых будут сохранены ID частей локации. Для случая выше при сабмите контрол «сгенерирует» и отправит на сервер переменные _some_location_country, _some_location_region, _some_location_city;
  • valueNotFoundText — значение этого поля будет присвоено одноименному конфигурационному полю всех Ext.form.ComboBox контрола;
  • validator — функция будет вызываться при событии выбора любой части локации и позволит обязать, например, к указанию локации полностью.
о незаметном:

обратите внимание на символ подчеркивания в именах генерируемых по префиксу переменных. Дело в том, что комбобокс в ExtJS реализации состоит из двух полей ввода — одно из которых и отправляет значение при сабмите формы т.е. приходится генерировать два похожих имени для каждого комбобокса.

Все остальные поля — наследие конфигурации суперкласса.

Загрузка локации целиком

Существуют задачи когда необходимо позволить выбирать локацию и при этом начать выбор с той, что уже установлена — редактирование анкетных данных, оформление переезда и прочее.

Для этой цели контрол имеет метод loadLocation(), который принимает конфигурационный объект формата {country: integer, region: integer, city: integer}. При его вызове будет произведено обращение по url, указанном при создании, с передачей параметров локации. Если вернувшиеся данные соответствуют допустимому формату, то в соответствующие комбобоксы будут загружены списки с данными, а те части локации, которые были заданы в конфигурационном объекте будут выбраны.

Серверная часть

Так как контрол подкачивает данные с помощью AJAX, необходимо «договориться» о протоколе общения его с сервером. Здесь имеется развилка:

  1. При выборе какой-либо одной части локации посредством комбобокса контрол «расчитывает» на один массив со значениями для следующего комбобокса в json-формате {rows: [{id: numeric, name: string}, …]};
  2. При полной загрузке локации контролу необходимы массивы для двух последних комбобоксов единовременно в json-формате {rows: {region: [{id: numeric, name: string}, …], city: [{id: numeric, name: string}, …]}}.

Мне было удобнее реализовать обе подгрузки в одном действии locationSelectGetSublocations() контроллера AjaxController.

  1. public function locationSelectGetSublocationsAction() {  
  2.         $this->_helper->viewRenderer->setNoRender();  
  3.         $filter = new Zend_Filter_Digits();  
  4.         $location = new Location();  
  5.         if ($this->getRequest()->getParam('country', 0) && $this->getRequest()->getParam('region', 0)){  
  6.             $countryId = $filter->filter($this->getRequest()->getParam('country', 0));  
  7.             $regionId = $filter->filter($this->getRequest()->getParam('region', 0));  
  8.             $result = array(  
  9.                 'region' => $location->getSublocations($countryId),  
  10.                 'city' => $location->getSublocations($regionId)  
  11.             );  
  12.             //добавляем опции по умолчанию  
  13.             array_unshift($result['region'], array('id' => 0, 'name' => 'Не имеет значения'));  
  14.             array_unshift($result['city'], array('id' => 0, 'name' => 'Не имеет значения'));  
  15.             echo json_encode(array('rows' => $result));  
  16.         } else {  
  17.             $id = $filter->filter($this->getRequest()->getParam('parentId', 1));  
  18.             $result = $location->getSublocations($id);  
  19.             //добавляем опцию по умолчанию  
  20.             array_unshift($resultarray('id' => 0, 'name' => 'Не имеет значения'));  
  21.             echo json_encode(array('rows' => $result));  
  22.         }  
  23.     }  
на заметку:

вы можете не использовать ZF, хранить данные локаций в любом удобном для вас виде и делать, что заблагорассудится, главное — возвращать данные как это указано выше.

Белым пятном действия является класс Location — модель таблицы location. Это ничто иное как наследник класса Application_Db_Table_Nestedset о котором велась речь в предыдущей статье. Код модели имеет вид:

  1. class Location extends Application_Db_Table_Nestedset{  
  2.   
  3.     protected $_name = 'location';  
  4.     protected $_primary = 'id';  
  5.   
  6.     /** 
  7.      *  Return child locations of location 
  8.      * 
  9.      * @param integer parent location id 
  10.      * @return array location items 
  11.      */  
  12.     public function getSublocations($id){  
  13.         $result = array();  
  14.         foreach ($this->getChildren($idas $row)  
  15.             $result[] = array('id' => $row['id'], 'name' => $row['name']);  
  16.         return $result;  
  17.     }  
  18. }  

Таблица данных локаций

Таблицу локаций было решено вынести в отдельный пункт из-за самой её сути. В свое время пришлось попотеть, чтобы найти в Сети довольно полные данные по странам, регионам и городам. Теперь, когда эта задача решена можно скачать порядка 20к объектов одним кликом.

Локации упорядочены и собраны в одной таблице по схеме Вложенных Множеств. Лично мне кажется, что эта схема является очень удачной для такого рода задач:

  • данные очень редко изменяются, но часто выбираются;
  • нет многих таблиц данных и таблиц связей между ними.

Ресурсы



Опубликовал admin
19 Мар, Среда 2008г.



Программирование для чайников.