Архитектура библиотеки Pikaday построена вокруг конструктора
Pikaday, экземпляры которого наследуют поведение через
прототип. Это позволяет модифицировать и дополнять функциональность без
изменения исходного кода библиотеки. Подход основан на прототипном
наследовании JavaScript и часто используется для внедрения кастомных
методов, переопределения поведения и создания расширяемых календарных
компонентов.
Расширение прототипа применяется в случаях, когда требуется унифицировать поведение всех экземпляров календаря, внедрить общие утилиты или интегрировать библиотеку в сложную UI-систему без дублирования логики.
Каждый экземпляр создаётся через конструктор:
const picker = new Pikaday({
field: document.querySelector('#date'),
format: 'YYYY-MM-DD'
});
Внутренне методы доступны через Pikaday.prototype.
Именно этот объект определяет поведение всех инстансов:
show,
hide)gotoDate, nextMonth,
prevMonth)draw, render)onSelect, onOpen,
onClose)Любое изменение Pikaday.prototype влияет на все
созданные и будущие экземпляры.
Расширение прототипа чаще всего заключается в добавлении новых методов, которые не предусмотрены стандартным API.
Pikaday.prototype.getMonthRange = function () {
const start = new Date(this._d.getFullYear(), this._d.getMonth(), 1);
const end = new Date(this._d.getFullYear(), this._d.getMonth() + 1, 0);
return { start, end };
};
Теперь любой экземпляр получает доступ к новому поведению:
const range = picker.getMonthRange();
Метод опирается на внутреннее состояние _d, которое
представляет текущую дату календаря.
Прототип можно использовать для изменения стандартного поведения. Этот подход требует аккуратности, поскольку влияет на всю систему отображения.
showconst originalShow = Pikaday.prototype.show;
Pikaday.prototype.show = function () {
console.log('Календарь открывается');
originalShow.call(this);
this.el.classList.add('custom-open-state');
};
Здесь выполняются три действия:
Метод draw отвечает за построение DOM-структуры
календаря. Его переопределение используется для добавления нестандартных
элементов интерфейса.
const originalDraw = Pikaday.prototype.draw;
Pikaday.prototype.draw = function (force) {
originalDraw.call(this, force);
const caption = this.el.querySelector('.pika-title');
if (!caption) return;
const label = document.createElement('div');
label.className = 'pika-month-label';
label.textContent = 'Активный месяц';
caption.appendChild(label);
};
Такой подход позволяет внедрять визуальные элементы без изменения исходных шаблонов библиотеки.
Pikaday предоставляет события жизненного цикла: открытие, закрытие, выбор даты. Прототип можно расширить логикой глобальных обработчиков.
const originalOnSelect = Pikaday.prototype._onInputChange;
Pikaday.prototype._onInputChange = function (e) {
document.dispatchEvent(new CustomEvent('pikaday:input-change', {
detail: {
value: e.target.value
}
}));
return originalOnSelect.call(this, e);
};
Таким образом создаётся дополнительный слой событий, доступный всей системе приложения.
Расширение прототипа часто используется для упрощения управления датами.
Pikaday.prototype.gotoStartOfYear = function () {
const year = this.getDate().getFullYear();
const date = new Date(year, 0, 1);
this.setDate(date);
this.gotoDate(date);
};
Метод объединяет несколько внутренних операций:
Механизм ограничения дат (minDate, maxDate)
можно расширить дополнительными правилами через прототип.
Pikaday.prototype._isWeekend = function (date) {
const day = date.getDay();
return day === 0 || day === 6;
};
const originalIsSelectable = Pikaday.prototype._isSelectable;
Pikaday.prototype._isSelectable = function (date) {
if (this._isWeekend(date)) {
return false;
}
return originalIsSelectable.call(this, date);
};
Здесь добавляется новая бизнес-логика поверх существующей проверки.
При сложной кастомизации календаря возможно добавление кэш-слоя через прототип.
Pikaday.prototype._cachedDraw = function (year, month) {
const key = `${year}-${month}`;
if (!this._drawCache) {
this._drawCache = {};
}
if (this._drawCache[key]) {
return this._drawCache[key];
}
const result = this._renderMonth(year, month);
this._drawCache[key] = result;
return result;
};
Подобная техника снижает количество повторных вычислений при навигации по месяцам.
Прямое изменение прототипа может приводить к конфликтам. Альтернативный подход — декорирование методов.
function decorateMethod(obj, methodName, decorator) {
const original = obj[methodName];
obj[methodName] = function (...args) {
return decorator.call(this, original.bind(this), ...args);
};
}
Использование:
decorateMethod(Pikaday.prototype, 'show', function (original) {
console.log('before show');
const result = original();
console.log('after show');
return result;
});
Такой подход позволяет избежать потери оригинальной логики и упрощает откат изменений.
Расширение прототипа часто опирается на приватные поля
(_d, _o, _el). Их использование
увеличивает гибкость, но требует согласованности с версией
библиотеки.
Pikaday.prototype.getState = function () {
return {
selectedDate: this.getDate(),
viewDate: this._d,
options: this._o
};
};
Метод объединяет внутренние данные в единый объект состояния.
При масштабной кастомизации расширения прототипа группируются в модули.
function extendPikaday() {
Pikaday.prototype.getYear = function () {
return this.getDate().getFullYear();
};
Pikaday.prototype.getMonthIndex = function () {
return this.getDate().getMonth();
};
}
extendPikaday();
Такой подход упрощает управление расширениями и предотвращает засорение глобального пространства прототипа.
При наличии нескольких расширений одного метода важно учитывать порядок подключения. Последнее определение в цепочке прототипа перезаписывает предыдущее.
Pikaday.prototype.show = function () {
console.log('A');
};
Pikaday.prototype.show = function () {
console.log('B');
};
Результатом выполнения будет только B, поскольку
предыдущее определение заменяется.
Для предотвращения конфликтов используется создание обёрток над конструктором.
function createExtendedPikaday(options) {
const picker = new Pikaday(options);
picker.logState = function () {
console.log(this.getState());
};
return picker;
}
Такой подход ограничивает область влияния расширений конкретным экземпляром, не затрагивая глобальный прототип.
Расширение прототипа Pikaday обычно комбинируется с несколькими архитектурными паттернами:
Каждый подход выбирается исходя из требований к изоляции, повторному использованию и совместимости с будущими обновлениями библиотеки.