Home > Back-end >  How to access refs for a nested v-for in Vue 3 Typescript Composition API
How to access refs for a nested v-for in Vue 3 Typescript Composition API

Time:04-15

I'm trying to convert some Vue2 to Vue3 Composition API using Typescript. Refs don't seem to work the same way and I'm not sure how to get it work in Vue3. In Vue 2 it was pretty straightforward this.$refs[docForm.value.fields[i].name][0].focus()

I'm not able to just use the method described in the Docs where you just put the ref on the v-for and access it via the index because When I need to access it, I'll only know the name of the field, and not the index order (long story).

<div v-for="(p, i) in docForm.pages">
    <div v-for="(f, i) in p.fields">
        <input type="text" :name="f.name" :ref="f.name" />
    </div>
</div>
const goToNextEmptyField = (): void => {
    if (docForm.value) {
        for (var i = 0; i < docForm.value.fields.length; i  ) {
            if (docForm.value.fields[i].value == null || docForm.value.fields[i].value == '') {
               refs[docForm.value.fields[i].name][0].focus() //THIS LINE NEEDS FIXING - WORKED IN Vue2
               return
            }
        }
    }
}

The structure of the docForm object is this (simplified):

export interface DocForm {
    pages: DocFormPageModel[]
    fields: DocFieldModel[]
}

export interface DocFormPageModel {
    fields: DocFormField[]
    ...
}

export interface DocFieldModel {
    name: string
    ....
}

CodePudding user response:

In Vue 3 you need to declare refs.

Try this:

import { ref } from 'vue'

const inputs = ref([]);
// This is a guess at TS implementation
// const inputs : Ref<HTML element[]> = ref([]);

const goToNextEmptyField = (): void => {
    if (docForm.value) {
        for (var i = 0; i < docForm.value.fields.length; i  ) {
            if (docForm.value.fields[i].value == null || docForm.value.fields[i].value == '') {
               inputs.value[docForm.value.fields[i].name][0].focus()
               return
            }
        }
    }
}

What this is doing is initialising a reactive object which is populated when the refs have been detected.

Please also note that you always need to reference a ref by its value property in the script (not in the template).

This is explained in the Vue docs. https://vuejs.org/guide/essentials/template-refs.html#refs-inside-v-for

Make sure you hit the "Composition API" toggle switch!

CodePudding user response:

Danial Storey put me on the right path and here's the working solution:

<script setup lang="ts">
import { ref, onMounted } from 'vue'
  
interface DocForm {
    pages: DocFormPageModel[]
    fields: DocFieldModel[]
}

interface DocFormPageModel {
    fields: DocFormField[]
}

interface DocFieldModel {
    name: string
    value: string
}

const docForm = ref<DocForm>(
  {pages: [
    { fields: [
      { name: "test1", value: "1", masterFieldIndex: 0 },
      { name: "test2", value: "", masterFieldIndex: 1 },
      { name: "test3", value: "", masterFieldIndex: 2 },
    ]}
  ], fields: [
    { name: "test1", value: "1", masterFieldIndex: 0 },
    { name: "test2", value: "", masterFieldIndex: 1 },
    { name: "test3", value: "", masterFieldIndex: 2 },
  ]}
)

const pageRefs = ref<HtmlElement[]>([])
const fieldRefs = ref<HtmlElement[]>([])

const goToNextEmptyField = (): void => {
    if (docForm.value) {
        for (var i = 0; i < docForm.value.fields.length; i  ) {
            if (docForm.value.fields[i].value == null || docForm.value.fields[i].value == '') {
               var inputToFocus = fieldRefs.value.filter((f)=>{ 
                   return f.children[0].name == docForm.value.fields[i].name
               })
               inputToFocus[0].children[docForm.value.fields[i].name].focus()
               return
            }
        }
    }
}
</script>

<template>
    <div v-for="p in docForm.pages">
      <div v-for="f in p.fields" ref="fieldRefs">
        <input type="text" v-model="docForm.fields[f.masterFieldIndex].value" :name="f.name" />
      </div>
    </div><br />
  <button @click="goToNextEmptyField">
    Go to on First Empty Field
  </button>
</template>

You can paste the above code here to run it.

  • Related