简体   繁体   中英

Custom Knockout binding not working

I am currently using an event binding to format telephone numbers (into xxx-xxx-xxxx format) and I want to create a reusable custom binding to this for future use in our app. The current event binding works perfectly but I cannot get the custom binding to work correctly. Can anyone take a look below and tell me my issue?

Current event binding with viewModel method:

<input class="form-control" id="Phone"  type="text" 
       data-bind="event: {blur: formatPhone}, enable: isInputMode, value: Phone" />

self.Phone = ko.observable(model.MainPhone).extend({ maxLength: 20 });

self.formatMainPhone = function() {
        var tempString = self.Phone().replace(/\D+/g, "").replace(/^[01]/, "").replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3").substring(0, 12);
        self.Phone(tempString);
    }

Custom binding handler that does not work:

<input class="form-control max225" id="Phone" type="text" 
           data-bind="formatPhoneNumber: Phone, enable: isInputMode, value: Phone" />

self.Phone = ko.observable(model.MainPhone).extend({ maxLength: 20 });

ko.bindingHandlers.formatPhoneNumber = {
        update: function (element, valueAccessor) {            
            var phone = ko.utils.unwrapObservable(valueAccessor());
            var formatPhone = function () {
                return phone.replace(/\D+/g, "").replace(/^[01]/, "").replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3").substring(0, 11);
            }
            ko.bindingHandlers.value.update(element, formatPhone);
        }
    };

Your binding is attempting to hijack the update of the default "value" binding, which looking at the knockout source code, appears to have been deprecated.

'update': function() {} // Keep for backwards compatibility with code that may have wrapped value binding

You'll have to change your binding so that it uses init instead.

ko.bindingHandlers.value.init(element, formatPhone, allBindings);

EDIT:

This might be closer to what you want. Instead of using the update binding this creates an intermediary computed observable and then uses value.init to bind the textbox to that. We never need the update binding because the computed will take care of propagating changes for you.

ko.bindingHandlers.formatPhoneNumber = {
    init: function (element, valueAccessor, allBindings) {            
      var source = valueAccessor();      
      var formatter = function(){
        return ko.computed({
          read: function(){ return source(); },
          write: function(newValue){
            source(newValue.replace(/\D+/g, "").replace(/^[01]/, "").replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3").substring(0, 12));
          }
        })
      };

      ko.bindingHandlers.value.init(element, formatter, allBindings);
    }
  };

EDIT 2 - more explanation

Using the update binding for formatPhoneNumber tells knockout to run that code any time the value changes. That sounds good in theory, but lets break it down.

  1. Unwraps the accessor and gets the flat value.
  2. Creates a format function that returns a formatted value.
  3. piggyback's the value binding using the format function.

Because this is an update binding unwrapping the accessor in step 1 creates a trigger to re-evaluate the binding whenever the accessor value changes. Then in step 3 you're telling knockout to re-execute the value.update binding which currently is just an empty function and does nothing. If you change that to use value.init instead things might actually function for formatting the output, but you're telling knockout to re-initialize the init binding every time the value changes.

update: function(element, valueAccessor, allBindings) {
    var phone = ko.utils.unwrapObservable(valueAccessor());
    var formatPhone = function() { return phone.replace(...)}
    ko.bindingHandlers.value.init(element, formatPhone, allBindings);
}

The binding is getting re-created and passed new initial values. This also means that it's only a one-way binding because changes to the front-end can't make it back to your model to update the backing observable.

Now if you change your own binding to an init binding, and from there call the value.init binding as well it will only get initialized the one time, but the next problem is that the function you're binding to isn't going to know when to update.

init: function(element, valueAccessor, allBindings) {
    var phone = ko.utils.unwrapObservable(valueAccessor());
    var formatPhone = function() { return phone.replace(...)}
    ko.bindingHandlers.value.init(element, formatPhone, allBindings);
}

Since it's just a normal js function which is being passed an already-unwrapped flat value it will always always give the same result based on that original value of phone. Passing the value.init binding a computed observable instead ensures that updates to the accessor observable trigger the format function to update from within the existing binding.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM