kontent-rich-text-for-vue

2.0.0 • Public • Published

Render rich text from Kentico Kontent in Vue

This package replaces the resolveHtml method provided by the Kontent Delivery SDK to display value of rich text elements.

Kentico Kontent allows you to write content in rich text elements, where you can do all sorts of formatting. What's more interesting, you can also compose your content from inline or reusable bits of content, referred to as components and linked items respectively.

When you don't need this package

  • You're not using these, you can stop reading and go on with your life.
  • You're using components or linked items and happy with the way the Kontent Delivery SDK handles rendering linked items.
  • You're not using Kentico Kontent with Vue.
  • You're not using Kentico Kontent at all.

When you want components to be components, though...

The richTextResolver extension point of the SDK does decent job, but I wanted my components to be proper components in the framework I was using. That happened to be Vue.js at the moment. So I wrote this tiny component that makes some of my dreams come true.

Installation

Pick the package up form npm:

npm install kontent-rich-text-for-vue --save

Basic usage

This package contains a component that accepts the raw rich text value from Kentico Kontent delivery API and your component that will replace each linked item or component found in the text.

Let's say we have content in items of content type blog_post where there are 2 elements:

  • title is a regular text that can be rendered as-is
  • content is a rich text and needs a bit more love
<template>
  <h1>{{blogPost.title.value}}</h1>
  <div> <!-- here you would normally put attribute v-html='blogPost.content.resolveHtml()' but no more -->
    <rich-text
      :content='blogPost.content.value'
      :linkedItemComponent='linkedItemComponent'
    />
  </div>
</template>

<script>
  import { RichText } from 'kontent-rich-text-for-vue';
  import LinkedItem from './components/linked-item.vue';

  export default {
    components: { RichText },
    computed: {
      blogPost: () => blogPost, // Pick it up from a vuex store or wherever you happen to keep it
      linkedItemComponent: () => LinkedItem
    }
  }
</script>

Now, my linked-item component will need to decide how various linked items and components appearing in the text should look like.

To make pairing of linked item components with linked item data easier, one more thing is exported from this package.

<script>
  import { linkedItemFactory } from 'kontent-rich-text-for-vue';
  import YoutubeVideo from './youtube-video.vue';
  import Quote from './quote.vue';
  
  const selectComponent = (contentTypeCodeName) => {
    switch (contentTypeCodeName) {
      case 'youtube_video':
        return YoutubeVideo;
      case 'quote':
        return Quote;
    }
  };

  export default {
    functional: true,
    render: (createElement, context) => {
      const {props} = context;

      const selectLinkedItemData = (itemCodeName) => {
        // Again, pick the blogPost from a vuex store or wherever.
        // I ended up having page 'provide' the blogPost  and then injected it here.
        return blogPost.linkedItems[itemCodeName]; 
      };      

      const component = linkedItemFactory(selectComponent, selectLinkedItemData);
      return createElement(component, {props})
    }
  }
</script>

With this rather complicated setup out of the way, we can start writing components for individual linked item types. The rich-text component passes to the linked items one prop and that is the item containing all the linked item data. In the case of our YouTube video, it contains a video ID and a short description.

<template>
  <figure>
    <iframe
      :src='`https://youtube.com/embed/${item.videoId.value}?rel=0`'
    >    
    </iframe>
    <figcaption>{{item.description.value}}</figcaption>
  </figure>
</template>

<script>
  export default {
    props: ['item']
  }
</script>

Links

Kontent allows you to add various types of links in your rich text. Out of these types, the content item link needs its URL resolved to render properly. The link itself contains only ID of the linked content item. Usually more data, such as codename, content type, or url slug of the particular content item is needed for construction of the URL. These values can be obtained from the links field in the rich text element value.

To make this resolution a bit easier, another factory is exported from this package. However, the factory needs to be injected with the url construction logic, and possibly with customized component to be rendered instead of the plain a element.

<template>
  <h1>{{blogPost.title.value}}</h1>
  <div>
    <rich-text
      :content='blogPost.content.value'
      :linkComponent='linkComponent'
    />
  </div>
</template>

<script>
  import { RichText, linkFactory } from 'kontent-rich-text-for-vue';
  import CustomEmailLink from './components/custom-email-link.vue';

  export default {
    components: { RichText },
    computed: {
      blogPost: () => blogPost, // Pick it up from a vuex store or wherever you happen to keep it
      linkComponent: () => linkFactory({
        getLinkComponent: linkType => {
          // Can be 'content-item' | 'asset' | 'email' | 'web-url'
          if (linkType === 'email') {
            return CustomEmailLink;
          }          
          // Otherwise use the default component.
          return null;
        },
        getItemLinkUrl: itemId => {
          const {codename, urlSlug, type} = blogPost.content.links.find(link => link.linkId === itemId);
          return `https://www.myblog.com/posts/${urlSlug}`;
        }
      })
    }
  }
</script>

The linkComponent is an optional prop and is not necessary unless you need content item link's URLs resolved or link rendering customized in any other way.

Rich text in a rich text

Another content type used as a component or linked item in our example also contains a rich text element quote. We'll wrap the value in the same rich-text component we used on the parent page. And in case this text contains nested linked items, we'll also pass the linked-item component.

<template>
  <blockquote>
    <rich-text
      :content='item.quote.value'
      :linkedItemComponent='linkedItemComponent'
    />
  </blockquote>
</template>

<script>
  import { RichText } from 'kontent-rich-text-for-vue';
  import LinkedItem from './linked-item.vue';

  export default {
    props: ['item'],
    components: {RichText},
    computed: {
      linkedItemComponent: () => LinkedItem
    }
  }
</script>

These example components are extremely small and could be easily handled by the out-of-the-box richTextResolver, but as the components grew to render more and more of markup, messing with string interpolation became rather obnoxious.

Feedback & Contributions

The provided component is quite bare-bones, but does everything I need. I'm interested if your use case differs and thus my implementation lacks.

Furthermore, I'm no Vue expert, so I'd love to hear if there are nicer or more elegant ways of doing things.

Package Sidebar

Install

npm i kontent-rich-text-for-vue

Weekly Downloads

160

Version

2.0.0

License

MIT

Unpacked Size

20.7 kB

Total Files

5

Last publish

Collaborators

  • vit-svoboda