26

I want to execute an event on a knockout observable bound to an input. This function should be executed when the control lose focus, even without typing anything. I tried to change the event binding but it doesn't fire when the user moves away from the control without typing anything. I tried mouseout event, but that only fires when the user clicks elsewhere in the form, after losing focus - not exactly what I want. I want the even to fire as soon as the focus is moved away from the control, even with tab.

Following is the code I used for mouseout event:

<input
    type="text"
    id="txtFirstName"
    tabindex="1"
    maxlength="25"
    class="txtbox" 
    style="width: 200px;"
    data-bind="value: FirstName, 
               attr: {title: FirstNameErrorMessage },
               css: {validationFailed: !IsValidFirstName() },
               event: {mouseout: ValidateFirstName}" 
/>

this.ValidateFirstName = function () {
    self.IsValidFirstName(true);
    self.FirstNameErrorMessage('');
    if (self.FirstName() == '') {
        self.IsValidFirstName(false);
        self.FirstNameErrorMessage('First Name is required');
    }
}

Can anyone help please?

Gab
  • 5,604
  • 6
  • 36
  • 52
devC
  • 1,384
  • 5
  • 32
  • 56

6 Answers6

36

I think that there are a few approaches that you could use. A nice option would be to use KO's hasfocus binding: http://knockoutjs.com/documentation/hasfocus-binding.html.

You can bind against a boolean observable, and then subscribe to it. In the subscription, you can choose to only react when the value is now false.

Something like:

self.FirstName = ko.observable();
self.FirstName.focused = ko.observable();

self.FirstName.focused.subscribe(function(newValue) {
   if (!newValue) {
       //do validation logic here and set any validation observables as necessary
   }
});

Bind against it like:

data-bind="value: FirstName, hasfocus: FirstName.focused"

I think that this would be a good option if you want it to fire everytime a user leaves the field no matter how they leave it and regardless of whether a change was actually made.

Oleg Grishko
  • 4,132
  • 2
  • 38
  • 52
RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • +1: OMG I've been chopping and hacking at a snippet of code that I'd written very nearly exactly like this. I couldn't for the life of me figure out what on earth was different that would work in your answer that I hadn't written myself. I had `data-bind="text` instead of `data-bind="value` for an input. Thanks for adding the binding statement in your answer. I needed that kick in the pants today apparently. – Joel Etherton Jun 16 '14 at 13:42
  • @JoelEtherton - I haven't definitely done that a time or two myself. Glad you figured it out. – RP Niemeyer Jun 16 '14 at 13:43
  • I have tried this technique. The problem, for me, is that hasFocus always seems to "fire" (turn to true) on the initial page load, which puts everything in the wrong state for what I'm trying to do. I initialize the corresponding viewmodel value to false, but this didn't change anything. – Dave Munger Jul 08 '14 at 23:31
  • Whoops, just figured out that ANY focus change - including losing focus - will cause the bound value to change. So I just need to interrogate newValue to see if focus is true or false. The fact that it has an event name in it ('focus') sort of made me forget how observables work. :) – Dave Munger Jul 08 '14 at 23:55
  • I'm having issues with this approach. I think it's because I have multiple input fields binding to the same observable. What happens is the thing gets fired like four times with newValue toggling back and forth between true and false. Gonna just use jquery blur. – Sean May 22 '15 at 01:04
  • If fires on initial page load also, does anyone has a solution for this? thanks – Yaron Feb 02 '18 at 10:44
23

This worked for me:

data-bind="event: { blur: OnBlurEvent }"
arntjw
  • 803
  • 7
  • 12
  • 3
    If this is all you want, I dare say this is the best way. I wonder what @rp-niemeyer thinks about this though. Surely this approach crossed his mind? – aaaidan Oct 15 '14 at 05:00
22

I like @RPNiemeyer's answer. However, I just wanted to point out that not everything has to be done via Knockout. It's just a tool, and sometimes it's not the best tool for the job. You an always just use direct event binding like you've always done in JS, i.e.

$('#FirstName').on('blur', function () {
    // do something
});

If you need to actually interact with your view model in there, then you can simply use ko.dataFor as described in the Knockout's documentation on event delegation:

$('#FirstName').on('blur', function () {
    var data = ko.dataFor(this);
    // do something with data, i.e. data.FirstName()
});
Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
6

I just had the same problem, solved it by creating a custom binding:

ko.bindingHandlers.modifyOnFocusOut = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        $(element).blur(function() {
            //Do your work
        });
    }
}

And then called it like this:

 data-bind="value: FirstName, modifyOnFocusOut: FirstName"
jfc37
  • 199
  • 4
  • 11
5

Have you tried event:{blur: ValidateFirstName} this event will be fired if the user click out or 'tab out' of the input.

<input
    type="text"
    id="txtFirstName"
    tabindex="1"
    maxlength="25"
    class="txtbox" 
    style="width: 200px;"
    data-bind="value: FirstName, 
               attr: {title: FirstNameErrorMessage},
               css: {validationFailed: !IsValidFirstName()},
               event: {blur: ValidateFirstName}"

Here's a JSFiddle of a working example.

Gab
  • 5,604
  • 6
  • 36
  • 52
0

Using TypeScript I solved it using 2 custom bindings, a SetFocusBinding and a OnBlur Binding... Using the SetFocusBinding I make sure the input field has focus. Using the OnBlur binding a function is called when the blur event is triggered.

module Fx.Ko.Bindings {
    export class SetFocusBinding implements KnockoutBindingHandler {
        public update(element, valueAccessor, allBindingsAccessor) {
            var value = valueAccessor();
            var valueUnwrapped = ko.unwrap(value);
            if (valueUnwrapped == undefined) {
                return;
            }
            if (valueUnwrapped)
                $(element).focus();
        }
    }
}

and ...

    module Fx.Ko.Bindings {
        export class OnBlurBinding implements KnockoutBindingHandler {
            public init(element, valueAccessor, allBindings, viewModel, bindingContext) {
                var value = valueAccessor();
                $(element).on('blur', function (event) {
                    value();
                });
            }
        }
    }
interface KnockoutBindingHandlers {
    onBlur: KnockoutBindingHandler;
}
ko.bindingHandlers.onBlur = new Fx.Ko.Bindings.OnBlurBinding();
Paul0515
  • 23,515
  • 9
  • 32
  • 47