GA Logo

Frontend - Vue


Prerequisites

  • Deployed Todos API
  • NodeJS
  • vue-cli installed npm install -g @vue/cli

Setup

  • In your terminal spin-up a new React Project vue create todofrontend
  • Install support Libraries to be used npm install vue-router@4 milligram
  • test out dev server npm run serve and go to localhost:8080

Setting Up Vue Router

In src create a file called routes.js that will contain our array of routes.

/////////////////////////
// Components Imports
/////////////////////////

/////////////////////////
// Array of Routes
/////////////////////////
export default [
  { path: "/", component: Home },
  { path: "/about", component: About },
];
  • Then let's refactor main.js to add router functionality to the Vue application
import { createApp } from "vue";
import App from "./App.vue";
import { createWebHistory, createRouter } from "vue-router";
import routes from "./routes";
import "milligram";

//Create our Router
const router = createRouter({
  // Create a web history
  history: createWebHistory(),
  // inject our routes
  routes,
});

//Create our vue application
const app = createApp(App);

// inject our router into our app
app.use(router);

// Mount our App to the DOM
app.mount("#app");

Scoping Our Components

  • in src create a folder called pages and create these components

AllPosts.vue

<template>
  <h1>AllPosts</h1>
</template>

<script>
  export default {
    name: "AllPost",
  };
</script>

<style></style>

SinglePost.vue

<template>
  <h1>SinglePost</h1>
</template>

<script>
  export default {
    name: "SinglePost",
  };
</script>

<style></style>

Form.vue

<template>
  <h1>Form</h1>
</template>

<script>
  export default {
    name: "Form",
  };
</script>

<style></style>

Setting Up Our Routes

src/routes.js

/////////////////////////
// Component Imports
/////////////////////////
import AllPosts from "./pages/AllPosts.vue";
import SinglePost from "./pages/SinglePost.vue";
import Form from "./pages/Form.vue";
/////////////////////////
// Array of Routes
/////////////////////////
export default [
  // main page that shows all todos
  { path: "/", component: AllPosts },
  // page for seeing an individual todo
  { path: "/post/:id", component: SinglePost, name: "post" },
  // route for creating a new todo
  { path: "/new", component: Form },
  // route for updating a todo
  { path: "/edit", component: Form },
];

Setting Up The App Component

src/App.js

<template>
  <div>
    <h1>Our Todos</h1>
    <router-view />
  </div>
</template>

<script>
  export default {
    name: "App",
  };
</script>

<style></style>

Getting Our Todos

We will be using the new Composition API introduced in Vue 3 which gives a React Hooks like API for setting up your Vue components. We will grab the posts when the app component mounts and pass it as an attribute to all our routes via the router-view tag.

<template>
  <div class="app">
    <h1>Our Todos</h1>
    <router-view :posts="posts" :url="url" :getPosts="getPosts" />
  </div>
</template>

<script>
  import { ref, onMounted } from "vue"; // Import Composition API Hooks
  // ref hook allows use to create reactive variables
  // onMounted let's us execute code when component mounts

  export default {
    name: "App",
    // Setup property allows us to use new composition api to define properties/methods
    // Returns an object with any properties/methods the component should have
    setup(props) {
      // variable with base url for API calls
      const url = "https://api.herokuapp.com/todos/";
      // ref for holding all the posts
      const posts = ref([]);
      // method for getting posts
      const getPosts = async () => {
        const response = await fetch(url);
        const data = await response.json();
        posts.value = await data;
      };
      //run getPosts once when component loads
      onMounted(() => getPosts());
      // return component properties and methods
      return {
        posts,
        getPosts,
        url,
        ...props,
      };
    },
  };
</script>

<style>
  .app {
    text-align: center;
  }
</style>

Then let's render all those posts in our AllPosts component

AllPosts.vue

<template>
  <div>
    <div class="post" v-for="(post, index) in $attrs.posts" v-bind:key="index">
      <router-link :to="{name: 'post', params: {id: index}}">
        <h1>{{post.subject}}</h1>
      </router-link>
      <h2>{{post.details}}</h2>
    </div>
  </div>
</template>

<script>
  export default {
    name: "AllPost",
  };
</script>

<style>
  .post {
    text-align: center;
    border: 3px solid green;
    margin: 10px auto;
    width: 80%;
  }
</style>

Then let's edit SinglePost to show a SinglePost

  <div class="post">
      <h1>{{post.subject}}</h1>
      <h2>{{post.details}}</h2>
      <router-link to="/"><button>Back to Main</button></router-link>
  </div>
