Создание простых пользовательских привязок

Knockout.js предоставляет мощный механизм привязки данных между моделью представления (ViewModel) и пользовательским интерфейсом. Помимо стандартных встроенных привязок, таких как text, value или visible, фреймворк позволяет создавать пользовательские привязки, которые расширяют функциональность и упрощают повторное использование кода.

Пользовательская привязка представляет собой объект с определёнными методами, который регистрируется через ko.bindingHandlers.

ko.bindingHandlers.highlight = {
    init: function(element, valueAccessor) {
        const value = ko.unwrap(valueAccessor());
        element.style.backgroundColor = value ? 'yellow' : 'transparent';
    },
    update: function(element, valueAccessor) {
        const value = ko.unwrap(valueAccessor());
        element.style.backgroundColor = value ? 'yellow' : 'transparent';
    }
};

В данном примере создаётся привязка highlight, которая окрашивает фон элемента в жёлтый, если значение истинно.


Методы пользовательской привязки

init — вызывается один раз при инициализации элемента. Здесь выполняется настройка, установка обработчиков событий или начальная модификация DOM.

update — вызывается каждый раз при изменении привязанного значения. Этот метод гарантирует реактивное обновление интерфейса в соответствии с текущими данными модели.

Привязки могут использовать только update, если начальная настройка не требуется:

ko.bindingHandlers.uppercase = {
    update: function(element, valueAccessor) {
        const value = ko.unwrap(valueAccessor());
        element.textContent = value.toUpperCase();
    }
};

Получение и обработка данных привязки

Метод valueAccessor возвращает функцию, которая возвращает текущее привязанное значение. Для доступа к его реальному значению используется ko.unwrap, позволяющий работать как с обычными значениями, так и с observables.

const value = ko.unwrap(valueAccessor());

Если привязка должна обрабатывать несколько параметров, можно передавать объект:

<div data-bind="fadeVisible: { show: isVisible, duration: 500 }"></div>
ko.bindingHandlers.fadeVisible = {
    update: function(element, valueAccessor) {
        const options = ko.unwrap(valueAccessor());
        if (options.show) {
            $(element).fadeIn(options.duration);
        } else {
            $(element).fadeOut(options.duration);
        }
    }
};

Контекст и дочерние элементы

Внутри пользовательских привязок доступен контекст данных через allBindings, bindingContext и element.

  • allBindings — объект всех привязок текущего элемента.
  • bindingContext — полный контекст, включая родительские модели.
  • element — DOM-элемент, к которому применяется привязка.

Пример использования контекста:

ko.bindingHandlers.showParentName = {
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        element.textContent = bindingContext.$parent.name();
    }
};

Обработка событий в привязках

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

ko.bindingHandlers.clickLogger = {
    init: function(element) {
        element.addEventListener('click', function() {
            console.log('Элемент был нажат');
        });
    }
};

Если обработчик зависит от данных модели, используется valueAccessor:

ko.bindingHandlers.clickAction = {
    init: function(element, valueAccessor) {
        element.addEventListener('click', function() {
            valueAccessor()(); // вызываем функцию из модели
        });
    }
};

Поддержка подписок на observables

Knockout автоматически отслеживает observables внутри update. Если привязанное значение изменится, метод update будет вызван повторно:

ko.bindingHandlers.showDate = {
    update: function(element, valueAccessor) {
        const date = ko.unwrap(valueAccessor());
        element.textContent = date.toLocaleDateString();
    }
};

const viewModel = {
    currentDate: ko.observable(new Date())
};

Каждое обновление currentDate автоматически обновит отображение.


Расширение существующих привязок

Можно создавать привязки, которые добавляют функциональность к стандартным. Например, модифицируем text для форматирования чисел:

ko.bindingHandlers.formattedText = {
    update: function(element, valueAccessor) {
        const value = ko.unwrap(valueAccessor());
        element.textContent = value.toLocaleString();
    }
};

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

<span data-bind="formattedText: price"></span>

Практические рекомендации

  • init используется для одноразовой настройки элемента и подписки на события.
  • update отвечает за реактивное изменение DOM в ответ на изменения модели.
  • Для безопасного доступа к observables всегда использовать ko.unwrap.
  • Передача параметров через объект повышает гибкость привязки.
  • Подписки на родительские данные доступны через bindingContext.$parent и $root.

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