[EVO] Зависимые списки в MODX Evolution на примере городов и улиц

Задачка довольно тривиальная:
Имеется список городов. В каждом городе есть свой список улиц. Необходимо подгружать список улиц в ТВ параметре в зависимости от выбранного города в другом ТВ параметре.

Для заполнения базы городов/улиц можно придумать тысячу способов. Самый примитивный — города и улицы создаются прямо в дереве документов MODX как обычные странички. Но если данных будет много, то управлять ими будет не очень удобно. А вдвойне не приятно — такие документы будут попадать в aliasListing и раздувать кеш движка, что скажется на общей производительности системы.
С другими бюджетными вариантами решения этой задачи можно ознакомиться в топике Harand Оптимальная работа с большим TV параметром. Собственно мое решение теперь вполне может претендовать на роль бюджетного.
Для решения задачи я создал 2 таблички и небольшой модуль для управления записями в этих таблицах. Кому интересно — можете ознакомиться с кодом на GitHub и там же с инструкцией по установке.

Вообще это решение допиливать и допиливать. Хотя бы это:
— Добавить inline редактирование данных
— Автокомплит данных при вводе в ТВ параметр street
— Хлебные крошки в модуль при просмотре списка улиц в городе
Но к сожалению, это уже выходило за рамки бюджета на разработку данного модуля.

P.S. Спасибо за решение говорим все тому же Harand . Именно он оплатил разработку и позволил поделиться решением с сообществом.
P.P.S. Модуль/Сниппеты/TV параметры в данном решении используют сниппет DocLister в том или ином виде. Так что кому интересен пример использования — смотрите исходники.

25 комментариев

avatar
Еще не смотрел, но за развитие вещей для evo всегда спасибо!
avatar
Ну, со временем, думаю, допилим для народа, и бюджеты на это изыщем :)
avatar
Когда ты всё успеваешь (
avatar
Этот модуль был сделан за пару часов
avatar
Кста очень рекомендую сервис
kladr-api.ru/examples/

на сайт интегрируется или php или jQuery плагин — работает быстро и красиво :)
как ввод адресса на Яндексе или Гугл картах
avatar
Я знаю про этот сервис. Но взгляни на ситуацию с другой стороны: для ввода городов/улиц мы еще можем интегрировать КладрAPI, а вот для ввода зависимых списков — уже нет. Тут как раз именно реализация зависимых списков отображена и города/улицы это лишь как наиболее распространенный пример. Более того, когда нужен полный перечень всех улиц города — то да. КладрAPI поможет. А когда нужен свой набор — то уже увы.
avatar
ага полностью согласен, я добавил ссылку на сервис только потому то фигурировали страны и города

а так зависимые списки можно использовать очень много где
avatar
Дмитрий, я посмотрел этот сервис поподробнее. Сервис интересный, реализован достойно, работает более-менее быстро и чётко (проверял на своём сайте).

Но в итоге пришлось отказаться из-за одного глобального косяка, о котором я им написал письмо: слово «проспект», «переулок» и подобные они не считают частью топонима, а считают типом гео-объекта.

Вроде бы и логично, но это приводит к тому, что при поиске улицы «Красный проспект» в поле выбора остается только слово «Красный». А у нас в городе нет улицы с названием «Красный», есть только Красный проспект, и посетитель сайта будет в небольшом ступоре. То же самое с каким-нибудь «Васильевским переулком» в любом городе.

А мы-то знаем, чем заканчиваются ступоры пользователей — уходом с сайта :)
avatar
Да, как быстрое решение именно для городов-улиц — это тема, я и не знал о нём :) Надо только глянуть, как у них с частотой обновлений и полнотой информации.

