- Upon page load, focus shifts to the order number input field
- After scanning a order number, focus moves to the product barcode input
- Following barcode scanning, focus remains on the barcode input
- Once all barocdes are scanned, focus returns to the order number input
To address such scenarios, I developed a Vue directive named vue-auto-focus. You can find it on GitHub at vue-auto-focus, and contributions are welcome.
Example Usage
<template>
<form v-auto-focus="focusControl" :data-current="activeIndex" :data-action="actionType">
<input @focus="handleFocus(0)" type="text" data-index="0">
<input @focus="handleFocus(1)" type="text" data-index="1">
<textarea @focus="handleFocus(2)" name="" id="" cols="30" rows="10" data-index="2"></textarea>
<input @focus="handleFocus(3)" type="text" data-index="3">
</form>
</template>
<script>
export default {
data() {
return {
focusControl: 0,
activeIndex: 0,
actionType: 'next',
}
},
methods: {
/**
* Triggers the auto-focus directive
* @param actionType {string} Type of focus action: 'next'/'prev'/'first'/'last'/'jump'
* @param index {string} Target index when actionType is 'jump'
**/
setFocus(actionType, index) {
if (actionType === 'jump') {
this.activeIndex = index
}
this.focusControl++
this.actionType = actionType
},
/**
* Updates the active index when an element gains focus
* @param index {number} Index of the focused element
**/
handleFocus(index) {
this.activeIndex = index
},
}
}
</script>
Supported Actions
- next: Move focus to the next element
- prev: Move focus to the previous element
- first: Move focus to the first element
- last: Move focus to the last element
- jump: Move focus to a specific element
Focus Control Logic
/**
* Handles focus actions based on directive parameters
* @param el {Element} The element where the directive is applied
*/
const handleFocusAction = function (el) {
const action = el.dataset.action
const focusElements = getAllFocusElements(el)
const totalElements = focusElements.length
let currentIndex = getActiveIndex(el, focusElements)
switch (action) {
case 'next':
if (currentIndex <= totalElements - 1) {
currentIndex++
}
focusElements[currentIndex].focus()
break
case 'prev':
if (currentIndex > 0) {
currentIndex--
}
focusElements[currentIndex].focus()
break
case 'first':
currentIndex = 0
focusElements[currentIndex].focus()
break
case 'last':
currentIndex = totalElements - 1
focusElements[currentIndex].focus()
break
case 'jump':
if (currentIndex >= 0 && currentIndex < totalElements) {
focusElements[currentIndex].focus()
}
break
}
}
Each focusable element must have a data-index attribute. The parent container requires data-action and data-current attributes to define the focus behavior and current position respectively.
/**
* Retrieves all focusable elements with data-index attribute
* @param parentElement {Element} The directive's container element
* @returns {NodeList} List of focusable elements
*/
const getAllFocusElements = function (parentElement) {
return parentElement.querySelectorAll('[data-index]')
}
/**
* Determines the index of the currently focused element
* @param el {Element} Directive container
* @param elements {Array} List of focusable elements
* @returns {number} Index of the active element
*/
const getActiveIndex = function(el, elements) {
const targetElement = document.querySelector(`[data-index="${el.dataset.current}"]`)
return Array.from(elements).indexOf(targetElement)
}
Directive Lifecycle Hooks
Inserted Hook
Automatically focuses on the designated element upon component mounting.
inserted: function (el) {
const focusElements = getAllFocusElements(el)
let currentIndex = getActiveIndex(el, focusElements)
if (currentIndex < 0 || currentIndex >= focusElements.length) {
currentIndex = 0
}
focusElements[currentIndex].focus()
},
Update Hook
Executes focus changes whenever the directive's value updates.
update: function (el, binding) {
if (binding.value !== binding.oldValue) {
handleFocusAction(el)
}
},