@meeg/gridsome-source-kentico-kontent
v0.5.0
Published
Kentico Kontent data source plugin for Gridsome.
Downloads
1,189
Maintainers
Readme
@meeg/gridsome-source-kentico-kontent
A Kentico Kontent data source plugin for Gridsome that aims to support all of the main features of Kentico Kontent:
✔ Content (including all content element types)
✔ Taxonomy
✔ Assets
The plugin also provides additional features and extension points to ease working with your Kentico Kontent content in Gridsome. Please keep reading to learn about:
- How to get started with this plugin
- The object types and object models that are added to the Gridsome GraphQL schema by this plugin
- How to render Rich Text fields using Vue single file components that you define in your app
- How to customise routing of content and taxonomy objects
- How to work with Taxonomy in Gridsome
- How to work with Assets in Gridsome, and how to transform Asset URLs directly in your GraphQL queries
- How to create content models to allow you to customise how content from Kentico Kontent is represented as data in Gridsome
- The full list of plugin configuration options
Getting started
This getting started guide assumes that you have an existing Gridsome project, and that you want to add Kentico Kontent as a data source using this plugin. If you haven't yet created a Gridsome project, please follow the Gridsome getting started guide first and then come back here.
Install
Use your preferred package manager to add a dependency on @meeg/gridsome-source-kentico-kontent
to your Gridsome app, for example:
yarn add @meeg/gridsome-source-kentico-kontent
npm install @meeg/gridsome-source-kentico-kontent
Add and configure the plugin
Add @meeg/gridsome-source-kentico-kontent
to the plugins array in your gridsome.config.js
file, and configure the Kentico Kontent delivery client to fetch data from your project by specifying your project id in the plugin options:
plugins: [
{
use: '@meeg/gridsome-source-kentico-kontent',
options: {
deliveryClientConfig: {
projectId: process.env.KENTICO_KONTENT_PROJECT_ID
}
}
}
]
The above configuration assumes that you are using environment variables to manage parts of your project configuration that you want to keep private (or that can vary in different environments), but you could specify the project id directly in the plugin options if you want.
See the Gridsome docs for general advice on installing plugins and using environment variables.
🙋 This is the minimum configuration required for the plugin to function. Please see the configuration section for a complete list of all options available.
Configure Gridsome to resolve Rich Text fields using Vue single file components
First, configure Gridsome to use the "Runtime + Compiler" build of Vue because we will need the compiler to enable us to treat the Rich Text field content as a Vue component template.
In your gridsome.config.js
file:
module.exports = {
...
runtimeCompiler: true
...
}
Add a Vue single file component that will be used to render Rich Text fields. This component is a wrapper around v-runtime-template
and will be extended to resolve other components embedded inside your Rich Text fields e.g. content components/items, content links and assets.
Create a new .vue
file under your app components directory e.g. src/components/RichText.vue
:
<template>
<v-runtime-template :template="html" />
</template>
<script>
import VRuntimeTemplate from 'v-runtime-template';
export default {
components: {
VRuntimeTemplate
},
props: {
html: {
type: String,
required: true
}
},
methods: {
getNode: function(codename, id) {
const query = this.$static[codename];
if (typeof(query) === 'undefined') {
return null;
}
const edges = query.edges.filter(
edge => edge.node.id === id
);
if (edges.length === 1) {
return edges[0].node;
}
return null;
}
}
};
</script>
🙋 To learn how to extend this component to render content components/items, content links and assets (and how to opt-out of this approach if you don't like it), please see the section on rendering Rich Text fields.
Query and render your content
From this point on you are ready to work with your Kentico Kontent content as data in Gridsome 😎
If you are new to Gridsome, the following areas of the docs should help you get up and running with data:
Remember to use the GraphQL explorer to explore your schema, and test queries when in development mode.
🙋 Now we've covered the basics, please keep reading for a more in-depth discussion of the various features of this plugin.
Kentico Kontent GraphQL schema
The following types of data are sourced from Kentico Kontent and made available for querying via the Gridsome GraphQL data store:
Content objects
Content is available by querying against object types named using the codename of the content type they belong to converted to pascal case. For example:
- Given the codename
article
, the object type will be namedArticle
- Given the codename
landing_page
, the object type will be namedLandingPage
🙋 See the section on configuration for options on how to customise naming of content object types.
System fields
Every content object type shares a core set of fields that include:
- System fields provided by the Kentico Kontent delivery client
- Fields required by this plugin
- Fields required by Gridsome
As such, every content object has at least the following fields:
| Name | Type | Notes |
| --- | --- | --- |
| id
| String
| Kentico Kontent's id is used as the object's id |
| name
| String
| The name of the content item in Kentico Kontent |
| codename
| String
| The codename of the content item in Kentico Kontent |
| languageCode
| String
| The language codename of the content item |
| type
| String
| The codename of the content type in Kentico Kontent that this content item belongs to |
| typeName
| String
| The GraphQL object type name |
| isComponent
| Boolean
| true
if this object represents a content component/item; otherwise false
- see the section on content component objects for further details |
| date
| Date
| This is equal to the Kentico Kontent last_modified
date, but is named date
because that is the convention in Gridsome |
| slug
| String
| This is set to the value of the "URL slug" content element if one is defined on the content type that this content belongs to; otherwise the name
system field is slugified |
| path
| String
| This is the path generated by Gridsome and is based on the route defined for this object type; this field will be undefined
if no route has been specified |
Content element fields
As well as system fields, each object type contains fields that represent each of the content elements of the Kentico Kontent content type that the content belongs to.
These fields are named using the codename of the corresponding content element converted to camel case. For example:
- Given the codename
title
, the field will be namedtitle
- Given the codename
page_metadata_meta_title
, the field will be namedpageMetadataMetaTitle
If there is a collision of field name a positive auto-incremented integer will be added as a suffix to the field name.
For example, a Kentico Kontent content type has a content element with the codename
date
that will collide with the "system"date
field so it will receive the field namedate1
when added to the corresponding object type in the GraphQL schema.
All types of content element available in Kentico Kontent are supported and are represented in the object type definition as fields:
| Content element | Type | Notes |
| --- | --- | --- |
| Text | String
| Text elements are represented as string fields |
| Rich text | String
| Rich text elements contain HTML markup, but are represented as strings - see the section on rendering Rich Text fields for details of how to render Rich Text fields in your Gridsome app |
| Number | Number
| Number elements are represented as numbers |
| Multiple choice | Object
| Multiple choice elements are represented as objects containing two properties: name
, and codename
|
| Date & time | Date
| Date & time elements are represented as dates |
| Asset | Asset[]
| Asset elements contain an array of references to asset objects |
| Linked items | <Content>[]
| Linked items elements contain an array of references to the content objects they are linked to - the plugin assumes that the objects are all of the same object type |
| Custom element | String
| Custom elements are represented as strings |
| Taxonomy | <Taxonomy>[]
| Taxonomy elements contain an array of references to taxonomy objects belonging to the relevant taxonomy object type |
| URL slug | String
| URL slug elements are represented as strings - they are regarded as a system field and will always be assigned to a field called slug
, if present |
🙋 See the section on creating content models for details on how you can customise how content elements are translated to content object fields.
Content component objects
Content components and content items used in Rich Text fields are also added to object types in the Gridsome GraphQL schema.
The difference between content component objects and "regular" content objects is that content component objects are components of larger pieces of content and therefore not intended to be used in isolation i.e. have their own Gridsome template.
The isComponent
system field can be used to filter content component objects in content queries if required.
Item links
Item links are primarily used when rendering Rich Text fields so feel free to skip this section unless you really want to read it! 🤓
When editing content inside Rich Text elements in Kentico Kontent you can add links to other content items within your Kentico Kontent project. To resolve these links within your Gridsome app, the path
of the content item that has been linked to must be used as the URL of the link.
Gridsome generates the path
value for an object (based on a defined route
) when it is inserted into the GraphQL data store via the Data Store API.
To get the path
of an object in the GraphQL data store you must either:
- Already have a reference to the relevant content object; or
- Know the
id
andtypeName
of the content item that has been linked to, and form a GraphQL query to fetch an object with theid
from the object type that corresponds to thetypeName
If neither of the above are true, the ItemLink
object type can be queried using only the id
of the content item that has been linked to to find the path
of the corresponding content object.
The ItemLink
object type has these fields:
| Name | Type | Notes |
| --- | --- | --- |
| id
| String | Kentico Kontent's id and the id of the corresponding content object |
| typeName
| String | The GraphQL object type name of the corresponding content object |
| path
| String | The path of the corresponding content object |
🙋 See the section on configuration for options on how to customise naming of the ItemLink
object type.
Taxonomy objects
Taxonomy terms are available by querying against object types named using the codename of the taxonomy group they belong to converted to pascal case, and prefixed with "Taxonomy". For example:
- Given the taxonomy group codename
tag
, the object type will be namedTaxonomyTag
- Given the taxonomy group codename
article_topics
, the object type will be namedTaxonomyArticleTopics
🙋 See the section on configuration for options on how to customise naming of taxonomy object types.
Taxonomy term fields
Taxonomy term object types share a core set of fields that include:
- System fields provided by the Kentico Kontent delivery client
- Fields required by this plugin
- Fields required by Gridsome
As such, every taxonomy term object has the following fields:
| Name | Type | Notes |
| --- | --- | --- |
| id
| String
| The codename of the taxonomy term in Kentico Kontent is used as the object's id |
| name
| String
| The name of the taxonomy term in Kentico Kontent |
| slug
| String
| A slug is generated so that it can be used when specifying routes for object types |
| terms
| <Taxonomy>[]
| Taxonomy terms can have child terms, which are an array of references to taxonomy objects |
| path
| String
| If a route has been specified for this taxonomy object type Gridsome will generate a path for each object belonging to that type; otherwise the path will be undefined
|
🙋 For some examples of how taxonomy can be used in Gridsome, please see the section on working with taxonomy.
Asset objects
Assets are available by querying against an object type named Asset
.
🙋 See the section on configuration for options on how to customise naming of the asset object type.
Asset fields
Every asset object has the following fields provided by the Kentico Kontent delivery client:
| Name | Type | Notes |
| --- | --- | --- |
| id
| String
| The URL of the asset is used as the object's id |
| type
| String
| MIME type of the asset |
| size
| Number
| Size of the asset in bytes |
| description
| String
| Description of the asset |
| url
| String
| Absolute URL of the asset - this field accepts arguments allowing you to transform image URLs directly in your GraphQL queries |
| width
| Number
| Width of the image in pixels, if the asset is an image |
| height
| Number
| Height of the image in pixels, if the asset is an image |
🙋 For some examples of how assets can be used in Gridsome, please see the section on working with assets.
Rendering Rich Text fields
A Rich Text field is a string containing HTML markup, and that HTML markup can contain standard HTML elements as well as:
- Anchor links to other content that require resolving the link URL to the actual URL within your application
- Assets such as images that may require some flexibility in rendering (such as the use of lazy loading and/or
srcset
andsizes
) - Custom elements that represent content components
As briefly touched on in the getting started guide, the recommended way to render Rich Text fields when using this plugin is to use a Vue single file component to represent a Rich Text field, which will:
- Compile the HTML markup as a dynamic template using
v-runtime-template
; and - Allow you to write other Vue single file components to represent content components/items, content links and assets
The following sub-sections detail how to implement the above, and assume that you have already created the RichText
component outlined in the getting started section.
Rendering content links in Rich Text fields
First add a query to your RichText
component inside a <static-query>
block that will fetch all ItemLink
objects. The alias for allItemLink
must be item_link
as shown below:
query RichText {
item_link: allItemLink {
edges {
node {
id,
path
}
}
}
}
If you already have a
RichText
query you can add theitem_link
alias and fields alongside other aliases.
Next you must create an ItemLink
Vue single file component that has a node
prop, and will render the link appropriately. The "shape" of the node
prop object will match the node defined on the item_link
query. The component must also have a slot
via which the link text will be passed. For example:
<template>
<g-link :to="node.path">
<slot />
</g-link>
</template>
<script>
export default {
props: {
node: {
type: Object,
required: true
}
}
};
</script>
Finally, you must add the ItemLink
component to your RichText
component:
<script>
import VRuntimeTemplate from 'v-runtime-template';
import ItemLink from '~/components/ItemLink.vue';
export default {
components: {
VRuntimeTemplate,
ItemLink
},
props: {
html: {
...
}
},
methods: {
getNode: function(codename, id) {
...
}
}
};
</script>
Now if the Rich Text field HTML passed in the html
prop of the RichText
component contains any item-link
components, those components have a node
attribute that will call the getNode
method with a codename
of item_link
, and an id
that matches the content they are linking to. If an object with a matching id
is found in the collection, it will be passed to the item-link
component's node
prop, otherwise null
will be passed.
🙋 See the section on configuration for options on how to customise component names.
Rendering assets in Rich Text fields
First add a query to your RichText
component inside a <static-query>
block that will fetch all Asset
objects. The alias for allAsset
must be asset
as shown below:
query RichText {
asset: allAsset {
edges {
node {
id,
url(width: 1200, format: "webp"),
placeholderUrl: url(width: 50, format: "webp")
description
}
}
}
}
If you already have a
RichText
query you can add theasset
alias and fields alongside other aliases.
Next you must create an Asset
Vue single file component that has a node
prop, and will render the asset appropriately. The "shape" of the node
prop object will match the node defined on the asset
query. For example:
<template>
<v-lazy-image
:src="node.url"
:src-placeholder="node.placeholderUrl"
:alt="node.description"
/>
</template>
<script>
import VLazyImage from 'v-lazy-image';
export default {
components: {
VLazyImage
},
props: {
node: {
type: Object,
required: true
}
}
};
</script>
<style scoped>
img {
width: 100%;
}
.v-lazy-image {
filter: blur(5px);
transition: filter 1.6s;
will-change: filter;
}
.v-lazy-image-loaded {
filter: blur(0);
}
</style>
Unfortunately Gridsome's g-image component doesn't appear to work with
v-runtime-template
so the example above is using v-lazy-image, but you can try whatever you wish in your own component!
Finally, you must add the Asset
component to your RichText
component:
<script>
import VRuntimeTemplate from 'v-runtime-template';
import Asset from '~/components/Asset.vue';
export default {
components: {
VRuntimeTemplate,
Asset
},
props: {
html: {
...
}
},
methods: {
getNode: function(codename, id) {
...
}
}
};
</script>
Now if the Rich Text field HTML passed in the html
prop of the RichText
component contains any asset
components, those components have a node
attribute that will call the getNode
method with a codename
of asset
, and an id
that matches the asset they represent. If an object with a matching id
is found in the collection, it will be passed to the asset
component's node
prop, otherwise null
will be passed.
🙋 See the section on configuration for options on how to customise component names.
Rendering content components/items in Rich Text fields
First add a query to your RichText
component inside a <static-query>
block that will fetch all <Content>
objects. The alias for all<Content>
must match the codename of the Kentico Kontent content type that the <Content>
belongs to:
In the following examples, we will create a component to render code snippets, so the
<Content>
object type isCodeSnippet
and content type codename iscode_snippet
:
query RichText {
code_snippet: allCodeSnippet {
edges {
node {
id,
code,
language {
name
}
}
}
}
}
If you already have a
RichText
query you can add thecode_snippet
alias and fields alongside other aliases.
Next you must create a CodeSnippet
Vue single file component that has a node
prop, and will render the asset appropriately. The "shape" of the node
prop object will match the node defined on the code_snippet
query. For example:
<template>
<pre class="code-snippet">{{ node.code }}</pre>
</template>
<script>
export default {
props: {
node: {
type: Object,
required: true
}
}
};
</script>
Finally, you must add the CodeSnippet
component to your RichText
component:
<script>
import VRuntimeTemplate from 'v-runtime-template';
import CodeSnippet from '~/components/CodeSnippet.vue';
export default {
components: {
VRuntimeTemplate,
CodeSnippet
},
props: {
html: {
...
}
},
methods: {
getNode: function(codename, id) {
...
}
}
};
</script>
Now if the Rich Text field HTML passed in the html
prop of the RichText
component contains any code-snippet
components, those components have a node
attribute that will call the getNode
method with a codename
of code_snippet
, and an id
that matches the code snippet they represent. If an object with a matching id
is found in the collection, it will be passed to the code-snippet
component's node
prop, otherwise null
will be passed.
🙋 See the section on configuration for options on how to customise component names.
Opting-out of this approach
Although the approach to Rich Text outlined above is recommended it may not be for everybody or every application so it is possible to "opt-out" by setting certain option values to null
in the plugin configuration. The following options are all keys of contentItemCong.richText
:
| Option key | Behaviour when set to null
|
| --- | --- |
| wrapperCssClass
| Prevents any transformation of Rich Text HTML elements to components |
| itemLinkSelector
| Content links are not transformed to item-link
components |
| assetSelector
| Assets are not transformed to asset
components |
| componentSelector
| Content components/items are not transformed to <content>
components |
For example:
plugins: [
{
use: '@meeg/gridsome-source-kentico-kontent',
options: {
...
contentItemConfig: {
...
richText: {
wrapperCssClass: null
}
...
}
...
}
}
}
If you opt out of this approach you can use features of the Kentico Kontent delivery client to resolve content links and content components/items, but not assets. These features of the delivery client are exposed by this plugin when creating content models.
🙋 See the section on configuration for options on how to customise Rich Text transformation.
Routing
Routing in Gridsome is defined in the templates section of gridsome.config.js
. All routing must be explicitly configured in your config - there is no default routing in place.
Templates can be used for all content types and taxonomy groups in your project.
Routes are not resolved in any particular order so you may wish to avoid setting a route such as
/:slug
as this could take precedence over and conflict with other routes.
Content routing
Any route permitted by Gridsome's templates feature can be used for content types, but a common route pattern would be:
/{codename}/:slug
Where:
codename
is the "slugified" codename of the Kentico Kontent content type that the content belongs to; andslug
is the system field named "slug"
If you do not wish for a content type to have a route (and therefore no path i.e. to not represent a navigable page) then you can decline to specify a template for it in gridsome.config.js
.
If you have changed how content item type names are generated through this plugin's configuration options, remember to use the actual type name of the object in Gridsome's GraphQL schema when defining your templates.
Taxonomy routing
Taxonomy groups can be routed in the same way as content types using templates. A common route pattern for a taxonomy group would be:
/{codename}/:slug
Where:
codename
is the "slugified" codename of the taxonomy group; andslug
is the taxonomy term field named "slug"
The slug
field is added to each taxonomy term object by this plugin and is generated like so:
- The
codename
of the taxonomy term is slugified - If the term belongs to a "parent" term, the parent's slug is prepended to the current term's slug in the format
{parent-slug}/{slug}
If you do not wish for a taxonomy group to have a route (and therefore no path i.e. to not represent a navigable page) then you can decline to specify a template for it in gridsome.config.js
.
If you have changed how taxonomy group names are generated through this plugin's configuration options, remember to use the actual type name of the object in Gridsome's GraphQL schema when defining your templates.
🙋 See the next section for some examples of how you can work with taxonomy in Gridsome.
Working with Taxonomy in Gridsome
The Gridsome documentation describes how to create a taxonomy page template to display a Tag
object and the Post
objects that reference that Tag
.
To achieve this with the Kentico Kontent plugin you will need to ensure that you first set up routing for the taxonomy group that you want to list (i.e. your equivalent of Tag
from the Gridsome example), and then you can follow along with the documented approach.
The plugin will take care of adding the required references between Taxonomy objects (i.e. your equivalent of
Tag
from the Gridsome example) and Content objects (i.e. your equivalent ofPost
from the Gridsome example) via any Taxonomy content elements defined on the the Content object's content type in Kentico Kontent.
Other Taxonomy scenarios
Below are a couple of examples of other things you can do with Taxonomy in Gridsome.
To do the inverse of what is documented and create a template to display a Post
object and the Tag
objects that the Post
references (for example to list tags for a post), you can use a query like the below:
query Post ($id: String!) {
post (id: $id) {
title,
tags {
name,
path
}
}
}
If you wanted to list all Tag
objects including a count of how many Post
objects reference each tag (for example in a "tag cloud" component), you can use a query like the below:
query Tags {
tags: allTag {
edges {
node {
name,
path,
belongsTo {
totalCount
}
}
}
}
}
Working with Assets in Gridsome
Assets are represented by the Asset
object type in the Gridsome GraphQL schema and working with assets is largely the same as working with any other object type. The only "special" thing about Asset
objects is that the url
field accepts arguments that allow you to specify image transformations via a custom field resolver that uses the ImageUrlBuilder
class provided by the Kentico Kontent delivery client.
The arguments accepted are (omit any of the arguments if you do not wish to use it):
| Name | Type | Equivalent ImageUrlBuilder
function name | Notes |
| --- | --- | --- | --- |
| width
| Int
| withWidth
| Specify the desired width in pixels |
| height
| Int
| withHeight
| Specify the desired height in pixels |
| automaticFormat
| Boolean
| withAutomaticFormat
| true
if you wish to automatically use webp
format, and fallback to the asset's source format |
| format
| String
| withFormat
| Accepts any of these values: gif
, jpg
, jpeg
, pjpg
, pjpeg
, png
, png8
, webp
|
| lossless
| Boolean
| withCompression
| If true
use lossless compression; if false
use lossy compression |
| quality
| Int
| withQuality
| Specify a value in the range of 0 to 100 |
| dpr
| Int
| withDpr
| Specify a value in the range of 1 to 5 |
There are currently no arguments that are equivalent to the following functions of the
ImageUrlBuilder
:
withCustomParam
withRectangleCrop
withFocalPointCrop
withFitMode
You can use it in a GraphQL query like the below:
query Assets {
assets: allAsset {
edges {
node {
id,
name,
url(width: 1200, format: "webp"),
placeholderUrl: url(width: 50, format: "webp"), # You can use aliases and supply different arguments to fetch multiple transformed URLs per query
type,
size,
description,
width,
height
}
}
}
}
Creating content models
The default behaviour of this plugin when translating content from Kentico Kontent to objects in the Gridsome GraphQL data store should hopefully be sufficient in the majority of cases - a goal of the plugin is to allow consumers to get up and running with as little configuration as possible. However, the plugin does provide an extension point should you find yourself in a position where you want to modify the default behaviour.
Extending the translation of
Taxonomy
andAsset
objects is not currently supported as those types are essentially comprised only of "system" fields and cannot be extended in Kentico Kontent; compared to content types that also have "system" fields, but are designed to be extended in Kentico Kontent by adding content elements.
The majority of the work required to translate content from Kentico Kontent to the Gridsome GraphQL data store is performed via a custom ContentItem
model that is automatically passed to the delivery client as a type resolver.
The default behaviour is to use the same content model for all Kentico Kontent content types. This content model is represented by the GridsomeContentItem
class, which is a sub-class of ContentItem
.
To modify the default behaviour you can create a new class that extends GridsomeContentItem
and register it as the type resolver for one or more Kentico Kontent content types.
Extending GridsomeContentItem
For demonstration purposes we will use an example where we want to manipulate content data as it is inserted into Gridsome's data store so that we don't have to keep applying some logic on the data each and every time we use it within our application.
This is our scenario:
- There is a Kentico Kontent content type called
Post
that has a codename ofpost
- The content type has a content element called
Date
with a codename ofdate
that can be used to manually specify the date that the post was posted, with the intention being to fall back to the system last modified date if noDate
value is specified - When this plugin runs it creates an object type in the Gridsome GraphQL schema named
Post
, but the field that represents theDate
content element is calleddate1
because it collides with the systemdate
field
The goal is to:
- Set the system
date
field to the value of theDate
content element, but only if a value has been set; otherwise leave thedate
field value as it is - Remove the
date1
field as it is redundant once thedate
field value has been resolved as above
To do this we must first create a custom content model somewhere in our application that extends GridsomeContentItem
and implements the desired behaviour:
const { GridsomeContentItem } = require('@meeg/gridsome-source-kentico-kontent');
class PostContentItem extends GridsomeContentItem {
// Override the `addFields` function - this is called after all "system" fields are set
async addFields(node) {
/*
Call the `addFields` function of the base class - we want the default behaviour to run
first, and then we will manipulate the data to enforce our custom behaviour
*/
await super.addFields(node);
this.ensureDateField(node);
return node;
}
ensureDateField(node) {
/*
`node.item` contains the data that will eventually end up as an object in the
Gridsome GraphQL data store
*/
const postDate = new Date(node.item.date1);
const lastModified = new Date(node.item.date);
// If a date has been provided, use it instead of the system date
if (!this.isNullDate(postDate)) {
node.item.date = postDate;
}
// Add the system date as a custom field i.e. one that does not correspond to a content element
node.item.lastModified = lastModified;
// Remove the redundant `date1` field
node.item.date1 = undefined;
}
isNullDate(date) {
if (typeof(date) === 'undefined') {
return true;
}
if (date === null) {
return true;
}
// If a date value is null in Kentico Kontent it can be parsed as a zero UTC date
return date.getTime() === 0;
}
}
module.exports = PostContentItem;
Now we have implemented our desired behaviour we need to register the class model as a type resolver.
Registering type resolvers
Type resolvers can be registered via the options exposed by this plugin. To do so you must add an item to the contentItemConfig.contentItems
object with a key matching the content type codename you wish to specify the type resolver for, and a value that references the content model class that you wish as the type resolver for that content type. For example:
const PostContentItem = require('./the/path/to/PostContentItem');
module.exports = {
...
plugins: [
{
use: '@meeg/gridsome-source-kentico-kontent',
options: {
...
contentItemConfig: {
...
contentItems: {
post: PostContentItem
}
...
}
...
}
}
}
...
}
Other scenarios
There could be any number of scenarios in which you may wish to extend GridsomeContentItem
, but here are a few more examples of ways in which you can extend content models.
Overriding field resolvers
The addFields
function seen earlier effectively loops through each content element defined on the field type and:
- Attempts to find and execute an instance function named
${fieldName}FieldResolver
wherefieldName
is the codename of the content element converted to camel case; and if none is found - Attempts to find and execute an instance function named
${fieldType}TypeFieldResolver
wherefieldType
is the type of the content element converted to camel case; and if none is found - Executes an instance function named
defaultFieldResolver
The function that is executed receives a node
and field
as arguments:
node
represents the data that will eventually be inserted into the Gridsome GraphQL data storefield
represents the content element data that has come from Kentico Kontent
The responsibility of the function is to set a value on the node
that will contain the data from the field
transformed into whatever format is deemed suitable for the Gridsome GraphQL data store.
Field resolvers can be used like this:
const { GridsomeContentItem } = require('@meeg/gridsome-source-kentico-kontent');
class PostContentItem extends GridsomeContentItem {
// This function will be used when resolving the "author" content element of this type
authorFieldResolver(node, field) {
const fieldName = field.fieldName;
const value = field.value;
// This is a pretty contrived example, but hopefully illustrates the point!
if (!value) {
value = 'Joe Bloggs';
}
this.addField(node, fieldName, value);
}
// This function will be used when resolving all "Multiple choice" content elements of this type
multipleChoiceTypeFieldResolver(node, field) {
const fieldName = field.fieldName;
// By default this value would be an object containing `name` and `codename` properties, but we just want `name`
const value = field.value.name;
this.addField(node, fieldName, value);
}
}
module.exports = PostContentItem;
Set a field value based on linked content items
We saw earlier that you can extend how field data is set within a content model by overriding the addFields
function. The addFields
function receives a node
object that represents the data from a single content item from Kentico Kontent transformed into a data structure that will be used to populate the Gridsome GraphQL data store.
In the previous example we used the item
property of the node
to get and set field values, but the node
object has other properties that can be used when manipulating field data.
For example, in this scenario:
- We have a
Post series
content type in Kentico Kontent with a codename ofpost_series
- The
Post series
content type has a "Linked items" content element calledPosts in series
with codenameposts_in_series
Posts in series
has a constraint requiring at least one linked item be set
The goal is to:
- Add a
lastUpdated
field to the correspondingPostSeries
object type in the Gridsome GraphQL schema (there is no corresponding content element on the Kentico Kontent content type) - Set the value of the
lastUpdated
field by getting the linked posts in itspostsInSeries
field, and using the most recentdate
value of the linkedPost
objects
const { GridsomeContentItem } = require('@meeg/gridsome-source-kentico-kontent');
class PostSeriesContentItem extends GridsomeContentItem {
async addFields(node) {
await super.addFields(node);
await this.setLastUpdatedField(node);
return node;
}
async setLastUpdatedField(node) {
// Find the `postsInSeries` field and then run it through a map function to return the most recent post date
const postsInSeriesFields = node.linkedItemFields
.filter(field => field.fieldName === 'postsInSeries');
const postLastUpdated = await Promise.all(
postsInSeriesFields.map(async field => this.getPostLastUpdated(field.linkedItems))
);
// Add the most recent post date as a new field called `lastUpdated`
const value = postLastUpdated[0];
// Calling the `addField` function will make sure there are no field name collisions
this.addField(node, 'lastUpdated', value);
}
async getPostLastUpdated(posts) {
const postDates = await Promise.all(
posts.map(async post => {
const node = await post.createNode();
const date = node.item.date;
return date;
})
);
const sortedPostDates = postDates.reduce((prevDate, currentDate) => {
return prevDate > currentDate ? prevDate : currentDate
});
return sortedPostDates;
}
}
module.exports = PostSeriesContentItem;
When you are inside an instance function such as
addFields
you also have access to all of the properties of a regularContentItem
such asthis.system
andthis._raw.elements
should you need them.
Custom Rich Text and Link resolvers
GridsomeContentItem
uses custom richTextResolver and urlSlugResolver functions to aid in the approach for rendering Rich Text fields, but if you decide to opt out of that approach you may want to provide your own resolver functions.
You can do so like this:
const { GridsomeContentItem } = require('@meeg/gridsome-source-kentico-kontent');
class PostContentItem extends GridsomeContentItem {
// We need to override the constructor of `GridsomeContentItem`
constructor(typeName, richTextHtmlTransformer) {
/*
Set our resolvers - N.B this `data` object mimics the object that you can pass to the `ContentItem` constructor
*/
const data = {
richTextResolver: (item, context) => {
return `<h3 class="resolved-item">${item.name.text}</h3>`;
},
urlSlugResolver: (link, context) => {
return { url: `/posts/${url_slug}` };
}
};
// Execute the super constructor, passing in your resolver functions
super(typeName, richTextHtmlTransformer, data);
}
}
module.exports = PostContentItem;
Logging and debugging
The plugin uses the debug library for logging. It can be used for debugging or just gathering logs about what the plugin is doing.
The plugin defines the following namespaces:
| Namespace | Description |
| --- | --- |
| gridsome-source-kentico-kontent
| Use this to log basic info about when the plugin starts and finishes its work |
| gridsome-source-kentico-kontent:source
| Use this to log information about the work that the plugin is doing |
| gridsome-source-kentico-kontent:delivery-client
| Use this to log information from the Kentico Kontent delivery client |
To have the log output to your console you will need to set a DEBUG
environment variable to include one or more of the above namespaces, for example (the actual command to set the environment variable may differ depending on the OS or shell in use):
DEBUG=gridsome-source-kentico-cloud,gridsome-source-kentico-cloud:*
Please read the debug docs for full usage instructions.
Configuration
The plugin exposes various configuration options that are split into the following top level objects:
plugins: [
{
use: '@meeg/gridsome-source-kentico-kontent',
options: {
deliveryClientConfig: {
// Options for the Kentico Kontent delivery client
},
contentItemConfig: {
// Options used when loading Kentico Kontent content data
},
taxonomyConfig: {
// Options used when loading Kentico Kontent taxonomy data
}
}
}
]
The only required option that must be set for this plugin to function is
deliveryClientConfig.projectId
as seen in the getting started section. All other options are set with default values that can be overridden by the consuming application.
deliveryClientConfig
options
These options are identical to the Kentico Kontent delivery client configuration options, with one exception:
| Key | Type | Default value | Notes |
| --- | --- | --- | --- |
| contentItemsDepth
| Number
| 3
| Sets the depth
parameter on content queries, which can be used to handle missing referenced linked items |
Please see the Kentico Kontent documentation for a description of all other available options, which include options for setting preview mode, secure mode, and language.
contentItemConfig
options
| Key | Type | Default value | Notes |
| --- | --- | --- | --- |
| contentItemTypeNamePrefix
| String
| ''
| If set, this value will be added as a prefix to generated content object type names |
| assetTypeName
| String
| 'Asset'
| If set, this value will be used as the asset object type name |
| itemLinkTypeName
| String
| 'ItemLink'
| If set, this value will be used as the item link object type name |
| contentItems
| Object
| {}
| Please see the creating content models section |
| richText
| Object
| | Please see the richText options section |
richText
options
| Key | Type | Default value | Notes |
| --- | --- | --- | --- |
| wrapperCssClass
| String
| 'rich-text'
| When used as a Vue template, Rich Text HTML must have a single root node and so the plugin wraps the HTML in a div with a class attribute set to this value; can be set to null
to opt out of the default approach to rendering Rich Text fields |
| componentNamePrefix
| String
| ''
| If set, this value will be added as a prefix to the component name used to render content components/items in Rich Text fields |
| componentSelector
| String
| 'p[data-type="item"]'
| This CSS selector is used to find elements that represent content components/items in Rich Text HTML; can be set to null
to opt out of the default approach to rendering content components/items in Rich Text fields |
| itemLinkComponentName
| String
| 'item-link'
| This value will be used as the component name used to render content links in Rich Text fields |
| itemLinkSelector
| String
| 'a[data-item-id]'
| This CSS selector is used to find elements that represent content links in Rich Text HTML; can be set to null
to opt out of the default approach to rendering content links in Rich Text fields |
| assetComponentName
| String
| 'asset'
| This value will be used as the component name used to render assets in Rich Text fields |
| assetSelector
| String
| 'figure[data-asset-id]'
| This CSS selector is used to find elements that represent assets in Rich Text HTML; can be set to null
to opt out of the default approach to rendering assets in Rich Text fields |
taxonomyConfig
options
| Key | Type | Default value | Notes |
| --- | --- | --- | --- |
| taxonomyTypeNamePrefix
| String
| 'Taxonomy'
| If set, this value will be added as a prefix to generated taxonomy object type names |