Библиотека Choices.js построена вокруг класса Choices,
инкапсулирующего состояние списка, выбранных значений, элементов
интерфейса и событийную модель. Добавление собственных методов к
экземпляру требует понимания того, как именно создаётся объект и где
допустимо расширение поведения без нарушения внутренней логики.
Существует несколько устойчивых подходов к расширению функциональности: обёртка класса, наследование, прототипное расширение и композиция через фабрики. Каждый подход имеет свои ограничения в части совместимости с обновлениями библиотеки и контроля состояния.
Наиболее безопасный способ добавления кастомных методов — создание
функции-обёртки, возвращающей расширенный экземпляр
Choices.
Идея заключается в том, чтобы не изменять оригинальный класс, а добавлять поверх него дополнительные методы:
import Choices from 'choices.js';
function createExtendedChoices(element, options = {}) {
const instance = new Choices(element, options);
instance.clearAll = function () {
const currentValue = this.getValue(true);
if (!Array.isArray(currentValue)) return;
currentValue.forEach(value => {
this.removeActiveItemsByValue(value);
});
};
instance.selectByLabel = function (label) {
const choices = this._store.choices;
const found = choices.find(item => item.label === label);
if (found) {
this.setChoiceByValue(found.value);
}
};
return instance;
}
Такой подход изолирует расширения от внутренней реализации библиотеки
и позволяет безопасно обновлять Choices.js без риска
сломать цепочку наследования.
При необходимости глубокой интеграции с внутренним состоянием используется наследование.
import Choices from 'choices.js';
class ExtendedChoices extends Choices {
constructor(element, options) {
super(element, options);
}
clearAll() {
const values = this.getValue(true);
if (Array.isArray(values)) {
values.forEach(v => this.removeActiveItemsByValue(v));
}
}
toggleValue(value) {
const current = this.getValue(true);
if (current.includes(value)) {
this.removeActiveItemsByValue(value);
} else {
this.setChoiceByValue(value);
}
}
}
Преимущество данного подхода — прямой доступ к защищённым методам и
внутренним структурам экземпляра. Недостаток — высокая связанность с
конкретной версией библиотеки, поскольку изменения внутри
Choices.js могут повлиять на поведение наследника.
В случаях, когда требуется глобальное добавление функциональности ко всем экземплярам, используется модификация прототипа.
import Choices from 'choices.js';
Choices.prototype.clearAll = function () {
const values = this.getValue(true);
if (!Array.isArray(values)) return;
values.forEach(value => {
this.removeActiveItemsByValue(value);
});
};
Choices.prototype.selectFirst = function () {
const choices = this._store.choices;
if (choices.length > 0) {
this.setChoiceByValue(choices[0].value);
}
};
Данный подход изменяет поведение всей системы, что делает его потенциально опасным при использовании в больших приложениях или при совместной работе нескольких модулей.
Более управляемая альтернатива monkey patching — композиция через функцию расширения.
function extendChoices(instance) {
instance.clearAll = function () {
const values = this.getValue(true);
if (Array.isArray(values)) {
values.forEach(v => this.removeActiveItemsByValue(v));
}
};
instance.syncWithExternal = function (externalList) {
const current = this.getValue(true);
externalList.forEach(item => {
if (!current.includes(item)) {
this.setChoiceByValue(item);
}
});
};
return instance;
}
Использование:
const choices = extendChoices(new Choices(element, options));
Такой способ сохраняет контроль над тем, какие методы добавлены, и позволяет централизованно управлять расширениями.
Многие кастомные методы требуют доступа к внутреннему хранилищу
_store. Эта структура содержит:
choices)Пример метода, работающего с внутренним store:
instance.findByPrefix = function (prefix) {
return this._store.choices.filter(item =>
item.label.toLowerCase().startsWith(prefix.toLowerCase())
);
};
Использование внутренних структур увеличивает гибкость, но создаёт зависимость от внутренней реализации библиотеки, которая не гарантируется как стабильная.
instance.clearAllSafe = function () {
if (this.config.maxItemCount === 1) {
this.removeActiveItems();
return;
}
const values = this.getValue(true);
values.forEach(v => this.removeActiveItemsByValue(v));
};
instance.toggleItem = function (value) {
const selected = this.getValue(true);
if (selected.includes(value)) {
this.removeActiveItemsByValue(value);
} else {
this.setChoiceByValue(value);
}
};
instance.selectByRegex = function (regex) {
const pattern = new RegExp(regex);
const matches = this._store.choices.filter(item =>
pattern.test(item.label)
);
matches.forEach(match => {
this.setChoiceByValue(match.value);
});
};
instance.syncFromArray = function (data) {
this.clearStore?.();
data.forEach(item => {
this.setChoices([
{
value: item.value,
label: item.label,
selected: item.selected || false
}
]);
});
};
При масштабных приложениях кастомные методы часто выносятся в отдельный слой расширений:
export function attachCustomMethods(instance) {
instance.resetAll = function () {
this.removeActiveItems();
};
instance.hasValue = function (value) {
return this.getValue(true).includes(value);
};
instance.selectMany = function (values) {
values.forEach(v => this.setChoiceByValue(v));
};
return instance;
}
Такой слой позволяет поддерживать единый стандарт расширений и переиспользовать их в разных частях приложения.
При добавлении кастомных методов критически важно учитывать возможные конфликты с будущими версиями библиотеки. Используется префиксация:
instance.ext_clearAll = function () {
const values = this.getValue(true);
values.forEach(v => this.removeActiveItemsByValue(v));
};
Или использование namespace-объекта:
instance.custom = {
clearAll: () => {},
toggle: () => {},
selectByRegex: () => {}
};
Второй вариант уменьшает риск пересечения с внутренними API и упрощает поддержку.
Многие расширения требуют интеграции с системой событий Choices.js:
instance.clearAndNotify = function () {
this.removeActiveItems();
this.passedElement.triggerEvent('custom:cleared', {
timestamp: Date.now()
});
};
Использование событий позволяет интегрировать кастомные методы в общий жизненный цикл компонента без изменения ядра библиотеки.
При работе с кастомными методами ключевым ограничением остаётся
отсутствие гарантий стабильности внутренних структур _store
и приватных методов. Любое расширение должно учитывать:
Наиболее устойчивыми считаются методы, опирающиеся только на
публичный API: setChoiceByValue, getValue,
removeActiveItems, setChoices.