Forms for Page Meta-Tabs

Do you need a special meta-information for pages in your theme? Or do you want to create a SEO-tab for each page? No problem, because you can add your own meta-fields or even your own meta-tabs with a plugin and with the help of vue.js.

Add Fields to a Tab

By default, each page has a meta-tab called "meta". To add a new field to that tab, simply create a plugin or a theme with a new YAML definition, that starts with the keyword metatabs followed by the name of the tab meta like this:

name: Example Plugin
version: 1.0.0
description: Add a short description
author: Firstname Lastname
homepage: http://your-website.net
licence: MIT
metatabs:
  meta:
    fields:
      myfield:
        type: select
        label: Select an Option
        options:
          green: Green
          blue: Blue

This will add a new select-field to the existing tab "meta".

Store and Use Meta-Information

Similar to the famous "frontmatter", Typemill can store any kind of meta-information for a each content page. Different to frontmatter, Typemill does not store the meta-information in the markdown-file, but it creates a separate yaml-file to keep the markdown-file clean and readable.

Typemill uses some standard-meta-information like the title, the description and the author. But you can also add your own fields to the meta-tab and use all meta-information in your twig-templates with the variable metatabs like this:

{{ metatabs.meta.myfield }}

You can also access the information in your plugin with the event onMetaLoaded like this:

<?php
namespace Plugins\Example;
use \Typemill\Plugin;
class Example extends Plugin
{   
    public static function getSubscribedEvents()
    {
        return array(
            'onMetaLoaded'      => 'onMetaLoaded'
        );
    }
    public function onMetaLoaded($meta)
    {
        $meta = $meta->getData();
        if($meta['meta']['myfield'] == "green")
        {
            # make something green here
        }
}

Create a New Meta-Tab

With a plugin, you can also create a new meta-tab for pages. Just add a new yaml-configuration to your plugin and give your meta-tab a name like this:

name: Example Plugin
version: 1.0.0
description: Add a short description
author: Firstname Lastname
homepage: http://your-website.net
licence: MIT
metatabs:
  mytab:
    fields:
      myfield:
        type: text
        label: Add some text

If you activate the plugin, then you will already see a new tab called "mytab" for each page.

However, if you click on the tab, you will not see any forms yet. The reason is quite simple: Different to forms for plugins, themes or public pages, the forms for meta-tabs are rendered with Vue.js and not with PHP. In order to render the forms, you have to activate the vue form generator. This is done with a simple copy & paste.

Add the Vue Component

You can activate the vue form-generator in your plugin with a simple script (a vue-component). In the first step we add that script to the editor like this:

<?php
namespace Plugins\Example;
use \Typemill\Plugin;
class Example extends Plugin
{   
    public static function getSubscribedEvents()
    {
        return array(
            'onTwigLoaded'      => 'onTwigLoaded'
            'onMetaLoaded'      => 'onMetaLoaded'
        );
    }
    public function onTwigLoaded()
    {
        if($this->editorroute)
        {
                $this->addJS('/demo/js/editordemo.js');
        }
    }
    public function onMetaLoaded($meta)
    {
        $meta = $meta->getData();
        # do something with the fields:
        $myTabInformation = $meta['mytab'];
    }
}

We used the event onTwigLoaded to add the script, but you can also use other events if you want.

In the next step we will create the script. The script is a simple vue-component that looks always like this:

app.component('tab-demo', {
    props: ['item', 'formData', 'formDefinitions', 'saved', 'errors', 'message', 'messageClass'],
    template: `<section class="dark:bg-stone-700 dark:text-stone-200">
                    <form>
                        <div v-for="(fieldDefinition, fieldname) in formDefinitions.fields">
                            <fieldset class="flex flex-wrap justify-between border-2 border-stone-200 p-4 my-8" v-if="fieldDefinition.type == 'fieldset'">
                                <legend class="text-lg font-medium">{{ fieldDefinition.legend }}</legend>
                                <component v-for="(subfieldDefinition, subfieldname) in fieldDefinition.fields"
                                    :key="subfieldname"
                                    :is="selectComponent(subfieldDefinition.type)"
                                    :errors="errors"
                                    :name="subfieldname"
                                    :userroles="userroles"
                                    :value="formData[subfieldname]" 
                                    v-bind="subfieldDefinition">
                                </component>
                            </fieldset>
                            <component v-else
                                :key="fieldname"
                                :is="selectComponent(fieldDefinition.type)"
                                :errors="errors"
                                :name="fieldname"
                                :userroles="userroles"
                                :value="formData[fieldname]" 
                                v-bind="fieldDefinition">
                            </component>
                        </div>
                        <div class="my-5">
                            <div class="block w-full h-8 my-1">
                                <transition name="fade">
                                    <div v-if="message" :class="messageClass" class="text-white px-3 py-1  transition duration-100">{{ $filters.translate(message) }}</div>
                                </transition>
                            </div>
                            <input type="submit" @click.prevent="saveInput()" :value="$filters.translate('save')" class="w-full p-3 my-1 bg-stone-700 dark:bg-stone-600 hover:bg-stone-900 hover:dark:bg-stone-900 text-white cursor-pointer transition duration-100">
                        </div>                      
                    </form>
                </section>`,
    methods: {
        selectComponent: function(type)
        { 
            return 'component-' + type;
        },
        saveInput: function()
        {
            this.$emit('saveform');
        },
    }
})

You can simply copy and paste this component, because the vue-component will always look the same. Just change the name of the component Vue.component('tab-mytab', {}) to the name of your tab starting with the prefix "tab-". Please use an unique name for your tab, because there might be conflicts if another plugin uses the same name for a tab.

This is what the vue component does:

  • It loops over the fields that you defined in your YAML file.
  • It dynamically adds the field component according to the type of each field.
  • It binds all neccessary data like the field-definitions, the errors, the name of the field and so on.
  • It adds a save button and messages for success and error.

If you want to know how it works in detail, then check the file vue-meta.js in the folder system/author/js.

Extend the Vue Component

The vue-component above just renders the forms and adds a store-button. If you are a vue-developer, then you can do much more and extend the vue-component with all kind of functionalities. You can create whole applications that run in a tab. If you want to see an example, then download the eBook plugin and check the tab. You will see a highly complex application that runs in several sub-tabs and generates eBooks from Typemill websites.

Limitations

The meta-tabs are flexible as hell, but the actual vue-form-builder has some limitations compared to public forms or to configuration-forms for plugins and themes:

  • You cannot add text with the field-type paragraph.
  • You cannot use the help text.
  • You can use attributes like required or pattern in your yaml-definition. They are used for the backend validation, but they have no effect in the frontend.