</template>

<script>
// get useRoute hook to get access to params
import {useRoute} from "vue-router"
// getting toRefs hook to maintain props reactivity
import {toRefs} from "vue"

export default {
  name: "SinglePost",
  props: ['posts'],
  setup(props){
      // get route object to access params
      const route = useRoute()
      // retrieve posts from props
      const {posts} = toRefs(props)
      // grab target post from posts
      const post = posts.value[route.params.id]
      //return properties
      return {
          post
      }
  }
};
</script>

<style>
button {
    margin: 10px
}

</style>

Setting Up Our Form

<template>
  <form v-on:submit.prevent="handleSubmit">
    <input type="text" placeholder="subject" v-model="subject" />
    <input type="text" placeholder="subject" v-model="details" />
    <input type="submit" :value="buttonLabel" />
  </form>
</template>

<script>
  // get router hooks
  import { useRoute, useRouter } from "vue-router";
  // get vue hooks
  import { ref, toRefs } from "vue";

  export default {
    name: "Form",
    props: ["posts", "url", "getPosts"],
    setup(props) {
      const route = useRoute(); //get route
      const router = useRouter(); //get router
      const { posts, url, getPosts } = toRefs(props); // get posts from props
      const subject = ref(""); // variable for subject in form
      const details = ref(""); // variable for details in form
      console.log(url);
      let buttonLabel; // label for submit button
      let handleSubmit; //variable to hold submit function
      // If edit route setup for editing, if not setup for creating
      if (route.name === "edit") {
        //get post to be edited from posts
        const post = posts.value.find((p) => p.id == route.params.id);
        // fill the form with that posts values
        subject.value = post.subject;
        details.value = post.details;
        // label for submit button
        buttonLabel = "edit todo";
        // define function to update
        handleSubmit = async () => {
          await fetch(url.value + route.params.id + "/", {
            method: "put",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              subject: subject.value,
              details: details.value,
            }),
          });

          getPosts.value();
          router.push("/");
        };
      } else {
        // label for submit button
        buttonLabel = "create todo";
        // define function to create
        handleSubmit = async () => {
          await fetch(url.value, {
            method: "post",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              subject: subject.value,
              details: details.value,
            }),
          });

          getPosts.value();
          router.push("/");
        };
      }
      return {
        subject,
        details,
        handleSubmit,
        buttonLabel,
      };
    },
  };
</script>

<style></style>

Add a create todo button in App

<template>
  <div class="app">
    <h1>Our Todos</h1>
    <router-link to="/new"><button>New Todo</button></router-link>
    <router-view :posts="posts" :url="url" :getPosts="getPosts" />
  </div>
</template>

and an edit todo button in SinglePost

<template>
  <div class="post">
    <h1>{{post.subject}}</h1>
    <h2>{{post.details}}</h2>
    <router-link to="/"><button>Back to Main</button></router-link>
    <router-link :to="{name: 'edit', params: {id: post.id}}"
      ><button>Edit Todo</button></router-link
    >
  </div>
</template>

Deleting Our Todos

Update Our Single Post as Follows

<template>
  <div class="post">
    <h1>{{ post.subject }}</h1>
    <h2>{{ post.details }}</h2>
    <router-link to="/"><button>Back to Main</button></router-link>
    <router-link :to="{ name: 'edit', params: { id: post.id } }"
      ><button>Edit Todo</button></router-link
    >
    <button v-on:click="deletePost">Delete Todo</button>
  </div>
</template>

<script>
  // get useRoute hook to get access to params
  import { useRoute, useRouter } from "vue-router";
  // getting toRefs hook to maintain props reactivity
  import { toRefs } from "vue";

  export default {
    name: "SinglePost",
    props: ["posts", "url", "getPosts"],
    setup(props) {
      // get route object to access params
      const route = useRoute();
      // get Router
      const router = useRouter();
      // retrieve posts from props
      const { posts, url, getPosts } = toRefs(props);
      // grab target post from posts
      const post = posts.value[route.params.id];

      const deletePost = async () => {
        await fetch(url.value + post.id + "/", {
          method: "delete",
        });

        await getPosts.value();

        router.push("/");
      };

      //return properties
      return {
        post,
        deletePost,
      };
    },
  };
</script>

<style>
  button {
    margin: 10px;
  }
</style>

Done!

What you will learn



Resources to Learn More