Select() Input Field with Knockout.js
Asked Answered
E

2

10

I am new to Knockout.js.

What is the best way to select() an <input /> when it becomes visible?

View:

<p>
    Name: 
    <b data-bind="visible: !editing(), text: name, click: edit">&nbsp;</b>
    <input data-bind="visible: editing, value: name, hasfocus: editing" />
</p>

ViewModel:

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

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

ko.applyBindings(new PersonViewModel("Bert Bertington"));

http://knockoutjs.com/documentation/hasfocus-binding.html

http://jsfiddle.net/RnCUd/

Thanks!

Ethel answered 6/9, 2012 at 7:19 Comment(0)
F
20

You can create a new binding to handle selection.

ko.bindingHandlers.selected = {
    update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var selected = ko.utils.unwrapObservable(valueAccessor());
        if (selected) element.select();
    }
};

Add this binding to your input field.

<input data-bind="visible: editing, value: name, hasfocus: editing, selected: editing" />

Here is a fiddle: http://jsfiddle.net/RnCUd/2/


Alternatively, you could create a custom binding which wraps the hasfocus binding:

ko.bindingHandlers.hasSelectedFocus = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        ko.bindingHandlers['hasfocus'].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
    },        
    update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        ko.bindingHandlers['hasfocus'].update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);        

        var selected = ko.utils.unwrapObservable(valueAccessor());
        if (selected) element.select();
    }
};

This binding simply delegates initialization and update to hasfocus and takes care of selecting the element if the observable is true. Use it instead of hasfocus.

<input data-bind="visible: editing, value: name, hasSelectedFocus: editing" />

Here is a fiddle: http://jsfiddle.net/RnCUd/1/

Filling answered 6/9, 2012 at 10:5 Comment(3)
thanks for fast answer :) is using custom bindings too much at this point? Perhaps that is the best, but I had thought custom bindings was advance... is there a "good enough" easier way that doesn't use custom bindings? Or is custom bindings no big deal and I should learn how to use them early & often? (Note: I was never one to make custom jQuery selectors either) :)Ethel
@QuangVan I would urge you to get to know custom bindings. When the problem crosses the line between your data and the DOM and can't be solved by the standard bindings, then custom bindings are the right choice. I would say that you should feel free to "use them early & often" as you said. They are a powerful tool that need not be last resort.Litt
okay yeh, I had the mindset of it being "last resort"... Thank you :)Ethel
E
1

I attempted to use John Earles custom binding above (Thanks John!) together with a text field that also used a valueUpdate: 'afterkeydown' binding and found out that that didn't really work as expected. (I'm guessing that this is because all bindings fire again when one of the bindings need to fire, and valueUpdate most likely causes the value binding to fire after each character has been written).

After a couple of attempts I did a semi-fix for this issue that seems to be working alright for me. The basic idea is that before we fire the hasfocus binding we check if the element in question already has focus and we only actually select the text when the element didn't actually have focus before the hasfocus binding has fired.

I have used jquery to check for focus, but you could probably do it in some other way as well.

ko.bindingHandlers.hasSelectedFocus = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        ko.bindingHandlers['hasfocus'].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var focusBefore = $(element).is(':focus');
        ko.bindingHandlers['hasfocus'].update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);

        var selected = ko.utils.unwrapObservable(valueAccessor());
        if (selected && !focusBefore) {
            element.select();   
        }
    }
};

Edit: I've noticed that this kind of binding might not work exactly as you want when used on an iOS device. There's nothing wrong with the binding as such, but the autofocus and select logic causes the devices keyboard to come up as soon as the binding executes which may or may not be exactly what you want to happen on such a device. To compare, on the android devices that I use to test I do not automatically get the keyboard as soon as this binding executes. For my sake I ended up creating yet another binding to do nothing on iOS devices in the following way.

ko.bindingHandlers.hasNonIosSelectedFocus = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        if (navigator.userAgent.match(/iPad/i) == null && navigator.platform.indexOf("iPhone") == -1 && navigator.platform.indexOf("iPod") == -1) {
            ko.bindingHandlers['hasSelectedFocus'].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
        }
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        if (navigator.userAgent.match(/iPad/i) == null && navigator.platform.indexOf("iPhone") == -1 && navigator.platform.indexOf("iPod") == -1) {
            ko.bindingHandlers['hasSelectedFocus'].update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
        }
    }
};

Tl;dr:

If you use this and want to cater to tablets/smartphones then be sure to test that this is the interaction logic that you actually expect.

Educational answered 4/10, 2013 at 13:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.