calebgrove / select-a-structure

A select field for Kirby populated by a structure field.
43 stars 7 forks source link

Dynamic value binding #11

Open mciszczon opened 6 years ago

mciszczon commented 6 years ago

The problem

Currently, the plugin loads the defined value(s) from the structure, and stores them as static text with just the fields defined in the blueprint.

It would be much more handier if the defined optionkeys were there only to make <select> options easier to read, but under the hood, the plugin would fetch the results dynamically with all the fields of the desired structure.

Use case: Footnotes & bibliography

I am currently working on an academical project. I have a bibliography page with all the entries stored in a structure field. It's rather extensive, as you can see below:

printed:
    label: Printed books and materials
    type: structure
    entry: >
      {{author}}, <i>{{title}}</i>.
    fields:
      author:
        label: Author
        type: text
        icon: user
      groupWork:
        label: Joint publication
        type: toggle
        text: yes/no
        required: true
      title:
        label: Title
        type: text
        icon: font
        required: true
      translation:
        label: Translator
        type: text
        icon: language
      inGroup:
        label: Joint publication title
        type: text
        icon: book
      redaction:
        label: Redakcja
        type: text
        icon: user
      print:
        label: Press
        type: text
        icon: book
      year:
        label: Year
        type: number
        min: 1900
        icon: calendar

Then, in different parts of my website, I have content that requires to have the source written down beneath. Every source I use must go to bibliography too, so it seems logical to make source a select field with all the entries from the bibliography.

For that purpose I wanted to use this plugin. First issue I encountered was the inability to use multiple optionkey values, which I have fixed. This is not enough in my case, though, I want not only few fields in my source, but the entire bibliography entry.

It's not a good solution to enter all the fields into the optionkey, as it would render <select> unreadable and be basically hard-coded. Any future changes in the bibliography structure and it might get broken.

Idea

I have come up with an idea how to fix that. There could be another dynamic option in the blueprint for the type: selectastructure field:

selecty:
    type: selectastructure
    structurepage: staffpage
    structurefield: stafflist
    optionkey: staffname
    dynamic: true

It would default to false to keep this backward-compatible. Then it would work just as now.

With the dynamic option however, the plugin would rather fetch a unique ID of each structure entry, and store this dynamic link instead of the value.

Then in the template, Kirby could dynamically load a structure directly from the page. Since we already store information about what page it is and how the field is named, it would work like that:

$selectedStructure = page($structurepage)->structurefield()->toStructure()

To get just one structure entry, we have the powerful get() method:

$selectedStructure->get($uniqueId)

Caveat

One and only caveat I can see, is that there would have to be something like uniqueid field in the structure, and its value would have to be unique per structure. This could be solved either by the user:

printed:
    label: Printed books and materials
    type: structure
    fields:
     uniqueId:
         label: Unique ID
         type: text
         required: true
      ...

Or, alternatively, it could be handled by this very plugin, with the introduction of a new custom field type, e.g.:

...
fields:
    uniqueID:
        type: selectastructureid
        required: true

This would get filled automatically for the user. UUID would be my option-of-choice in implementing that.

Summary

I will probably try to implement that in the following days, as it's something I have to have in my project. If there's any other ideas on how to accomplish that, then I'll be more than happy to read them.

Also, I want to thank you @calebgrove for the great plugin!

mciszczon commented 6 years ago

Just realized, that @texnixe has already created a Kirby Structure ID plugin that automatically adds unique hash to structure items. This plugin could be used as a dependency.

Still wondering on how I can pass structurepage and structurefield values to the template engine. Probably Kirby Architect plugin will do. Will look into this today or tommorow.

EdwardLecock commented 6 years ago

@mciszczon did you find a way to implement it yet?

mciszczon commented 6 years ago

@EdwardLecock Yes, I have implemented it using Select A Structure, Kirby Architect and another plugin for generating unique IDs for structure entries. I will come back here later today with the details.

mciszczon commented 6 years ago

