Home > database >  How to change text inputs into tags, in an existing Vue 3 form, using a Tag Input Component?
How to change text inputs into tags, in an existing Vue 3 form, using a Tag Input Component?

Time:10-05

I have an existing form in a Vue 3 project that posts successfully to a Cloud Firestore database.

How do I use change two of the type="text" input fields into tag inputs, using a Tag Input Component like this? Here's a link to it on CodeSandbox

The standalone tutorial is very clear and works fine. What I'm struggling with is how to incorporate that into an existing Vue 3 form and continue posting everything to the Firestore database.

I stripped-away styles and many other input fields from the "base" code below:

<template>
  
<form @submit.prevent="handleSubmit" >

    <h3>What kind of pet?</h3>
    <div class="space-y-0">
        <select required name="pet_type" id="pet_type" v-model="pet_type">
          <option value="">Please select</option>
          <option value="cat">Cat</option>
          <option value="dog">Dog</option>
          <option value="wolverine">Wolverine</option>
        </select>
      </div>

      <hr>

      <div> <!-- Want to change this into the first tag input -->
        <h3>Pros</h3>
        <div for="petPros">What are 3 positive things about this pet?</div>
        <input type="text" placeholder="separate , with , commas" id="petPros" v-model="petPros">
        <label for="petPros">separate , with , commas</label>
      </div>

      <hr>

     <div> 
        <h3>Cons</h3>  <!-- Want to change this into a second tag input -->
        <div for="petCons">And what are 3 negative things about this pet?</div>
        <input type="text" placeholder="separate , with , commas" id="petPros" v-model="petCons">
        <label for="petCons">separate , with , commas</label>
      </div>

      <hr>


<h3>Privacy</h3>
      
      <div>
          <div>
            <input type="radio" id="Fullpublic" value="Fullpublic" name="reviewPrivacy" required v-model="reviewPrivacy">
            <label class="text-base inline-flex ml-4 align-bottom" for="Fullpublic">Public</label>
          </div>

          <div>
            <input type="radio" id="keepFullyPrivate" value="keepFullyPrivate" name="reviewPrivacy" required v-model="reviewPrivacy">
            <label class="text-base inline-flex ml-4 align-bottom" for="keepFullyPrivate">Private</label>
          </div>
      </div>

      <hr>

      <button v-if="!isPending" >Submit</button>
      <button v-else disabled>Saving...</button>
    </form>

</template>

<script>
import { ref } from 'vue'
import useStorage from '@/composables/useStorage'
import useCollection from '@/composables/useCollection'

export default {
    setup() {

    const { filePath, url, uploadImage } = useStorage()
    const { error, addDoc } = useCollection('reviews')
    const { user } = getUser()
    const router = useRouter()

    const pet_type = ref('')
    const petPros = ref('')
    const petCons = ref('')
    const reviewPrivacy = ref('')


    const file = ref(null)
    const fileError = ref(null)
    const isPending = ref(false)

    const handleSubmit = async () => {
      if (file.value) {
        isPending.value = true
        await uploadImage(file.value)
        const res = await addDoc({
          pet_type: pet_type.value,
          petPros: petPros.value,
          petCons: petCons.value,
          reviewPrivacy: reviewPrivacy.value,

          userId: user.value.uid,
          userName: user.value.displayName,

          createdAt: timestamp()
        })
        isPending.value = false
        if (!error.value) {
          router.push({ name: 'ReviewDetails', params: { id: res.id }})
        }
      }
    }

    // allowed file types
    const types = ['image/png', 'image/jpeg']

    const handleChange = (e) => {
      let selected = e.target.files[0]
      console.log(selected)

      if (selected && types.includes(selected.type)) {
        file.value = selected
        fileError.value = null
      } else {
        file.value = null
        fileError.value = 'Please select an image file (png, jpg or jpeg)'
      }
    }
    
    return { pet_type, petPros, petCons, reviewPrivacy, handleSubmit, fileError, handleChange, isPending }  
  }
}
</script>


<style>

</style>

Thanks for any help!

CodePudding user response:

  1. In the form, replace the two <input type="text">s with <TagInput>. Keep the v-model the same, as TabInput implements v-model.

    <!-- BEFORE -->
    <input type="text" placeholder="separate , with , commas" id="petPros" v-model="petPros">
    <input type="text" placeholder="separate , with , commas" id="petPros" v-model="petCons">
    
    <!-- AFTER -->
    <TagInput id="petPros" v-model="petPros" />
    <TagInput id="petCons" v-model="petCons" />
    
  2. Locally register TagInput.vue in the form component:

    import TagInput from "./TagInput.vue";
    
    export default {
      components: {
        TagInput,
      },
    }
    
  3. Change the initial values of petPros and petCons refs to arrays, as the TabInput outputs a string array of the input tag values:

    // BEFORE
    const petPros = ref('')
    const petCons = ref('')
    
    // AFTER
    const petPros = ref([])
    const petCons = ref([])
    
  4. TabInput normally adds a tag upon hitting TAB, but your placeholder suggests that you want to use the comma key , instead. To do that, update the modifier in the @keydown binding:

    <!-- BEFORE -->
    <input v-model="newTag"
       @keydown.prevent.tab="addTag(newTag)"
    >
    
    <!-- AFTER -->
    <input v-model="newTag"
       @keydown.prevent.,="addTag(newTag)"
    >
    

demo

  • Related