#Vue Components
If you are not a developer, you should skip this section. Vue Components are an extremely powerful tool for adding complex functionality to websites such as a checkout process, a modal image gallery, store item search and filtering, or blog search.
This documentation assumes you are familiar with Vue's syntax and are experienced and comfortable working with Components in MercuryCMS already. If so, let's get started!
#To Vue or Not to Vue
Most of the Components you will make in MercuryCMS will be Static Components. These are Components whose content is determined at Build time and baked into the Page.
As a rule, if you can add the functionality you need as a Static Component, do it as a Static Component. Vue Components require processing on the frontend and can slow the site down and make it harder to maintain if overused.
There will be many times, however, that it is much simpler and easier to use a Vue Component. For example:
- You need multiple Components on a Page that have complex internal state. Note that some basic internal state can be managed by adding attributes and classes (see the
<tab-list>
example in the Components documentation) - You need extensive DOM reactivity such as customizing a product where manually updating DOM would be confusing and hard to maintain
- You need two-way data binding for form fields
- You're building an extended-time user experience like purchasing tickets for a show that requires getting information from the user in multiple steps
- You need to manage multiple requests and responses with a live API to show content on a Page such as availability
#Vue Component Basics
To make a Vue Component, simply pick "Vue" in the "Type" dropdown in the Component's Markup tab. This will allow you to write your normal Vue <template>
, <style>
and <script>
tags in the Component's "Markup", "Style", and "Script" tags respectively.
Here's an example of a simple Vue Component written in Mercury:
.click-counter
button(@click="handleButtonClick") Click Me
.count You've clicked the button {{{ count }}} time{{{ count == 1 ? '' : 's' }}}!
.message(v-if="count > 10") You sure like clicking this button!
.click-counter
border 1px solid rgba(0, 0, 0, 0.1)
padding 16px
.count
margin-top 16px
export default {
data() {
return {
count: 0,
}
},
methods: {
// handle the user clicking the button
handleButtonClick() {
this.count++
},
},
}
#Static vs Frontend Data
You must use {{{ }}}
rather than {{ }}
to use values from Vue's state in the Component's markup. This is to avoid conflicting with MercuryCMS' static templating, however, all Vue attribute binding using :
is unchanged.
You will find there are a few MercuryCMS-specific functions and features to keep in mind when working with Vue Components that enhance Vue's functionality. You will also need to keep in mind what data is being staticly baked in at Build time, and what data is available on the frontend.
Avoid baking in large chunks of data. If you pass an entry from a Collection into a Vue Component, be sure to staticly access that data within the Component at Build time or include only the specific data you need in the frontend.
#Script Templating
Unlike Static Components, Vue Component scripts are added for each Component instance used on the Page. This means if you have 20 of the same Vue Component on the Page, that Page will have 20 versions of the script on it.
Each of these Components is a "root" Vue instance created using new Vue()
on the Page and targeting the Component's element. One advantage to this, is you can access the data
and attr
objects within the Component's script. This means you can do things like this:
.media-list
.media-item(v-for="mediaItem in mediaItems")
img(:src="mediaItem.url")
.info
.name {{{ mediaItem.name }}}
.caption {{{ mediaItem.caption }}}
.media-list
.media-item
display flex
padding 8px
border 1px solid rgba(0, 0, 0, 0.1)
& + .media-item
margin-top 8px
img
width 250px
height auto
border-radius 4px
.info
margin-left 16px
export default {
data() {
return {
mediaItems: {{ data.mediaItems }},
}
},
}
Attributes has a "Media IDs" Array Attribute with a default value of []
.
data.mediaItems = mediaList.filter(media => attr.mediaIds.includes(media._id)).map(media => {
return {
name: media.name,
caption: media.caption,
url: media.getImageUrl(250),
}
})
Then on the Home Page you could write:
.home-page
media-list(media-ids=["6266302056f8b6186cd7db0d", "6266302556f8b6186cd7db10", "6266302056f8b6186cd7db0e"])
Here's the resulting script.js
for the Page. You can see that the script itself was dynamically generated at Build time.
(async()=>{
const Vue = (await import("https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.min.js")).default;
new Vue({
el: document.querySelector("media-list[data-cid-1]"),
template: "<div class=\"media-list\"><div class=\"media-item\" v-for=\"mediaItem in mediaItems\"><img :src=\"mediaItem.url\"><div class=\"info\"><div class=\"name\">{{ mediaItem.name }}</div><div class=\"caption\">{{ mediaItem.caption }}</div></div></div></div>",
data() {
return {
mediaItems: [{
name: "Blue",
caption: "This image is blue.",
url: "/media/6266302056f8b6186cd7db0d/medium.webp"
}, {
name: "Red",
caption: "This image is red.",
url: "/media/6266302056f8b6186cd7db0e/medium.webp"
}, {
name: "Yellow",
caption: "This image is yellow.",
url: "/media/6266302556f8b6186cd7db10/medium.webp"
}]
}
}
})
}
)();
In this way, you can Template in any values available at Build time into the script. For more information, see Templating.
#Static Subcomponents
You can use Static Components defined in MercuryCMS in your Vue markup, but be aware they are baked into the Vue Component at build time. This means if you want to do something like bind a dynamic prop to a Static Component, this will not have the effect of updating its style or content because that was all determined during the Build, not while running in the browser.
#Vue Subcomponents
If you want to use Vue Subcomponents, you can define each Vue Component in MercuryCMS and simply use them within another Vue Component. MercuryCMS will automatically create the components: {}
object for you, so you don't need to define it.
If you want to have dynamic props used by the Subcomponents, define those as you usually would. You can do all of the normal Vue Subcomponent things like dynamic props, emit and react to events, use refs, call child methods, use Vue's slot system, etc.
#Static Slots
Vue already has its own slot system. When you use slot
and template
within your Vue Components and Subcomponents, it will utilize that system. If you want to have a Page add content at Build time just like with Static Components, you can use Static Slots.
These work exactly like Slots in Static Components, where the content is determined at Build time and baked into the Vue Component's markup. All features are supported such as named and default Slots and default Slot content.
The parent uses <template slot="someSlotName">
to target either type of Slot. To define a Static Slot within a Vue Component, simply use <slot name="someSlotName" static>
instead of <slot name="someSlotName">
. You can use both Static Slots and Vue's slots within the same Component, but you should of course avoid naming them the same thing.
#Accessing Vue Component Methods
Vue Components are dynamic and will often have many methods defined within them that you may want to access from other Components on the Page. You can access those functions using the __vue__
key on the element provided by Vue.
For example. Consider this Page's markup:
.gallery-page
.gallery-page-content
.gallery-page-columns
each galleryImage in galleryImageList
.gallery-page-column
static-media.gallery-image(media-id=galleryImage.image sizes="300px" onclick=`document.querySelector('.static-media-viewer').__vue__.show("${galleryImage.image}")`)
static-media-viewer(media-ids=galleryImageList.map(galleryImage => galleryImage.image))
In this example, we show a list of images from the "Gallery Image" Collection and when you click them, it runs the show()
method on the Vue Component passing in the Media ID of the image that was clicked.
#Build Scripts
Component Build Scripts give you the ability to create assets needed by a Component during the Build. For example, you may want to make a Blog List Component with a search bar. To improve speed, you want to distill the searchable data into a JSON file and fetch it when the Page loads.
For this, you can use the addAsset()
function, which lets you define a file path and the file's content (which will automatically be JSON-encoded if it's an object or array).
The path to the file is implicitly created if it doesn't exist.
You would probably write a Build Script like this:
addAsset('component/blog-post-list/blog_post_index.json', {
categories: categoryList.map(category => {
return {
name: category.name,
slug: category.slug,
}
}),
blogPosts: blogPostList.map(blogPost => {
return {
title: blogPost.title,
slug: blogPost.slug,
featuredImage: blogPost.featuredImage,
featuredImageAltText: blogPost.featuredImageRef.altText || blogPost.featuredImageRef.name,
featuredImageWidth: blogPost.featuredImageRef.sizes[0].width,
featuredImageHeight: blogPost.featuredImageRef.sizes[0].height,
publishedAt: blogPost.publishedAt,
categorySlug: blogPost.categoryRef.slug,
}
})
})
#SEO
A Vue Component's Markup is sent within the Page's script.js
file. This means none of the Vue Component's Markup will be visible to search engines or any other SEO tools. This includes any Media, text content, and links.
To solve this problem, you can write special SEO Markup for your Vue Components that is only visible to search engines and SEO tools. SEO Markup is placed directly onto the Page, just at it is with regular Components, but there are a few key differences:
- SEO Markup is automatically given the style
display: none
so regular users don't see it. - The SEO Markup's main element is automatically given a
-seo
class name. For example, if your Vue Component is calledMy Component
, your main Vue element will be given the classmy-component
, and its special SEO Markup will be given the classmy-component-seo
. - Any additional classes you add when using the Vue Component on a Page, Layout, or within another Component do not apply to the SEO Markup element.
- SEO Markup does not support Slots.
- Templating of Attributes and other data works as usual.
- The SEO Markup element will be inserted directly before the Vue Component's main element.
Writing SEO Markup is simple and requires no styling as it's only ever seen by SEO tools. You can write a simple list of Media, text content, and links, although it is still important to group these items in a meaningful way.