3

I am developing a table editor with Ember.js. I created a view called FocusedTextField that focuses the text field when it is rendered. I want to implement a tabbing ability such that hitting tab will focus the next cell in a row. The current code will change the currently selected cell to be uneditable and change the next cell to be a text field, but will not focus on the next field's value. It seems that the code not working is an effect of timing. What's a better way of approaching this problem?

Here's my JSBin: http://emberjs.jsbin.com/tey/12/edit

jQuery 1.10.2 Handlebars 1.2.1 Ember 1.1.2

HTML:

<script type="text/x-handlebars" data-template-name="row">
    <tr>
        {{#collection cellCollection}}
            {{view view.content.view}}
        {{/collection}}
    </tr>
</script>

<script type="text/x-handlebars" data-template-name="cell">
    <td {{action "click"}}>
        {{#if editMode}}
            {{view textField valueBinding=value}}
        {{else}}
            {{value}}
        {{/if}}
    </td>
</script>

<script type="text/x-handlebars" data-template-name="table">
    <table>
        <tr>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Points</th>
        </tr>

        {{#collection rowCollection}}
            {{view view.content.view}}
        {{/collection}}
    </table>
</script>

<div id="main"></div>

JavaScript:

App = Ember.Application.create();

var TemplatedViewController = Ember.Object.extend({
    templateFunction: null,
    viewArgs: null,
    viewBaseClass: Ember.View,
    view: function () {
        var controller = this;
        var viewArgs = this.get('viewArgs') || {};
        var args = {
            template: controller.get('templateFunction'),
            controller: controller
        };
        args = $.extend(viewArgs, args);
        return this.get('viewBaseClass').extend(args);
    }.property('templateFunction', 'viewArgs'),
    appendView: function (selector) {
        this.get('view').create().appendTo(selector);
    },
    appendViewToBody: function () {
        this.get('view').create().append();
    },
    appendPropertyViewToBody: function (property) {
        this.get(property).create().append();
    }
});

var FocusedTextField = Ember.TextField.extend({
    focusTheTextField: function() {
        this.$().focus();
    }.on('didInsertElement')
});

var Cell = TemplatedViewController.extend({
    row: null,
    editMode: false,
    value: null,
    textField: function () {
        var cell = this;
        return FocusedTextField.extend({
            keyDown: function (event) {
                // Hitting the enter key disables edit mode
                if (event.keyCode === 13) {
                    cell.set('editMode', false);
                // Hitting the tab key selects the next cell
                } else if (event.keyCode === 9) {
                    cell.set('editMode', false);
                    var nextCell = cell.getNextCell();
                    nextCell.set('editMode', true);
                }
            }
        });
    }.property(),
    flipEditMode: function () {
        if (this.get('editMode')) {
            this.set('editMode', false);
        } else {
            this.set('editMode', true);
        }
    },
    click: function () {
        console.log('cell clicked, value: '+this.get('value'));
        this.flipEditMode();
    },
    getNextCell: function () {
        return this.get('row').getNextCell(this);
    },
    view: function () {
        var controller = this;
        return this.get('viewBaseClass').extend({
            controller: controller,
            templateName: 'cell'
        });
    }.property()
});

var Row = TemplatedViewController.extend({
    headers: ['firstName', 'lastName', 'points'],
    firstName: null,
    lastName: null,
    points: null,
    cells: null,
    cellCollection: function () {
        return Ember.CollectionView.extend({
            content: this.get('cells')
        });
    }.property('cells'),
    init: function () {
        this._super();
        var row = this;
        var cells = [];
        this.get('headers').forEach(function (item, index, enumerable) {
            var header = item;
            var value = row.get(header);
            var cell = Cell.create({
                row: row,
                value: value
            });
            cells.pushObject(cell);
        });
        this.set('cells', cells);
    },
    getNextCell: function (cell) {
        if (this.get('cells').contains(cell)) {
            var lastIndex = this.get('cells').length - 1;
            var cellIndex = this.get('cells').indexOf(cell);
            if (cellIndex < lastIndex) {
                var nextIndex = cellIndex + 1;
                return this.get('cells')[nextIndex];
            }
        }
    },
    view: function () {
        var controller = this;
        return this.get('viewBaseClass').extend({
            controller: controller,
            templateName: 'row'
        });
    }.property()
});

var rows = [];

var DATA = [
    {first_name: 'Jill', last_name: 'Smith', points: 50},
    {first_name: 'Eve', last_name: 'Jackson', points: 94},
    {first_name: 'John', last_name: 'Doe', points: 80},
    {first_name: 'Adam', last_name: 'Johnson', points: 67}
];

DATA.forEach(function (item, index, enumerable) {
    var row = Row.create({
        firstName: item.first_name,
        lastName: item.last_name,
        points: item.points
    });
    rows.pushObject(row);
});

var Table = TemplatedViewController.extend({
    view: function () {
        var controller = this;
        return this.get('viewBaseClass').extend({
            controller: controller,
            templateName: 'table'
        });
    }.property(),
    rows: null,
    rowCollection: function () {
        return Ember.CollectionView.extend({
            content: this.get('rows')
        });
    }.property('rows')
});

var table = Table.create({rows: rows});

$(function () {
    table.appendView('#main');
});
hekevintran
  • 22,822
  • 32
  • 111
  • 180

1 Answers1

3

Wrap the call to focus() in Ember.run.next like so:

var FocusedTextField = Ember.TextField.extend({
    focusTheTextField: function() {
      var self = this;
      Ember.run.next( function() { self.$().focus(); });
    }.on('didInsertElement')
});

For a description of Ember.run.next, see: http://emberjs.com/api/classes/Ember.run.html#method_next A good description of the Ember run loop: What is Ember RunLoop and how does it work?

Community
  • 1
  • 1
chopper
  • 6,649
  • 7
  • 36
  • 53
  • That's closer. http://emberjs.jsbin.com/tey/14/edit Now the problem is that on the first click the text is not selected. Pressing tab will select the text in the first field. – hekevintran Feb 21 '14 at 00:30
  • My bad - I'd forgotten something in my code (`this`->`self` in the lambda function). See my edited answer – chopper Feb 21 '14 at 01:00
  • The function can also be called like `Ember.run.next(this, function () {this.$().focus();});`. Is there a good description of what `Ember.run.next()` does anywhere? – hekevintran Feb 21 '14 at 01:21