One place for hosting & domains

      How To Create User Interactions with Events in Vue


      The author selected Open Sourcing Mental Illness to receive a donation as part of the Write for DOnations program.

      Introduction

      In Vue.js development, a client’s web browser reads HTML and JavaScript and renders web pages based off of the instructions that the developer writes for it. But the web page or application not only needs to process data; it also needs to process user interactions. To do this, developers use events in JavaScript that execute code when the user interacts with HTML elements.

      An event can capture any user interaction with a user interface button or a physical keyboard or mouse. In JavaScript, you would create event listeners that wait for that event to occur and then execute a block of code. In Vue.js, you are not required to listen for an event; that is done automatically with the v-on: directive.

      In this tutorial, you will use events in Vue to create an application of airport codes. When the user selects an airport code, the app will add that airport to a “favorites” collection. By following along with this project, you will learn what events are, how to use Vue’s built-in events, and how to create your own custom events.

      Prerequisites

      To complete this tutorial, you will need:

      Step 1 — Setting Up the Project

      The first step in this tutorial will be to set up a demo project with some data to display in the view. This will include an array of JavaScript objects that contain airport data and a Vue component to iterate over and render the data.

      First, generate a project using Vue CLI:

      • vue create favorite-airports

      This will create a project named favorite-airports. This tutorial will use Vue 3, so when prompted, select the option Default (Vue 3) ([Vue 3] babel, eslint):

      Output

      Vue CLI v4.5.6 ? Please pick a preset: Default ([Vue 2] babel, eslint) ❯ Default (Vue 3) ([Vue 3] babel, eslint) Manually select features

      Once you have created the project, make a directory to hold all of your local data for this project. First, make the new project folder your working directory:

      Next, make a data directory in the src directory:

      In your text editor of choice, open a file called src/data/airports.js. Add the following data to the file:

      favorite-airports/src/data/airports.js

      export default [
        {
          name: 'Cincinnati/Northern Kentucky International Airport',
          abbreviation: 'CVG',
          city: 'Hebron',
          state: 'KY',
        },
        {
          name: 'Seattle-Tacoma International Airport',
          abbreviation: 'SEA',
          city: 'Seattle',
          state: 'WA',
        },
        {
          name: 'Minneapolis-Saint Paul International Airport',
          abbreviation: 'MSP',
          city: 'Bloomington',
          state: 'MN',
        },
        {
          name: 'Louis Armstrong New Orleans International Airport',
          abbreviation: 'MSY',
          city: 'New Orleans',
          state: 'LA',
        },
        {
          name: `Chicago O'hare International Airport`,
          abbreviation: 'ORD',
          city: 'Chicago',
          state: 'IL',
        },
        {
          name: `Miami International Airport`,
          abbreviation: 'MIA',
          city: 'Miami',
          state: 'FL',
        }
      ]
      

      This data is an array of objects consisting of a few airports in the United States. Next, you are going to iterate through this data to generate cards consisting of the name, abbreviation, city, and state properties. When the user clicks on a card, the app will emit an event up to the parent, which will add that airport to a collection of data that will represent your favorite airports.

      Save and close the airport.js file.

      To render the data, create a single-file component (SFC) with the name src/components/AirportCard.vue and open it in your text editor. This component will contain all of the styles and logic for the airport card.

      Add the following contents to the file:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      
      <script>
      export default {
        props: {
          airport: {
            type: Object,
            required: true
          }
        }
      }
      </script>
      
      <style scoped>
      .airport {
        border: 3px solid;
        border-radius: .5rem;
        padding: 1rem;
      }
      
      .airport p:first-child {
        font-weight: bold;
        font-size: 2.5rem;
        margin: 1rem 0;
      }
      
      .airport p:last-child {
        font-style: italic;
        font-size: .8rem;
      }
      </style>
      

      This component contains a prop, which in Vue.js is a way to pass data down from a parent component to a child component. The template section then renders this data. For more on single-file components, check out the How To Create Reusable Blocks of Code with Vue Single-File Components tutorial.

      You may notice that there is some CSS included in the code snippet. In the AirportCard.vue component, the wrapper <div> contains the class of airport. This CSS adds some styling to the generated HTML by adding borders to give each airport the appearance of a card. :first-child and :last-child are pseudo-selectors that apply different styling to the first and last p tags in the HTML inside of the div with the class of airport.

      Save the file and exit from your text editor.

      Next, modify the existing App.vue component to iterate through the airports.js data and render a series of AirportCards.vue components. Open src/App.vue in your text editor and replace the contents with the following highlighted code:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
        </div>
      </template>
      
      <script>
      import { ref } from 'vue'
      import allAirports from '@/data/airports.js'
      import AirportCard from '@/components/AirportCard.vue'
      
      export default {
        components: {
          AirportCard
        },
        setup() {
          const airports = ref(allAirports)
          return { airports }
        }
      }
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      
      .wrapper {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-column-gap: 1rem;
        max-width: 960px;
        margin: 0 auto;
      }
      </style>
      

      This imports the data and the SFC, then uses the v-for directive to iterate over the data, creating an airport card for each object in the airport.js array. It also adds additional CSS targeted to the wrapper class, which uses CSS grid to manage the layout of the cards.

      Save and exit the file. With the project now set up, run a local development server with the following command:

      This will start a server on your localhost, usually on port :8080. Open your web browser of choice and visit localhost:8080 to find the following:

      A view of the airport data rendered on cards, with the airport abbreviation, full name, and location rendered in black, sans-serif font.

      Now that you have your sample project set up, you’ll next explore built-in events using the v-on directive. When this event is fired, an alert pop-up box will appear with the airport code of the airport associated with that event.

      Step 2 — Listening for Events With the v-on Directive

      As stated earlier, events are a way to execute functions when the user interacts with HTML elements in the DOM (Document Object Model). When writing vanilla JavaScript, to execute a function on an event, you may write something called an event listener. An event listener is a function that waits for that interaction to occur, then executes some code. With Vue, however, you can use the v-on directive for this purpose. A directive is a piece of re-useable code that a developer can use in order to manipulate the DOM. The v-on directive is provided by Vue.js out of the box.

      In this step, you will create a function in your application that runs when a user clicks on a card. Open the src/components/AirportCard.vue component in your text editor of choice.

      Create a function that alerts the user of the airport that they clicked on by adding the following highlighted code:

      favorite-airports/src/components/AirportCard.vue

      ...
      <script>
      export default {
        props: {
          airport: {
            type: Object,
            required: true
          }
        },
        setup() {
          function selectAirport(airport) {
            alert(`You clicked on ${airport.abbreviation}. It's located in ${airport.city}, ${airport.state}.`)
          }
      
          return { selectAirport }
        }
      }
      </script>
      ...
      

      In Vue.js 3, reactive functions need to be defined and exported in the setup component method. This tells Vue that it can execute the selectAirport function in the <template>.

      With the function defined, you’ll now attach it to an event on an HTML element. As stated before, you can use the v-on directive and attach an event with the name of click; this is an event provided by Vue.js. In the AirportCard.vue component, add the v-on directive to the wrapper <div>:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" v-on:click="selectAirport(airport)">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      ...
      

      Once you have added this code, save and exit the file.

      Now, when you click on a card, an alert will pop-up with the message provided. If you click on CVG for example, you will find the following:

      Vue site with alert pop-up that reads "localhost:8080 says You clicked on CVG. It's located in Hebron, KY."

      The click event is not the only event that is provided to you out-of-the-box by Vue.js. In fact, you can use v-on any native JavaScript event, like:

      • keyup
      • mouseover
      • focus
      • mouseenter
      • change

      Next, you will change this v-on:click listener to mouseover to illustrate how Vue.js listens for events. mouseover is an event that fires whenever a mouse cursor moves over an HTML element.

      Open up src/components/AirportCard.vue again and update your file with the following highlighted code:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" @mouseover="selectAirport(airport)">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      

      As shown here, Vue also has shorthand syntax for v-on: events. To use the shorthand syntax, you replaced v-on with @. Save and exit the file.

      Now when you visit localhost:8080 and hover over a card, that function will execute and display a native alert.

      This functionality is good for testing purposes, but may be undesired since it displays the alert every time a user hovers over it. A better experience might be to only display it the first time a user hovers over that card. In vanilla JavaScript, you may track the amount of times a user hovers over a card, then prevent further executions. Vue.js has event modifiers that you can leverage to accomplish the same thing with less code.

      In the next section, you are going to explore event modifiers and use them for a better user experience.

      Step 3 — Using Event and Key Modifiers

      In the previous section, you executed a function on the click and mouseover events. You also learned about the Vue.js shorthand for v-on events. Now you will expand on this further by attaching a modifier to this mouseover event so your function executes only once.

      Vue.js provides a number of event modifiers for you. Some of these include:

      • .stop: stops event propagation
      • .prevent: prevents the HTML element’s default behavior
      • .capture: handles an event targeting an inner element before the selected element
      • .self: only triggers the handler if event.target is the element itself
      • .once: only executes the function once
      • .passive: enables the element’s default behavior to happen immediately instead of waiting for the event, which can be used for optimizing performance for scroll on mobile devices

      In this case, you’ll use the .once modifier. In your text editor, open the AirportCard.vue component and add the modifier to the existing mouseover event:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" @mouseover.once="selectAirport(airport)">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      

      Save the file. Visit your application in the browser and you’ll find that the event only fires once on the first mouseover event.

      Next, you’ll continue exploring modifiers by using key modifiers. These key modifiers are associated with keystroke events, such as keyup. For this next part, imagine that you want to make this clicking action a little more explicit. One way you can do that is by adding a key modifier to the @click event on the .airport <div> in your template.

      To do that, change the @mouseover to @click and add the .shift modifier:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" @click.shift="selectAirport(airport)">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      

      Save the changes and open the application in your browser. If you click on a card without holding the SHIFT key, the alert does nothing. Now, try holding down the SHIFT key when clicking on a card. Your function will now execute, and you will receive an alert.

      In this section, you learned about Vue’s built-in events and the modifiers associated with those events. You can get a lot done with these built-in events, but there will be times when you’ll need to have a custom event. In the next section, you’re going to use custom events to emit an action up to a parent so that it will execute a function.

      Step 4 — Creating Custom Events

      When developing applications in Vue.js, there will be times when you need to pass data up to a parent component via a custom event. Props are read-only data that are passed down to a child from the parent, but a custom action via an $emit is the opposite of that. To create the most reusable components, it’s best to think of these as functions. You pass data down through props (arguments), and emit values back up to the parent (a return value).

      To emit an event from the child component to the parent, you use the $emit function. Before implementing this, this tutorial will guide you through an example to demonstrate how this works.

      The $emit function accepts two arguments: the action name (a string), and the value to pass up to the parent. In the following example, when the user clicks on the button, you are sending the value CVG to the parent component under the action favoriteAirport:

      ChildComponent.vue

      <template>
        <button @click="$emit('favoriteAirport', 'CVG')">A button</button>
      </template>
      

      In the parent component, you would use the v-on directive and listen for the favoriteAirport event. When this custom event is fired, the code will do something with the value:

      ParentComponent.vue

      <template>
        <child-component @favoriteAirport="favoriteAirport = $event" />
      </template>
      
      <script>
      import { ref } from 'vue'
      export default {
        setup() {
          const favoriteAirport = ref('')
      
          return { favoriteAirport }
        }
      }
      </script>
      

      The value of the event will be $event. In this case, $event is actually CVG, which you then store in a reactive data property called favoriteAirport.

      Now that you know what a custom event looks like, you will put it into practice by implementing this custom event into your application.

      Open the AirportCards.vue component in your text editor. In the @click event, remove the reference to the function and replace it with $emit("favoriteAirport", airport). Remember, the first arugment is the name of the event and the second is the value that you are emitting:

      favorite-airports/src/components/AirportCard.vue

      <template>
        <div class="airport" @click="$emit('favoriteAirport', airport)">
          <p>{{ airport.abbreviation }}</p>
          <p>{{ airport.name }}</p>
          <p>{{ airport.city }}, {{ airport.state }}</p>
        </div>
      </template>
      ...
      

      Save the file. Now, when the user clicks on the airport card, a custom event will fire and pass up that airport object.

      Next, open src/App.vue to add some HTML to the template. You will show the favorite airports list after the six cards that are already present:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
          </div>
          <h1 v-if="favoriteAirports.length">Favorite Airports</h1>
          <div v-for="airport in favoriteAirports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
         </div>
        </div>
      </template>
      
      <script>
      import { ref } from 'vue'
      import allAirports from '@/data/airports.js'
      import AirportCard from '@/components/AirportCard.vue'
      
      export default {
        components: {
          AirportCard
        },
        setup() {
          const airports = ref(allAirports)
          const favoriteAirports = ref([])
      
          return { airports, favoriteAirports }
        }
      }
      </script>
      ...
      

      In this code snippet, you are creating a reactive data property called favoriteAirports, which is an empty array. In the <template>, you iterate through the empty array to render the <airport-card /> components, much like you did in an earlier step.

      Now you need to add the v-on event for your custom event:

      favorite-airports/src/App.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation">
            <airport-card :airport="airport" @favoriteAirport="favoriteAirports.push($event)" />
          </div>
          <h1 v-if="favoriteAirports.length">Favorite Airports</h1>
          <div v-for="airport in favoriteAirports" :key="airport.abbreviation">
            <airport-card :airport="airport" />
         </div>
        </div>
      </template>
      ...
      

      In the @favoriteAiport custom event, you used the JavaScript push() method to add the airport from the child ($event) to the favoriteAirports reactive data property.

      Open you browser and navigate to your project at localhost:8080. When you click on one of the airport cards, that card will appear under Favorite Airports.

      Vue airport app with a list of favorite airports that includes the CVG airport card.

      In this section, you learned about custom events, what they are, and how to use them. A custom event is a way to pass data up to a parent component through the $emit function provided by Vue. Once that data has been emitted, you can further manipulate it in the parent component, like adding it to an array.

      Conclusion

      In this tutorial, you learned how Vue.js listens for a number of built-in events, such as click and mouseover. In addition to that, you tried out event and key modifiers, small pieces of code that you appended to your event to provide additional functionality. With this, you set up your app to execute the function once with the .once modifier and to only fire when holding down the SHIFT key using the .shift modifier.

      Vue provides an efficient way to listen for events that lets you focus on manipulating data over manually setting up event listeners. In addition to that, Vue allows you to think of components as functions: They accept data props and can return a value with $emit.

      To learn more about Vue components, it is recommended to read through the Vue documentation. For more tutorials on Vue, check out the How To Develop Websites with Vue.js series page.



      Source link

      How To Create Reusable Blocks of Code with Vue Single-File Components


      The author selected Open Sourcing Mental Illness to receive a donation as part of the Write for DOnations program.

      Introduction

      When creating a web application using Vue.js, it’s a best practice to construct your application in small, modular blocks of code. Not only does this keep the parts of your application focused, but it also makes the application easier to update as it grows in complexity. Since an app generated from the Vue CLI requires a build step, you have access to a Single-File Components (SFC) to introduce modularity into your app. SFCs have the .vue extension and contain an HTML <template>, <script>, and <style> tags and can be implemented in other components.

      SFCs give the developer a way to create their own HTML tags for each of their components and then use them in their application. In the same way that the <p> HTML tag will render a paragraph in the browser, and hold non-rendered functionality as well, the component tags will render the SFC wherever it is placed in the Vue template.

      In this tutorial, you are going to create a SFC and use props to pass data down and slots to inject content between tags. By the end of this tutorial, you will have a general understanding of what SFCs are and how to approach code re-usability.

      Prerequisites

      Step 1 — Setting Up the Project

      In this tutorial, you are going to be creating an airport card component that displays a number of airports and their codes in a series of cards. After following the Prerequisites section, you will have a new Vue project named sfc-project. In this section, you will import data into this generated application. This data will be an array of objects consisting of a few properties that you will use to display information in the browser.

      Once the project is generated, open your terminal and cd or change directory into the root src folder:

      From there, create a new directory named data with the mkdir command, then create a new file with the name us-airports.js using the touch command:

      • mkdir data
      • touch data/us-airports.js

      In your text editor of choice, open this new JavaScript file and add in the following local data:

      sfc-project/data/us-airports.js

      export default [
        {
          name: 'Cincinnati/Northern Kentucky International Airport',
          abbreviation: 'CVG',
          city: 'Hebron',
          state: 'KY'
        },
        {
          name: 'Seattle-Tacoma International Airport',
          abbreviation: 'SEA',
          city: 'Seattle',
          state: 'WA'
        },
        {
          name: 'Minneapolis-Saint Paul International Airport',
          abbreviation: 'MSP',
          city: 'Bloomington',
          state: 'MN'
        }
      ]
      

      This data is an array of objects consisting of a few airports in the United States. This will be rendered in a Single-File Component later in the tutorial.

      Save and exit the file.

      Next, you will create another set of airport data. This data will consist of European airports. Using the touch command, create a new JavaScript file named eu-airports.js:

      • touch data/eu-airports.js

      Then open the file and add the following data:

      sfc-project/data/eu-airports.js

      export default [
        {
          name: 'Paris-Charles de Gaulle Airport',
          abbreviation: 'CDG',
          city: 'Paris',
          state: 'Ile de France'
        },
        {
          name: 'Flughafen München',
          abbreviation: 'MUC',
          city: 'Munich',
          state: 'Bavaria'
        },
        {
          name: 'Fiumicino "Leonardo da Vinci" International Airport',
          abbreviation: 'FCO',
          city: 'Rome',
          state: 'Lazio'
        }
      ]
      

      This set of data is for European airports in France, Germany, and Italy, respectively.

      Save and exit the file.

      Next, in the root directory, run the following command in your terminal to start your Vue CLI application running on a local development server:

      This will open the application in your browser on localhost:8080. The port number may be different on your machine.

      Visit the address in your browser. You will find the following start up screen:

      Vue default template page

      Next, start a new terminal and open your App.vue file in your src folder. In this file, delete the img and HelloWorld tags in the <template> and the components section and import statement in the <script>. Your App.vue will resemble the following:

      sfc-project/src/App.vue

      <template>
      
      </template>
      
      <script>
      export default {
        name: 'App',
      }
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      

      After this, import the us-airports.js file that you created earlier. In order to make this data reactive so you can use it in the <template>, you need to import the ref function from vue. You will need to return the airport reference so the data can be used in the HTML template.

      Add the following highlighted lines:

      sfc-project/src/App.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation" class="card">
            <p>{{ airport.abbreviation }}</p>
            <p>{{ airport.name }}</p>
            <p>{{ airport.city }}, {{ airport.state }}</p>
          </div>
        </div>
      </template>
      
      <script>
      import { ref } from 'vue'
      import data from '@/data/us-airports.js'
      
      export default {
        name: 'App',
        setup() {
          const airports = ref(data)
      
          return { airports }
        }
      }
      </script>
      ...
      

      In this snippet, you imported the data and rendered it using <div> elements and the v-for directive in the template.

      At this point, the data is imported and ready to be used in the App.vue component. But first, add some styling to make the data easier for users to read. In this same file, add the following CSS in the <style> tag:

      sfc-project/src/App.vue

      ...
      <style>
      #app { ... }
      
      .wrapper {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-column-gap: 1rem;
        max-width: 960px;
        margin: 0 auto;
      }
      
      .card {
        border: 3px solid;
        border-radius: .5rem;
        padding: 1rem;
        margin-bottom: 1rem;
      }
      
      .card p:first-child {
        font-weight: bold;
        font-size: 2.5rem;
        margin: 1rem 0;
      }
      
      .card p:last-child {
        font-style: italic;
        font-size: .8rem;
      }
      </style>
      

      In this case, you are using CSS Grid to compose these cards of airport codes into a grid of three. Notice how this grid is set up in the .wrapper class. The .card class is the card or section that contains each airport code, name, and location. If you would like to learn more about CSS, check out our How To Style HTML with CSS.

      Open your browser and navigate to localhost:8080. You will find a number of cards with airport codes and information:

      Three airport cards rendering the data for the US airports dataset

      Now that you have set up your initial app, you can refactor the data into a Single-File Component in the next step.

      Step 2 — Creating a Single-File Component

      Since Vue CLI uses Webpack to build your app into something the browser can read, your app can use SFCs or .vue files instead of plain JavaScript. These files are a way for you to create small blocks of scalable and reusable code. If you were to change one component, it would be updated everywhere.

      These .vue components usually consist of these three things: <template>, <script>, and <style> elements. SFC components can either have scoped or unscoped styles. When a component has scoped styles, that means the CSS between the <style> tags will only affect the HTML in the <template> in the same file. If a component has un-scoped styles, the CSS will affect the parent component as well as its children.

      With your project successfully set up, you are now going to break these airport cards into a component called AirportCards.vue. As it stands now, the HTML in the App.vue is not very reusable. You will break this off into its own component so you can import it anywhere else into this app while preserving the functionality and visuals.

      In your terminal, create this .vue file in the components directory:

      • touch src/components/AirportCards.vue

      Open the AiportCards.vue component in your text editor. To illustrate how you can re-use blocks of code using components, move most of the code from the App.vue file to the AirportCards.vue component:

      sfc-project/src/components/AirportCards.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation" class="card">
            <p>{{ airport.abbreviation }}</p>
            <p>{{ airport.name }}</p>
            <p>{{ airport.city }}, {{ airport.state }}</p>
          </div>
        </div>
      </template>
      
      <script>
      import { ref } from 'vue'
      import data from '@/data/us-airports.js'
      
      export default {
        name: 'Airports',
        setup() {
          const airports = ref(data)
      
          return { airports }
        }
      }
      </script>
      
      <style scoped>
      .wrapper {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-column-gap: 1rem;
        max-width: 960px;
        margin: 0 auto;
      }
      
      .card {
        border: 3px solid;
        border-radius: .5rem;
        padding: 1rem;
        margin-bottom: 1rem;
      }
      
      .card p:first-child {
        font-weight: bold;
        font-size: 2.5rem;
        margin: 1rem 0;
      }
      
      .card p:last-child {
        font-style: italic;
        font-size: .8rem;
      }
      </style>
      

      Save and close the file.

      Next, open your App.vue file. Now you can clean up the App.vue component and import AirportCards.vue into it:

      sfc-project/src/App.vue

      <template>
        <AirportCards />
      </template>
      
      <script>
      import AirportCards from '@/components/Airports.vue'
      
      export default {
        name: 'App',
        components: {
          AirportCards
        }
      }
      </script>
      
      <style scoped>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      

      Now that AirportCards is a standalone component, you have put it in the <template> HTML as you would a <p> tag.

      When you open up localhost:8080 in your browser, nothing will change. The same three airport cards will still display, because you are rendering the new SFC in the <AirportCards /> element.

      Next, add this same component in the template again to illustrate the re-usability of components:

      /src/App.vue

      <template>
        <AirportCards />
        <airport-cards />
      </template>
      ...
      

      You may notice that this new instance of AirportCards.vue is using kebab-case over PascalCase. When referencing components, Vue does not care which one you use. All capitalized words and letters will be separated by a hyphen and will be lower case. The same applies to props as well, which will be explained in the next section.

      Note: The case that you use is up to personal preference, but consistency is important. Vue.js recommends using kebab-case as it follows the HTML standard.

      Open the browser and visit localhost:8080. You will find the cards duplicated:

      The US airport cards rendered twice in the browser.

      This adds modularity to your app, but the data is still static. The row of cards is useful if you want to show the same three airports, but changing the data source would require changing the hard-coded data. In the next step, you are going to expand this component further by registering props and passing data from the parent down to the child component.

      Step 3 — Leveraging Props to Pass Down Data

      In the previous step, you created an AirportCards.vue component that rendered a number of cards from the data in the us-airports.js file. In addition to that, you also doubled the same component reference to illustrate how you can easily duplicate code by adding another instance of that component in the <template>.

      However, leaving the data static will make it difficult to change the data in the future. When working with SFCs, it can help if you think of components as functions. These functions are components that can take in arguments (props) and return something (HTML). For this case, you will pass data into the airports parameter to return dynamic HTML.

      Open the AirportCards.vue component in your text editor. You are currently importing data from the us-airports.js file. Remove this import statement, as well as the setup function in the <script> tag:

      sfc-project/src/components/AirportCards.vue

      ...
      <script>
      
      export default {
        name: 'Airports',
      }
      </script>
      ...
      

      Save the file. At this point, nothing will render in the browser.

      Next, move forward by defining a prop. This prop can be named anything; it just describes the data coming in and associates it with a name.

      To create a prop, add the props property in the component. The value of this is a series of key/value pairs. The key is the name of the prop, and the value is the description of the data. It’s best to provide as much description as you can:

      sfc-project/src/components/AirportCards.vue

      ...
      <script>
      
      export default {
        name: 'Airports',
        props: {
          airports: {
            type: Array,
            required: true
          }
        }
      }
      </script>
      ...
      

      Now, in AirportCard.vue, the property airports refers to the data that is passed in. Save and exit from the file.

      Next, open the App.vue component in your text editor. Like before, you will need to import data from the us-airports.js file and import the ref function from Vue to make it reactive for the HTML template:

      sfc-project/src/App.vue

      <template>
        <AirportCards :airports="usAirports" />
        <airport-cards />
      </template>
      
      <script>
      import { ref } from 'vue'
      import AirportCards from '@/components/Airports.vue'
      import usAirportData from '@/data/us-airports.js'
      
      export default {
        name: 'App',
        components: {
          AirportCards
        },
        setup() {
          const usAirports = ref(usAirportData)
      
          return { usAirports }
        }
      }
      </script>
      

      If you open your browser and visit localhost:8080, you will find the same US airports as before:

      US airports rendered on cards in the browser

      There is another AirportCards.vue instance in your template. Since you defined props within that component, you can pass any data with the same structure to render a number of cards from different airports. This is where the eu-airports.js file from the initial setup comes in.

      In App.vue, import the eu-airports.js file, wrap it in the ref function to make it reactive, and return it:

      /src/App.vue

      <template>
        <AirportCards :airports="usAirports" />
        <airport-cards :airports="euAirports" />
      </template>
      
      <script>
      ...
      import usAirportData from '@/data/us-airports.js'
      import euAirportData from '@/data/eu-airports.js'
      
      export default {
        ...
        setup() {
          const usAirports = ref(usAirportData)
          const euAirports = ref(euAirportData)
      
          return { usAirports, euAirports }
        }
      }
      </script>
      ...
      

      Open your browser and visit localhost:8080. You will find the European airport data rendered below the US airport data:

      US airport data rendered as cards, followed by European airport data rendered in the same way.

      You have now successfully passed in a different dataset into the same component. With props, you are essentially re-assigning data to a new name and using that new name to reference data in the child component.

      At this point, this application is starting to become more dynamic. But there is still something else that you can do to make this even more focused and re-usable. In Vue.js, you can use something called slots. In the next step, you are going to create a Card.vue component with a default slot that injects the HTML into a placeholder.

      Step 4 — Creating a General Card Component Using Slots

      Slots are a great way to create re-usable components, especially if you do not know if the HTML in that component will be similar. In the previous step, you created another AirportCards.vue instance with different data. In that example, the HTML is the same for each. It’s a <div> in a v-for loop with paragraph tags.

      Open your terminal and create a new file using the touch command. This file will be named Card.vue:

      • touch src/components/Card.vue

      In your text editor, open the new Card.vue component. You are going to take some of the CSS from AirportCards.vue and add it into this new component.

      Create a <style> tag and add in the following CSS:

      sfc-project/src/components/Card.vue

      <style>
      .card {
        border: 3px solid;
        border-radius: .5rem;
        padding: 1rem;
        margin-bottom: 1rem;
      }
      </style>
      

      Next, create the HTML template for this component. Before the <style> tag, add a <template> tag with the following:

      sfc-project/src/components/Card.vue

      <template>
        <div class="card">
      
        </div>
      </template>
      ...
      

      In between the <div class="card">, add the <slot /> component. This is a component that is provided to you by Vue. There is no need to import this component; it is globally imported by Vue.js:

      sfc-project/src/components/Card.vue

      <template>
        <div class="card">
          <slot />
        </div>
      </template>
      

      This slot is a placeholder for the HTML that is between the Card.vue component’s tags when it is referenced elsewhere.

      Save and exit the file.

      Now go back to the AirportCards.vue component. First, import the new Card.vue SFC you just created:

      sfc-project/src/components/AirportCards.vue

      ...
      <script>
      import Card from '@/components/Card.vue'
      
      export default {
        name: 'Airports',
        props: { ... },
        components: {
          Card
        }
      }
      </script>
      ...
      

      Now all that is left is to replace the <div> with <card>:

      /src/components/AirportCards.vue

      <template>
        <div class="wrapper">
          <card v-for="airport in airports" :key="airport.abbreviation">
            <p>{{ airport.abbreviation }}</p>
            <p>{{ airport.name }}</p>
            <p>{{ airport.city }}, {{ airport.state }}</p>
          </card>
        </div>
      </template>
      ...
      

      Since you have a <slot /> in your Card.vue component, the HTML between the <card> tags is injected in its place while preserving all styles that have been associated with a card.

      Save the file. When you open your browser at localhost:8080, you will find the same cards that you’ve had previously. The difference now is that your AirportCards.vue now reference the Card.vue component:

      US airport data rendered as cards, followed by European airport data rendered in the same way.

      To show the power of slots, open the App.vue component in your application and import the Card.vue component:

      sfc-project/src/App.vue

      ...
      <script>
      ...
      import Card from '@/components/Card.vue'
      
      export default {
        ...
        components: {
          AirportCards,
          Card
        },
        setup() { ... }
      }
      </script>
      ...
      

      In the <template>, add the following under the <airport-cards /> instances:

      sfc-project/src/App.vue

      <template>
        <AirportCards :airports="usAirports"/>
        <airport-cards :airports="euAirports" />
        <card>
          <p>US Airports</p>
          <p>Total: {{ usAirports.length }}</p>
        </card>
        <card>
          <p>EU Airports</p>
          <p>Total: {{ euAirports.length }}</p>
        </card>
      </template>
      ...
      

      Save the file and visit localhost:8080 in the browser. Your browser will now render additional elements displaying the number of airports in the datasets:

      US and European airports displayed, along with two cards that display that there are three airports in each dataset

      The HTML between the <card /> tags is not exactly the same, but it still renders a generic card. When leveraging slots, you can use this functionality to create small, re-usable components that have a number of different uses.

      Conclusion

      In this tutorial, you created single-file components and used props and slots to create reusable blocks of code. In the project, you created an AirportCards.vue component that renders a number of airport cards. You then broke up the AirportCards.vue component further into a Card.vue component with a default slot.

      You ended up with a number of components that are dynamic and can be used in a number of different uses, all while keeping code maintainable and in keeping with the D.R.Y. software principle.

      To learn more about Vue components, it is recommended to ready through the Vue documentation. For more tutorials on Vue, check out the How To Develop Websites with Vue.js series page.



      Source link

      How To Navigate Between Views with Vue Router


      The author selected Open Sourcing Mental Illness to receive a donation as part of the Write for DOnations program.

      Introduction

      Most websites or applications have multiple HTML pages that are either static or dynamically generated. With more traditional websites, there is one Document Object Model (DOM) and one page per URL route. With single-page applications however, every “page” or view is rendered within one HTML page. User interface (UI) frameworks like Vue.js render these pages and components when needed through the use of a Virtual DOM. This Virtual DOM is a JavaScript representation of the original DOM and is much easier for the client to update.

      When the user visits a single-page application, that application will compare itself to the Virtual DOM and re-render only the parts of the web page that are changed. This technique prevents the page from flashing white when clicking through links. Let’s say you have three sections on a web page: a header, content, and a footer. When you navigate to another URL, Vue will only render the content area of the application that has changed between pages.

      In Vue.js, you can create several views using the first-party library Vue Router. This router makes an association with a view to a URL. In this tutorial, you are going to learn how to add the Vue Router library, integrate it into your project, and create dynamically generated routes. You will also learn the different types of routes available to you as a developer. When completed, you will have an application that you can navigate using a variety of methods.

      To illustrate these concepts, you’ll create a small application that displays airport information. When the user clicks on an airport card, the application will navigate to a dynamic view of the airport’s details. To do this, the program will read a URL parameter and filter out data based on that parameter.

      Prerequisites

      To complete this tutorial, you will need:

      Step 1 — Setting Up the Sample Application

      The application that you are going to build to learn Vue Router will require some initial data. In this step, you will create and structure this data to be accessible to your Vue app later in the tutorial.

      To add this dataset, you need to create a data directory and create a JavaScript file with the name airports.js. If you are not in the src directory, cd into it first:

      Then make a data directory:

      Now create a data/airports.js file and open it in your text editor.

      Add the following to create data for your application:

      airport-codes/src/data/airports.js

      export default [
        {
          name: 'Cincinnati/Northern Kentucky International Airport',
          abbreviation: 'CVG',
          city: 'Hebron',
          state: 'KY',
            destinations: {
            passenger: [ 'Toronto', 'Seattle/Tacoma', 'Austin', 'Charleston', 'Denver', 'Fort Lauderdale', 'Jacksonville', 'Las Vegas', 'Los Angeles', 'Baltimore', 'Chicago', 'Detroit', 'Dallas', 'Tampa' ],
              cargo: [ 'Anchorage', 'Baltimore', ' Chicago' , 'Indianapolis', 'Phoenix', 'San Francisco', 'Seattle', 'Louisville', 'Memphis' ]
            }
        },
        {
          name: 'Seattle-Tacoma International Airport',
          abbreviation: 'SEA',
          city: 'Seattle',
          state: 'WA',
            destinations: {
            passenger: [ 'Dublin', 'Mexico City', 'Vancouver', 'Albuquerque', 'Atlanta', 'Frankfurt', 'Amsterdam', 'Salt Lake City', 'Tokyo', 'Honolulu' ],
              cargo: [ 'Spokane', 'Chicago', 'Dallas', ' Shanghai', 'Cincinnati', 'Luxenbourg', 'Anchorage', 'Juneau', 'Calgary', 'Ontario' ]
            }
        },
        {
          name: 'Minneapolis-Saint Paul International Airport',
          abbreviation: 'MSP',
          city: 'Bloomington',
          state: 'MN',
            destinations: {
            passenger: [ 'Dublin', 'Paris', 'Punta Cana', 'Winnipeg', 'Tokyo', 'Denver', 'Tulsa', 'Washington DC', 'Orlando', 'Mexico City' ],
              cargo: [ 'Cincinnati', 'Omaha', 'Winnipeg', 'Chicago', 'St. Louis', 'Portland', 'Philadelphia', 'Milwaukee', 'Ontario' ]
            }
        }
      ]
      

      This data is an array of objects consisting of a few airports in the United States. In this application, you are going to iterate through this data to generate cards consisting of the name, abbreviation, city, and state properties. When the user clicks on a card, you will route them to a dynamic view with Vue Router and read from one of these properties. From there, you will create a nested route to display information stored in the destination property.

      Save and exit from the file.

      Next, create a Home.vue component inside of a directory called views. You can create this directory and component by opening your terminal and using the mkdir and touch commands.

      Run the following command to make the directory:

      Then create the Home.vue file:

      This Home.vue component will act as this application’s homepage. In it, you will leverage the v-for directive to iterate through the airports.js dataset and show each airport in a card.

      Add the following code to Home.vue:

      airport-codes/src/views/Home.vue

      <template>
        <div class="wrapper">
          <div v-for="airport in airports" :key="airport.abbreviation" class="airport">
            <p>{{ airport.abbreviation }}</p>
            <p>{{ airport.name }}</p>
            <p>{{ airport.city }}, {{ airport.state }}</p>
          </div>
        </div>
      </template>
      
      <script>
      import { ref } from 'vue'
      import allAirports from '@/data/airports.js'
      
      export default {
        setup() {
          const airports = ref(allAirports)
              return { airports }
          }
      }
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      .wrapper {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-column-gap: 1rem;
        max-width: 960px;
        margin: 0 auto;
      }
      .airport {
        border: 3px solid;
        border-radius: .5rem;
        padding: 1rem;
      }
      .airport p:first-child {
        font-weight: bold;
        font-size: 2.5rem;
        margin: 1rem 0;
      }
      .airport p:last-child {
        font-style: italic;
        font-size: .8rem;
      }
      </style>
      

      You may notice that there is some CSS included in this code snippet. In the Home.vue component, you are iterating through a collection of airports, each of which is assigned a CSS class of airport. This CSS adds some styling to the generated HTML by making borders to give each airport the apperance of a card. :first-child and :last-child are pseudo selectors that apply different styling to the first and last p tags in the HTML inside of the div with the class of airport.

      Save and close the file.

      Now that you have this initial view created along with the local dataset, you will install Vue Router in the next step.

      Step 2 — Installing Vue Router

      There are a few ways you can install Vue Router. If you are creating a new project from scratch with the Vue CLI, you can select Vue Router in the prompt; Vue CLI will then install and configure it for you. For the sake of this tutorial, however, it is assumed that you did not select the Vue Router option in the CLI setup. You will instead install Vue Router via npm.

      To install Vue Router, first move from the src directory back to the root of your project directory:

      Then run the following in your terminal window in the root directory of your project:

      You may notice the @next in this command. Since this project is using Vue 3 and the Composition API, you are telling npm to download the latest experimental version of this library. If you would like more information on current releases, check out the Vue Router release page on GitHub.

      This will download the vue-router library from npm and add it to your package.json file, so that it automatically downloads the next time you run npm install.

      The next step is to create your routes file. This file will contain all the possible routes that the user can navigate to. When a certain route is visited in the URL bar, the component that is associated with a URL route will mount.

      In your terminal, in the src directory, move into the src directory and create a router directory:

      Next, create an index.js file inside the router directory:

      Open the file you just created into an editor of your choice. The first thing to do is import the Vue Router library. You actually don’t need to access everything in this library in order to create routes. You can opt to destructure or import only what you need to minimize the bundle size. In this case, you need two functions from vue-router: createWebHistory and createRouter. These functions create a history that a user can go back to and construct a router object for Vue, respectively.

      Add the following code to router/index.js:

      airport-codes/src/router/index.js

      import { createWebHistory, createRouter } from "vue-router"
      

      Next, add the following highlighted lines to create and export your router:

      airport-codes/src/router/index.js

      import { createWebHistory, createRouter } from "vue-router"
      
      const router = createRouter({
        history: createWebHistory(),
      })
      
      export default router
      

      This file will export a router object that is returned from the createRouter function. The object you pass in has two properties: history and routes. The history property contains the generated history from createWebHistory and routes is an array of objects. You will add routes later in this tutorial.

      Next, import the Home view and create an array of objects, storing them into a const called routes:

      airport-codes/src/router/index.js

      import { createWebHistory, createRouter } from "vue-router"
      import Home from "@/views/Home.vue"
      
      const routes = [
        {
          path: "/",
          name: "Home",
          component: Home,
        },
      ]
      
      const router = createRouter({ ... })
      
      export default router
      

      Each route is an object with three properties:

      • path: The URL address
      • name: An assigned name to reference a route in your project
      • component: The component that gets mounted when the path is entered in the URL bar of the browser

      Now that your routes array as been created, you will need to add it to the exported router object.

      After the history key/value pair, add routes. This is shorthand for routes: routes in JavaScript:

      airport-codes/src/router/index.js

      import { createWebHistory, createRouter } from "vue-router"
      
      const routes = [ ... ]
      
      const router = createRouter({
        history: createWebHistory(),
        routes
      })
      
      export default router
      

      At this point, Vue Router is integrated into your project, and you have a route registered. Save and exit the file.

      In another terminal, run the following command to start a development server on your local machine:

      If you visit localhost:8080/ in your browser window, you will not see the Home.vue component just yet. The last step in this integration process is to tell Vue to listen to this router/index.js file and inject the mounted component where <router-view /> is referenced. To do this, you need to reference it in the src/main.js file of your application.

      First, open src/main.js. Then add the following highlighted lines:

      airport-codes/src/main.js

      import { createApp } from 'vue'
      import App from './App.vue'
      import router from './router'
      
      createApp(App).use(router).mount('#app')
      

      With this .use() function that is chained to createApp, Vue.js is now listening to route changes and leveraging your src/router/index.js file. However, Vue Router has no way to display the mounted Home.vue component. To do this, you need to add the router-view component inside your App.vue file. This component tells Vue Router to mount any component associated with a route where <router-view /> is.

      Save and exit the main.js file.

      Next, open the App.vue file. Delete the default contents and replace it with the following:

      airport-codes/src/App.vue

      <template>
        <router-view />
      </template>
      

      Save and exit the file.

      Now visit localhost:8080/ in your browser. You will find the Home.vue component rendered, as shown in the following screenshot:

      Three cards displaying information about airports, retrieved from the data/airports.js file.

      Vue Router has now been downloaded and integrated with a registered route. In the next section, you are going to create additional routes, including two internal pages and a default 404 page if no route was detected.

      Step 3 — Creating Internal Pages

      At this point, your App.vue can render any component configured in your src/router/index.js file. When working with a Vue CLI-generated project, one of the directories that is created for you is views. This directory contains any .vue component that is directly mapped to a route in the router/index.js file. It’s important to note that this isn’t done automatically. You will need to create a .vue and import it into your router file to register it, as detailed earlier.

      Before you have all of your other routes defined, you can create a default route. In this tutorial, this default route will act as a 404 - Not Found page—a fallback in the case no route is found.

      First, create the view that will act as the 404 page. Change into the views directory:

      Then create a file called PageNotFound.vue:

      In your text editor, open this PageNotFound.vue file that you just created. Add the following HTML code to give the view something to render:

      airport-codes/src/views/PageNotFound.vue

      <template>
        <div>
          <h1>404 - Page Not Found</h1>
          <p>This page no longer exists or was moved to another location.</p>
        </div>
      </template>
      

      Save and close the file.

      Now that the PageNotFound.vue component has been created, it’s time to create a catch-all route for your application. Open up the src/router/index.js file in your text editor and add the following highlighted code:

      airport-codes/src/router/index.js

      import { createWebHistory, createRouter } from "vue-router"
      import Home from "@/views/Home.vue"
      import PageNotFound from '@/views/PageNotFound.vue'
      
      const routes = [
        {
          path: "/",
          name: "Home",
          component: Home,
        },
        {
          path: '/:catchAll(.*)*',
          name: "PageNotFound",
          component: PageNotFound,
        },
      ]
      ...
      

      Vue Router for Vue 3 uses a custom RegEx. The value of path contains this new RegEx, which is telling Vue to render PageNotFound.vue for every route, unless the route is already defined. The catchAll in this route refers to a dynamic segment within Vue Router, and (.*) is a regular expression that captures any string.

      Save this file and visit your application in your browser window at localhost:8080/not-found. You will find the PageNotFound.vue component rendered in your browser, as shown in the following image:

      A 404 page that tells the user that the page they are looking for has not been found.

      Feel free to change this URL to anything else; you will get the same result.

      Before moving on, create another route for your application. This route will be an about page.

      Open your terminal of choice and create the file in the views directory:

      In your text editor, open this About.vue file that you just created. Add the following HTML to create more information about your site:

      airport-codes/src/views/About.vue

      <template>
        <div>
          <h1>About</h1>
          <p>This is an about page used to illustrate mapping a view to a router with Vue Router.</p>
        </div>
      </template>
      

      Save and close the file.

      With that view created, open the src/router/index.js file in your text editor, import the About.vue component, and register a new route with a path of /about:

      airport-codes/src/router/index.js

      import { createWebHistory, createRouter } from "vue-router"
      import Home from '@/views/Home.vue'
      import About from '@/views/About.vue'
      import PageNotFound from '@/views/PageNotFound.vue'
      
      ...
      const routes = [
        {
          path: "/",
          name: "Home",
          component: Home,
        },
        {
          path: '/about',
          name: "About",
          component: About,
        },
        {
          path: '/:catchAll(.*)*',
          name: "PageNotFound",
          component: PageNotFound,
        },
      ]
      ...
      

      Save and close the file.

      At this point, you have three different routes:

      • localhost:8080/, which routes to Home.vue
      • localhost:8080/about, which routes to About.vue
      • Any other route, which by default goes to PageNotFound.vue

      Once you save this file, open your browser and first visit localhost:8080/. Once the application loads, you will find the contents of Home.vue: the collection of airport cards.

      Continue testing these routes by visiting localhost:8080/about. This is a static route, so you will find the contents of the About.vue component, which at this point contains a heading and a paragraph.

      A placehold about page for the sample app that reads

      Next, you can test the PageNotFound.vue component by visiting anything else in your browser. For example, if you visit, localhost:8080/some-other-route, Vue Router will default to that catchAll route since that route is not defined.

      As this step illustrates, Vue Router is a handy first-party library that renders a component that is associated with a specific route. In this step, this library was downloaded and integrated globally through the main.js file and was configured in your src/router/index.js file.

      So far, most of your routes are exact routes. This means a component will only mount if the URL fragment matches the path of the router exactly. However, there are other types of routes that have their own purpose and can dynamically generate content. In the next step, you are going to implement the different types of routes and learn when to use one or the other.

      Step 4 — Creating Routes With Parameters

      At this point, you have created two exact routes and a dynamic route to a 404 page. But Vue Router has more than these types of routes. You can use the following routes in Vue Router:

      • Dynamic Routes: Routes with dynamic parameters that your application can reference to load unique data.
      • Named Routes: Routes that can be accessed using the name property. All the routes created at this point have a name property.
      • Nested Routes: Routes with children associated with them.
      • Static or Exact Routes: Routes with a static path value.

      In this section, you will create a dynamic route displaying individual airport information and a nested route for airport destinations.

      Dynamic Routes

      A dynamic route is useful when you want to reuse a view to display different data depending on the route. For example, if you wanted to create a view that displays airport information depending on the airport code in the URL bar, you could use a dynamic route. In this example, if you were to visit a route of localhost:8080/airport/cvg, your application would display data from the airport with the code cvg, the Cincinnati/Northern Kentucky International Airport. Next, you will create this view as described.

      Open up your terminal and create a new .vue file with the touch command. If src is your current working directory, the command will look like this:

      • touch views/AirportDetail.vue

      After that is created, open this file in your text editor of choice. Go ahead and create your template and script tags to set up this component:

      airport-codes/src/views/AirportDetail.vue

      <template>
        <div>
      
        </div>
      </template>
      
      <script>
      export default {
        setup() { }
      }
      </script>
      

      Save and close this file.

      Next, you need to register this view in the src/router/index.js file. Open up this file in your text editor, then add the following highlighted lines:

      airport-codes/src/router/index.js

      import Home from '@/views/Home.vue'
      import About from '@/views/About.vue'
      import AirportDetail from '@/views/AirportDetail.vue'
      import PageNotFound from '@/views/PageNotFound.vue'
      
      ...
      const routes = [
        {
          path: "/",
          name: "Home",
          component: Home,
        },
        {
          path: '/about',
          name: "About",
          component: About,
        },
        {
          path: '/airport/:code',
          name: "AirportDetail",
          component: AirportDetail,
        },
        {
         path: '/:catchAll(.*)*',
         name: "PageNotFound",
         component: PageNotFound,
        },
      ]
      ...
      

      The :code in this new route is called a parameter. A parameter is any value that can be accessed in your application via this name. In this case, you have a parameter named code. Next, you will display the information associated with this abbreviation by leveraging this parameter.

      Save and close this file.

      Now that you have some data, open the AirportDetail.vue component again. Import the airport data in this component:

      airport-codes/src/views/AirportDetail.vue

      ...
      <script>
      import airports from '@/data/airports.js'
      
      export default {
        setup() { }
      }
      </script>
      

      Next, create a computed property that returns one object from the array if the abbreviation property of that object matches the :code parameter in the URL. In Vue 3, you need to destructure computed from the vue library:

      airport-codes/src/views/AirportDetail.vue

      ...
      <script>
      import { computed } from 'vue'
      import { useRoute } from 'vue-router'
      import airports from '@/data/airports.js'
      
      export default {
        setup() {
        const route = useRoute()
          const airport = computed(() => {
              return airports.filter(a => a.abbreviation === route.params.code.toUpperCase())[0]
          })
      
          return { airport }
        }
      }
      </script>
      

      This computed property uses the filter array method in JavaScript to return an array of objects if the condition is met. Since you only want one object, the code will always return the first object, which it will access with the [0] index syntax. The route.params.code is how you access the parameter that was defined in your router file. In order to access the route’s properties, you will need to import a function from vue-router named useRoute. Now when visiting a route, you have immediate access to all of the route’s properties. You are using dot notation to access this code param and retrieve its value.

      At this point, this computed property will return a single airport object if the code in the URL matches the abbreviation property. This means that you have access to all of the object properties of an airport and can construct the template of this component.

      Continue editing the AirportDetail.vue file in your text editor and build out your template to display the information of the airport:

      airport-codes/src/views/AirportDetail.vue

      <template>
       <div>
         <p>{{ airport.name }} ({{ airport.abbreviation }})</p>
         <p>Located in {{ airport.city }}, {{ airport.state }}</p>
       </div>
      </template>
      ...
      

      Open your browser and visit localhost:8080/airport/sea. Information related to the Seattle-Tacoma International Airport will render in your browser, as shown in the following image:

      Informational page about the Seattle-Tacoma International Airport, including its abbreviation and location.

      Nested Routes

      As your application grows, you may find that you have a number of routes that are related to a parent. A good illustration of this could be a series of routes associated with a user. If there is a user named foo, you could have multiple nested routes for the same user, each starting with /user/foo/. When the user is on the /user/foo/profile route, the user would be viewing a profile page associated with them. A posts page for the user might have a route of /user/foo/posts. This nesting can give you the ability to organize your routes along with your components. For more information on this, check out the Vue Router documentation.

      You can apply this same pattern to the application you’ve built throughout this tutorial. In this section, you’re going to add a view to display the destinations that each airport supports.

      Open your terminal and, in the src directory, create a new file with the name AirportDestinations.vue:

      • touch views/AirportDestinations.vue

      Next, open your text editor and add the following:

      airport-codes/src/views/AirportDestinations.vue

      <template>
              <h1>Destinations for {{ airport.name }} ({{ airport.abbreviation }}</h1>
              <h2>Passenger</h2>
              <ul>
                  <li v-for="(destination, i) in airport.destinations.passenger" :key="i">
                      {{ destination }}
                  </li>
              </ul>
          <h2>Cargo</h2>
          <ul>
                  <li v-for="(destination, i) in airport.destinations.cargo" :key="i">
                      {{ destination }}
                  </li>
          </ul>
      </template>
      
      <script>
          import { computed } from 'vue'
        import { useRoute } from 'vue-router'
        import airports from '@/data/airports.js'
      
          export default {
              setup() {
            const route = useRoute()
                  const airport = computed(() => {
                      return airports.filter(a => a.abbreviation === route.params.code.toUpperCase())[0]
                  })
                  return { airport }
              }
          }
      </script>
      

      This view will render all of the destinations for each airport from the airports.js file. In this view, you are using the v-for directive to iterate through the destinations. Much like the AirportDetail.vue view, you are creating a computed property called airport to get the airport that matches the :code parameter in the URL bar.

      Save and close the file.

      To create a nested route, you need to add the children property to your route object in the src/router/index.js file. The child (nested) route object contains the same properties as its parent.

      Open up router/index.js and add the following highlighted lines:

      airport-codes/src/router/index.js

      import Home from '@/views/Home.vue'
      import About from '@/views/About.vue'
      import AirportDetail from '@/views/AirportDetail.vue'
      import AirportDestinations from '@/views/AirportDestinations.vue'
      import PageNotFound from '@/views/PageNotFound.vue'
      
      ...
      const routes = [
          ...
        {
          path: '/airport/:code',
          name: "AirportDetail",
          component: AirportDetail,
              children: [
                  {
                    path: 'destinations',
                      name: 'AirportDestinations',
                      component: AirportDestinations
                  }
              ]
        },
          ...
      ]
      ...
      

      The path for this child route is short compared to its parent. That is because, with nested routes, you do not need to add the entire route. The child inherits its parent’s path and will prepend it to the child path.

      Open your browser window, and visit localhost:8080/airport/msp/destinations. Nothing appears to be different from the parent, AirportDetails.vue. That is because when using nested routes, you need to include the <router-view /> component in the parent. When visiting the child route, Vue will inject the content from the child view into the parent:

      airport-codes/src/views/AirportDetail.vue

      <template>
       <div>
         <p>{{ airport.name }} ({{ airport.abbreviation }})</p>
         <p>Located in {{ airport.city }}, {{ airport.state }}</p>
           <router-view />
       </div>
      </template>
      

      In this case, when visiting the destinations route in the browser, AirportDestinations.vue will display an unordered list of the destinations that the Minneapolis-Saint Paul International Airport supports, for both passenger and cargo flights.

      A page rendered with a list of passenger and cargo destinations for Minneapolis-Saint Paul International Airport.

      In this step, you created dynamic and nested routes and learned how to use a computed property that checks against the :code parameter in the URL bar. Now that all the routes have been created, in the final step, you are going to navigate between the different types of routes by creating links with the <router-link /> component.

      Step 5 — Navigating Between Routes

      When working with single-page applications, there are a few caveats you need to be aware off. Since every page is bootstrapped into a single HTML page, navigating between internal routes using the standard anchor (<a />) will not work. Instead, you will need to use the <router-link /> component provided by Vue Router.

      Unlike the standard anchor tag, router-link provides a number of different ways to link to other routes in your application, including using named routes. Named routes are routes that have a name property associated with them. Every link that you have created up to this point has a name already associated with it. Later in this step, you’ll learn how to navigate using name rather than the path.

      The <router-link /> component was globally imported when you initially integrated Vue Router into your application. To use this component, you will add it to your template and provide it with a prop value.

      Open the Home.vue component within the views directory in your editor. This view displays every airport that you have in the airports.js file. You can use router-link to replace the containing div tag in order to make each card clickable to their respective detail view.

      Add the following highlighted lines to Home.vue:

      airport-codes/src/views/Home.vue

      <template>
          <div class="wrapper">
              <router-link v-for="airport in airports" :key="airport.abbreviation" class="airport">
                  <p>{{ airport.abbreviation }}</p>
                  <p>{{ airport.name }}</p>
                  <p>{{ airport.city }}, {{ airport.state }}</p>
              </router-link>
          </div>
      </template>
      ...
      

      At the bare minimum, router-link requires one prop called to. This to prop accepts an object with a number of key/value pairs. Since this card uses the v-for directive, these cards are data driven. You’ll need to be able to navigate to the AirportDetail.vue component. If you review the path for that component, it is accessible via /airports/:code.

      When navigating via the path property, it is a one-to-one match. Add the following highlighted segments:

      airport-codes/src/views/Home.vue

      <template>
          <div class="wrapper">
              <router-link :to="{ path: `/airports/${airport.abbreviation}` }" v-for="airport in airports" :key="airport.abbreviation" class="airport">
                  ...
              </router-link>
          </div>
      </template>
      ...
      

      In this code, you are using JavaScript string interpolation to insert the airport code dynamically as the value for the path property. However, as your applications grows in scale, you may find that navigating to a different route via the path is not the best method. In fact, it is generally considered best practice to navigate using named routes.

      To implement a named route, use the name of the route over the path. If you reference src/router/index.js you will find that AirportDetail.vue has a name of AirportDetail:

      airport-codes/src/views/Home.vue

      <template>
          <div class="wrapper">
              <router-link :to="{ name: 'AirportDetail' }" v-for="airport in airports" :key="airport.abbreviation" class="airport">
                  ...
              </router-link>
          </div>
      </template>
      ...
      

      The benefit of using named routes over exact routes is that named routes are not dependent on the path of the route. URL structures will always change in development, but the name of the route will seldom change.

      You might notice that you cannot pass in the airport code as you did earlier with the exact route. If you need to pass in parameters into a named route, you will need to add the params property containing an object that represents your parameter:

      airport-codes/src/views/Home.vue

      <template>
          <div class="wrapper">
              <router-link :to="{ name: 'AirportDetail', params: { code: airport.abbreviation } }" v-for="airport in airports" :key="airport.abbreviation" class="airport">
                  ...
              </router-link>
          </div>
      </template>
      ...
      

      Save this file and view it in your browser at localhost:8080. Clicking on the Seattle-Tacoma airport card will now navigate you to localhost:8080/airport/sea.

      Programmatic Navigation

      The <router-link /> component is great to use when you need to navigate to another view within your HTML template. But what about those cases when you need to navigate between routes within a JavaScript function? Vue Router offers a solution to this problem called programmatic navigation.

      Earlier in this tutorial, you created a catch-all route called PageNotFound. It would be a good user experience to always navigate to that page if the airport computed property returned undefined in the AirportDetail.vue and AirportDestinations.vue components. In this section, you will implement this feature.

      In your text editor, open the AirportDetail.vue component. To achieve this, detect if airport.value is undefined. This function will be called when the component is first mounted, which means you will need to use Vue’s lifecycle methods.

      Add the following highlighted lines:

      airport-codes/src/views/AirportDetail.vue

      <template>
        <div v-if="airport">
         <p>{{ airport.name }} ({{ airport.abbreviation }})</p>
         <p>Located in {{ airport.city }}, {{ airport.state }}</p>
         <router-view />
        </div>
      </template>
      
      <script>
          import { computed, onMounted } from 'vue'
          import { useRoute } from 'vue-router'
          import airports from '@/data/airports.js'
          import router from '@/router'
      
          export default {
                  setup() {
              const route = useRoute()
                      const airport = computed(() => {
                          return airports.filter(a => a.abbreviation === route.params.code.toUpperCase())[0]
                      })
      
                      onMounted(() => {
                          if (!airport.value) {
                              // Navigate to 404 page here
                          }
                      })
      
                      return { airport }
                  }
          }
      </script>
      

      In this onMounted function, you are checking if airport.value is a falsy value. If it is, then you route to PageNotFound. You can handle programmatic routing similarly to how you handled the router-link component. Since you cannot use a component in JavaScript functions, you need to use router.push({ ... }). This push method of the router object accepts the value of the to prop when using the link component:

      /src/views/AirportDetail.vue

      <script>
          ...
        onMounted(() => {
              if (!airport.value) {
                  router.push({ name: 'PageNotFound' })
              }
        })
          ...
      </script>
      

      If the route doesn’t exist, or if the data is not returned properly, this will protect the user from the broken web page.

      Save the file and navigate to localhost:8080/airport/ms. Since ms is not an airport code in the data, the airport object will be undefined, and the router will redirect you to the 404 page.

      Conclusion

      In this tutorial, you used Vue Router to create a web application that routes between different views. You learned the different types of routes, including exact, named, and nested, as well as created links with parameters using the router-link component. In the final step, you provided programmatic navigation using JavaScript by leveraging the router’s push() function.

      For more information on Vue Router, it’s recommended to read through their documentation. The CLI tool specifically has many additional features that weren’t covered in this tutorial. For more tutorials on Vue, check out the Vue Topic Page.



      Source link