Due to lack of documentation, I go through Kirby’s source code to figure out how native fields work so I can create mine as correctly as possible. So far, I was doing great, but I can’t understand one thing - when is a field’s value
prop called?
I’m trying to create a field in which you can select a page, among other things. I have reused most of the functionality of the native pages field. I need to store just the selected page id
under the key page
, and send the necessary meta data for the given page before giving it to the front-end and the k-pages-field
there.
Essentially, I want to replicate the native pages field behavior. It stores an array of page ids and returns an array with data for each of these pages to the front-end. I want to store a single id under the page
key of my field value, and return that page’s meta data under the same page
field to the front-end.
Here’s what I currently have in my index.php file of the plugin:
$fieldsSource = $kirby->root('kirby') . DS . 'config' . DS . 'fields';
$pagesField = include $fieldsSource . DS . 'pages.php';
Kirby::plugin('me/plugin', [
'fields' => [
'myfield' => [
'props' => [
'value' => function ($value = null) {
$data = Yaml::decode($value);
// if (isset($data['page']) && is_string($data['page'])) {
if (!empty($data['page'])) {
$page = kirby()->page($data['page']);
$data['page'] = [$this->pageResponse($page)];
}
return $data;
}
],
'methods' => [
'pageResponse' => $pagesField['methods']['pageResponse'],
],
'api' => function () use ($pagesField) {
return [
[
'pattern' => '/pages',
'method' => 'GET',
'action' => $pagesField['api']()[0]['action']
]
];
},
'save' => function ($value = null) {
if (!empty($value['page'])) {
$value['page'] = $value['page'][0]['id'];
}
return $value;
}
]
]
]);
…and index.js:
panel.plugin("me/plugin", {
fields: {
myfield: {
props: {
value: Object,
endpoints: Object,
label: String
},
data: function() {
return {
data: this.value
}
},
methods: {
input: function (data) {
this.$emit('input', {
page: data
})
}
},
watch: {
value: function (value) {
this.data = value
}
},
template: `
<k-pages-field
v-model="data.page"
:endpoints="{
field: this.endpoints.field + '/pages'
}"
@input="input"
></k-pages-field>
`
}
}
})
So…
What I expect is that the value
prop is called whenever the stored Field value must be parsed before being sent to the front-end. This means parsing the YAML and fetching the page data. When save
is called, I expect it to receive the data sent by the front-end and filter it so that only the necessary parts are saved.
Well, this kind of happens. If I manually set the field value to page: home
for example, then value
is indeed called, it fetches the page, the front-end receives it, and the page is displayed. However, if I change the page and click Save, the value is not saved. In fact, it’s removed from the page and there’s nothing in the txt file.
After looking at the native pages field and playing around with my own field, I noticed that if I change:
if (!empty($data['page'])) {
…to:
if (isset($data['page']) && is_string($data['page'])) {
…then everything starts to work perfectly. The page id
is saved, the meta data is displayed on the front-end.
My question is…
Why does that work? Why should I check if the page
key is a string? Why can’t I check whether it merely exists? I mean, that value comes from the parsed YAML and if the YAML has a page
key, it should always be a single string, since the save
function leaves only that. If I have to make the string check, this means that at some point, that value is not a string, therefore the value
prop is called in more than one scenario. What is it?
Since this is a pretty core-related question, I’ll tag @distantnative and @bastianallgeier since I doubt someone else can help me.
By the way, this lack of documentation is a great pain. Kirby has such a powerful and well-designed API. It’s a pity that you have to shoot in the dark to figure out how to use it… The last plugin I made would have been so much easier to make if I only knew about this save
setting of fields…
Edit: After further digging, I think that save
simply isn’t called with the value from the front-end, at least not always. I think the front-end value goes through the value
prop function before reaching save
for some reason? Someone please explain what the value
prop and save
actually do.