Студия разработки сайтов и приложений

Netspark.ru

Заметки и разработки

Drupal

Vue.js и установка фокуса

Довольно часто встречается задача реализации ин-плейс-редактирования. То есть когда кликнули например два раза на пункт списка, и текст тут же заменился редактором, мы его поменяли, нажали enter, сохранили.

Сделать это во Vue.js несложно: рядом с каждым редактируемым пунктом выводится инпут, и в v-show указываются противоположные условия. А по двойному клику значение условия изменяется. Например:

<template>
<!-- ... -->
    <ul>
        <li v-for="(item, index) in items" :key="item.id">
            <div @dblclick="editTitleMode(item)">
                <div v-show="!item.edit_mode">{{ item.title }}</div>
                <div v-show="item.edit_mode">
                    <input v-model="item.title" @keyup.enter="saveItemTitle(item)" type="text" />
                </div>
            </div>
        </li>
    </ul>
</template>
<script>
    export default {
        data() {
            return {
                items: []
            }
        },
        methods {
            editTitleMode: function (item) {
                this.$set(item, 'edit_mode', true);
            },
            saveItemTitle: function (item) {
                // Do the saving
                item.edit_mode = false;
            }
        }
    }
</script>

И что еще хотелось бы сделать в этой ситуации — так это каждый раз, когда появляется инпут для редактирования, автоматически ставить на него фокус. В принципе, с этим можно легко справиться с помощью jQuery, зная, какой инпут сейчас будет показан. Но когда работаешь с Vue.js, каждое обращение к jQuery в коде кажется малодушием. И в данном случае прекрасно можно обойтись без jQuery.

У компонентов Vue.js есть массив this.$refs, содержащий элементы с заданным атрибутом ref. Тогда найти нужный элемент и поставить на него фокус можно вот так:

<template>
<!-- ... -->
    <ul>
        <li v-for="(item, index) in items" :key="item.id">
            <div @dblclick="editTitleMode(item)">
                <div v-show="!item.edit_mode">{{ item.title }}</div>
                <div v-show="item.edit_mode">
                    <input :ref="'input_item_' + item.id" v-model="item.title" @keyup.enter="saveItemTitle(item)" type="text" />
                </div>
            </div>
        </li>
    </ul>
</template>
<script>
    export default {
        // ...
        methods {
            editTitleMode: function (item) {
                this.$set(item, 'edit_mode', true);
                this.$refs['input_item_' + item.id][0].focus();
            },
            saveItemTitle: function (item) {
                // Do the saving
                item.edit_mode = false;
            }
        }
    }
</script>

Правда, если добавить этот код в метод, по которому показывается инпут, установки фокуса не произойдет. Всё дело в том, что в момент вызова метода editTitleMode(), значение item.edit_mode, используемое в v-show уже изменилось, но Vue.js еще не выполнил цикл проверки реактивных переменных и соответствующие действия. То есть, сам инпут все еще невидим и не может получить фокус.

Чтобы заработало, нужно ставить фокус позже, после цикла проверки, с помощью метода nextTick() вот так:

editTitleMode: function (item) {
    this.$set(item, 'edit_mode', true);
    this.$nextTick(() => {
        this.$refs['input_item_' + item.id][0].focus();
    });
}

В качестве бонуса — поставим курсор в конец текста в инпуте так, чтобы его (курсор) было видно:

editTitleMode: function (item) {
    this.$set(item, 'edit_mode', true);
    this.$nextTick(() => {
        let element = this.$refs['input_item_' + item.id][0];
        let position = element.value.length;
        element.focus();
        if (element.setSelectionRange) {
            element.setSelectionRange(position, position);
        } else if (element.createTextRange) {
            let range = element.createTextRange();
            range.collapse(true);
            range.moveEnd('character', position);
            range.moveStart('character', position);
            range.select();
        }
    });
}

Комментарии