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