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


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

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

Synopsis

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

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

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

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

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

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

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

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

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

кстати:

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

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

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

Подключение

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

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

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

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

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

важно:

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

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

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

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

  • 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.

public function locationSelectGetSublocationsAction() {
        $this->_helper->viewRenderer->setNoRender();
        $filter = new Zend_Filter_Digits();
        $location = new Location();
        if ($this->getRequest()->getParam('country', 0) && $this->getRequest()->getParam('region', 0)){
            $countryId = $filter->filter($this->getRequest()->getParam('country', 0));
            $regionId = $filter->filter($this->getRequest()->getParam('region', 0));
            $result = array(
                'region' => $location->getSublocations($countryId),
                'city' => $location->getSublocations($regionId)
            );
            //добавляем опции по умолчанию
            array_unshift($result['region'], array('id' => 0, 'name' => 'Не имеет значения'));
            array_unshift($result['city'], array('id' => 0, 'name' => 'Не имеет значения'));
            echo json_encode(array('rows' => $result));
        } else {
            $id = $filter->filter($this->getRequest()->getParam('parentId', 1));
            $result = $location->getSublocations($id);
            //добавляем опцию по умолчанию
            array_unshift($result, array('id' => 0, 'name' => 'Не имеет значения'));
            echo json_encode(array('rows' => $result));
        }
    }
на заметку:

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

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

class Location extends Application_Db_Table_Nestedset{

    protected $_name = 'location';
    protected $_primary = 'id';
    /**
     *  Return child locations of location
     *
     * @param integer parent location id
     * @return array location items
     */
    public function getSublocations($id){
        $result = array();
        foreach ($this->getChildren($id) as $row)
            $result[] = array('id' => $row['id'], 'name' => $row['name']);
        return $result;
    }
}

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

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

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

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

Ресурсы

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

  1. wd,

    Пару часов назад я несколько обновил логику кеширования: теперь при создании контрола можно передать конфигурационный параметр «cache» с переменной, глобальной относительно контрола, где и будут храниться кешируемые данные (следствие несомненного плюса того, что переменные в JS передаются по ссылке). Таким образом достигается еще большая экономия запросов, наиболее ощутимая с ростом числа контролов на странице. В случае же, если используются два разных источника данных, «cache» в конфигурации можно опустить и контрол создаст кеш локальный.

  2. ExtJS: контрол выбора локации | Записки маленького юниксоида,

    [...] Если на ее обозрение имеется желание, то это можно сделать в индивидуальном [...]

  3. wd,

    Еще одно изменение, и довольно большое, было внесено в идею конфигурирования комбобоксов. Теперь контрол принимает объект, который может содержать индивидуальные настройки комбобоксов. Более подробно в комментарии к правке №29. Демо также отражает изменения.

  4. hornet,

    просьба скинуть на почту исходники демки — так удобнее разбираться и адаптировать под себя

    не хватает квалификации по кускам кода повторить функционал )))

    спасибо за контрол!

  5. kostiaGt,

    У меня передается в параметере только

    _dc 1257063754550

    parentId 9

    xaction load

    как мне определить какую конкретно таблицу загружать?

    данные передаются методом _GET, как я понимаю, должны передаваться методом _POST

  6. wd,

    Серверную логику контрол не реализует. Запросы, что не принципиально, типа POST. Т.е. это уже вам решать в каком виде хранить данные и как к ним/их обращаться/отдавать.

  7. kostiaGt,

    правильно ли я понимаю, вот я выбираю из списка регион, посылаются данные на сервер. правильно?

    на серверной части (например в php файле) по какому параметру мне определить, что нужно прочитать базу со списком регионов или городов? какой параметр в методе ($_GET или $_POST) показывает, что было выбрано? просто я вижу только параметр parentId и как с одним параметром мне выбрать из базы город, где нужно как минимум два параметра — id города и id региона?

  8. wd,

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

    Поглядите статью по реализации класса дерева вложенных множеств для Zend Framework. В ней есть ссылка на теоретическое описание почему достаточного одного ID узла чтобы выбрать всех его предков. Т.е. если хранить страны->регионы->города->улицы в виде узлов дерева связанных отношением родства, достаточного одного ID.

Оставить отзыв

 

Март 2008
Пн Вт Ср Чт Пт Сб Вс
« Фев   Апр »
 12
3456789
10111213141516
17181920212223
24252627282930
31