Extending all Fields in a Panel Plugin / modifying k-field

Hi there,

I’m looking for some pointers for a problem I’m currently facing while developing a panel plugin. The plugin I want to create should enable me to add a note to every field in my blueprints similar in style to the character counter that some text fields have. The note should for now only be a small bit of text.

The main problem I’m facing is that I’m conceptually unclear on how to extend all fields and not just a specific one. Afterall I only want to modify the wrapper that the fields are inside of, not necessarily the fields itself. I’m thinking this would be the Fields.vue component aka the k-field component but I’m not sure how I can modify this without having to extend all possible fields.

Some additional context: I need this to let panel users know wether the value of a specific field was set from an external Data import that I implemented or wether it was set manually inside Kirby. If set manually the plugin should in the long term show a small button next to the field label that lets users easily reset the field value to the Externally set Value. Its supposed to look something like this

Happy if someone can point me in the right direction!

As you might noticed, you can indeed overwrite the k-field component, like so:

panel.plugin(“your/plugin”, {
  components: {
    “k-field”: YourField
  },
}

Thanks for the hint, this already helped a lot. I’ve now gotten so far as to get Kirby to use my Custom version of k-fields which I copied from Kirbys Core alongside the mixins it required. That way I’ve managed to include some custom UI in the k-field component.

What I’m still struggling with though is setting the data to be displayed inside the button. I’m not able to both access values from my Blueprint or compute them inside the index.php. Long term the value should be computed by accessing the pages field raw_catalog_data and comparing the value of the field with raw_catalog_data[fieldname].

This is my setup so far:

Blueprint

      subheadline:
        type: text
        buttonmessage: "Hello from Blueprint"
      text:
        type: writer
        buttonmessage: "Hello from Blueprint"
      tags: 
        label: Tags
        type: tags
        buttonmessage: "Hello from Blueprint"

/src/components/mycomponent.vue

<template>
[…]
		<slot name="header my-special-header">
			[…]
                <div class="k-field-header-extra">
                    <k-button variant="filled" size="xs" icon="split">{{ buttonmessage ?? "No button message set" }}</k-button>
				[…]
			</header>
		</slot>
		<slot />
 
[…]

	</div>
</template>

<script>
// The imports below were also copied from Kirby's Core
import { disabled, help, id, label, name, required, type } from "../default-kirby-mixins/props.js";

export const props = {
	mixins: [disabled, help, id, label, name, required],
	props: {
		counter: [Boolean, Object],
		endpoints: Object,
		input: {
			type: [String, Number, Boolean],
			default: null
		},
		translate: Boolean,
		type: String,
        buttonmessage: String // My additional Prop
	}
};

export default {
	mixins: [props],
	inheritAttrs: false,
	emits: ["blur", "focus"]
};
</script>

<style>[…]
</style>

/src/index.js

import mycomponent from './components/mycomponent.vue';

panel.plugin("mms/field-label-extension", {
  components: {
    "k-field": mycomponent
  }
});

/index.php

<?php
Kirby::plugin('mms/field-label-extension', [
    'name'        => 'Field Label Extension',
    'description' => 'Shows a badge next to field labels containing buttonmessage prop',
    'version'     => '1.0.0',

    'panel' => [
        'js'  => 'index.js',
        'css' => 'index.css'
    ],
    'components' => [
        'k-field' => [
            'props' => [    
                'buttonmessage' => function ($buttonmessage = "button message from index.php") {
                    return $buttonmessage;
                }
            ]
        ]
    ],
    'fields' => [
        // This should at least extend text fields
        'text' => [
            'extends' => 'text',
            'props' => [
                'buttonmessage' => function ($buttonmessage = "button message from index.php") {
                    return $buttonmessage;
                }
            ]
        ]
    ]
]);

While I can access regular Field Properties like label or type, I’m unable to get the value of the custom property “buttonmessage”

Any ideas?

Oh right. I think you will have to extend every field separately to add the new prop. It won’t work to just add it to the k-field wrapper since that is nested inside the separate fields.

Thank you! I developed this further and ended up on the solution below. To keep the code as dry as possible I’m now adding the extension options in index.php and index.js dynamically for all the fields I’ll need:

index.php

<?php

Kirby::plugin('mms/field-label-extension', [
    'name'        => 'Field Label Extension',
    'description' => 'Shows a badge next to field labels containing buttonmessage prop',
    'version'     => '1.0.0',

    'panel' => [
        'js'  => 'index.js',
        'css' => 'index.css'
    ],
    'fields' => getFieldExtensionOptions(['text', 'tags', 'textarea', 'writer' ])
]);

// This will create an object used to extend multiple field types with the same component and props
function getFieldExtensionOptions(Array $fieldsToExtend) {
    $fieldExtensionOptions = [];
    foreach ($fieldsToExtend as $field) {
        $fieldExtensionOptions[$field] = [
            'extends' => $field,
            'props' => [
                'check_catalog_sync' => function ($check_catalog_sync = false) {   // New prop to hold the button message
                    return $check_catalog_sync;
                },
                'in_sync' => function ($in_sync = false) {   // New prop to indicate if field is in sync                    
                    return $in_sync;
                },
        
            ],
            'computed' => [
                'catalog_data' => function ($catalog_data = null) {   // New prop to hold the catalog value
                    if($this->model()->catalogData()->isNotEmpty()){
                        return json_encode($this->model()->catalogData());
                    }
                    return null;
                }
            ]
        ];
    }
    return $fieldExtensionOptions;
}

/src/index.js
(gotcha for anyone else attempting this, computed values from index.php only worked for me if in the index.js they are set as regular props

import './index.css';
import FieldCustom from './components/FieldCustom.vue';


panel.plugin("mms/field-label-extension", {
  components: {
    "k-field": FieldCustom
  },
  fields: getFieldExtensionOptions(['text', 'tags', 'writer'])
});


// This will create an object used to extend multiple field types with the same component and props
function getFieldExtensionOptions(fieldsToExtend) {
  const extensionOptions = {};
  fieldsToExtend.forEach(name => {
    extensionOptions[name] = {
      extends: `k-${name}-field`,
      props: {
        check_catalog_sync: Boolean,    // show the catalog sync check
        in_sync: Boolean,               // indicate if field is in sync
        catalog_data: Object            // hold the original catalog value
      }
    };
  });

  return extensionOptions;  
}