Создание собственных элементов управления

В JavaScript API HERE Technologies элементы управления картой строятся поверх базового контейнера UI и интегрируются в систему управления состоянием карты через UI Manager. Архитектура предполагает расширяемость: разработчик может добавлять собственные компоненты, которые ведут себя как встроенные элементы управления масштабом, слоями или навигацией, сохраняя единый стиль взаимодействия и корректную работу с жизненным циклом карты.

UI-слой в HERE Maps построен вокруг объекта H.ui.UI, который управляет набором элементов интерфейса и их размещением относительно карты. Каждый элемент управления наследует поведение базового класса H.ui.Control, обеспечивающего:

  • привязку к DOM-контейнеру карты
  • управление событиями пользовательского ввода
  • синхронизацию с состоянием карты
  • поддержку позиционирования (top-left, top-right, bottom-left, bottom-right)

Пользовательский элемент управления фактически является DOM-компонентом с логикой, интегрированной в UI Manager.

Базовый шаблон создания элемента управления

Создание собственного контроллера начинается с определения конструктора и наследования от H.ui.Control:

function CustomControl(options) {
    H.ui.Control.call(this);
    this.options = options || {};
    this.buildUI();
}

CustomControl.prototype = Object.create(H.ui.Control.prototype);
CustomControl.prototype.constructor = CustomControl;

Метод buildUI отвечает за создание DOM-структуры:

CustomControl.prototype.buildUI = function () {
    const container = document.createElement('div');
    container.className = 'custom-control';

    const button = document.createElement('button');
    button.innerText = 'Action';

    container.appendChild(button);
    this.setElement(container);
};

Ключевой момент заключается в вызове setElement, который регистрирует DOM-узел в системе UI.

Интеграция элемента управления в карту

После создания контрол добавляется в интерфейс через UI Manager:

const ui = H.ui.UI.createDefault(map, defaultLayers);

const customControl = new CustomControl();

ui.addControl('custom-control', customControl, H.ui.ControlPosition.TOP_RIGHT);

Позиционирование определяется константами H.ui.ControlPosition, которые задают область закрепления элемента.

Работа с событиями внутри контролов

Элементы управления должны корректно обрабатывать пользовательские события, не нарушая поведение карты (панорамирование, зумирование, клик по объектам).

Пример обработки клика:

button.addEventListener('click', function (e) {
    e.stopPropagation();
    console.log('Custom action triggered');
});

Остановка всплытия событий критична, поскольку без неё взаимодействие с картой может конфликтовать с UI-слоем.

Доступ к карте из пользовательского элемента

Контрол может взаимодействовать с объектом карты через замыкание или явную передачу ссылки:

function CustomControl(map) {
    H.ui.Control.call(this);
    this.map = map;
    this.buildUI();
}

Использование карты внутри контролов позволяет:

  • изменять центр карты
  • управлять масштабом
  • включать/выключать слои
  • выполнять геокодирование и обратное геокодирование

Пример изменения масштаба:

button.addEventListener('click', () => {
    const zoom = this.map.getZoom();
    this.map.setZoom(zoom + 1);
});

Стилизация пользовательских элементов управления

UI-слой HERE Maps не накладывает строгих ограничений на стили, однако рекомендуется соблюдать структуру, совместимую с системным дизайном карты.

Пример CSS:

.custom-control {
    background: white;
    padding: 8px;
    border-radius: 4px;
    box-shadow: 0 2px 6px rgba(0,0,0,0.2);
    font-family: Arial, sans-serif;
}

.custom-control button {
    border: none;
    background: #0078ff;
    color: white;
    padding: 6px 10px;
    cursor: pointer;
}

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

Расширение стандартных контролов

Вместо создания элемента с нуля можно расширять существующие контролы, например H.ui.ZoomControl или H.ui.SpatialLayersControl, добавляя дополнительную функциональность.

function ExtendedZoomControl() {
    H.ui.ZoomControl.call(this);
}

