Сегодня будем ломать поле «Местоположение» — добавим опцию, при которой в форме и фильтре мы исключим выбор страны, потому что на сайте она одна. Чтобы ничего не испортить, мы создадим новое поле, наследуемое от поля «Местоположение».
Сначала немного расскажу, что мы будем делать и для чего. Допустим, у вас есть сайт, где весь контент для аудитории одной страны. Для примера возьмем Россию. Вы хотите, чтобы пользователи регистрировались только из России, чтобы при добавлении материалов указывали только российские города и т.д. Конечно, можно просто удалить/отключить другие страны. Но тогда в поле все равно первым шагом будет предложено выбрать страну. Но зачем, если она одна?
В поле «Местоположение» есть возможность создать группировку Страна->Область->Город. Причем, страна должна быть обязательно.
Если же мы не создадим страну, а создадим только область и город, то в форме и фильтре получим пустые выпадающие списки. Всё правильно, страна ведь не выбрана, какие подгружать регионы?
Вот это мы и будем исправлять.
Начнем с создания нужных файлов. Как известно, все поля состоят из двух обязательных файлов — файла поля и файла шаблона. Традиционно, начнем со второго. Создадим в папке templates/default/assets/fields файл newcity.tpl.php и скопируем в него содержимое из файла city.tpl.php из этой же папки. Проделаем то же самое в папке templates/modern/assets/fields — создадим файл newcity.tpl.php и скопируем содержимое из файла city.tpl.php, но уже из шаблона modern. Для чего это всё? Для того, что в шаблоне Модерн используется бутстрап, а в дефолтном нет. Соответственно, в дефолтном нет классов бутстрапа, а в Модерне нет стилей дефолтного. Как-то так примерно)) В общем, это не сложно. Просто сделайте так. Позже мы вернемся к этим файлам, а пока переходим к файлу поля.
Создадим файл с именем newcity.php в папке system/fields. В нем объявим класс fieldNewcity, но наследуемый не от системного класса cmsFormField, а от класса поля «Местоположение» — fieldCity:
<?php class fieldNewcity extends fieldCity { }
Теперь все методы поля «Местоположение» работают и в нашем новом поле. Если мы хотим изменить какой-то определенный метод, то добавлять будем только его. Сначала нам надо изменить название, поэтому добавим его:
<?php class fieldNewcity extends fieldCity { public $title = 'Местоположение NEW'; }
Теперь опции. Нам нужно добавить опцию выбора страны. Для этого из файла city.php полностью копируем метод getOptions():
<?php class fieldNewcity extends fieldCity { public function getOptions() { return [ new fieldList('location_type', [ 'title' => LANG_PARSER_CITY_LOCATION_TYPE, 'default' => 'cities', 'items' => [ 'countries' => LANG_COUNTRY, 'regions' => LANG_REGION, 'cities' => LANG_CITY ] ]), new fieldCheckbox('auto_detect', [ 'title' => LANG_PARSER_CITY_AUTO_DETECT ]), new fieldString('location_group', [ 'title' => LANG_PARSER_CITY_LOCATION_GROUP, 'hint' => LANG_PARSER_CITY_LOCATION_GROUP_HINT, 'rules' => [ ['sysname'], ['max_length', 20] ] ]), new fieldString('output_string', [ 'title' => LANG_PARSER_CITY_OUTPUT_STRING, 'hint' => LANG_PARSER_CITY_OUTPUT_STRING_HINT, 'extended_option' => true ]), new fieldCheckbox('is_autolink', [ 'title' => LANG_PARSER_LIST_IS_AUTOLINK, 'hint' => LANG_PARSER_LIST_IS_AUTOLINK_FILTER, 'default' => false, 'extended_option' => true ]) ]; } }
Теперь удалим из опции location_type тип «Страна» (countries):
new fieldList('location_type', [ 'title' => LANG_PARSER_CITY_LOCATION_TYPE, 'default' => 'cities', 'items' => [ 'regions' => LANG_REGION, 'cities' => LANG_CITY ] ]),
После этой опции добавим новую — выбор страны:
new fieldList('country', [ 'title' => 'Страна', 'generator' => function () { return cmsCore::getModel('geo')->getCountries(); // Получаем список стран, используя метод getCountries() компонента "География" } ]),
Теперь мы можем создать наше поле в ТК (или где-то еще). Форма опций будет выглядеть так:
Давайте еще изменим подсказку для опции «Название для группировки», ведь теперь нам в группировке страна не нужна:
new fieldString('location_group', [ 'title' => LANG_PARSER_CITY_LOCATION_GROUP, 'hint' => 'Если необходимо объединить местоположения область-город, укажите одно имя объединения для всех полей. Поле типа "Область" для группировки обязательно.', // Новая подсказка 'rules' => [ ['sysname'], ['max_length', 20] ] ]),
И чтобы точно всё было совсем красиво, изменим подсказку у опции строки вывода значений:
new fieldString('output_string', [ 'title' => LANG_PARSER_CITY_OUTPUT_STRING, 'hint' => 'Например, "{region}, {city}", где region и city системные имена полей. Полезно использовать, отключая для вывода остальные поля группировки, показывая нужные значения в этом поле. Если не указано - выводится значение текущего поля.', 'extended_option' => true ]),
Еще давайте удалим опцию автоопределения страны (auto_detect). Теперь наш файл выглядит так:
<?php class fieldNewcity extends fieldCity { public $title = 'Местоположение NEW'; public function getOptions() { return [ new fieldList('location_type', [ 'title' => LANG_PARSER_CITY_LOCATION_TYPE, 'default' => 'cities', 'items' => [ 'regions' => LANG_REGION, 'cities' => LANG_CITY ] ]), new fieldList('country', [ 'title' => 'Страна', 'generator' => function () { return cmsCore::getModel('geo')->getCountries(); } ]), new fieldString('location_group', [ 'title' => LANG_PARSER_CITY_LOCATION_GROUP, 'hint' => 'Если необходимо объединить местоположения область-город, укажите одно имя объединения для всех полей. Поле типа "Область" для группировки обязательно.', 'rules' => [ ['sysname'], ['max_length', 20] ] ]), new fieldString('output_string', [ 'title' => LANG_PARSER_CITY_OUTPUT_STRING, 'hint' => 'Например, "{region}, {city}", где region и city системные имена полей. Полезно использовать, отключая для вывода остальные поля группировки, показывая нужные значения в этом поле. Если не указано - выводится значение текущего поля.', 'extended_option' => true ]), new fieldCheckbox('is_autolink', [ 'title' => LANG_PARSER_LIST_IS_AUTOLINK, 'hint' => LANG_PARSER_LIST_IS_AUTOLINK_FILTER, 'default' => false, 'extended_option' => true ]) ]; } }
С опциями разобрались. Теперь изменим метод getListItems(). Копируем его из файла city.php и изменяем:
public function getListItems() { $model = cmsCore::getModel('geo'); // Модель компонента "География" $items = []; $location_type = $this->getOption('location_type'); // Тип метоположения из опций $country = $this->getOption('country'); // Страна из опций if ($location_type === 'regions') { // Если тип местоположения "Область" $items = ['0' => ''] + $model->getRegions($country); // Получаем список регионов страны из опций } return $items; }
В форме получилось так:
В фильтре также:
Теперь нам надо сделать то же самое, только без группировки. Откроем файлы шаблона, которые мы создавали в самом начале: templates/default/assets/fields/newcity.tpl.php и templates/modern/assets/fields/newcity.tpl.php. В этих файлах в самом низу есть ссылка, вызывающая модальное окно с формой выбора города. В дефолтном она выглядит так:
<a class="ajax-modal" href="<?php echo href_to('geo', 'widget', array($field->id, $value['id'])); ?>"><?php echo LANG_SELECT; ?></a>
В модерне так:
<a class="ajax-modal ml-auto btn btn-sm btn-outline-secondary" title="<?php html($field->title ? $field->title : LANG_SELECT); ?>" href="<?php echo href_to('geo', 'widget', array($field->id, $value['id'])); ?>"> <?php echo LANG_SELECT; ?> </a>
Нас интересует атрибут href — он одинаковый в обоих файлах. Заменим его на это:
href="<?php echo href_to('geo', 'widget_new', array($field->options['country'], $field->id, $value['id'])); ?>"
Что мы здесь сделали? Мы заменили название экшена с widget на widget_new и добавили дополнительный параметр — id страны. Давайте создадим экшен widget_new. В папке system/controllers/geo создаем файл widget_new.php. Объявляем класс actionGeoWidgetNew, наследуемый от cmsAction:
<?php class actionGeoWidgetNew extends cmsAction { }
Добавляем метод run(), в котором получаем те же параметры, что и метод actionWidget() в файле frontend.php, но первым параметром сюда в нашем случае приходит id страны:
<?php class actionGeoWidgetNew extends cmsAction { public function run($country_id, $field_id, $city_id = false) { } }
Код этого метода я тоже взял в файле frontend.php в методе actionWidget(), но внес изменения. Вот, что получилось:
<?php class actionGeoWidgetNew extends cmsAction { public function run($country_id, $field_id, $city_id = false) { if (!$this->request->isAjax()) { cmsCore::error404(); } $regions = $this->model->getRegions($country_id); // Получаем регионы нужной страны $cities = []; $region_id = false; if(!$region_id ){ $city_parents = $this->model->getCityParents($city_id); $region_id = $region_id ? $region_id : $city_parents['region_id']; } if ($city_id){ $cities = $this->model->getCities($region_id); } 'field_id' => $field_id, 'city_id' => $city_id, 'country_id' => $country_id, 'region_id' => $region_id, 'regions' => $regions, 'cities' => $cities )); } }
Не могу подобрать слова, чтобы объяснить, что я тут наделал. Честно говоря, я и сам не понял)) Можете сравнить код здесь с кодом из метода actionWidget() в файле frontend.php и рассказать, что вы увидели, в комментариях))
Но это еще не всё. Надо создать шаблон, для этого экшена. Создаем файл widget_new.tpl.php в папке templates/default/controllers/geo, и копируем содержимое из файла widget.tpl.php. То же самое делаем в папке templates/modern/controllers/geo. Но просто скопировать мало, нам надо удалить поле выбора страны. В дефолтном шаблоне:
<div class="list"> <?php echo html_select('countries', $countries, $country_id, array('onchange'=>"icms.geo.changeParent(this, 'regions')", 'rel'=>'regions')); ?> </div>
В Модерне:
<div class="list mb-3" <?php if (!$city_id){?>style="display:none"<?php } ?>> <?php echo html_select('regions', $regions, $region_id, array('onchange'=>"icms.geo.changeParent(this, 'cities')", 'rel'=>'cities')); ?> </div>
Удаляем их. И надо показать поле выбора региона. Оно скрыто, пока не будет выбрана страна, но страна у нас больше не выбирается. Поэтому после только что удаленного поля в следующем поле находим:
<?php if (!$city_id){?>style="display:none"<?php } ?>
И удаляем.
Теперь код формы должен выглядеть так в дефолтном шаблоне:
<form data-items-url="<?php echo $this->href_to('get_items'); ?>"> <div class="list"> <?php echo html_select('regions', $regions, $region_id, array('onchange'=>"icms.geo.changeParent(this, 'cities')", 'rel'=>'cities')); ?> </div> <div class="list" <?php if (!$city_id){?>style="display:none"<?php } ?>> <?php echo html_select('cities', $cities, $city_id, array('onchange'=>"icms.geo.changeCity(this)")); ?> </div> </form>
А в Модерне так:
<form data-items-url="<?php echo $this->href_to('get_items'); ?>"> <div class="list mb-3"> <?php echo html_select('regions', $regions, $region_id, array('onchange'=>"icms.geo.changeParent(this, 'cities')", 'rel'=>'cities')); ?> </div> <div class="list mb-3" <?php if (!$city_id){?>style="display:none"<?php } ?>> <?php echo html_select('cities', $cities, $city_id, array('onchange'=>"icms.geo.changeCity(this)")); ?> </div> </form>
Результат:
Вот и всё. Осталось собрать установщик (если вдруг захотите использовать поле на других сайтах, чтобы быстро установить).
Создаем папку newcity, внутри нее создаем папку package. Внутри этой папки создаем такую структуру папок и файлов:
system
> fields
>> newcity.php
> controllers
>> geo
>>> actions
>>>> widget_new.php
templates
> default
>> assets
>>> fields
>>>> newcity.tpl.php
>> controllers
>>> geo
>>>> widget_new.tpl.php
> modern
>> assets
>>> fields
>>>> newcity.tpl.php
>> controllers
>>> geo
>>>> widget_new.tpl.php
Рядом с папкой package создаем файл manifest.ru.ini с таким содрежимым (или другим):
[info] title = "Поле Местоположение NEW" [version] major = "1" minor = "0" build = "0" date = "20220721" [depends] core = "2.12.2" [author] name = "Нифигассе о-го-гошеньки" url = "https://nifigasse.ru" [description] text[] = "Поле позволяет выбрать город без выбора страны." text[] = "Распространяется БЕСПЛАТНО и без каких-либо ограничений." text[] = "Код полностью открыт. Автор не несет никакой ответственности в связи с использованием вами поля на своих сайтах. Поддержка не оказывается."
Теперь в папке newcity должно быть два элемента — папка package с файлами и файл манифеста. Выделяем эти два элемента и упаковываем в zip-архив.
Демо: nifigasse.ru/board
Реклама #
Happy 2 года назад #
Спасибо, тема актуальная. единственное меня смущает в этом поле как и до доработки, почему на компе есть строчный поиск, а на мобильных устройствах нет, только модалка с перечнем городов. Это как то можно поправить ?
Vlad 2 года назад #
Благодарю. Очень полезное дополнение и спасибо за детальную инструкцию.
Abobo 1 год назад #
Неплохо бы было если еще:
Можно было выбрать,
Только страну
Только Область
Только Город
--
А также в таком виде :
Страну и город в таком виде:
Россия, Москва
Или таком виде
Украина, Киевская область, Киев