Работа с временными интервалами в интерфейсах выбора требует аккуратной модели данных, поскольку пользовательский ввод должен оставаться простым, а внутренняя структура — однозначной. В связке с компонентами выбора типа select удобнее всего представлять временные диапазоны как строковые значения фиксированного формата или как объектные структуры, преобразуемые перед сохранением. Tom Select в таких сценариях выступает как слой управления списком предопределённых интервалов, поддержкой поиска, кастомной отрисовки и динамического формирования элементов.
Модель временного диапазона чаще всего сводится к двум точкам: началу и завершению интервала. В пользовательском интерфейсе это можно упаковать в единый элемент списка:
Каждый такой элемент фактически является атомарным значением, хотя логически включает два времени. Tom Select работает с этим без дополнительных адаптаций, если заранее подготовить список options.
Базовая инициализация с фиксированными интервалами выглядит следующим образом:
new TomSelect("
options: [
{ value: "09:00-12:00", text: "09:00 — 12:00" },
{ value: "12:00-15:00", text: "12:00 — 15:00" },
{ value: "15:00-18:00", text: "15:00 — 18:00" },
{ value: "18:00-21:00", text: "18:00 — 21:00" }
],
maxItems: 1,
placeholder: "Выбор интервала"
});
Такой подход подходит для расписаний с фиксированными слотами. При
необходимости множественного выбора достаточно убрать ограничение
maxItems.
Гораздо более гибкая модель — генерация временных интервалов программно. Это позволяет менять шаг (например, 15, 30 или 60 минут) без изменения разметки.
function buildTimeRanges(stepMinutes = 60) {
const result = [];
for (let minutes = 0; minutes < 24 * 60; minutes += stepMinutes) {
const start = formatTime(minutes);
const end = formatTime(minutes + stepMinutes);
result.push({
value: `${start}-${end}`,
text: `${start} — ${end}`
});
}
return result;
}
function formatTime(totalMinutes) {
const h = String(Math.floor(totalMinutes / 60)).padStart(2, "0");
const m = String(totalMinutes % 60).padStart(2, "0");
return `${h}:${m}`;
}
Инициализация:
new TomSelect("#time-ranges", {
options: buildTimeRanges(30),
maxItems: null,
placeholder: "Интервалы времени"
});
Такой подход делает интерфейс независимым от заранее заданного набора данных и позволяет легко адаптировать расписание под разные бизнес-логики.
В более сложных сценариях один селект недостаточен. Например, при создании бронирования важно контролировать валидность диапазона: конец не может быть раньше начала, а пересечения интервалов должны исключаться.
В этом случае используется связка из двух Tom Select компонентов.
<select id="time-start"></select>
<select id="time-end"></select>
Инициализация:
const times = buildTimeOptions(15);
const startSelect = new TomSelect("#time-start", {
options: times,
placeholder: "Начало"
});
const endSelect = new TomSelect("#time-end", {
options: times,
placeholder: "Окончание"
});
Генерация списка времени с шагом:
function buildTimeOptions(step = 15) {
const list = [];
for (let i = 0; i < 24 * 60; i += step) {
const t = formatTime(i);
list.push({ value: t, text: t });
}
return list;
}
После разделения полей возникает необходимость в контроле
корректности выбора. Tom Select предоставляет события
change, которые удобно использовать для динамической
фильтрации доступных значений.
startSelect.on("change", (value) => {
const startMinutes = toMinutes(value);
endSelect.clearOptions();
const filtered = times.filter(t => toMinutes(t.value) > startMinutes);
endSelect.addOptions(filtered);
endSelect.refreshOptions(false);
});
Преобразование времени в минуты:
function toMinutes(time) {
const [h, m] = time.split(":").map(Number);
return h * 60 + m;
}
Такая схема гарантирует, что конечное время всегда больше начального, а список автоматически перестраивается при изменении первого селекта.
Tom Select поддерживает переопределение шаблонов отображения. Это позволяет визуально выделять диапазоны, добавлять подсказки или форматирование.
new TomSelect("#time-ranges", {
options: buildTimeRanges(60),
render: {
option: function(data, escape) {
return `
<div class="option-range">
<strong>${escape(data.text)}</strong>
</div>
`;
},
item: function(data, escape) {
return `<div class="selected-range">${escape(data.text)}</div>`;
}
}
});
Такой подход особенно полезен, если диапазоны имеют дополнительные метаданные: занятость, стоимость, ограничения.
При переходе от простых строк к объектной модели появляется возможность хранить дополнительные параметры:
{
value: "09:00-12:00",
start: "09:00",
end: "12:00",
capacity: 5,
disabled: false
}
Tom Select игнорирует неизвестные поля, но они доступны в логике приложения:
new TomSelect("#time-ranges", {
options: ranges,
render: {
option: function(data, escape) {
const status = data.capacity === 0 ? "нет мест" : `мест: ${data.capacity}`;
return `
<div>
<span>${escape(data.start)} — ${escape(data.end)}</span>
<small>${status}</small>
</div>
`;
}
}
});
При работе с расписаниями часто требуется исключать уже занятые периоды. Это достигается фильтрацией массива options перед обновлением Tom Select.
function excludeBusyRanges(ranges, busy) {
return ranges.filter(r => {
const start = toMinutes(r.start);
const end = toMinutes(r.end);
return !busy.some(b => {
const bStart = toMinutes(b.start);
const bEnd = toMinutes(b.end);
return start < bEnd && end > bStart;
});
});
}
Применение:
const busy = [
{ start: "10:00", end: "11:00" },
{ start: "14:00", end: "15:30" }
];
const available = excludeBusyRanges(ranges, busy);
select.clearOptions();
select.addOptions(available);
select.refreshOptions();
Tom Select поддерживает множественный выбор, что позволяет формировать расписание из нескольких интервалов:
new TomSelect("#time-ranges", {
options: buildTimeRanges(60),
maxItems: null,
plugins: ["remove_button"]
});
Каждый выбранный элемент остаётся независимым, что удобно для графиков смен, бронирований или планирования ресурсов.
При необходимости можно дополнительно проверять пересечения уже выбранных значений:
select.on("change", (values) => {
const parsed = values.map(v => {
const [start, end] = v.split("-");
return { start, end };
});
// проверка пересечений
});
В серверной части диапазоны обычно хранятся как отдельные поля, а не строка. Поэтому перед отправкой данные преобразуются:
function serializeRanges(values) {
return values.map(v => {
const [start, end] = v.split("-");
return { start, end };
});
}
И обратная операция при загрузке:
function deserializeRanges(data) {
return data.map(r => ({
value: `${r.start}-${r.end}`,
text: `${r.start} — ${r.end}`
}));
}
Если шаг интервалов уменьшается до 5–10 минут, количество options резко возрастает. В таких условиях важна оптимизация:
refreshOptions(false)select.addOptions(bigDataset);
select.refreshOptions(false);
Отдельные интервалы можно блокировать через поле
disabled:
{
value: "13:00-14:00",
text: "13:00 — 14:00",
disabled: true
}
Tom Select автоматически исключает такие элементы из выбора, но оставляет их видимыми, что важно для отображения занятости времени.