Knockout JS: работа со старым кодом проверки, который изменяет DOM

См. редактирование внизу.

У моей компании огромная кодовая база, и мы хотим начать использовать нокаут более эффективно. Однако у нас уже есть код проверки, который заботится обо всех аспектах проверки на стороне клиента. Он использует jQuery для отображения сообщений об ошибках проверки и для очистки пользовательского ввода.

Например, если я добавлю класс «validate-range» к входным данным, он будет использовать события jQuery change/focusout для отслеживания изменений, а затем, если значение выходит за пределы диапазона, оно заменит его значением min/max, используя $(ввод).val(). Поскольку этот код проверки вносит изменения таким образом программно, моя модель нокаутирующего представления не будет обновляться при внесении таких изменений.

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

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

Я надеялся, что кто-то сталкивался с этой проблемой раньше, когда ему приходилось работать с нокаутом с существующим кодом javascript, который изменяет значения DOM и, следовательно, не обновляет модель представления. Любая помощь приветствуется.

Код Javascript для пользовательской привязки, создания модели представления и старого кода проверки:

// custom value binding for amounts
ko.bindingHandlers.amountValue = {
  init: function (element, valueAccessor) {
    var underlyingObservable = valueAccessor(),

        interceptor = ko.computed({
          read: function () {
            var value = underlyingObservable();
            return formatAmount(value);
          },

          write: function (newValue) {
            var current = underlyingObservable(),
                valueToWrite = parseAmount(newValue);

            if (valueToWrite !== current)
              underlyingObservable(valueToWrite);
            else if (newValue !== current.toString())
              underlyingObservable.valueHasMutated();
          }
        });

    // i apply a change event handler when applying the bindings which calls the write function of the interceptor.
    // the intention is to have the change handler be called anytime the old validation code changes an input box's value via 
    // $(input).val("new value"); In the case of the foreach binding, whenever the observable array changes, and the table rows
    // are re-rendered, this code does get ran when re-applying the bindings, however the change handler doesn't get called when values are changed.
    ko.applyBindingsToNode(element, { value: interceptor, event: { change: function () { interceptor($(element).val()); } } });
  }
};

// view model creation
// auto create ko view model from json sent from server
$(function () {
  viewModel = ko.mapping.fromJS(jsonModel);
  ko.applyBindings(viewModel);
});

// old validation code
$(document).on("focusout", ".validate-range", function () {
  var $element = $(this),
      val = $element.val(),
      min = $element.attr("data-val-range-min"),
      max = $element.attr("data-val-range-max");

  if (val < min)
    // my change handler from custom binding doesn't fire after this to update view model
    $element.val(min);

  if (val > max)
    // my change handler from custom binding doesn't fire after this to update view model
    $element.val(max);

  // more code to show error message
});

Код HTML, который использует пользовательскую привязку внутри привязки foreach:

<table>
  <thead>
    <tr>
      <td>Payment Amount</td>
    </tr>
  </thead>
  <tbody data-bind="foreach: Payments">
    <tr>
      <td><input type="text" class="validate-range" data-val-range-min="0" data-val-range-max="9999999" data-bind="amountValue: Amount" /></td>
    </tr>
  </tbody>
</table>

Таким образом, в приведенном выше примере, если я ввожу «-155» в текстовое поле суммы, моя пользовательская привязка запускается и устанавливает сумму модели представления на -155. Затем запускается старая проверка и повторно устанавливается значение текстового поля на «0» с помощью $(input).val(0). Моя модель представления не обновляется на данный момент и по-прежнему отражает значение -155. Мой обработчик событий изменения из пользовательской привязки должен запускаться для обновления модели представления до 0, но это не так.

Редактировать:

Как указано в ответе, .val() не вызывает никаких событий изменения. Обработчик события изменения, который я добавил, ничего не сделал. Причина, по которой модель представления обновлялась, когда код проверки изменил значение вне привязки foreach, заключалась в том, что у нас была логика где-то еще в нашем коде javascript, которая вручную запускала событие изменения с использованием события размытия, которое, в свою очередь, запускало мою пользовательскую привязку. для запуска и обновления модели представления. Этот обработчик событий размытия был напрямую привязан к текстовым полям, а не делегирован, поэтому он работал для текстовых полей, которые были там при первом отображении страницы, но не для тех, которые были динамически вставлены привязкой foreach.

На данный момент я просто изменил эту логику, чтобы делегировать события внутри документа, чтобы он включал динамически вставляемые текстовые поля, и, похоже, он работает нормально. Я надеюсь придумать лучшее решение в будущем.


person wired_in    schedule 21.12.2012    source источник
comment
Не могли бы вы опубликовать пример пользовательского обработчика и образец кода модели/html, чтобы сделать его немного более конкретным?   -  person Matthew Cox    schedule 21.12.2012


Ответы (1)


Вызов $(element).val("some value"); не вызывает событие изменения.

Вам нужно будет сделать: $(element).val("some value").change();

person RP Niemeyer    schedule 21.12.2012
comment
Почему этот подход работает для всех моих полей, которые находятся за пределами привязки foreach? - person wired_in; 22.12.2012
comment
Редактировать: Нм... кажется, мой добавленный обработчик событий изменения бесполезен. Я его снял, и все работает так же. Для всех входных данных за пределами foreach модель представления обновляется, когда код проверки изменяет значение, и не обновляется для входных данных внутри foreach. - person wired_in; 22.12.2012
comment
Это все еще оставляет мне вопрос, почему модель представления обновляется, когда код проверки изменяет значение, используя $(input).val(какое-то значение). У меня сложилось впечатление, что если вы изменяете какие-либо значения программно без использования наблюдаемых, модель представления не будет обновляться. - person wired_in; 22.12.2012
comment
Можете ли вы получить что-то базовое, демонстрирующее вашу проблему в jsFiddle? Похоже, событие change чем-то вызвано ($.val его не вызывает). Одна вещь, которую следует отметить в отношении foreach, заключается в том, что он копирует исходные дочерние элементы в качестве шаблона. Если вы добавили к нему обработчики до того, как были применены привязки, то обработчики не будут работать. - person RP Niemeyer; 22.12.2012
comment
Правильно, поэтому я применил свои обработчики внутри пользовательской привязки в вызове applybindingstonode. Старый код проверки применяет свои обработчики с помощью $.on, поэтому он отслеживает элементы DOM, которые добавляются динамически. - person wired_in; 22.12.2012
comment
Ну, я понятия не имею, почему входные данные за пределами foreach обновляются, когда код проверки изменяет значение, потому что код проверки использует $.val, поэтому ничто не должно запускать мою пользовательскую привязку для обновления значения модели представления. - person wired_in; 22.12.2012
comment
Я должен идти прямо сейчас. Я вернусь к этому на следующей неделе. Спасибо за помощь - person wired_in; 22.12.2012
comment
Ты был прав. У нас была некоторая логика, которая должна исправить проблемы с текстовыми полями автозаполнения, в которых мы вручную запускаем событие изменения через событие размытия. Поскольку событие размытия прикрепляется непосредственно к элементам, а не делегируется, это событие размытия не привязано к каким-либо текстовым полям, добавленным позже с помощью нокаутирующей привязки foreach. Спасибо еще раз. - person wired_in; 26.12.2012