Get label for select field value from Blueprint

Hi!

Wondering if anyone else has run up against this scenario:

I have a number of select fields in a blueprint with complicated names, like this:

fields:
  ...
  density:
    label: Planting Density
    type: select
    options:
      bourbon-like: 'Bourbon-like (3000-4000 a/ha)'
      caturra-like: 'Caturra-like (5000-6000 a/ha)'
      f1-hybrid-like: 'F1 hybrid-like (4000-5000 a/ha)'
  ...

I want to save a slug-like version of each of the long labels (rather than storing Bourbon-like (3000-4000 a/ha) in a content file, because those exact labels may be edited over time).

However! I canā€™t seem to come up with a clean way of fetching those nicer labels when it comes time to output the saved values. I understand that the Panel itself isnā€™t really a part of the core Kirby system, but I canā€™t help but think one of you have solved this already!

What I imagine being able to do is something like $page->field_name()->labelFor() and having either the base Page class (or an extended CustomPage model) figure out what Blueprint to fetch the original value labels from.

At the moment, Iā€™m defining a second static hash of field keys and possible values on the extended Page model that looks a lot like the pageā€™s Blueprint, and doing a lookup, there. Seems like overkill, right?

The ideal solution takes into consideration that this is a multi-language site.

Shortcuts?

:heart_decoration:

Edit: I just discovered the $page->blueprint() methodā€¦ Looking into how to fetch labels, now.
Edit 2: Looks like $page->blueprint() is only available within a Panelā€™s Page modelā€¦ Uncertain how to instantiate the Blueprint class outside the context of a Panel request!

Have a look at using a category map as described here: https://getkirby.com/forum/archive/general/20141013/is-there-an-easy-way-to-get-the-label-of-checkboxes

Right, this is just about the (fallback) solution Iā€™ve come toā€” the only difference being that the array is defined as a static property on the extended PageModel for that blueprint.

Additionally, theyā€™re in need of translation, so some kind of lookup function will be included in the class to handle a label array like this:

class VarietalPage extends Page {
  # ...
  public static $attribute_labels = [
    'rust' => [
      'resistant' => [
        'en' => 'Resistant',
        'es' => 'Resistente'
      ],
      'tolerant' => [
        'en' => 'Tolerant',
        'es' => 'Tolerante'
      ]
      'susceptible' => [
        'en' => 'Susceptible',
        'es' => 'Susceptible'
      ]
    ],
    'cbd' => [
      'resistant' => [
        'en' => 'Resistant',
        'es' => 'Resistente'
      ],
      'tolerant' => [
        'en' => 'Tolerant',
        'es' => 'Tolerante'
      ]
      'susceptible' => [
        'en' => 'Susceptible',
        'es' => 'Susceptible'
      ]
    ]
  ];
  # ...
}

The trouble here is that Iā€™ve already defined these translations in the blueprint, so itā€™s dozens (probably 100 or more by the time all the fields are included) of lines of duplicate codeā€” basically the entire blueprint translated to native PHP.

Seems like there ought to be a programmatic solution, right!?

There were several similar discussions in the new forum already, but I canā€™t find them right now. If I find them, I will link to them here.

Theoretically it should be possible to parse the blueprints, and extract the information. The question is if there is an easy way to do that.

I hate to bump, butā€” Iā€™ve developed a solution over the last few days!

The core bit is this function (which Iā€™ve packaged into a static ā€œHelpersā€ plugin class, out of habit)

<?php class Helpers {
  public static function blueprint ($template) {
    $yaml = f::read(kirby()->roots()->blueprints() . DS . $template . '.yml');
    return yaml::decode($yaml);
  }
}

Fortunately, the blueprints ā€œrootā€ was available on the Kirby object, so it was a simple matter of assembling the proper filename, reading it into a string, and decoding it with Kirbyā€™s Yaml parser. At the moment, itā€™ll only work with yml files, as I havenā€™t looked into how the panel resolves the php/yml extensions.

In the process, I also created a couple of other static methods in the Helper class for working with blueprints:

<?php class Helpers {
  public static function blueprint ($template) {
    $yaml = f::read(kirby()->roots()->blueprints() . DS . $template . '.yml');
    return yaml::decode($yaml);
  }

  # Get all field-related data off a particular blueprint (if the field exists)
  public static function field_info ($template, $field) {
    $fields = static::blueprint($template)['fields'];
    return array_key_exists($field, $fields) ? $fields[$field] : false;
  }

  # Get a localized label from a template name and field key:
  public static function localized_field_name ($template, $key, $language = null) {
    if ( is_null($language) ) $language = site()->language()->locale();
    if ( $field = static::field_info($template, $key) ) {
      $label = $fields[$key]['label'];
      if ( is_array($label) ) {
        return isset($label[$language]) ? $label[$language] : $label[site()->defaultLanguage()->locale()];
      } else {
        return $label;
      }
    }
  }

  # Get possible values for a options-based field, and their labels:
  public static function field_options ($template, $field) {
    $info = static::field_info($template, $field);
    return isset($info['options']) ? $info['options'] : [];
  }

  # Get a localized label for a particular value from a particular field:
  public static function localized_field_option ($template, $key, $value, $language = null) {
    if ( is_null($language) ) $language = site()->language()->locale();

    $options = static::field_options($template, $key);

    if ( isset($options[$value]) ) {
      return isset($options[$value][$language]) ? $options[$value][$language] : $options[$value][site()->defaultLanguage()->locale()];
    } else {
      return $options;
    }
  }
}

There are a few more functions that are less widely adaptableā€” part of the initial problem was that the site needed to render select menus in the front-end that contained localized options, while keeping the definition of possible values/labels all in one place.

2 Likes

@AugustMiller : Thanks for sharing, this will probably come in handy at one point or the other :slight_smile:

Thanks a lot for sharing!

With this custom field method ā€¦

<?php
  field::$methods['option'] = function($field) {
    $options = helpers::field_options($field->page()->template(), $field->key());
    if (is_array($options) && isset($options[$field->value()])) {
      return $options[$field->value()];
    }
    return;
  };
?>

ā€¦ I can use this in my template ā€¦

<?php echo $page->myselectfield()->option() ?>

Yep! I didnā€™t want to bump the thread, but Iā€™ve published this as a plugin, in case anyone wants it.

I mention the possibility of implementing the plugin as field methods in the readme, but left it up to the implementing developer to add them, as needed.

1 Like