npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@meeg/gridsome-source-kentico-kontent

v0.5.0

Published

Kentico Kontent data source plugin for Gridsome.

Downloads

1,189

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:


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 named Article
  • Given the codename landing_page, the object type will be named LandingPage

🙋 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 named title
  • Given the codename page_metadata_meta_title, the field will be named pageMetadataMetaTitle

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 name date1 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:

  1. Already have a reference to the relevant content object; or
  2. Know the id and typeName of the content item that has been linked to, and form a GraphQL query to fetch an object with the id from the object type that corresponds to the typeName

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 named TaxonomyTag
  • Given the taxonomy group codename article_topics, the object type will be named TaxonomyArticleTopics

🙋 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 and sizes)
  • 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 the item_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 the asset 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 is CodeSnippet and content type codename is code_snippet:

query RichText {
  code_snippet: allCodeSnippet {
    edges {
      node {
        id,
        code,
        language {
          name
        }
      }
    }
  }
}

If you already have a RichText query you can add the code_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; and
  • slug 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:

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 of Post 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 and Asset 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 of post
  • The content type has a content element called Date with a codename of date 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 no Date value is specified
  • When this plugin runs it creates an object type in the Gridsome GraphQL schema named Post, but the field that represents the Date content element is called date1 because it collides with the system date field

The goal is to:

  • Set the system date field to the value of the Date content element, but only if a value has been set; otherwise leave the date field value as it is
  • Remove the date1 field as it is redundant once the date 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 where fieldName 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 where fieldType 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 store
  • field 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 of post_series
  • The Post series content type has a "Linked items" content element called Posts in series with codename posts_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 corresponding PostSeries 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 its postsInSeries field, and using the most recent date value of the linked Post 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 regular ContentItem such as this.system and this._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 |