3

In KO 3.0.0.beta (and I am almost sure it'd be the same in 2.3) I am trying to add new row to a dynamically created table:

HTML

<div id="root">
    <table>
        <tbody data-bind="foreach: events">
            <tr>
                <td>
                    <input type="text" data-type="name" data-bind="value: name, css: {hidden: !editing()}, hasFocus: true">
                </td>
                <td>
                    <input type="text" data-type="method" data-bind="value: age, css: {hidden: !editing()}">
                </td>
            </tr>
        </tbody>
    </table>
</div>

JavaScript

var $root = $('#root');

$root.on('blur', 'table input', function(event) {
    var data = ko.dataFor(event.target), 
        context = ko.contextFor(event.target),
        $input = $(event.target), 
        newData;

    data.editing(false);

    if($input.closest('td').is(':last-child') && $input.closest('tr').is(':last-child')) {
        newData = {
            name: '', 
            age: '', 
            editing: ko.observable(false)
        };

        context.$root.events.push(newData);
        newData.editing(true);  
    }
});

var data = {                
    events: [
        {name: 'aaa', age: '22', editing: false},
        {name: 'bbb', age: '33', editing: false},
        {name: 'ccc', age: '44', editing: false}
    ]
};

var mapping = {
    events: {
        key: function(data) {
            return ko.unwrap(data.name);
        }
    }
};

var observable = ko.mapping.fromJS(data, mapping);

ko.applyBindings(observable, $root[0]);

JSFiddle

and it almost works.

I am successful with row creation - which was the siple part, but for the life of me I can't make the first input in the created raw to be focused.

Any ideas (and I went through a ton of suggestions, none of which worked in the above setting)?

ZenMaster
  • 12,363
  • 5
  • 36
  • 59
  • 2
    You´re mixing jQuery and KO, its doomed to fail. Add a observable isFocused, bind it against hasFocus binding – Anders Oct 23 '13 at 07:28
  • 1
    Second @Anders. You should turn that into an answer. It may not be what the OP's after, but it's the cleanest solution to the actual problem, and besides: others may find it helpful too. – Jeroen Oct 23 '13 at 07:32
  • @Anders jquery is for events binding. Totally orthogonal. I am open to suggestions so please DO make this an answer. And I tried with isFocused. Couldn't get it to work. – ZenMaster Oct 23 '13 at 07:46

2 Answers2

0

I might be missing the point, but why not just set your hasFocus to editing() instead of true?

<div id="root">
    <table>
        <tbody data-bind="foreach: events">
            <tr>
                <td>
                    <input type="text" data-type="name" data-bind="value: name, css: {hidden: !editing()}, hasFocus: editing()">
                </td>
                <td>
                    <input type="text" data-type="method" data-bind="value: age, css: {hidden: !editing()}">
                </td>
            </tr>
        </tbody>
    </table>
</div>

Fiddle here

Jacques Snyman
  • 4,115
  • 1
  • 29
  • 49
0

It often pays of to avoid mixing jQuery with Knockout like that. For most cases Knockout also has options. In your case, the ones to look at are the event binding and the hasFocus binding.

Looking at your code, I'd update the View to something like this:

<tr>
    <td>
        <input type="text" data-bind="value: name, 
                                      hasFocus: editingName">
    </td>
    <td>
        <input type="text" data-bind="value: age, 
                                      hasFocus: editingAge, 
                                      event: { blur: $root.onBlurAgeEdit }">
    </td>
</tr>

As an aside, if you're after "MS Word Style" creation of new rows when a user presses tab, you can also bind to the "keypress" event and check for the correct keycode (for tab).

Notice that I've split the editing observable in two observables: editingName and editingAge.

The function onBlurAgeEdit will fire whenever the age input blurs. It looks like this:

self.onBlurAgeEdit = function (item) {
    // Unwrap the array
    var myEvents = self.events();

    // Check if this function was called for the *last* item in the array
    if (myEvents[myEvents.length-1] === item) {

        // Make sure the last item isn't in edit mode anymore
        item.editingAge(false);

        // Push a new item that has *name* in edit mode
        self.events.push({name: ko.observable(''),
                          age: ko.observable(''),
                          editingName: ko.observable(true),
                          editingAge: ko.observable(false)});
    }
};

Note that for Firefox you'll need to throttle the observables to make the hasFocus binding play nice.

See all this at work in this fiddle.

Community
  • 1
  • 1
Jeroen
  • 60,696
  • 40
  • 206
  • 339
  • I must be explaining it really bad. Yours does **exactly** what my original does. And it's not good. After pressing tab on the last visible input - a new row should be added and the first input in that row should be **focused** and ready to type. Also, you can see that I used `hasFocus` in the code above. – ZenMaster Oct 23 '13 at 18:21
  • The correct behavior *does* occur in IE10 for me. To my surprise Webkit didn't work [until I let bits of the onBlur function run on its own thread](http://jsfiddle.net/hDCa4/2/). I didn't have time to get it to work on Firefox though, a straight up throttle extend didn't seem to work. For more info you may need to follow mentioned link and investigate some more. – Jeroen Oct 23 '13 at 19:44