Напишем поле выбора города без выбора страны

+11
311
Напишем поле выбора города без выбора страны

Сегодня будем ломать поле «Местоположение» — добавим опцию, при которой в форме и фильтре мы исключим выбор страны, потому что на сайте она одна. Чтобы ничего не испортить, мы создадим новое поле, наследуемое от поля «Местоположение».

Сначала немного расскажу, что мы будем делать и для чего. Допустим, у вас есть сайт, где весь контент для аудитории одной страны. Для примера возьмем Россию. Вы хотите, чтобы пользователи регистрировались только из России, чтобы при добавлении материалов указывали только российские города и т.д. Конечно, можно просто удалить/отключить другие страны. Но тогда в поле все равно первым шагом будет предложено выбрать страну. Но зачем, если она одна?

В поле «Местоположение» есть возможность создать группировку Страна->Область->Город. Причем, страна должна быть обязательно.

Изображение

Если же мы не создадим страну, а создадим только область и город, то в форме и фильтре получим пустые выпадающие списки. Всё правильно, страна ведь не выбрана, какие подгружать регионы?

Изображение

Вот это мы и будем исправлять.

Начнем с создания нужных файлов. Как известно, все поля состоят из двух обязательных файлов — файла поля и файла шаблона. Традиционно, начнем со второго. Создадим в папке 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:

  1. <?php class fieldNewcity extends fieldCity {
  2.  
  3. }

Теперь все методы поля «Местоположение» работают и в нашем новом поле. Если мы хотим изменить какой-то определенный метод, то добавлять будем только его. Сначала нам надо изменить название, поэтому добавим его:

  1. <?php class fieldNewcity extends fieldCity {
  2.  
  3. public $title = 'Местоположение NEW';
  4.  
  5. }

Теперь опции. Нам нужно добавить опцию выбора страны. Для этого из файла city.php полностью копируем метод getOptions():

  1. <?php class fieldNewcity extends fieldCity {
  2.  
  3. public function getOptions() {
  4.  
  5. return [
  6. new fieldList('location_type', [
  7. 'title' => LANG_PARSER_CITY_LOCATION_TYPE,
  8. 'default' => 'cities',
  9. 'items' => [
  10. 'countries' => LANG_COUNTRY,
  11. 'regions' => LANG_REGION,
  12. 'cities' => LANG_CITY
  13. ]
  14. ]),
  15. new fieldCheckbox('auto_detect', [
  16. 'title' => LANG_PARSER_CITY_AUTO_DETECT
  17. ]),
  18. new fieldString('location_group', [
  19. 'title' => LANG_PARSER_CITY_LOCATION_GROUP,
  20. 'hint' => LANG_PARSER_CITY_LOCATION_GROUP_HINT,
  21. 'rules' => [
  22. ['sysname'],
  23. ['max_length', 20]
  24. ]
  25. ]),
  26. new fieldString('output_string', [
  27. 'title' => LANG_PARSER_CITY_OUTPUT_STRING,
  28. 'hint' => LANG_PARSER_CITY_OUTPUT_STRING_HINT,
  29. 'extended_option' => true
  30. ]),
  31. new fieldCheckbox('is_autolink', [
  32. 'title' => LANG_PARSER_LIST_IS_AUTOLINK,
  33. 'hint' => LANG_PARSER_LIST_IS_AUTOLINK_FILTER,
  34. 'default' => false,
  35. 'extended_option' => true
  36. ])
  37. ];
  38. }
  39.  
  40. }

Теперь удалим из опции location_type тип «Страна» (countries):

  1. new fieldList('location_type', [
  2. 'title' => LANG_PARSER_CITY_LOCATION_TYPE,
  3. 'default' => 'cities',
  4. 'items' => [
  5. 'regions' => LANG_REGION,
  6. 'cities' => LANG_CITY
  7. ]
  8. ]),

После этой опции добавим новую — выбор страны:

  1. new fieldList('country', [
  2. 'title' => 'Страна',
  3. 'generator' => function () {
  4.  
  5. return cmsCore::getModel('geo')->getCountries(); // Получаем список стран, используя метод getCountries() компонента "География"
  6.  
  7. }
  8. ]),

Теперь мы можем создать наше поле в ТК (или где-то еще). Форма опций будет выглядеть так:

Изображение

Изображение

Давайте еще изменим подсказку для опции «Название для группировки», ведь теперь нам в группировке страна не нужна:

  1. new fieldString('location_group', [
  2. 'title' => LANG_PARSER_CITY_LOCATION_GROUP,
  3. 'hint' => 'Если необходимо объединить местоположения область-город, укажите одно имя объединения для всех полей. Поле типа "Область" для группировки обязательно.', // Новая подсказка
  4. 'rules' => [
  5. ['sysname'],
  6. ['max_length', 20]
  7. ]
  8. ]),

И чтобы точно всё было совсем красиво, изменим подсказку у опции строки вывода значений:

  1. new fieldString('output_string', [
  2. 'title' => LANG_PARSER_CITY_OUTPUT_STRING,
  3. 'hint' => 'Например, "{region}, {city}", где region и city системные имена полей. Полезно использовать, отключая для вывода остальные поля группировки, показывая нужные значения в этом поле. Если не указано - выводится значение текущего поля.',
  4. 'extended_option' => true
  5. ]),

