AVS
AVS

Reputation: 531

Knockout hasFocus binding with number input not working on Firefox

I have an input field that holds a currency value. I am trying to display formatted currency amount when the input field does not have focus. The input field type is set to number. While this works fine on Chrome, IE and Edge; on firefox, the input textbox loses focus immediately after getting it. Here is the simplified jsfiddle

https://jsfiddle.net/nkkfmLmc/2/

This is similar to example 2 at http://knockoutjs.com/documentation/hasfocus-binding.html, except for the number field. The problem is resolved if I change the input type to text from number (but also lose all the goodness). Any pointers or workarounds would be appreciated.

HTML:

<p>
    Name: 
    <b data-bind="visible: !editing(), text: '$'+amount(), click: edit">&nbsp;</b>
    <input type="number" min="0" max="100" step="1" data-bind="visible: editing, value: amount, hasFocus: editing" />
</p>
<p><em>Click the amount to edit it; click elsewhere to apply changes.</em></p>

JS:

function PersonViewModel(amount) {
    // Data
    this.amount = ko.observable(amount);
    this.editing = ko.observable(false);

    // Behaviors
    this.edit = function() { this.editing(true) }
}

ko.applyBindings(new PersonViewModel(51.22));

Upvotes: 1

Views: 1534

Answers (2)

AVS
AVS

Reputation: 531

Got it working by using an extra observable to control visibility and adding a throttle to the observable that controls focus. Looks like the hasFocus binding check was happening before the element had become visible.

https://jsfiddle.net/nkkfmLmc/8/

JS:

<p>
    Name: 
    <b data-bind="visible: !editingWrapper(), text: '$'+amount(), click: edit">&nbsp;</b>
    <input type="number" min="0" max="100" step="1" data-bind="visible: editingWrapper, value: amount, hasFocus: editing" />
</p>
<p><em>Click the amount to edit it; click elsewhere to apply changes.</em></p>

HTML:

function PersonViewModel(amount) {
    // Data
    var self = this;
    this.amount = ko.observable(amount);
    this.editing = ko.observable(false).extend({throttle:200});
    this.editingWrapper = ko.observable(false);

    // Behaviors
    this.edit = function() { self.editingWrapper(true);self.editing(true); }

    this.editing.subscribe(function(newVal){
        self.editingWrapper(newVal);
    })
}

ko.applyBindings(new PersonViewModel(51.22));

Upvotes: 0

user3297291
user3297291

Reputation: 23372

The hasFocus binding works with Document.activeElement to determine which element is in focus:

Source: https://github.com/knockout/knockout/blob/master/src/binding/defaultBindings/hasfocus.js#L17

var ownerDoc = element.ownerDocument;
if ("activeElement" in ownerDoc) {
  var active;
  try {
    active = ownerDoc.activeElement;
  } catch (e) {
    // IE9 throws if you access activeElement during page load (see issue #703)
    active = ownerDoc.body;
  }
  isFocused = (active === element);
}

When stepping through this binding in your fiddle, I noticed it returns the body element as the document's activeElement, making isFocused to be false.

On the MDN documentation page about activeElement, I then read:

Note: On Mac, elements that aren't text input elements tend not to get focus assigned to them.

Which, to me, suggests there's simply no support for this binding in a number input.

I'd suggest to either:

  • stick with the text input and add custom up and down buttons and key listeners
  • create a custom binding that's based on event listeners rather than document.activeElement.

The knockout developers probably had a good reason to stick with the activeElement approach. In their comments they state:

// Where possible, ignore which event was raised and determine focus state using activeElement,
// as this avoids phantom focus/blur events raised when changing tabs in modern browsers.
// However, not all KO-targeted browsers (Firefox 2) support activeElement. For those browsers,
// prevent a loss of focus when changing tabs/windows by setting a flag that prevents hasfocus
// from calling 'blur()' on the element when it loses focus.
// Discussion at https://github.com/SteveSanderson/knockout/pull/352

So it might be wise to go for the first option...

Upvotes: 2

Related Questions