Реализация модуля выбора города
По проекту была поставлена задача реализовать модуль выбора города в шапке сайта. При выборе города из списка, адрес сайта будет добавлять нужный домен к адресной строке.
Примерно это должно выглядеть так. Адрес в строке «https://example.ru» должен превратиться в «https://dallas.example.ru» при выборе города Dallas. Или немного сложнее: «https://сайт.рф» будет переписан в строку «https://москва.сайт.рф» при выборе — Москва. Ну и поместить данный модуль нужно было в шапку сайта.

Сначала разметка HTML
<!-- CITY CHANGER -->
<div class="city-changer">
<a href="#" class="city-changer__link">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>
<span data-role="current-city">Ваш город</span>
</a>
<div class="cities city-changer__menu">
<ul class="cities__list"></ul>
</div>
</div>
<!-- CITY CHANGER -->По сути. Это простой блок, в котором лежит ссылка с будущим названием города (по умолчанию «Ваш город»). А также блок с раскрывающимся меню-списком городов. Будем генерировать налету — поэтому список пока пустой.
Стилизация модуля в CSS
/* CITY CHANGER */
.city-changer {
--link-color: #fff;
--menu-link-color: #000;
--bg-color: #fff;
--menu-link-hover: #0038a5;
width: fit-content;
position: relative;
margin-left: 32px;
}
.city-changer__link {
display: flex;
align-items: center;
gap: 3px;
color: var(--link-color);
}
.city-changer__link.active {
opacity: 1;
}
.city-changer a {
font-size: 14px;
text-decoration: none;
opacity: 0.6;
}
.city-changer a:hover {
opacity: 1;
}
.city-changer__menu {
display: none;
position: absolute;
top: calc(100% + 2ch);
left: -2ch;
}
.city-changer__menu.active {
display: block;
}
.city-changer__menu a {
color: var(--menu-link-color);
padding-block: 2px;
}
.city-changer__menu a:hover {
color: var(--menu-link-hover);
}
.cities {
border: 1px solid #00000022;
border-radius: 8px;
padding: 16px 32px;
width: fit-content;
background-color: var(--bg-color);
box-shadow: 0 0 16px #00000033;
}
.cities__list {
padding: 0;
margin: 0;
white-space: nowrap;
line-height: 1.3;
columns: 3;
column-gap: 20px;
}
.cities__list-item{
margin-left: 20px;
margin-bottom: 6px;
}
.cities__list-item::marker {
color: #00000066;
}
.cities__list-item.accent::marker {
color: #000000aa;
}
.cities__list-item:hover::marker{
color: var(--menu-link-hover);
}
.cities__list-item:hover{
color: var(--menu-link-hover);
}
.cities__list-item.accent {
font-weight: 700;
}
.cities__list-item.accent:has(+ :not(.accent)) {
margin-bottom: calc(1lh + 12px);
}
/* CITY CHANGER */Тут стандартная реализация блока. Выставляем кастомные свойства цветов — так проще оперировать далее при стилизации. Абсолютом стилизуем всплывашку. Реализуем разбивку на колонки самого списка городов — их будет много (будут добавляться постепенно).
JavaScript код для функционирования самого модуля
Сначала массив с данными городов (объект)
let cities = {
"Москва": ["москва", "xn--80adxhks", true],
"Нижний Новгород": ["нн", "xn--m1aa", true],
"Краснодар": ["краснодар", "xn--80aalwqglfe"]
}Флаг true в коде будет подсказывать, что этот город в разметке нужно будет выделить жирным, так как он наиболее важен.
Ключ объекта нам нужен для подстановки его в разметку в виде названия города в ссылке. В качестве значения у нас будет массив, в котором первое значение — строка, которая выступит в рли поддомена, а второе — к сожалению, необходимая строка для идентификации города через адресную строку (в браузерах, кириллица копируется с помощью кодировки).
Создадим переменные
let changerLink = document.querySelector(".city-changer__link");
changerLink.addEventListener("click", openCitiesLst);
let changerMenu = document.querySelector('.city-changer__menu');
let list = changerMenu.querySelector(".cities__list");changerLink — сама ссылка с текущим городом. Поставим на нее прослушиватель click и коллбэк.
changerMenu — всплывающая плашка для выбора города.
list — список городов, который будем отрисовывать с помощью JavaScript.
Функция для замены или добавления поддомена
function replaceSubdomain(city) {
// Получаем текущий URL
const currentUrl = window.location.href;
// Разбираем URL на компоненты
const url = new URL(currentUrl);
// Получаем хост (например, "москва.сайт.рф" или "сайт.рф")
const hostParts = url.hostname.split(".");
// Определяем, есть ли уже поддомен (если хостов > 2)
let newHostname;
if (hostParts.length > 2) {
// Уже есть поддомен, заменяем его: "москва.сайт.рф" -> "краснодар.сайт.рф"
hostParts[0] = city;
newHostname = hostParts.join(".");
} else {
// Нет поддомена, добавляем: "социалка52.рф" -> "москва.сайт.рф"
newHostname = `${city}.${url.hostname}`;
}
// Устанавливаем новый хост
url.hostname = newHostname;
// Возвращаем готовый URL
return url.toString();
}В целом, функция не очень сложная. Но для экономии времени, была написана с помощью ИИ.
Добавляем города в список
Нам нужен список такого вида:

for (let [key, value] of Object.entries(cities)) {
let newHref = replaceSubdomain(value[0]);
if (typeof value == "object" && value[2]) {
list.insertAdjacentHTML(
"beforeend",
`<li class="cities__list-item accent"><a href="${newHref}">${key}</a></li>`
);
} else {
list.insertAdjacentHTML(
"beforeend",
`<li class="cities__list-item"><a href="${newHref}">${key}</a></li>`
);
}
}Производим перебор объекта по каждому элементу. Разбиваем объект на ключ—значение. Применяем функцию, описанную выше для генерации значения href в будущей ссылке. И, с помощью insertAdjacentHTML добавляем элементы в список.
Открытие окна выбора города
function openCitiesList(e) {
e.preventDefault();
this.classList.contains('active') ? this.classList.remove('active') : this.classList.add('active');
changerMenu.classList.contains('active') ? changerMenu.classList.remove('active') : changerMenu.classList.add('active');
}Функция установки текущего города
function setCurrentCity() {
let currentCity = changerLink.querySelector('[data-role="current-city"]');
// Получаем текущий URL
let currentUrl = window.location.href;
// Разбираем URL на компоненты
const url = new URL(currentUrl);
// Получаем хост (например, "москва.социалка52.рф" или "социалка52.рф")
const hostParts = url.hostname.split(".");
// Определяем, есть ли уже поддомен (если хостов > 2)
let newHostname;
if (hostParts.length > 2) {
// Уже есть поддомен, заменяем его: "москва.социалка52.рф" -> "тверь.социалка52.рф"
for (let [key, value] of Object.entries(cities)) {
if(hostParts[0] == value[1]){
currentCity.innerText = key;
}
}
}
}
setCurrentCity();Описана функция, для замены названия города в ссылке (основной) открытия списка. И ее запуск.
Для удобства
document.addEventListener('click', function(e){
if(e.target.closest('.city-changer')) return;
changerMenu.classList.remove('active');
changerLink.classList.remove('active');
});
Тут простое делегирование события по экрану, чтобы окно с выбором города закрывалось.
Как работает данный модуль можно посмотреть на этом сайте:
посмотреть готовое решениеПолный JavaScript код
let cities = {
"Москва": ["москва", "xn--80adxhks", true],
"Нижний Новгород": ["нн", "xn--m1aa", true],
"Краснодар": ["краснодар", "xn--80aalwqglfe", true],
"Казань": ["казань", "xn--80aauks4g", true],
"Екатеринбург": ["екатеринбург", "xn--80acgfbsl1azdqr", true],
"Самара": ["самара", "xn--80aaa0cvac", true],
"Санкт-Петербург": ["спб", "xn--90a1af", true],
"Челябинск": ["челябинск", "xn--90ahkico3a5b9d", true],
"Воронеж": ["воронеж", "xn--b1agd0aean", true],
"Ростов-на-Дону": ["ростов", "xn--b1axaggg", true],
"Новосибирск": ["новосибирск", "xn--90absbknhbvge", true],
"Уфа": ["уфа", "xn--80a1bd", true],
"Омск": ["омск", "xn--j1adfn", true],
"Пермь": ["пермь", "xn--e1aohf5d", true]
// и так далее...
};
let changerLink = document.querySelector(".city-changer__link");
changerLink.addEventListener("click", openCitiesList);
let changerMenu = document.querySelector('.city-changer__menu');
let list = changerMenu.querySelector(".cities__list");
function replaceSubdomain(city) {
// Получаем текущий URL
const currentUrl = window.location.href;
// Разбираем URL на компоненты
const url = new URL(currentUrl);
// Получаем хост (например, "москва.социалка52.рф" или "социалка52.рф")
const hostParts = url.hostname.split(".");
// Определяем, есть ли уже поддомен (если хостов > 2)
let newHostname;
if (hostParts.length > 2) {
// Уже есть поддомен, заменяем его: "москва.социалка52.рф" -> "тверь.социалка52.рф"
hostParts[0] = city;
newHostname = hostParts.join(".");
} else {
// Нет поддомена, добавляем: "социалка52.рф" -> "москва.социалка52.рф"
newHostname = `${city}.${url.hostname}`;
}
// Устанавливаем новый хост
url.hostname = newHostname;
// Возвращаем готовый URL
return url.toString();
}
for (let [key, value] of Object.entries(cities)) {
let newHref = replaceSubdomain(value[0]);
if (typeof value == "object" && value[2]) {
list.insertAdjacentHTML(
"beforeend",
`<li class="cities__list-item accent"><a href="${newHref}">${key}</a></li>`
);
} else {
list.insertAdjacentHTML(
"beforeend",
`<li class="cities__list-item"><a href="${newHref}">${key}</a></li>`
);
}
}
function openCitiesList(e) {
e.preventDefault();
this.classList.contains('active') ? this.classList.remove('active') : this.classList.add('active');
changerMenu.classList.contains('active') ? changerMenu.classList.remove('active') : changerMenu.classList.add('active');
}
function setCurrentCity() {
let currentCity = changerLink.querySelector('[data-role="current-city"]');
// Получаем текущий URL
let currentUrl = window.location.href;
// Разбираем URL на компоненты
const url = new URL(currentUrl);
// Получаем хост (например, "москва.социалка52.рф" или "социалка52.рф")
const hostParts = url.hostname.split(".");
// Определяем, есть ли уже поддомен (если хостов > 2)
let newHostname;
if (hostParts.length > 2) {
// Уже есть поддомен, заменяем его: "москва.социалка52.рф" -> "тверь.социалка52.рф"
for (let [key, value] of Object.entries(cities)) {
if(hostParts[0] == value[1]){
currentCity.innerText = key;
}
}
}
}
setCurrentCity();
document.addEventListener('click', function(e){
if(e.target.closest('.city-changer')) return;
changerMenu.classList.remove('active');
changerLink.classList.remove('active');
});