Еще давайте удалим опцию автоопределения страны (auto_detect). Теперь наш файл выглядит так:

  1. <?php class fieldNewcity extends fieldCity {
  2.  
  3. public $title = 'Местоположение NEW';
  4.  
  5. public function getOptions() {
  6.  
  7. return [
  8. new fieldList('location_type', [
  9. 'title' => LANG_PARSER_CITY_LOCATION_TYPE,
  10. 'default' => 'cities',
  11. 'items' => [
  12. 'regions' => LANG_REGION,
  13. 'cities' => LANG_CITY
  14. ]
  15. ]),
  16. new fieldList('country', [
  17. 'title' => 'Страна',
  18. 'generator' => function () {
  19.  
  20. return cmsCore::getModel('geo')->getCountries();
  21.  
  22. }
  23. ]),
  24. new fieldString('location_group', [
  25. 'title' => LANG_PARSER_CITY_LOCATION_GROUP,
  26. 'hint' => 'Если необходимо объединить местоположения область-город, укажите одно имя объединения для всех полей. Поле типа "Область" для группировки обязательно.',
  27. 'rules' => [
  28. ['sysname'],
  29. ['max_length', 20]
  30. ]
  31. ]),
  32. new fieldString('output_string', [
  33. 'title' => LANG_PARSER_CITY_OUTPUT_STRING,
  34. 'hint' => 'Например, "{region}, {city}", где region и city системные имена полей. Полезно использовать, отключая для вывода остальные поля группировки, показывая нужные значения в этом поле. Если не указано - выводится значение текущего поля.',
  35. 'extended_option' => true
  36. ]),
  37. new fieldCheckbox('is_autolink', [
  38. 'title' => LANG_PARSER_LIST_IS_AUTOLINK,
  39. 'hint' => LANG_PARSER_LIST_IS_AUTOLINK_FILTER,
  40. 'default' => false,
  41. 'extended_option' => true
  42. ])
  43. ];
  44. }
  45.  
  46. }

С опциями разобрались. Теперь изменим метод getListItems(). Копируем его из файла city.php и изменяем:

  1. public function getListItems() {
  2.  
  3. $model = cmsCore::getModel('geo'); // Модель компонента "География"
  4.  
  5. $items = [];
  6.  
  7. $location_type = $this->getOption('location_type'); // Тип метоположения из опций
  8.  
  9. $country = $this->getOption('country'); // Страна из опций
  10.  
  11. if ($location_type === 'regions') { // Если тип местоположения "Область"
  12.  
  13. $items = ['0' => ''] + $model->getRegions($country); // Получаем список регионов страны из опций
  14.  
  15. }
  16.  
  17. return $items;
  18.  
  19. }

В форме получилось так:

Изображение

Изображение

В фильтре также:

Изображение

Теперь нам надо сделать то же самое, только без группировки. Откроем файлы шаблона, которые мы создавали в самом начале: templates/default/assets/fields/newcity.tpl.php и templates/modern/assets/fields/newcity.tpl.php. В этих файлах в самом низу есть ссылка, вызывающая модальное окно с формой выбора города. В дефолтном она выглядит так:

  1. <a class="ajax-modal" href="<?php echo href_to('geo', 'widget', array($field->id, $value['id'])); ?>"><?php echo LANG_SELECT; ?></a>

В модерне так:

  1. <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'])); ?>">
  2. <?php echo LANG_SELECT; ?>
  3. </a>

Нас интересует атрибут href — он одинаковый в обоих файлах. Заменим его на это:

  1. 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:

  1. <?php class actionGeoWidgetNew extends cmsAction {
  2.  
  3. }

Добавляем метод run(), в котором получаем те же параметры, что и метод actionWidget() в файле frontend.php, но первым параметром сюда в нашем случае приходит id страны:

  1. <?php class actionGeoWidgetNew extends cmsAction {
  2.  
  3. public function run($country_id, $field_id, $city_id = false) {
  4.  
  5. }
  6.  
  7. }

Код этого метода я тоже взял в файле frontend.php в методе actionWidget(), но внес изменения. Вот, что получилось:

  1. <?php class actionGeoWidgetNew extends cmsAction {
  2.  
  3. public function run($country_id, $field_id, $city_id = false) {
  4.  
  5. if (!$this->request->isAjax()) { cmsCore::error404(); }
  6.  
  7. $regions = $this->model->getRegions($country_id); // Получаем регионы нужной страны
  8. $regions = array('0'=>LANG_GEO_SELECT_REGION) + $regions;
  9.  
  10. $cities = [];
  11.  
  12. $region_id = false;
  13.  
  14. if(!$region_id ){
  15.  
  16. $city_parents = $this->model->getCityParents($city_id);
  17.  
  18. $region_id = $region_id ? $region_id : $city_parents['region_id'];
  19.  
  20. }
  21.  
  22. if ($city_id){
  23.  
  24. $cities = $this->model->getCities($region_id);
  25. $cities = array('0'=>LANG_GEO_SELECT_CITY) + $cities;
  26.  
  27. }
  28.  
  29. $this->cms_template->render('widget_new', array(
  30. 'field_id' => $field_id,
  31. 'city_id' => $city_id,
  32. 'country_id' => $country_id,
  33. 'region_id' => $region_id,
  34. 'regions' => $regions,
  35. 'cities' => $cities
  36. ));
  37.  
  38. }
  39.  
  40. }

