Hi and welcome to the first article of my Vue serie. This serie will hopefully bring another way of breaking down Vue for developers in order to get started on grasping more and more complex Vue Components.
Our Goal
This article will offer a way to create the BreadCrumb Input of our dream and allow it to reuse it inside of your project.
When searching for it this was the closest example I could think of hence the name:
This way of input is useful in situation where you have to want to display to the user an input which depends on the previous one.
We will demonstrate it using a very simple example. In our case we want to find the closest retailer for our wood supply.
Coding it
I will try to maintain all of the code of my vue serie inside of this repository.
Basic Layout
We will present this as a basic form with this layout:
<script setup>
// BreadCrumbsInput.vue
</script>
<template>
<div class="form">
<label for="country">Country</label>
<select id="country">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
<label for="city">City</label>
<select>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
<label for="neighborhood">City</label>
<select>
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
</div>
</template>
<style scoped>
.form {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 1rem;
}
</style>
Make it smarter (just a little)
Our basis is set but now we want to actually select precisely.
I want to select just a country or a city in a country or even a neighborhood in a city.
We have to be careful because I don't want to select a city not present in a country.
I like to put my constant in a different place to focus on my code. I create a constant/country.ts
export const countryList = ["USA", "Canada", "Mexico"];
export const cityPerCountry = {
USA: ["New York", "Los Angeles", "Chicago"],
Canada: ["Toronto", "Vancouver", "Montreal"],
Mexico: ["Mexico City", "Guadalajara", "Monterrey"],
};
export const neighborhoodPerCity = {
"New York": ["Manhattan", "Brooklyn", "Queens"],
"Los Angeles": ["Hollywood", "Venice", "Beverly Hills"],
"Chicago": ["The Loop", "Lincoln Park", "Wicker Park"],
Toronto: ["Downtown", "Scarborough", "Etobicoke"],
Vancouver: ["West End", "Gastown", "Yaletown"],
Montreal: ["Old Montreal", "Downtown", "Plateau-Mont-Royal"],
"Mexico City": ["Polanco", "Condesa", "Roma"],
Guadalajara: ["Zapopan", "Tlaquepaque", "Tonala"],
Monterrey: ["San Pedro Garza Garcia", "Cumbres", "Centro"],
};
Remember my 3 selectors let's make useful. We start by creating 3 refs corresponding to my needs:
<script setup>
//BreadCrumbsInput.vue
import { ref } from "vue";
import { countryList, cityPerCountry, neighborhoodPerCity } from "../constants/countries";
let country = ref("");
let city = ref("");
let neighborhood = ref("");
</script>
Now let's leverage vue directives to clean up the html, we will use v-model to bind the changes to our ref and v-for our country to make it more sustainable:
<template>
<select id="country" v-model="country">
<option v-for="country in countryList" :key="country">{{ country }}</option>
</select>
<template>
Now it should look like this :
Making it reusable
It's great but like this we don't really know what he have selected and we want to display it inside our App.vue component for using it somewhere outside of the components.
Here comes the not new but recently working defineModel
directive. It allow you to use v-model inside of your custom component.
This means :
Removable of update
Automatic binding of ref
Less code overall
<script setup>
//BreadCrumbsInput.vue
const model = defineModel({ type: String })
...
</script>
As simple as that, now when we will modify the value of value of the model inside a function it will automatically update the vairable we set when we will call our component like so in out App.vue
<script setup>
//App.vue
import TableInput from "./components/TableInput.vue";
import { ref } from "vue";
const myModel = ref("");
</script>
<template>
<h1>Vue Series</h1>
<TableInput/>
Your selection : {{ myModel }}
<TableInput v-model="myModel"/>
</template>
There is one last step is to create our function to update our model, we will add on the @change
event of our select
input:
<select v-model="country" id="country" @input="updateValue($event,'country')">
<option v-for="country in countryList" :key="country">{{ country }}</option>
</select>
our update value takes in parameters the $event and the event type to know which select has been changed. So our updateValue function looks like this:
P.S: As I am writing this article I figured I could use find the select id through the $event hence reducing the number of parameters and cleaner code.
const updateValue = (e, type ) => {
switch(type) {
case "country":
country.value = e.target.value;
resetUnselected(["city","neighborhood"]);
break;
case "city":
city.value = e.target.value;
resetUnselected(["neighborhood"]);
break;
case "neighborhood":
neighborhood.value = e.target.value;
break;
}
model.value = e.target.value;
}
// resets the inputs
const resetUnselected = ( types : string[]) => {
for (let type of types) {
switch(type) {
case "country":
city.value = "";
neighborhood.value = "";
break;
case "city":
neighborhood.value = "";
break;
}
}
}
I also added a v-if directive to not show the next input as I thought it looks cleaner and user understand what is their selection and Voilà:
Enjoy !
I hope this article has been helpful to you. Reach out to me if you have questions, leave a ❤️ if you liked the article and see you next time !