Storyblok Raises $80M Series C - Read News

What’s the True Total Price of Enterprise CMS? Find out here.

Skip to main content

Building a List Field Type Plugin

Try Storyblok

Storyblok is the first headless CMS that works for developers & marketers alike.

In this article we'll take a look at how to build a field type plugin for Storyblok to handle content for lists. E.g. a list of benefits of your product or service.

Screenshot of a teaser with a list of benefits

In the screenshot above, you can see an example for a typical use case for a list. In the following chapters we'll find out how we can build a plugin which makes it very easy for content editors to add the content for such lists.

Setup

Storyblok provides us with a very potent plugin system based on Vue.js components. This makes it possible to build field type plugins for every imaginable use case. Although, for very basic plugins, you can use the simple in-browser editor provided by Storyblok itself, usually we need to set up a simple development environment first, before we can get started building our custom plugin.

There already is great documentation about how to set up a local development environment for building Storyblok plugins. Please follow the steps described in the article and come back when you‘re ready.

Creating a new plugin in Storyblok

After setting up a fresh local development environment and starting the development server with npm run dev we're ready to start developing our own plugin.

Local plugin development in Storyblok

Above you can see the example plugin provided by the Storyblok plugin boilerplate. In the next step we'll replace this with our own implementation of a list field type plugin.

Creating the list plugin

We want our plugin to be rather simple: it should allow our content editors to enter a couple of list items. In order to make that happen it should be possible to enter some text in an input field and add additional input fields for more list items. We also want to provide a configuration option to make it possible to limit the amount of list items a content editor should be able to add.

Planning the data structure

The final data structure, which we want to get as a response from the Storyblok API, should look something like this.

        
      {
  "items": [
    "Lorem Ipsum dolor",
    "Consetetur sadipscing elitr"
  ]
}
    

In order to keep it simple, our plugin returns an array of plain strings. If you're planning to add additional functionality like choosing the bullet characters or making it possible to nest list items, you should consider to use a data structure like the following in order to be prepared for future changes.

        
      {
  "items": [
    {
      "text": "Lorem Ipsum dolor",
      "children": []
    },
    {
      "text": "Consetetur sadipscing elitr",
      "children": []
    }    
  ]
}
    

By using objects to store the data for each list item, you'll be able to add additional properties in the future. But for this article we keep it as simple as possible.

Coding the plugin

Now that everything is set up and ready to go, let's get our hands dirty and write some code. In the following code snippet, you can see the basic code of our Plugin.vue file in the src directory of our project.

        
      <template>
  <div class="List">
    <ol class="List__list uk-margin-bottom-remove">
      <li
        v-for="(item, index) in model.items"
        :key="index"
        class="List__list-item"
      >
        <input
          v-model="model.items[index]"
          class="uk-form-small uk-width-1-1"
          :aria-label="`List item ${index}`"
        >
      </li>
    </ol>
  </div>
</template>

<script>
export default {
  mixins: [window.Storyblok.plugin],
  watch: {
    model: {
      deep: true,
      handler(value) {
        this.$emit('changed-model', value);
      },
    },
  },
  methods: {
    initWith() {
      return {
        items: [''],
        plugin: 'list',
      };
    },
  },
};
</script>

<style>
.List__list {
  padding-left: 0;
}

.List__list-item + .List__list-item {
  margin-top: 5px;
}
</style>
    

Above you can see the bare minimum implementation for our list plugin. In the <template> part of the component you can see that we render a list of <input> items. We connect each <input> item with the data in our model via v-model="model.items[index]".

In the <script> part we can see the boilerplate code to make our component work as a Storyblok field type plugin. In the initWith() method we specify the initial value of our plugin with items: [''], which means, at the beginning, we have one empty list item. Next we define the name of the plugin via plugin: 'list'.

The <style> section only contains a reset of the default padding on lists and we add some spacing between list items. All other styling is done via UIkit classes.

Adding new items

Next up we want to add the functionality to add additional list items. Let's update our code to enable this.

        
             </li>
     </ol>
