Маски с внешними источниками данных

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

Inputmask предоставляет несколько механизмов, позволяющих строить такие решения: динамическое определение маски через функцию, изменение правил через хуки (onBeforeMask, onBeforePaste, onBeforeWrite), а также программное обновление конфигурации экземпляра.


Архитектура взаимодействия с внешними данными

Интеграция с API или любым внешним источником обычно строится по следующей схеме:

  1. Получение данных (HTTP API, локальное хранилище, WebSocket).
  2. Преобразование ответа в структуру маски.
  3. Передача маски в Inputmask или её обновление у уже инициализированного инстанса.
  4. При необходимости — пересоздание маски без потери введённых данных.

Ключевая особенность: маска в Inputmask не обязана быть статичной строкой. Она может быть функцией, возвращающей конфигурацию.


Маска как функция, зависящая от внешнего состояния

Один из наиболее гибких механизмов — использование функции вместо фиксированного значения mask.

import Inputmask from "inputmask";

let currentCountry = "KZ";

const phoneMask = Inputmask({
  mask: function () {
    if (currentCountry === "KZ") {
      return "+7 (999) 999-99-99";
    }
    if (currentCountry === "DE") {
      return "+49 9999 9999999";
    }
    return "+9{1,3} 999999999";
  }
});

phoneMask.mask(document.querySelector("#phone"));

Маска пересчитывается каждый раз при инициализации или обновлении, что позволяет подменять поведение в зависимости от внешнего контекста.

Типичный сценарий: currentCountry обновляется после ответа API геолокации или выбора пользователя.


Подгрузка маски из API

При работе с сервером конфигурация маски может приходить в виде JSON:

{
  "country": "KZ",
  "mask": "+7 (999) 999-99-99"
}

Интеграция выполняется через асинхронную загрузку:

import Inputmask from "inputmask";

async function initPhoneMask() {
  const res = await fetch("/api/phone-mask");
  const data = await res.json();

  const im = new Inputmask({
    mask: data.mask
  });

  im.mask(document.querySelector("#phone"));
}

initPhoneMask();

В этом случае Inputmask используется как конечный слой, а вся логика определения формата переносится во внешнюю систему.


Обновление маски без пересоздания DOM

Частая задача — смена маски после выбора страны или типа документа. Вместо повторной инициализации можно обновлять инстанс:

const im = new Inputmask({ mask: "+7 (999) 999-99-99" });
im.mask(document.querySelector("#phone"));

function updateMask(newMask) {
  im.opts.mask = newMask;
  im.setValue(""); 
  im.mask(document.querySelector("#phone"));
}

Более корректный подход — полное снятие и повторное применение:

Inputmask.remove(document.querySelector("#phone"));

new Inputmask({ mask: newMask }).mask(document.querySelector("#phone"));

Это предотвращает накопление внутренних состояний.


Использование onBeforeMask для внешнего нормализатора

onBeforeMask часто применяется как слой адаптации данных, приходящих из API или базы.

Inputmask({
  mask: "+7 (999) 999-99-99",
  onBeforeMask: function (value) {
    // нормализация номера из внешнего источника
    return value.replace(/[^\d]/g, "");
  }
}).mask("#phone");

Если API возвращает номер в разных форматах (+7, 8, 007), функция позволяет привести данные к единому виду до применения маски.


Динамическая маска на основе ответа справочника

Сложный случай — выбор маски по типу документа, полученному из внешнего справочника:

async function getDocTypeMask(typeId) {
  const res = await fetch(`/api/doc-types/${typeId}`);
  return res.json();
}

async function applyDocMask(typeId) {
  const config = await getDocTypeMask(typeId);

  const maskInstance = new Inputmask({
    mask: config.mask,
    placeholder: config.placeholder
  });

  maskInstance.mask(document.querySelector("#document"));
}

Такой подход позволяет централизовать бизнес-логику в backend, оставляя frontend максимально универсальным.


Использование definitions, полученных из внешнего источника

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

const apiConfig = {
  definitions: {
    "X": {
      validator: "[A-F0-9]",
      casing: "upper"
    }
  },
  mask: "XX-9999"
};