Не могу подобрать слова, чтобы объяснить, что я тут наделал. Честно говоря, я и сам не понял)) Можете сравнить код здесь с кодом из метода actionWidget() в файле frontend.php и рассказать, что вы увидели, в комментариях))

Но это еще не всё. Надо создать шаблон, для этого экшена. Создаем файл widget_new.tpl.php в папке templates/default/controllers/geo, и копируем содержимое из файла widget.tpl.php. То же самое делаем в папке templates/modern/controllers/geo. Но просто скопировать мало, нам надо удалить поле выбора страны. В дефолтном шаблоне:

  1. <div class="list">
  2. <?php echo html_select('countries', $countries, $country_id, array('onchange'=>"icms.geo.changeParent(this, 'regions')", 'rel'=>'regions')); ?>
  3. </div>

В Модерне:

  1. <div class="list mb-3" <?php if (!$city_id){?>style="display:none"<?php } ?>>
  2. <?php echo html_select('regions', $regions, $region_id, array('onchange'=>"icms.geo.changeParent(this, 'cities')", 'rel'=>'cities')); ?>
  3. </div>

Удаляем их. И надо показать поле выбора региона. Оно скрыто, пока не будет выбрана страна, но страна у нас больше не выбирается. Поэтому после только что удаленного поля в следующем поле находим:

  1. <?php if (!$city_id){?>style="display:none"<?php } ?>

И удаляем.

Теперь код формы должен выглядеть так в дефолтном шаблоне:

  1. <form data-items-url="<?php echo $this->href_to('get_items'); ?>">
  2.  
  3. <div class="list">
  4. <?php echo html_select('regions', $regions, $region_id, array('onchange'=>"icms.geo.changeParent(this, 'cities')", 'rel'=>'cities')); ?>
  5. </div>
  6.  
  7. <div class="list" <?php if (!$city_id){?>style="display:none"<?php } ?>>
  8. <?php echo html_select('cities', $cities, $city_id, array('onchange'=>"icms.geo.changeCity(this)")); ?>
  9. </div>
  10.  
  11. </form>

А в Модерне так:

  1. <form data-items-url="<?php echo $this->href_to('get_items'); ?>">
  2.  
  3. <div class="list mb-3">
  4. <?php echo html_select('regions', $regions, $region_id, array('onchange'=>"icms.geo.changeParent(this, 'cities')", 'rel'=>'cities')); ?>
  5. </div>
  6.  
  7. <div class="list mb-3" <?php if (!$city_id){?>style="display:none"<?php } ?>>
  8. <?php echo html_select('cities', $cities, $city_id, array('onchange'=>"icms.geo.changeCity(this)")); ?>
  9. </div>
  10.  
  11. </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 с таким содрежимым (или другим):

  1. [info]
  2. title = "Поле Местоположение NEW"
  3.  
  4. [version]
  5. major = "1"
  6. minor = "0"
  7. build = "0"
  8. date = "20220721"
  9.  
  10. [depends]
  11. core = "2.12.2"
  12.  
  13. [author]
  14. name = "Нифигассе о-го-гошеньки"
  15. url = "https://nifigasse.ru"
  16.  
  17. [description]
  18. text[] = "Поле позволяет выбрать город без выбора страны."
  19. text[] = "Распространяется БЕСПЛАТНО и без каких-либо ограничений."
  20. text[] = "Код полностью открыт. Автор не несет никакой ответственности в связи с использованием вами поля на своих сайтах. Поддержка не оказывается."

Теперь в папке newcity должно быть два элемента — папка package с файлами и файл манифеста. Выделяем эти два элемента и упаковываем в zip-архив.

Демо: nifigasse.ru/board

0
Happy Happy 26 дней назад #

Спасибо, тема актуальная. единственное меня смущает в этом поле как и до доработки, почему на компе есть строчный поиск, а на мобильных устройствах нет, только модалка с перечнем городов. Это как то можно поправить ?

0
Tim T Tim T 25 дней назад #

Благодарю. Очень полезное дополнение и спасибо за детальную инструкцию.

Еще от автора

Поле "UpJump - поднятие записей"
Поднятие авторами своих записей в списках типов контента и групп.
Компонент "UpJump - продвижение записей в списках" стал доступнее
И снова делаю то, о чем вы просили. Теперь речь про компонент "UpJump - продвижение записей в списках" - теперь он стал доступнее.
Компонент Google Indexing дешевле!
Как вы и просили, теперь компонент стал доступнее. Но есть нюансы.
Используя этот сайт, вы соглашаетесь с тем, что мы используем файлы cookie.