Dean Friedland
Dean Friedland

Reputation: 803

Custom Knockout binding not working correctly

I created a computed observable that works well formatting phone numbers. It will take a string of numbers and strip it down to this format xxx-xxx-xxxx when focus is off the form field. then I a have a controller action stripping it down to format xxxxxxxxxx before it persists to the database. Then my computed observable re-formats it to the xxx-xxx-xxxx format.

I now want the to create a reusable custom binding handler that can be implemented across our application. The problem is that I cannot get it to do the last part where it re-formats it back in the form field. So, the issue is when I click update, the form field displays the number as xxxxxxxxxx (the same way as it is in the DB) Does anyone know what I need to change to make by custom binding work like my current computed observable?

Observable:

self.Phone = ko.observable(model.MainPhone ? model.MainPhone : "").extend({ maxLength: 20, minLength: 10 });

Computed Observable working correctly:

self.PhoneFormat = ko.computed(function () {
   var phoneFormatting = self.Phone()
       .replace(/\D+/g, "")
       .replace(/^[01]/, "")
       .replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3")
       .substring(0, 12);
   return self.Phone() ? self.Phone(phoneFormatting) : "";
}, self);

Custom binding not working correctly:

ko.bindingHandlers.formatPhone = {
    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);
    },

    update: 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.update(element, formatter, allBindings);
    }
};

Upvotes: 0

Views: 134

Answers (2)

Dean Friedland
Dean Friedland

Reputation: 803

I am not sure why this works but the custom binding did not. :(

HTML:

<input data-bind="value: Phone" />

KO Observable:

self.Phone = ko.observable(model.Phone ? model.Phone : "").trimmed();

KO Subscribable:

ko.subscribable.fn.trimmed = function () {
        return ko.computed({
            read: function () {
                return this().replace(/\D+/g, "")
                               .replace(/^[01]/, "")
                               .replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3")
                               .substring(0, 12);
            },
            write: function (value) {
                this(value.replace(/\D+/g, "")
                               .replace(/^[01]/, "")
                               .replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3")
                               .substring(0, 12));
                this.valueHasMutated();
            },
            owner: this
        }).extend({ notify: 'always' });
    };

Upvotes: 0

user3297291
user3297291

Reputation: 23372

It feels like the read and write should be swapped around...

I'd say you want your "source" data to be just the numbers. The formatted data is mainly for display purposes.

I'd expect a computed layer added in your binding, that does two things:

  • When writing, remove the formating before putting it in the source
  • When reading, add in the correct format

I'm not sure if I broke stuff that you had working previously, but it could be something like:

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

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

var vm = { phoneNr: ko.observable("3456546512") };

ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Via custom binding:
<input data-bind="formatPhone: phoneNr">
<br/>
Mimic external update:
<input data-bind="textInput: phoneNr">

Upvotes: 1

Related Questions