Working with placeholders in a JSS Vue.js sample app
Placeholders are vital components in any application built with JSS.
Refer to Page composition in JSS apps using Sitecore data for more information about how Sitecore layouts, placeholders, and renderings work and how JSS leverages them.
This topic describes the techniques you can use to work with placeholders when developing Vue.js applications with JSS.
Basic placeholder technique
The most basic, and most common way to add a placeholder is to use thePlaceholder
component:
<template>
<div>
<h1>My App</h1>
<sc-placeholder name="jss-main" :rendering="route" />
</div>
</template>
<script>
import { Placeholder } from '@sitecore-jss/sitecore-jss-vue';
export default{
name: 'MyComponent',
props: {
route: {
type: Object
}
},
components: {
ScPlaceholder: Placeholder,
}
}
</script>
The name
is the key of the placeholder you are exposing, and the rendering
is the current Sitecore-provided route/layout data, or parent component data if you are exposing a placeholder from within another component.
The Placeholder
component does not render a wrapper around child components. This is because of Vue's fragment technique.
SitecoreJssPlaceholderPlugin technique
This technique allows you to get an array of the Vue components in a placeholder attached as a computed property to the component instance that exposes the placeholder.
This technique enables very powerful manipulation techniques that would be nearly impossible in traditional Sitecore development.
To use this technique, you must install the SitecoreJssPlaceholderPlugin
Vue plugin provided in the sitecore-jss-vue
package, as follows:
import { createApp, createSSRApp } from 'vue';
import { SitecoreJssPlaceholderPlugin } from '@sitecore-jss/sitecore-jss-vue';
import componentFactory from './path/to/your/componentFactory.js';
const app = isSSR ? createSSRApp(vueOptions) : createApp(vueOptions);
// The plugin uses a "global" componentFactory, but you can override
// the `componentFactory` at the component level if needed.
app.use(SitecoreJssPlaceholderPlugin, { componentFactory });
To use the plugin in your component, add the withPlaceholder
property on the component:
<template>
<div>
<component v-for="(tab, index) in tabs" :is="tab" :key="`tab${index}`" />
</div>
</template>
<script>
export default {
name: 'MyComponent',
props: {
rendering: {
type: Object,
},
},
withPlaceholder: {
placeholders: 'tabs',
},
};
</script>
In the preceding example, the SitecoreJssPlaceholderPlugin
plugin uses a mixin that attaches to the beforeCreate
hook to look for the withPlaceholder
property on a component definition. The plugin then uses the value provided by the withPlaceholder
property to find the specified placeholder data, for example, tabs
, in the rendering
property data. The plugin then creates a computed property on the component, using the name of the placeholder as the property name by default, and assigns an array of all the Vue components for that placeholder to the computed property. This allows you to use the dynamic component feature in Vue.js to render the placeholder components in your template.
When you iterate over the component array in your template, Vue renders the components where you emit them, or you can achieve this using the Placeholder
component. If you emit the placeholder contents with this technique, the placeholder contents have no wrapping component and render inline. This is very useful when you are using Vue libraries that are based on a specific component hierarchy, such as vue-carousel
. For example, consider the following code sample:
<carousel>
<slide>Slide 1 Content</div>
<slide>Slide 2 Content</div
<slide>Slide 3 Content</div
</carousel>
In the preceding sample, it is expected that the component hierarchy is carousel -> slide
. If you want to add the slide
components using a placeholder, so that Sitecore could define them, you can use the Placeholder
component sc-placeholder
to achieve the same hierarchy.
<carousel>
<sc-placeholder name="jss-slides" :rendering="rendering" />
</carousel>
Component transformation
If you want a completely flat component hierarchy such as Carousel->Slide
in the previous example, you can take advantage of the computed property being an array to transform the child components with a wrapper using an inline template. When using this technique, the child Sitecore components can be completely unaware of the wrapping and render only their content for clean separation of concerns:
<template>
<carousel>
<template v-for="(slide, index) in $options.computed.slidesPlaceholder">
<!-- this `v-if` is important, as it helps prevents breakage of the carousel markup when using Sitecore Experience Editor -->
<component v-if="slide.isxEditorComponent" :is="slide" />
<!-- wraps _all_ child components in a <slide> component -->
<slide v-else :key="`slide${index}`">
<component :is="slide" />
</slide>
</template>
</carousel>
</template>
<script>
export default {
name: 'ContainerComponent',
props: {
rendering: {
type: Object,
},
},
withPlaceholder: {
// you can alias the computed prop name for the placeholder or pass an array of placeholders
placeholders: {
placeholder: 'slides',
computedPropName: 'slidesPlaceholder',
},
},
};
</script>
Slot API
If you prefer the slot pattern instead of the dynamic computed property offered by the SitecoreJssPlaceholderPlugin
plugin, JSS placeholders have a default slot scope. Using the <Placeholder>
component's default
scoped slot, you can take over the rendering of the placeholder contents in the same way as with the dynamically computed property.
The following example illustrates how to render the components array using the default scoped slot.
<template>
<sc-placeholder :rendering="rendering" name="jss-main">
<div v-slot="{components, isEmpty}">
<!--
The placeholder is considered "empty" if it contains no assigned presentation components.
However, if in Experience Editor (EE) mode, the placeholder may still contain EE-generated components.
-->
<template v-if="!isEmpty">
<template v-for="(component, index) in components">
<!--
If the component is a presentation component, i.e. not an EE-generated component,
we may want to wrap it in another component or markup.
-->
<div v-if="!component.isxEditorComponent" :key="index">
<component :is="component" />
</div>
<!-- Else the component is an EE-generated component, render it as-is -->
<component v-else :is="component" :key="index" />
</template>
</template>
<template v-else>
<!--
In EE-mode, we still want to render "empty" placeholders so that content authors can
assign components to them.
-->
<component v-for="(component, index) in components" :is="component" :key="index" />
</template>
</div>
</sc-placeholder>
</template>
<script>
import { Placeholder } from '@sitecore-jss/sitecore-jss-vue';
export default {
name: 'MyComponent',
props: {
rendering: {
type: Object,
}
},
components: {
ScPlaceholder: Placeholder,
},
};
</script>
Direct placeholder introspection
If you want to have complete control over the rendering of a placeholder, you can access the placeholder's data, as opposed to its components, and traverse it using $props.rendering
to discover child component data and fields, because the placeholder hierarchy is a JavaScript object. You can render this data however you choose.
A good use case for this is components such as tabs that you might want to render pieces of in multiple places (the tab title, and the tab contents). For example:
<template>
<div class="tabs-container">
<div class="tabs-heading">
<ul>
<li v-for="(tab, index) in rendering.placeholders['jss-tabs']" :key="`tab${index}`">
<button :class="`${index === activeTabIndex ? 'active' : null}`" v-on:click="activeTabIndex = index">
<sc-text :field="tab.fields.title" />
</button>
</li>
</ul>
</div>
<div class="tabs-content">
<component :is="activeTab" />
</div>
</div>
</template>
<script>
export default {
name: 'Tabs',
props: {
rendering: {
type: Object,
}
},
data() {
return {
activeTabIndex: 0,
};
},
withPlaceholder() {
return {
placeholders: [
{
placeholder: 'jss-tabs',
computedPropName: 'tabsPlaceholder',
},
],
};
},
computed: {
activeTab() {
return this.$options.computed.tabsPlaceholder && this.$options.computed.tabsPlaceholder[this.activeTabIndex];
},
},
}
</script>
Multiple placeholders
You can render multiple placeholders in your components using any of the previously described techniques. For example:
-
Using the basic technique:
RequestResponse<template> <div> <h1>My App</h1> <sc-placeholder name="jss-column-1" :rendering="rendering" /> <sc-placeholder name="jss-column-2" :rendering="rendering" /> </div> </template> <script> import { Placeholder } from '@sitecore-jss/sitecore-jss-vue'; export default{ name: 'MyComponent', props: { rendering: { type: Object } }, components: { ScPlaceholder: Placeholder, } } </script>
-
Using
SitecoreJssPlaceholderPlugin
:RequestResponse<template> <div> <div class="col-1"> <component v-for="(comp, index) in this.$options.computed.phColumn1" :is="comp" :key="`comp${index}`" /> </div> <div class="col-2"> <component v-for="(comp, index) in this.$options.computed.phColumn2" :is="comp" :key="`comp${index}`" /> </div> </div> </template> <script> export default { name: 'MyComponent', props: { rendering: { type: Object, }, }, withPlaceholder: { placeholders: [ { placeholder: 'jss-column-1', computedPropName: 'phColumn1', }, { placeholder: 'jss-column-2', computedPropName: 'phColumn2', }, ], }, }; </script>
Enhancing placeholders
The Placeholder component supports a number of customizations that can enhance your experience.
Error components
If a rendering error (that is, an exception) occurs in a placeholder, it displays an error component instead of the placeholder contents and logs the error details to the console. You can customize this component by substituting your own Vue component using the errorComponent
property.
Dealing with missing components
If a placeholder contains a rendering name that is unknown to the componentFactory
(for example, a back-end developer creates a Foo rendering and adds it to a page, but there is no Foo.js
file yet), the rendering is replaced with a MissingComponent
component defined in the missingComponentComponent
property of your Vue component. The default implementation is a simple message, but you can customize it by using your own Vue component on the missingComponentComponent
property.