Vue Blog Site - Part 1
Preliminary Thoughts and Notes
It helps to be aware of importing and exporting modules in JavaScript, which can be confusing because there are various ways to do it.
For this project we'll use Vue in a much different way than we did in the User Manager. This time we'll be using NPM packages that are specifically created for large scale Vue projects. The User Manager app that we created was a relatively simple app, with only a few Vue components. But many real-world projects are far more complicated. A complex project might require you to design hundreds of Vue components. Putting all the code into a single file (as we did in main.js for the User Manager) is not a good idea. A large project might have a team of designers and developers working on it, and would be extremely messy if they all had to do their work in one file.
The Vue team created various NPM packages to make Single File Vue Components. With this approach, every Vue component is defined in it's own file (which has a .vue file extension).
You learned from the User manager that you can create a component by calling Vue's component() method. And that this method takes to parameters. And that the second parameter is an object which is referred to as the options object because it allows you to set up all the options for your component. You know that the options object includes a template property that allows you to define the UI for a component (using HTML), and you know about other properties of the options object, such as props, data(), methods, and mounted(). With the 'single file component' approach, you'll see the options object, and it will work in almost the exact same way as it did when we put everything in main.js.
The only difference is that the template is defined outside of the options object. Here is an example of an extremely bare-bones single file Vue component:
<template>
<div>
<h2>The UI/HTML for a component goes here</h2>
<p>The value of someProp is: {{someProp}}</p>
</div>
</template>
<script>
// this is the options object:
export default {
props:["someProp"]
};
</script>
<style>
/* You can add CSS code to style your component here */
</style>
You can see that it's made up of three parts. The TEMPLATE element is where you put the HTML code that defines the UI for the component. After that is a SCRIPT element that exports an object. This is the options object, and it works in exactly the same way as it did for the User Manager project, except that the template property is not required, since it is now defined above.
Finally, there is a STYLE element. This allows you to apply CSS styles to a component. We'll have to discuss this more later, because you the CSS code you add here could be applied to other components in your app. But luckily Vue offers a clever way to limit the styles to the component in the same file.
The base way to get started with single file Vue components is to dive in!
Step 1 - Setting Up the App and Router
Install VueCLI (an npm package)
In order to create a Vue project that uses single file components, you must install the VueCLI npm package. Here is the official documentation for VueCLI.
To install the VueCLI package (globally) on WINDOWS, run this command:
npm install -g @vue/cli
If you are on Ubuntu install the VueCLI package (globally) by running this command:
sudo npm install -g @vue/cli
Note that if you have already installed the VueCLI package, you should update it with this command:
npm update -g @vue/cli
I ran into some issues when I tried to update the vue/cli package. So I had to uninstall it and then reinstall it.
To check to see if it's already installed (and see which version is intsalled):
npx vue --version
The latest version as of 6/23/2023 is 5.08.
Use VueCLI to create a new project
Open a terminal and make a note of the directory that you working in (because the following command will create a folder in this directory).
To create a new project enter this command (this will create a folder with the named vue-blog-site and it will put the starter code inside it):
npx vue create vue-blog-site
When the menu appears for choosing a 'preset', choose Default (Vue 3) ([Vue 3] babel, eslint). You can use the up and down arrow keys to choose a preset.
The will kick off a big process that creates the starter files, and sets up a Git repository for the project. It may take a few minutes.
Once it completes, cd into the vue-blog-site folder. You'll find lots of files and folder that we'll discuss soon. But first lets run the project so that you can see it in the browser.
VS Code may detect that you've just set up a Vue app and prompt you to install extensions that are designed for working with Vue projects. You can ignore it (I'm not sure which one(s) it will suggest). I like the Vetur extension. I suggest you install it, or ask me about this in class and we'll do it together.
To run the project enter this command in the terminal (make sure you run this command from the vue-blog-site folder):
npm run serve
This will launch a web server in the terminal and it should display the URL that you can enter into your browser to see the app in action.
The URL will look something like this (the port number may be different):
http://localhost:8080/
The browser should display a Vue icon and some other information about Vue. We'll be overhauling this soon.
Note that you cannot use the terminal to enter commands while your app is running.
To stop the web server, put your curser in the terminal and press Ctrl + C.
Take a look at all the files that were created for the project:
node_modules/
public/ - This is were the index.html page is located
src/ - for all of your source code
src/assets - images, fonts, etc (they get compiled into your project by Webpack)
src/components - for all of your Vue components
src/App.vue - The root component, in which all other components are nested
src/main.js - creates the app and mounts it to the DOM
package.json - you should be familiar with this file
babel.config.js - compiles your JS code to older versions so that you can use modern JS in your source code
jsconfig.json - allows you set options for how your code gets compiled
.gitignore - for files that that should be excluded from the Git repo
When you are ready to go live with your app you can run this command to compile it for distribution to a live web server:
npm run build
Go ahead and run the command to build (compile) the app and note that it creates a dist folder. To go live with your Vue app, you would copy the contents of the dist folder to your live web server.
Take a look inside App.vue, this is the root component of your app. In the template you'll see that it makes use of the HelloWorld component and passes in a prop called msg. Now look at HelloWorld.vue (in the components folder) and note that the H3 element in the template shows the value of the msg prop.
Start working on the project
Remember that a Vue app is a 'single page web app', which means that when you compile and run your code, everything takes place on a single page. But, like many single page web apps, Vue apps can give the illusion that they are made up of many different pages. You'll see this soon when we set up a 'router' (remember 'routes' from the node final project in Int Web Dev?).
As we get more into the project, you'll see that some of our components will represent what appears to be a web page. Other components will be just one part of a page (like the user-list and user-form components from the User Manager). In Vue, it is recommended that you put these two types of components into different folders. Further, it's recommended that you put 'page' components into a folder named views (I wonder why they chose that name, instead of 'pages'!). The 'parts' components are put in the components folder (where the HelloWorld component is)
Create a folder named views inside the src folder.
Add these files to the views folder:
HomeView.vue
BlogListView.vue
BlogPostView.vue
Put this code inside the HomeView component:
<template>
<h1>This is the home page</h1>
</template>
For now we are just defining the template for this component, we can add the options object (and css styles) later.
Put this code inside the BlogListView component:
<template>
<h1>This is the blog list page</h1>
</template>
Put this code inside the BlogPostView component:
<template>
<h1>This is a blog post page</h1>
</template>
Open the App.vue file and update it to use the HomeView.
Here are the changes (the updated code is shown below):
- Delete the contents inside the TEMPLATE element and replace it with a HomeView component
- Remove the import statement for the HelloWorld component (we don't need it any more)
- Add an import statement for the HomeView component
- Remove HelloWorld from the components property of the options object
- Add HomeView to the components property of the options object
- Get rid of the CSS code in the STYLE element
Here's what the code should look like in App.vue (I've included a few comments that hopefully help):
<template>
<home-view></home-view><!--add an instance of the HomeView here-->
</template>
<script>
// We don't need the HelloWorld component any more:
// import HelloWorld from './components/HelloWorld.vue'
// import the HomeView component:
import HomeView from './views/HomeView.vue'
export default {
name: 'App',
components: {
//HelloWorld
HomeView
}
}
</script>
<style>
/*we can add CSS code here later*/
</style>
Here are a few important things to note about using 'single file components' in Vue:
- When you add a component to the template you should use kabob-case (if you aren't sure what this is, please ask me)
- In order to use a component to another component's template, you must do two things:
- Import it
- Register it by adding it to the components property of the options object
If the app is not running, go ahead and run it, then check it out in the browser:
npm run serve
Notice that the app displays the HomeView component, which we added to the template of the root component (App.vue).
Next we'll set up the app so that we can navigate to the BlogListView and BlogPostView 'pages'. In order to do this, we need to add an npm package called vue-router, which allows us to define 'routes' for our app. Each route will have it's own URL and will direct you to one of the 'pages' in our app (one of the components in the 'views' folder).
Installing Vue Router
In the terminal, press Ctrl + c to stop the server (or open another terminal in VS Code, just make sure that you run the next command from the vue-blog-site folder).
Enter this command into the terminal to install the Vue Router package:
npm install vue-router@next --save
The @next means that we want to install the latest version of the package. And hopefully you remember from last year that the --save option tells NPM to update the package.json file for the project.
Now, in the src folder, add a file named router.js then put this code in it:
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from "./views/HomeView.vue";
import BlogListView from "./views/BlogListView.vue";
import BlogPostView from "./views/BlogPostView.vue";
const routes = [
{ path: "/", component: HomeView, name: "home" },
{ path: "/blog", component: BlogListView, name: "blog-list" },
{
path: "/blog/:postId",
component: BlogPostView,
name: "blog-post",
props: true
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
There's a lot going on in this file, but it's an important one that you'll be working with a lot in Vue projects. Here are some notes:
- The first line imports two of the modules that are part of the vue-router package that we just installed
- The next few lines import our view/page components.
- An array named routes is declared and it has 3 objects in it (one for each 'route' in our app).
- The first route will use the path (url) of / which will be the app's home page. When you run the app, you'll be able to navigate to this page/view by entering a URL of localhost:8080/. And you'll see the HomeView because the route's component property is set to HomeView.
- The next route object specifies a path (url) of /blog (so you can navigate to this 'page' by entering localhost:8080/blog in your browser). This route will display the BlogList component.
- The third route is a bit more complicated, it has a variable in the path (do you remember route variables/parameters from our Node final project last year?). The variable name is postId and it will be the id number of the specific blost post to display. So, if you enter a url of localhost:8080/blog/1 the postId parameter would be set to 1, which indicates that the app will display the blog post that has an id of 1. We'll be digging deeper into this route soon. This route also includes a props property, which is set to true. This will allow us pass the postId variable into the BlogPostView component as a prop, as you'll soon see.
- After the 'routes' array, we call the createRouter function, which we imported on line 1, and pass in the routes array. The function returns an object that represents the router for our app.
- The last line exports the router object so that we can import it into other files.
One more note, you may have noticed that each 'route' object in the routes array has a name property. This is optional, but it can make your app more managable, we'll talk more about this in the future.
Now that we have configured the router for our project, we can import it into main.js. Update main.js to look like this:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
We've added an import statement that imports the router. And we've added the use() method call when we create the app. If you want to add additional features to your Vue app you can call the use() method and pass in the additional feature (you might remember from the final project from last year that you can do something very similar to this with Express apps).
Now we'll overhaul App.vue to make use of the router. Replace everything in App.vue with the following code:
<template>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/blog">Blog</router-link>
</nav>
<router-view></router-view>
</template>
<script>
export default {
name: "App",
};
</script>
<style>
/*we can add CSS code here later*/
</style>
We'll go over some of the details in this code in a minute. But first, run the app. You should see a nav bar with two links in it. If you click on the Blog link, you'll be directed to the /blog route path, which displays the BlogListView component. If you click on the Home link, you'll be directed to the / route path, which displays the HomeView component.
This is all working because we've added router-link elements and a router-view element to the the template of the root component.
You can also visit routes by entering them directly into the URL bar of the browser. For example, paste this link into the browser: http://localhost:8080/blog/1. This will display the BlogPostView, which we'll working on in the next step. But note that this URL would presumable display blog post with an ID of 1.
You can configure your app's navigation by using router-link elements. Note that you set the to attribute by setting the path that the link should navigate to. When the app is compiled, Vue converts the router-link elements into hyperlinks and when you click on one, it will take you to the component that is specified for the route. The component will be displayed inside the router-view element.
Here is a link to more information about the [https://router.vuejs.org/guide/](Vue router).
Here is a [https://www.vuemastery.com/vue-router?coupon=ROUTER-DOCS&via=eduardo](cheat sheet) on the Vue router.
The BlogPostView component will be a dynamic one, which means that we can use it to display any of our blog posts.
Let's review the routes we have set up for our app (in routes.js):
const routes = [
{ path: "/", component: HomeView, name: "home" },
{ path: "/blog", component: BlogListView, name: "blog-list" },
{
path: "/blog/:postId",
component: BlogPostView,
name: "blog-post",
props: true
}
];
For this step we are interested in the third route. Note that it's path property is set to /blog/:postId. The :postId is a variable (aka a parameter), the Vue framework uses the colon at the beginning to declare variables in route paths (this is a convention that is used by other frameworks too, such as Express). The value of this variable should be the ID of the blog post to be displayed variable. We'll see how we can make use of this variable in the BlogPostView component in just a minute.
Also note that a props property has been added to the route object, and that it is set to a value of true. Setting the props property to true for a route, it allows us to declare a prop named postId inside the BlogPostView component which will get set by the router when the component is displayed. This allows us to get a handle :postId variable in BlogPostView.
Add a postId prop to the BlogPostView component and display the propId in the template. The code for the component should look like this:
<template>
<h1>This is a blog post page</h1>
<h3>TODO: display blog post that has the id of: {{ postId }}</h3>
</template>
<script>
export default {
props:["postId"]
}
</script>
Now run the app and paste this URL into the broswer:
http://localhost:8080/blog/5
Try changing the 5 to any number and you'll see how route variables work. The allow the app to extract. Actually, you could pass strings as route variables too, try this URL: http://localhost:8080/blog/foo.
One thing that can be confusing to beginners of Vue is that there are different ways to declare props in components. We are using what's known as the 'array syntax', in which you declare all of the props by adding their names to an array. If we wanted to add another prop to the BlogPostView component, we could do it by adding it's name to props array, like so:
props: ["postId", "someOtherProp"]
Another way to declare props for a component is known as the 'object syntax', and this allows you to specify the data type for each prop, and many other things, including validating the value of a prop.
For now, we'll stick to the simple 'array syntax' for declaring props, but we may see the object syntax in later projects.
For more information, check out the official documentation on Vue props.
Add a 404 page to the site.
Let's do one more thing before we wrap up this activity. We'll create a 404 page which will be displayed by our Vue app if an invalid path is entered into the URL bar of the browser (a path that is not specified in the 'routes' array in the router.js file).
Create a file in the views folder and name it NotFoundView.vue. Then put this code in it:
<template>
<h1>Sorry we can't find that page</h1>
</template>
Now, in router.js, import the NotFoundView component like so:
import NotFoundView from "./views/NotFoundView.vue";
And add this object to the routes array (don't forget to add a comma after the previous route object in the array):
{ path: '/:catchAll(.*)', component: NotFoundView }
Note that the path uses Vue's catchAll wild card, which will match any path entered into the URL bar of the browser, and note that this route will display the NotFoundView component.
Run the app to try it out:
npm run serve
Make sure to enter a path into the URL bar that doesn't exist, you should see the NotFoundView component.
Now that we've got all the routes set up for our app, we can move on to other things.