Dynamically Output Project Categories

Our block type currently outputs project categories, but they are hard-coded into the HTML:

Let’s make this use the available options within the Project Categories custom attribute that we have created for the Project page type. First, we’re going to use Concrete CMS’s API to retrieve our custom attribute within the view() method of the block controller. Import the page attribute service class as the top of the controller

use Concrete\Core\Attribute\Category\PageCategory;

and then retrieve an instance of it using the Concrete application property, which is available in any controller:

/**
* @var $pageAttributeService PageCategory
*/
$pageAttributeService = $this->app->make(PageCategory::class);
$category = $pageAttributeService->getAttributeKeyByHandle('project_category');

This gets an instance of the Concrete\Core\Entity\Attribute\Key\PageKey object for the Project Category custom attribute. Every page attribute key is an instance of this object, regardless of type. That means that in order to get the special option-list-specific data for our attribute key -- which we need in order to output the available options in our template -- we need to get the settings object for this particular key.

$settings = $category->getAttributeKeySettings();

This is more helpful. The $settings object here is an instance of the Concrete\Core\Entity\Attribute\Key\Settings\SelectSettings object, which is a settings object specific to the Option List attribute type (which used to be named Select -- in case you’re wondering about what Select is doing in the namespace up there). This object has a number of methods that will be useful when working with these types of attributes. For use, we’re going to use the method that retrieves all available options in the Option List.

$optionList = $settings->getOptionList();  
$options = $optionList->getOptions();  
$this->set('options', $options);

It’s a little verbose, but first we retrieve the Concrete\Core\Entity\Attribute\Value\Value\SelectValueOptionList object from the settings object with getOptionList(), and from there we get a collection of options by running getOptions(). This returns an array of Concrete\Core\Entity\Attribute\Value\Value\SelectValueOption objects. We use the controller set() method to send them into the view.php template.

Update the view template

Now we replace our hard-coded HTML with this:

<ul class="nav nav-tabs" id="myTab" role="tablist">
    <?php
        /**
        * @var $options \Concrete\Core\Entity\Attribute\Value\Value\SelectValueOption[]
        */
        $total = count($options);
        for ($i = 0; $i < $total; $i++) { 
        $option = $options[$i];
        $label = $option->getSelectAttributeOptionValue();
        $id = camelcase($label);
    ?>
    <li>
        <a class="<?php if ($i == 0) { ?>active<?php } ?>" id="<?=$id?>-tab" data-toggle="tab" href="#<?=$id?>"
        role="tab" aria-controls="<?=$id?>" aria-selected="true">
            <?=$label?>
        </a>
    </li>
    <?php }
    ?>
</ul>

Let’s walk through this. First, we retrieve the total number of options by using a simple PHP count() method. Then we loop through the total number of options, and retrieve an option object for each time through the loop. We get the name of the option using getSelectAttributeOptionValue(), and we also use the built-in camelcase() method to convert that name (which may contain spaces) into something that we can use to target the tabs on the page. If the tab is the first tab in the list, we add the active class to it. Finally, we output the $label within the content of our tab.

A quick refresh to the page shows that everything is working as expected!