DEV Community

Maciej Krawczyk
Maciej Krawczyk

Posted on • Originally published at Medium on

Keystone.js custom fields: menu component


Photo by Bicanski on Pixnio

Introduction

Last week my series about Keystone.js custom components had a little break, instead of building next component I’ve focused on more basic and low level topics. But this week, I’m back on track and main focus of this article is menu component. This time, we will build more content than data collection component.

Basic field types in Keystone.js works fine for crating pages and posts, but there’s no easy way to build page menu. Built-in JSON field has all necessary features to hold data describing page menu, but its visualization is pretty basic, contains only simple text area. From developer point of view it maybe sufficient, but for the end user of CMS system may be hard to grasp. And additionally there’s no guarantee that data from user input follows schema for our menu object. And from security point of view allowing user to input raw JSON data into system is at best bad idea. So we need to create custom view allowing user to create menu configuration object without possibility to add raw JSON data.

Requirements

Ok, let’s recap our requirements then. First, our component have to output menu configuration as JSON and store it inside database. This data has to follow known upfront schema and do not allow user to input raw data. Basically what we need is UI to hide JSON creation in user-friendly way.

Component creation

Like in the previous parts of we are going to use build in field type, and only creates custom visualization, but here also the goal is to abstract from user the duty of following the menu schema. But to begin with we have to create this schema and decides how this configuration should look like.

Let’s assume that our system requires menus which contains items with name and URL and optionally array of sub-items, but they can’t have additional children. So interfaces for our menu schema should look like this:

Based on that, we know that our example configuration should look something like this:

Like in previous component I’ve created separate subfolder menu for this field in views folder. Basic component structure will be pretty similar to previous components, so we have to import controller, Cell and CardValue from built-in version of JSON component and reexport them. Also, I’ve setup basic JSX using built-in FieldContainer and FieldLabel components:

I’ve already imported styles and two components InputField and IconButton, which were moved to other files in order to keep structure somewhat clean. First we have to crate state to hold our new menu and effect to react to this state changes and call props.onChange method:

Nothing fancy here, if value exists (case of editing menu) we are parsing JSON into object and set it as initial state, or if it doesn’t exist we set it as empty array. Effect at the other hand stringify current value of state and sets it as value. Next we have to create button allowing to create new menu items:

The only purpose of this button is to call method onAddItem on click event, create blank menu item and inset it into state:

Also, we have to render this newly added item with all necessary buttons including remove, move up or down and add children and inputs for setting name and path of this menu item.

Ok, here starts to happen much more. First we render a bunch of input and buttons components. Each input holds string information about item name or its path. Here it’s abstracted to other component:

Each field has own event handler, but logic here is pretty similar, so I’ve moved it into separate method. But two handlers stayed in order to provide clear structure:

Basically it takes field name, event and field index, creates new state object (in order to prevent mutating state), changes its value based on event value, but only when indexes are equal. And then sets it back to state. Next elements of item are functional buttons, their props contains also icon image path and additional CSS classes:

More important here are callback methods, e.g., onRemoveItem. It takes item index as prop and then removes this item from state, like before it creates new state and then sets it:

Next two buttons allows changing order of items, to move it up or down. Basically it rearranges the array of items and sets it back into state:

Purpose of last button is slightly different, it adds new children item to this menu element (creates drop-down), takes only index as parameter:

Ok, now we have the possibility to add children to our items, so we have to render them, they are pretty similar to main items. The main difference is in missing add children button.

Main difference here is in event handlers mostly due to need of finding of right children of right item to update/set. Most of them has additional parameter which is parent index. onRemoveChild method maps through all items and removes child only when both parent and child indexes matches:

Similarly, childChangeOrder maps children order only when both indexes match:

Input onChange handlers are also slightly different, due to nested nature of children elements:

To finish our component we have to add a bit more styling than usually, mostly due to the fact that we’ve created more custom elements than before, here are all the styles:

Here we have finished component inside admin UI:

And here is code for finished component:

Summary

Truth to be told it’s the biggest Keystone.js custom field view component I’ve ever build. Even though I had to build special visualization for this component I still was able to utilize built-in fields underneath. I believe it’s something what makes Keystone.js pretty useful and versatile tool. Everything depends on our knowledge of its possibilities and creative way of finding them usage.

This was the fourth article in my series about Keystone.js. I hope You liked it, if You have any questions or comments feel free to ask them. I’m not pretty sure what next article will be about, maybe I will write something more about usage of Keystone in real life projects, or maybe I’ll venture into other topics. Not sure. See you next time.

Top comments (0)