My first Panel field
Create your first custom Panel field from scratch
This cookbook recipe is based on these two video episodes…
…and a question that recently came up on the forum.
In this recipe, we start with the basics of creating a field from scratch using a Vue.js single file component, and then build on this by reusing parts of Kirby's UI Kit. We will deviate a little from the example in the videos though, and our final result will be a text input for DOI names, with a link that allows us to verify the input by calling the link
https://www.doi.org/ suffixed with the field input.
Let's start by creating a new folder in the
plugins folder called
doifield. Inside this folder, we create a
package.json file with the contents copied from the Pluginkit example mentioned above.
This will take care of compiling our source files into an
index.js file in the root of our
doifield plugin folder.
As for any plugin, we first create an
index.php file containing the Kirby plugin wrapper, in which we define the field. Within the fields array, we register the new
doi field type and assign an empty array as value. This array would contain the backend logic for our field if needed. We skip this part for now.
<?php Kirby::plugin('pluginAuthor/doi', [ 'fields' => [ 'doi' => [ // here we could define the backend logic for our field if needed ] ] ]);
Next, we create a new folder called
/src in the
doifield folder, and in the
/src folder an
index.js we also have to register the new field like in
To make the field work without errors, we also have to add a
template property. Let's start with something really basic.
Ok, now that we got the most basic field definition in place, we are ready to run…
npm run dev
/doifield folder to compile the code. If everything works as expected, there will now be a compiled
index.js file in the
Let's add this new field type in a blueprint and view the result in the Panel, for example in
/site/blueprints/pages/sandbox.yml in the Starterkit (you can use any other blueprint).
When we open the unlisted
Sandbox page in the Panel, we can see the
doi field in the Panel:
Ok, ok, this only prints
Hello and is far from impressive, but hey, it's a start.
Before we continue, let's clean up a bit and introduce a single file component, in which we will store the client-side logic of the field.
So let's create a
\components folder in the
/src folder, in that a
/fields folder and inside that a
DoiField.vue file, so that the folder structure looks like this:
In the single file component, we recreate the template inside
We are also adding the script tags that will shortly contain the logic for the field and an optional style tag (we will not use custom styles for this example, so you might as well omit them).
/src/index.php, we import this new component, assign the component to the
doi field and remove the
template property from the first iteration, so that it looks like this:
In the Panel, we won't see any changes at this point.
Let's move a little further and turn this into a real field input using a standard HTML input tag:
After the code compiles, we now have an input field with a an hard-wired
Hello label. And while you can type text into the input field, you will not be able to store it. But one step after the other, so how do we get the
Doi field label we already put into the blueprint into our field label?
In Vue.js, we have to define
Props are basically the data that once defined, you can then use in the component. In a Kirby context, they bridge the gap between the field options we define in our
.yml blueprints and the frontend. The
props take what is defined in the
.yml files and send it to the field component via Kirby's API automatically. Let's add a
label prop to the
A property consists of the key for the property and a value that contains at least the type we expect to have here, in this case we want a string.
We can now replace the hard-wired
Hello world string with the label defined in the blueprint using the most basic form of data binding. The Mustache tag will be replaced with the value of the
If you now change the field label in the blueprint and reload the page, you can see how the value for the
label changes as well.
Once we reload the page in the Panel, we see that the label has changed:
Currently, our input field has no content yet. Before we start editing the field in the Panel, let's add some content in our text file and see how we can load the value of the field into the input field.
value as a prop and bind this value to the input field with
Great, we can now bind the value in the text field to the input. But if we try to change the value in the Panel, nothing will happen yet, no orange save bar will pop up, and we cannot store changes.
At this point, the Panel doesn't know when something changes, so we have to tell it somehow.
In Vue.js, we can use the
input event, and call the
onInput() method when the event is triggered. We can do this in Vue.js with
v-on:input="onInput", but since we are a bit lazy we use the shorthand `@input="onInput" instead, so that the template code now looks like this:
To make the code work, we still have to add the
We have to define this method in the
event.target.value. Using Vue's
$emit directive, we emit the
input event which the Panel expects here, and pass the field value to it.
And that's it. When we now return to the Panel, we can edit the field value and as soon as we do so, the orange bar at the bottom pops up, allowing us to either store the new or revert to the original value 🎉.
Our field is still looking ugly and quite unlike the other fields around it. How can we change their appearance without having to write many lines of custom CSS?
Well, Kirby comes with a large set of ready-to-use standard components that we can reuse in our plugins and that free us from the trouble of having to style everything manually.
These standard components all start with
k- followed by the name of the component. You find the full set Kirby's UI components in the Reference.
We start with replacing the wrapper div with the
k-field component. Each component has a set of props that we can leverage to reduce the HTML code, in this case, the
k-field component has a
label property to which we can bind the value of the
label property. Once we've added that, we can remove the
Hurray, our field immediately starts looking better with the label above the input.
The input field we replace with a
k-input component and add the
theme prop to make it look like an input field:
While this looks good now, we get an error as soon as we start typing something now. That's because the input component also takes care of sending the value to the input event, so that we don't have to get it from the
event.target anymore. Instead, we pass the value as parameter to the
onInput() method and replace
event.target.value with just
With this, we now have a fully working input field that looks like a normal text field.
We can now add more props, if we want, to make the standard field properties like
required work as well.
The input element also accepts some props, like
Try it out by adding these properties in the blueprint and watch the results in the Panel.
As you probably know, the
url field has a clickable button that when clicked opens the link stored in the field in the browser. We are going to add such a link to our
doi field now.
We can achieve this by stealing the template code from the
url field, it's just a button that is inserted inside the
k-input field and takes the slot reserved for the icon.
What's new here? With the
v-if directive, we define that the button is only shown if the
link prop is set to
true, which it is by default (we can disable this via the blueprint). In the
link pro, we prepend a hard-wired URL to the field value. We could make this more dynamic by introducing a new property if we wanted, but we leave it as is.
We still have to define the new props
icon. This time, they look a bit different, because we add on object with the type and a default value.
Voilà, our new doi field is ready for use.