Inputmask(apiConfig).mask("#code");

Если definitions загружаются динамически, их можно применять перед инициализацией маски:

async function init() {
  const res = await fetch("/api/mask-definitions");
  const config = await res.json();

  Inputmask({
    definitions: config.definitions,
    mask: config.mask
  }).mask("#field");
}

Маски, зависящие от пользовательских данных

Часто внешним источником выступает не сервер, а локальные данные пользователя: выбранная страна, язык интерфейса, роль.

function buildMask(userProfile) {
  if (userProfile.role === "admin") {
    return "9999-AAAA";
  }

  if (userProfile.region === "EU") {
    return "+49 999 999999";
  }

  return "+7 (999) 999-99-99";
}

const mask = new Inputmask({
  mask: buildMask(currentUser)
});

Интеграция с debounce-обновлением данных

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

let timer;

function updateMaskFromQuery(query) {
  clearTimeout(timer);

  timer = setTimeout(async () => {
    const res = await fetch(`/api/country?q=${query}`);
    const data = await res.json();

    Inputmask.remove("#phone");

    new Inputmask({
      mask: data.mask
    }).mask("#phone");
  }, 300);
}

Задержка снижает количество обращений к API и предотвращает «дёргание» интерфейса.


Синхронизация маски с WebSocket-данными

В системах реального времени маска может обновляться через потоковые данные:

const socket = new WebSocket("wss://example.com/mask-stream");

socket.onmess age = (event) => {
  const data = JSON.parse(event.data);

  Inputmask.remove("#field");

  new Inputmask({
    mask: data.mask
  }).mask("#field");
};

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


Предобработка внешних значений через onBeforeWrite

onBeforeWrite позволяет контролировать итоговое значение перед его фиксацией, что полезно при внешней валидации:

Inputmask({
  mask: "+7 (999) 999-99-99",
  onBeforeWrite: function (event, buffer, caretPos, opts) {
    const raw = buffer.join("");

    if (raw.length < 5) {
      return false;
    }

    return buffer;
  }
}).mask("#phone");

При интеграции с API это позволяет блокировать некорректные состояния, пришедшие извне.


Каскадные источники масок

В сложных приложениях маска формируется из нескольких источников:

  • глобальные настройки системы;
  • профиль пользователя;
  • данные API;
  • локальные overrides;
  • состояние формы.

Пример композиции:

function resolveMask(system, user, api) {
  return api.mask
    || user.customMask
    || system.defaultMask;
}

const mask = new Inputmask({
  mask: resolveMask(systemConfig, userProfile, apiResponse)
});

Такой подход обеспечивает предсказуемую иерархию приоритетов.


Фильтрация и нормализация внешних масок

Внешние системы часто возвращают маски в несовместимом формате. Перед передачей в Inputmask требуется нормализация:

function normalizeMask(mask) {
  return mask
    .replace(/\d/g, "9")
    .replace(/[A-Z]/g, "a");
}

Использование:

const config = await fetch("/api/mask");

new Inputmask({
  mask: normalizeMask(config.mask)
}).mask("#input");

Ограничения и особенности внешних источников

При интеграции с API важно учитывать:

  • задержки сети приводят к «пустым» состояниям поля;
  • изменение маски после ввода может требовать очистки значения;
  • несовпадение форматов ведёт к невозможности корректной установки caret;
  • динамические definitions могут конфликтовать с уже применёнными правилами.

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


Управление жизненным циклом маски при внешних обновлениях

Корректная стратегия обновления обычно включает:

  1. снятие старой маски;
  2. сохранение текущего значения;
  3. получение новой конфигурации;
  4. нормализацию;
  5. повторное применение.
async function refreshMask() {
  const input = document.querySelector("#phone");
  const value = input.value;

  Inputmask.remove(input);

  const res = await fetch("/api/new-mask");
  const config = await res.json();

  const im = new Inputmask({ mask: config.mask });
  im.mask(input);

  input.value = value;
}

Такой подход минимизирует потерю данных и обеспечивает согласованность состояния поля.