А зависимые списки, действительно, много где нужны и кроме городов. Авто-тематика сразу напрашивается, да много что.
Комментарий отредактирован 2014-01-24 14:15:51 пользователем Harand
avatar
в плане интеграции еще вот dadata.ru/ интересен.
Города, улицы… есть еще обработка личных данных — ФИО и тд…
avatar
Да, спасибо, интересная штука. Возьму на заметку.
avatar
А для REVO есть такое?
avatar
Доброе время суток! Понимаю, что вопрос глупый, но я чайник. Каким образом такую вещь реализовать в WebLoginPE? Мне нужно, что бы при регистрации веб-пользователя на сайте можно было выбрать регион(страна одна) и город с выбранного региона. В WebLoginPE уже есть список стран, вот как туда можно засунуть города для выбора? и что прописать в других файлах? А уже потом можно и подредактировать немного.
avatar
Интересно, решили вы вопрос? Я случайно набрёл на ваш комментарий, и честно говоря, не знаю что ответить, ибо с WebLoginPE дела не имел.
avatar
Добрый день!
Такая проблемка:
Установлено
modx 1.0.14 из коробки
DocLister и DocInfo — последняя версия с github
eFilter от webber — последняя версия с github
multiTV 2.0 — последняя версия с github
Все загружал с помощью package Manager, на всякий случай и перепроверил вручную.
Ничего не допиливалось.
Все работает отлично — и DocLister, и eFilter, за что авторам огромное спасибо.
Собрался еще добавить зависимые списки на базе RelativeTVList и что-то не получается, делал по ридми (больше нигде ничего и не нашел):
— TV параметры Sity и Street создались при установке, cityID поменял на свой, таблицы в sql подгрузил, модуль работает отлично — списки городов-улиц создал.
А вот с TV проблема:
— City в админке на странице выводится нормально, города в раскрывающемся списке присутствуют и выбираются.
— а вот Street в админке выводит только имя параметра, заголовок и описание. Самого списка нет. При этом страница в админке ломается — вверху страницы белая полосы, а все другие имеющиеся здесь же TV параметры исчезают, вкладка Настройки страницы тоже исчезает. При отключении TV Street от шаблона страницы — все восстанавливается. Т.е. проблема в цепочке: TV Street — сниппет StreetList — мои кривые руки.
Скриншот
5POzx.jpg
Подскажите, пожалуйста, где и что поправить?
  • paic
  • 0
avatar
Хоть я в чужие админки дистанционно заглядывать и не умею, но осмелюсь предположить: А параметр selfName для каждого из TV у вас полностью соответствует его имени? И также имени параметра в вызовах сниппетов StreetName и CityName?
avatar
Извиняюсь, что сразу не указал (т.к. это загрузилось при инсталляции, используется по умолчанию)
TV параметр City:
Имя параметра:City
Заголовок:City
Id TV параметра City:1 (сейчас действительно 1, пожертвовал штатным TV blogContent)
Тип ввода: Custom Input
Возможные значения:
@EVAL return $modx->runSnippet('CityList', array('selfName'=>'City'));


TV параметр Street:
Имя параметра:Street
Заголовок:Street
Тип ввода: Custom Input
Возможные значения:
@EVAL return $modx->runSnippet('StreetList', array('cityID'=>1,'selfName'=>'Street'));


А вот по поводу
И также имени параметра в вызовах сниппетов StreetName и CityName?
ничего сказать не могу. Возможно, здесь и кроется причина. Поясните, пожалуйста.
avatar
Честно говоря, совершенно нет времени разбираться, поэтому отвечу наобум, тупо, без анализа.
У меня есть ещё 2 сниппета, помимо указанных вами StreetList и CityList. Это сниппеты CityName и StreetName. Я уже не просто не помню, для чего они используются. По-моему, для вывода на фронтэнде. Возможно, о них что-то сказано в readMe.

CityName
<code><?php
return $modx->db->getValue($modx->db->query("SELECT name FROM ".$modx->getFullTableName('City')." WHERE id=".(int)$id));
?></code>

StreetName
<code><?php
return $modx->db->getValue($modx->db->query("SELECT name FROM ".$modx->getFullTableName('Street')." WHERE id=".(int)$id));
?></code>

А про вашу ошибку — возможно, как-то надо сконфигурировать сами файлы сниппетов CityList.snippet.php и StreetList.snippet.php

Вот так для вас:
StreetList.snippet.php
<code><?php
if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest' &&
    $_SERVER['REQUEST_METHOD']=='POST' && isset($_POST['City']) && (int)$_POST['City']>0){
        define("MODX_API_MODE", true);
        include_once(dirname(dirname(dirname(dirname(__FILE__))))."/index.php");
        $modx->db->connect();

        if (empty ($modx->config)) {
            $modx->getSettings();
        }
        echo $modx->runSnippet("DocLister", array(
            "controller" => "onetable",
            "table" => "Street",
            "idType"=>"documents",
            "documents"=>"",
            "ignoreEmpty"=>"1",	
						"orderBy" => "name ASC",
            "addWhereList" => "hide=0 AND parent_id=".(int)$_POST['City'],						
            "tpl" => '@CODE: <option value="[+id+]">[+name+]</option>',
        ));
}
</code>
CityList.snippet.php
<code><?php
$selfName = isset($selfName) ? $selfName : '';
if(!empty($selfName)){
	if($pageID>0){
   		$CityValue = $modx->getTemplateVar($selfName, "id", $pageID);
   		$CityID = isset($CityValue['id']) ? $CityValue['id'] : '';
   		$CityValue = isset($CityValue['value']) ? $CityValue['value'] : '';
	}else{
		$q = $modx->db->select("id", $modx->getFullTableName('site_tmplvars'), "name='".$modx->db->escape($selfName)."'");
		$CityID = $modx->db->getValue($q);
		$CityValue = '';
	}
}

