# VuePress Tutorial 17 - IndexPost Layout
# What We're Doing
We're now ready to begin the development of the IndexPost
layout component. We'll be using the globally scoped $pagination
variable provided by the blog plugin Client API (opens new window) to access the pagination data. In this tutorial we'll be focusing on the post title and preview pagination data. To display the data on the pagination pages we'll be updating the IndexPost
layout component's template
tag.
Make sure you start the local development server which should be running at http://localhost:8080/ (opens new window) to see the changes we'll be making to the site. If the changes aren't appearing after you save them, then try restarting your local development server.
Be sure to add each block of code below one at a time to your project, then view the changes in the browser to get a better understanding of what each block is responsible for.
You can view all of the code in this tutorial by going to the tutorial-17
branch of the code-monkeys-blog-tutorials (opens new window) repository.
# Naming the Component
Before accessing and displaying the pagination data, we're going to first give the component a name and remove the created
lifecycle hook which we were using in the previous tutorial to log the pagination data to the console.
Here's the updated IndexPost.vue
file:
# Looping Over Pagination Pages
To display the pagination data we're going to loop over the $pagination.pages
property which if you remember from the previous tutorial VuePress Tutorial 16 - Pagination is an array of objects where each object contains data related to post pages that are accessible on the current pagination page.
# Using the v-for Directive
Since $pagination.pages
is an array that we want to loop over, we're going to use the v-for
directive to render a list of post pages based on the data within the array. The v-for
directive uses the following syntax v-for="item in items"
where items
is the array you want to loop over, and item
is an alias for the array element being iterated on. Here, items
corresponds to $pagination.pages
, and we'll use post
as the alias for the array element being iterated on. This means our v-for
directive will be v-for="post in $pagination.pages"
.
# Using the key Attribute
When using the v-for
directive the common best practice is to bind a key
attribute where each value given to the key
attribute should be unique. The key
attribute uses the following syntax :key="item.id"
where id
is a property with a unique value for every item
in the items
array.
You really only need to use the key
attribute when the rendered list relies on child component state or temporary Document Object Model (DOM) (opens new window) state, e.g., form input values . This means we don't actually need to include the key
attribute since we're currently just rendering a static list.
However, we're going to bind the key
attribute in case we ever need to use it in the future. To ensure the key
attribute has a unique value for each item in the list, we're going to use the key
property which is a unique value generated for each page object within the $pagination.pages
array. Here's what the key
attribute will look like in our case :key="post.key"
.
# Determining a Tag
Now we need to determine what tag we want to add our v-for
directive and key
attribute to. We're going to add them to a div
tag which will allow us to wrap the pagination data for each post in the list of post pages. Here's what the div
tag is going to look like after adding the v-for
directive and key
attribute <div v-for="post in $pagination.pages" :key="post.key"></div>
.
# Using a Root Element
Since the div
tag uses the v-for
directive, it's going to render multiple elements. This means we cannot use it as the root element, i.e., the first element in the template
tag because the template
tag can only have one root element. To resolve this issue we're going to wrap the div
tag that's using the v-for
directive within another div
tag which will serve as the root element.
# Adding the Loop
Here's what the IndexPost.vue
file should now look like:
If you any questions or want to learn more about the v-for
directive and the key
attribute then check out these resources:
- List Rendering (opens new window)
- Maintaining State (opens new window)
- key (opens new window)
- Does Vue v-for really need a key? (opens new window)
- Understanding the :key Attribute in Vue (opens new window)
# Entry Page HTML
After updating the IndexPost.vue
file with the code above, if you navigate to the entry page http://localhost:8080/posts/ (opens new window) you won't notice any changes on the page. This is because we've rendered div
tags without any of the pagination data inside of them.
If you navigate to the entry page, inspect the browser, and go to the Elements
tab, the HTML for the body
tag should now look something like this:
Notice how the entry page consists of two div
tags wrapped inside of the div
tag that's being used as the root element of the template
tag. Each of those div
tags inside of the parent div
tag corresponds to a post page that is accessible on the entry page.
# Page 2 HTML
If you navigate to the second page http://localhost:8080/posts/page/2/ (opens new window) you also won't notice any changes on the page. This is because we've rendered a div
tag without any of the pagination data inside of it.
The HTML for the body
tag for the second page should now look something like this:
Notice how the second page consists of one div
tag wrapped inside of the div
tag that's being used as the root element of the template
tag. There is only one div
tag since we have one post page accessible on the second page.
# Post Titles
Before we can access the post title data using the $pagination.pages
property, we need to first add titles to the post files we created in the _posts
directory:
2020-07-03-example-page-1.md
2021-11-16-example-page-2.md
2022-05-08-example-page-3.md
# Adding Titles to Post Files
Since the post files are Markdown files, we can add titles to the files by adding a heading level one, e.g., # Heading Level 1
to each file which gets converted to the following HTML <h1>Heading Level 1</h1>
.
Here's what the post files look like after adding the titles:
If you have any questions or want to learn more about Markdown headings, then check out the Basic Syntax (opens new window) guide.
# Adding Titles to $page Variables
When VuePress (opens new window) encounters a heading level one in a Markdown file it automatically adds a title
property to the globally scoped $page
variable.
The page objects in the $pagination.pages
property are the same as the $page
variables used by the post pages which means each page object will now have a title
property that we can access in the IndexPost
layout component.
You can take a look at the Global Computed (opens new window) documentation to learn more about the $page
variable and other globally scoped variables.
# Displaying Post Titles
Now that we can access the post titles in the $pagination.pages
property, we're ready to render the post titles on the pagination pages.
We're going to display the post titles as h2
tags which we'll be wrapping inside of two div
tags. We'll be using the div
tags to add styling to the list of post pages in a future tutorial.
To display the post titles we'll be using Text Interpolation (opens new window) which allows us to use variables inside of HTML tags by using the "Mustache" syntax. The "Mustache" syntax consists of wrapping a variable inside of double curly braces.
We can access the title
property on each page object in our loop by using post.title
. We can then display this by using the text interpolation described above.
The IndexPost.vue
file should now look like this:
# Entry Page HTML
After updating the IndexPost.vue
file with the code above, if you navigate to the entry page http://localhost:8080/posts/ (opens new window) you should now see the post titles being displayed with some styling provided by the default theme (opens new window).
The HTML for the body
tag for the entry page should now look something like this:
# Page 2 HTML
If you navigate to the second page http://localhost:8080/posts/page/2/ (opens new window) you should now see one post title being displayed with some styling which again is being provided by the default theme (opens new window).
The HTML for the body
tag for the second page should now look something like this:
# Post Previews
The post preview data is a snippet of text taken from the beginning of a post which is used as an introduction to the post in the list of post pages. Before we can access the post preview data using the $pagination.pages
property, we need to first add previews to the post files we created in the _posts
directory:
2020-07-03-example-page-1.md
2021-11-16-example-page-2.md
2022-05-08-example-page-3.md
# Adding Previews to Post Files
Since the post files are Markdown files, we can use YAML (opens new window) frontmatter blocks in the files and define a custom variable preview
.
Here's what the post files look like after adding the preview
variables:
If you have any questions or want to learn more about YAML (opens new window) frontmatter blocks in VuePress (opens new window) then check these resources:
Number of Characters Used in Preview
The value for each post preview
variable should be within a preferred minimum and maximum number of characters. This will ensure the post previews in the list of post pages looks consistent.
# Adding Previews to $page Variables
When VuePress (opens new window) encounters a YAML (opens new window) frontmatter block in a Markdown file it automatically adds each variable as a property to the globally scoped $page.frontmatter
variable.
The page objects in the $pagination.pages
property are the same as the $page
variables used by the post pages which means each page object will now have a frontmatter.preview
property that we can access in the IndexPost
layout component.
You can take a look at the Global Computed (opens new window) documentation to learn more about the $page
variable and other globally scoped variables.
Content Excerpt
Instead of defining the custom variable preview
in the YAML (opens new window) frontmatter blocks of Markdown files, VuePress (opens new window) provides the ability to use a Content Excerpt (opens new window) by adding a <!-- more -->
comment to a Markdown file. Any content above the comment gets extracted and exposed as a globally scoped $page.excerpt
variable. This variable can then be used to render the list of post pages with excerpts for each post just like our custom variable preview
. I prefer to use the custom variable preview
because the <!-- more -->
comment takes all of the content above it which includes any HTML as opposed to using just the text of a post.
# Displaying Post Previews
Now that we can access the post previews in the $pagination.pages
property, we're ready to render the post previews on the pagination pages.
We're going to display the post previews as p
tags which we'll place underneath the parent div
tag of the h2
tag.
To display the post previews we'll be using Text Interpolation (opens new window) just like we did when displaying the post titles.
We can access the preview
property on each page object in our loop by using post.frontmatter.preview
.
The IndexPost.vue
file should now look like this:
# Entry Page HTML
After updating the IndexPost.vue
file with the code above, if you navigate to the entry page http://localhost:8080/posts/ (opens new window) you should now see the post previews being displayed with some styling provided by the default theme (opens new window).
The HTML for the body
tag for the entry page should now look something like this:
# Page 2 HTML
If you navigate to the second page http://localhost:8080/posts/page/2/ (opens new window) you should now see one post preview being displayed with some styling which again is being provided by the default theme (opens new window).
The HTML for the body
tag for the second page should now look something like this:
# IndexPost Styling
When viewing the pagination pages you probably noticed the list of post pages stretches across the entire width of the page. This styling doesn't look too good, so we're going to update the styling by adding the following class theme-default-content
to the outermost div
tag.
# Adding theme-default-content
The IndexPost.vue
file should now look like this:
# theme-default-content Styles
The theme-default-content
class is provided by the default theme (opens new window) and provides the following styles:
.theme-default-content:not(.custom)
is used to select tags that have a class oftheme-default-content
and that don't have a class ofcustom
by using the:not()
pseudo-class. This selector is defined by the default theme (opens new window) since other pages can use thetheme-default-content
class along with a class ofcustom
to apply different styles than the ones shown here. The homepage is an example of a page that uses thetheme-default-content
class along with a class ofcustom
to apply different styles.max-width: 740px;
sets the maximum width of thediv
tag to be740px
. If the content is greater than themax-width
, then the height of thediv
tag will automatically be changed. If the content is smaller than themax-width
, then themax-width
property has no effect.margin: 0 auto;
sets the margins for thediv
tag by setting the top and bottom margins to be0
and the left and right margins to a value ofauto
. The value ofauto
means the browser will automatically set the left and right margins to horizontally center thediv
tag.padding: 2rem 2.5rem;
sets the padding for thediv
tag by setting the top and bottom paddings to be2rem
and the left and right paddings to be2.5rem
.@media (max-width: 959px)
will apply the styles within it when the maximum width of the display area, e.g., the browser window is less than or equal to the provided value which in this case is959px
.padding: 2rem;
sets the padding for all of the sides of thediv
tag to be2rem
when the width of the display area is less than or equal to959px
.@media (max-width: 419px)
will apply the styles within it when the maximum width of the display area is less than or equal to the provided value which in this case is419px
.padding: 1.5rem;
sets the padding for all of the sides of thediv
tag to be1.5rem
when the width of the display area is less than or equal to419px
.
If you have any questions about the CSS discussed above, then check out these resources:
- CSS Tutorial (opens new window)
- :not() (opens new window)
- CSS max-width Property (opens new window)
- When Does margin: 0 auto Center? (opens new window)
- CSS @media Rule (opens new window)
To view these styles in the browser you can navigate to the entry page http://localhost:8080/posts/ (opens new window) or to the second page http://localhost:8080/posts/page/2/ (opens new window), inspect the browser, go to the Elements
tab, locate the div
tag with a class of theme-default-content
, and then go to the Styles
tab.
Use in the Default Theme
The theme-default-content
class is used on the global Content
component within the Page
and Home
components provided by the default theme (opens new window). If you look at the HTML on the homepage, you'll see the theme-default-content
class being used on a div
tag within the main
tag with a class of home
. We'll be discussing the global Content
component, the Page
component, and the Home
component in more detail in future tutorials.
# IndexPost Heading
We're now going to add a heading to the pagination pages. This heading will be displayed on each pagination page.
# Displaying the Heading
We're going to display the heading as an h1
tag which we'll be adding as the first child tag of the outermost div
tag.
The IndexPost.vue
file should now look like this:
# Entry Page HTML
After updating the IndexPost.vue
file with the code above, if you navigate to the entry page http://localhost:8080/posts/ (opens new window) you should now see the heading being displayed with some styling provided by the default theme (opens new window).
The HTML for the body
tag for the entry page should now look something like this:
# Page 2 HTML
If you navigate to the second page http://localhost:8080/posts/page/2/ (opens new window) you should now see the heading being displayed with some styling which again is being provided by the default theme (opens new window).
The HTML for the body
tag for the second page should now look something like this:
# Next Steps
In the next tutorial we'll be continuing the development of the IndexPost
layout component by using the $pagination
variable to add images to each post in the list of post pages.