ExtendedZoomControl.prototype = Object.create(H.ui.ZoomControl.prototype);
ExtendedZoomControl.prototype.constructor = ExtendedZoomControl;

Дальнейшая модификация позволяет добавлять кнопки или изменять поведение стандартных действий.

Жизненный цикл элемента управления

Каждый контрол проходит несколько стадий:

  1. Создание экземпляра
  2. Инициализация DOM
  3. Регистрация в UI Manager
  4. Отображение в контейнере карты
  5. Уничтожение при удалении UI

Удаление осуществляется через UI:

ui.removeControl(customControl);

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

Динамическое обновление состояния UI

Контролы могут реагировать на изменения состояния карты, подписываясь на события:

map.addEventListener('zoom', function () {
    const zoomLevel = map.getZoom();
    console.log('Current zoom:', zoomLevel);
});

Более сложные элементы управления синхронизируют своё состояние с картой:

map.addEventListener('mapviewchange', function () {
    updateUI();
});

Использование HTML-шаблонов в контролах

Для сложных интерфейсов допускается использование шаблонов:

CustomControl.prototype.buildUI = function () {
    this.element = document.createElement('div');
    this.element.innerHTML = `
        <div class="panel">
            <input type="text" placeholder="Введите запрос">
            <button>Поиск</button>
        </div>
    `;

    this.setElement(this.element);
};

Такой подход упрощает построение мини-приложений внутри карты.

Взаимодействие с геосервисами внутри контролов

Элементы управления часто выступают интерфейсом к геосервисам платформы: маршрутизации, поиску объектов и геокодированию.

Пример интеграции с поиском:

button.addEventListener('click', () => {
    const geocoder = platform.getSearchService();

    geocoder.geocode({ q: input.value }, result => {
        const location = result.items[0].position;
        map.setCenter(location);
    });
});

Контрол становится точкой входа в бизнес-логику геоданных.

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

UI Manager поддерживает одновременное размещение нескольких пользовательских элементов. При этом важно учитывать:

  • приоритет отображения
  • пересечение DOM-областей
  • порядок добавления
  • адаптацию под мобильные устройства

Пример:

ui.addControl('search', searchControl, H.ui.ControlPosition.TOP_LEFT);
ui.addControl('layers', layersControl, H.ui.ControlPosition.TOP_RIGHT);
ui.addControl('tools', toolsControl, H.ui.ControlPosition.BOTTOM_RIGHT);

Адаптивное поведение элементов управления

Контролы должны учитывать размеры контейнера карты. Для этого используется обработка resize-событий:

window.addEventListener('resize', () => {
    map.getViewPort().resize();
});

Также возможно динамическое изменение структуры UI в зависимости от ширины экрана:

if (window.innerWidth < 600) {
    ui.removeControl(largePanelControl);
}

Обработка конфликтов с встроенными контролами

При добавлении кастомных элементов необходимо учитывать взаимодействие с системными контролами:

  • предотвращение перекрытия кнопок zoom
  • контроль z-index
  • корректная работа pointer-events
  • соблюдение отступов UI контейнера

Часто используется обёртка:

container.style.pointerEvents = 'auto';
container.style.zIndex = 1000;

Инкапсуляция логики контролов

Правильная архитектура требует отделения UI от логики:

  • UI отвечает за DOM
  • контрол — за события
  • сервисный слой — за геоданные

Пример разделения:

function SearchService(platform) {
    this.platform = platform;
}

SearchService.prototype.query = function (text, callback) {
    this.platform.getSearchService().geocode({ q: text }, callback);
};

Контрол вызывает сервис, не реализуя бизнес-логику напрямую.

Повторное использование и модульность

Контролы проектируются как переиспользуемые модули. Для этого:

  • логика не зависит от конкретной карты
  • параметры передаются через конфигурацию
  • DOM создаётся динамически
  • отсутствуют глобальные зависимости

Пример конфигурационного подхода:

const control = new CustomControl({
    label: 'Поиск',
    placeholder: 'Город или адрес'
});