Ok, so I pushed my changes to my fork: mciszczon/select-a-structure. In order to make it work you'll need some other plugins, but let's start with my fork:

Dynamic Select A Structure

Notable changes that I've made:

yourfield:
    optionid: hash_id
    optionkey: staffname, staffsurname
    optiondivider: ','

optionid is a field inside the structure that you are targeting. It must contain unique value for each structure entry! This value could be entered manually, or using a plugin. I have used texnixe/kirby-structure-id for this.

optionkey should contain name or names of the target structure fields. Let's say you have a structure consisting of staffname, staffsurname and staffposition. By using optionkeys as in the above example, the <select> options will consist of these two first parameters. You can use as many keys as you wish.

optiondivider is just for them looks, so you can specify how these keys should be separated. In our example it would be better to change it to ' ' (simply a space) to display names as John Doe and not John, Doe.

That would do for the back-end part!

Kirby Architect

We now need a way to use these values in our templates. Remember that this modified Select A Structure plugin only stores these unique IDs of elements that physically are on a different page. That's where Kirby Architect plugin comes in handy! It allows us to access blueprint data in the template, so we can easily grab the target page and field, and from them we can get our structure entries.

Firstly, you'll need to add these custom page methods: https://gist.github.com/mciszczon/93e4dd9edd58eb8844da53f6be42121c. You can simply save this file to yourkiby/site/plugins/page-methods.php.

The above file will give you two new page methods: structurepage() and idfield(). These are just shortcuts, under the hood they use Kirby Architect to get this data.

Let's say we want to display our staff list (from the example above). Let staffselect be our dynamic select a structure field, while the staff is stored on a page staff inside stafflist structure.

staff page could look like this:

title: Staff
fields:
    ...
    stafflist:
        label: Staff
        type: structure
        fields:
            hash_id: Unique ID
                type: text
                placeholder: Please, enter a unique value here!
            staffname: Name
                ...
            staffsurname: Surname
                ...
            staffposition: Position
                ...

Notice the hash_id field! In this example it must be filled manually, but it's easy to use Kirby Structure ID for this matter.

And now let's say we want to display it on About us page. That's how about.yml blueprint should look like:

title: About
fields:
    ...
    staffselect:
        structurepage: staff
        structurefield: stafflist
        optionid: hash_id
        optionkey: staffname, staffsurname
        optiondivider: ' '

And now, inside about.php template/snippet just do this:

<?php
        # Automatically get the staff page from blueprint
        $staffPage = $page->structurepage('staffselect');

        # Now get the name of the field that stores unique ids
        $idField = $page->idfield('staffselect');

        # With these two we can dynamically fetch from the target page!
        # page($staffpage) should return `staff` page.
        # findBy($idField, $page->staffselect()->value() will grab entry which unique id matches the unique id from select-a-structure select box.
        $position = page($staffPage)->stafflist()->toStructure()->findBy($idField, $page->staffselect()->value());

        echo $position;
?>

Select A Structure field inside a structure.

There's also one additional use case. In my project I have few select-a-structure fields that themselves are inside a structure. In such case, you must use $page->structurepage() and $page->idfield() differently:

$structurePage = $page->structurepage('parent_structure', true, 'select_a_structure_field');
$idField = $page->idfield('parent_structure', true, 'select_a_structure_field');

So, as you can see, both of these methods accept three parameters. First one is the parent structure, the one which contains select a structure field. Next there is true keyword to indicate that our target field is nested inside this parent structure. Third argument is the name of the select-a-structure field (like stafflist).


I know this needs refactoring and could be extended massively. One thing is that manually grabbing the right field (e.g. ->stafflist()) in the template is unnecessary, as we already store this information in the blueprint. It's just a matter of adding another custom page method. Should be easy.

I lack the time to do this right now, unfortunately. The plugin is working as-is however, so feel free to adapt this and maybe make it better!

If you have any other questions, feel free to ask me!

EdwardLecock commented 6 years ago

Thanks for the great write up. I'll take a look at your fork later on and give it a try.