$wrap = $modx->runSnippet("DocLister", array(
            "controller" => "onetable",
            "table" => "City",
            "idType"=>"documents",
            "documents"=>"",
            "ignoreEmpty"=>"1",
						"addWhereList" => "hide=0",
						"orderBy" => "id ASC",
            "cityDefault" => (int)$CityValue,
            "prepare" => function(array $data = array(), DocumentParser $modx, onetableDocLister $_DocLister){
                  $data['selected'] = ($_DocLister->getCFGDef('cityDefault')==$data['id']) ? 'selected="selected"' : '';
                  return $data;
            },
            "tpl" => '@CODE: <option value="[+id+]" [+selected+]>[+name+]</option>',
));
return "<script>
	function loadStreet(el){
		var getStreet = new Ajax('/assets/modules/DLCity/StreetList.php', {method:'post', postBody:'city='+el.value, onComplete:showStreet});
		getStreet.request();
	}
</script><select id=\"tv[+field_id+]\" name=\"tv[+field_id+]\" size=\"1\" onchange=\"loadStreet(this);documentDirty=true;\">".$wrap."</select>";
?></code>

Я мог где-то ошибиться с малыми и большими буквами в названиях Street и City.

P.S. Кстати, я не помню — а сами-то таблицы создаются при инсталляции? Если нет, их надо создать. Проверьте, есть ли они у вас.
Комментарий отредактирован 2014-10-30 12:01:30 пользователем Harand
avatar
Спасибо за консультация, буду разбираться дальше.

P.S. При инсталяции таблицы не создаются, но я их создал изначально — по инструкции в ридми. И модуль, в котором формируются зависимые списки, работает, запись в таблицы ведется.
avatar
Ура, заработало!

Пишу для тех, кто наступит на эти же грабли.
Суть проблемы в первую очередь в невнимательности — дело в том что по-видимому сниппеты CityList и StreetList переезжали из папки модуля DLCity в папку модуля RelativeTVList (или модуль переименовывался), а пути в сниппетах остались прежними, старые же пути остались и при инталляции (если устанвливать через package Manager) и в ридми.
Так же полезно обратить внимание на названия с большой или с маленькой буквы, как правильно указал Harand — если речь идет о TV-параметре, то с большой буквы, если о названии таблицы в базе данных — то с маленькой.

Отельное спасибо за сниппеты StreetName и CityName.
Дело в том, что если выводить во фронтэнде типа
[*City*]
то выведет просто id города в таблице city. Поэтому надо выводить так
[[CityName? &id=`[*City*]`]]

Еще раз спасибо авторам за разработку и за помощь, классная штука.
avatar
Может и глупый вопрос, а как во фронтэнд вывести списки как в админке? [[CityName? &id=`[*City*]`]] — выводит только один город, [[CityList]] — выводит список городов, [[StreetList]] вроде должен выводить Улицы, но почему-то пусто.
avatar
Я уже не помню в деталях, но общий путь решения вопроса в общем понятен.

Список id улиц находится в таблице Street, там же очевидно должно быть записано id города, к которому относится данная улица.

Ваша задача — при помощи DocLister вывести улицы, относящиеся к нужному вам городу. Это можно сделать при помощи параметра addWhereList.

Просто повнимательнее посмотрите код, почитайте про DocLister. Или напишите вопрос в личку paic , может он вам поможет, так как он решал такую задачу.
avatar
я выводил через eFilter и использовал для фильтрации.

При этом нужно добавить в config.default.php еще два селекта, один — для городов, второй — для улиц. Соответственно их и выводить в фильтр.
avatar
Устарела ссылка на GitHub'e, можете обновить?
Thx
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.