+    <a
+      class="blok__full-btn uk-margin-small-top"
+      @click="addItem"
+    >
+      <i class="uk-icon-plus-circle uk-margin-small-right"/>
+      Add item
+    </a>
   </div>
 </template>
    

First we add a new <a> element to the template section of our plugin. We also bind a click handler on it via @click="addItem". This means that the method addItem() is called every time the button is clicked.

        
         },
   methods: {
+    addItem() {
+      this.model.items.push('');
+    },
     initWith() {
       return {
    

Above you can see the addItem() method we've specified as a click handler on our newly added button. Every time it is called, a new empty string is added to the list of items. Adding a new list item automatically triggers a new <input> element to be rendered.

Button for adding new list items

Removing items

So far so good. Now we're able to add new items to our list, but what about removing existing ones?

        
               v-for="(item, index) in model.items"
         :key="index"
-        class="List__list-item"
+        class="List__list-item uk-flex uk-flex-middle"
       >
         <input
           v-model="model.items[index]"
           :aria-label="`List item ${index}`"
           class="uk-form-small uk-width-1-1"
         >
+        <a
+          class="assets__item-trash"
+          aria-label="Remove item"
+          @click="removeItem(index)"
+        >
+          <i class="uk-icon-minus-circle"></i>
+        </a>
       </li>
     </ol>
    

The first change you can see is that we've added the uk-flex and uk-flex-middle UIkit helper classes in order to display the elements inside of our list item side by side and vertically centered. Next we've added a new <a> tag containing a minus icon which signals to the user that this is for removing this item. We've also added a @click event handler to call the removeItem() method with the index of the current item.

        
             this.model.items.push('');
     },
+    removeItem(index) {
+      this.model.items = this.model.items.filter((_, i) => i !== index);
+    },
     initWith() {
       return {
    

The removeItem() method receives an index and uses it to filter the list of items to remove the item with the given index.

Button for removing list items

Item limit

Last but not least we want to make it possible to limit the number of list items our users are allowed to add to a certain piece of content. Fortunately it's possible to pass options to field type plugins. In the screenshot below, you can see how to pass options in development mode and that the Add item button is not rendered anymore if the limit is reached.

Plugin options for limiting the number of list items

Let's take a look at the changes we have to apply to the code of our plugin to make this work.

        
           </ol>
     <a
+      v-if="!limitReached"
       class="blok__full-btn uk-margin-small-top"
       @click="addItem"
    

In the <template> section we add a v-if="!limitReached" directive onto the <a> tag of our Add item button. That way we make sure that the button is only rendered if the specified limit is not reached yet.

        
       export default {
   mixins: [window.Storyblok.plugin],
+  computed: {
+    limitReached() {
+      return this.options.limit && this.model.items.length >= this.options.limit;
+    },
+  },
   watch: {
     model: {
    

In order to provide the information if the limit is already reached or not, we specify a new limitReached computed property inside the <script> section of our component. We can access the limit passed to the plugin via this.options. By comparing this value to the length of the array of items we can find out if the limit is already reached or not.

Using the list plugin

After we're done coding, we can build our plugin by running npm run build and copy and paste the generated code from dist/export.js into the editor text field of the Storyblok app and click the publish button.

Now that we've successfully added our plugin to our Storyblok space, we're able to use it in our content types. Let's edit the schema of the content type for which we want to provide a list of data.

Storyblok schema editor

Adding list content in Storyblok

After setting up the new field and adding some list items, we're ready to consume the data in our application.

Final notes

This is a rather simple implementation of this kind of field type plugin. Depending on your use case you can extend its functionality and tailor it exactly to your needs. For example you could add the possibility to choose which icon is used as bullet character. Or you could add the possibility to build nested lists or you might add a drag and drop feature for reordering list items.

Author

Markus Oberlehner

Markus Oberlehner

Markus Oberlehner is a Open Source Contributor and Blogger living in Austria and a Storyblok Ambassador. He is the creator of avalanche, node-sass-magic-importer, storyblok-migrate.