Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Select() Input Field with Knockout.js

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!

like image 607
Quang Van Avatar asked Sep 06 '12 07:09

Quang Van


2 Answers

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/

like image 118
John Earles Avatar answered Jan 04 '23 11:01

John Earles


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.

like image 40
wasatz Avatar answered Jan 04 '23 12:01

wasatz