{{craftSnippets}}
Home Articles Plugins Starter theme Components on Github Gists About
  • Home
  • Articles
  • Creating attributes table from entry fields in Craft CMS
Posted on Jun 25, 2020 by Piotr Pogorzelski

Creating attributes table from entry fields in Craft CMS

Attributes table on github gists
When you group fields in matrix block, you can loop through them to automatically create a table containing their values.
Table of contents:
  • Grouping fields
  • Outputting attributes in templates using Twig component
  • Looping through matrix block fields
    • class
    • label
    • value
  • Missing fields
  • Additional attributes
  • Outputting attributes table

Grouping fields #

When you deal with e-commerce sites made with Craft commerce or just catalog sites, your product pages often have attributes table. Each attribute needs its own field - so your field list can quickly grow to worrisome size. Handling a large number of fields can be inconvenient, so you might want to group them.

This can be achieved by using a matrix field. Set min number of blocks and max number of blocks setting to 1 and add one block type to the matrix field. That way, the matrix field will act as a container for attribute subfields defined in matrix block we just added. Thanks to setting min setting to 1, each time you create a new entry, one matrix block will be already present.

Outputting attributes in templates using Twig component #

The most common way of displaying attributes is table made of two columns - attribute labels and attribute values. To output such a table in the Twig template, we don't need to manually handle each field contained in the matrix block and hardcode field handle in the template. Instead, we can loop through these fields and output their labels and values.

Here's the Twig component that handles this:

{% apply spaceless %}

{# lightswitch field values #}
{% if lighswitchOn is not defined %}
	{% set lighswitchOn = 'yes' %}
{% endif %}
{% if lighswitchOff is not defined %}
	{% set lighswitchOff = 'no' %}
{% endif %}

{# attributes contents array #}
{% set allAttributes = [] %}

{% if matrixBlock is defined and matrixBlock is not null %}

{% set attributeFields = matrixBlock.getFieldLayout().getFields() %}

{% for singleField in attributeFields %}

    {% switch className(singleField) %}

		{# lightswitch #}
    	{% case 'craft\\fields\\Lightswitch' %}
	    {% set allAttributes = allAttributes|merge([{
	        class: className(singleField)|split('\\')[2]|lower,
	        label: singleField.name,
	        value: matrixBlock[singleField.handle] ? lighswitchOn : lighswitchOff,
	    }]) %}

	    {# plain text #}
    	{% case 'craft\\fields\\PlainText' %}
    	{% if matrixBlock[singleField.handle] is not empty %}
		    {% set allAttributes = allAttributes|merge([{
		    	class: className(singleField)|split('\\')[2]|lower,
		        label: singleField.name,
		        value: matrixBlock[singleField.handle],
		    }]) %}
	    {% endif %}

		{# number #}
    	{% case 'craft\\fields\\Number' %}
    	{% if matrixBlock[singleField.handle] is not empty %}
		    {% set allAttributes = allAttributes|merge([{
		    	class: className(singleField)|split('\\')[2]|lower,
		        label: singleField.name,
		        value: matrixBlock[singleField.handle] ~ (singleField.suffix is not empty ? ' ' ~ singleField.suffix),
		    }]) %}
	    {% endif %}

	    {# dropdown or radio #}
    	{% case 'craft\\fields\\Dropdown' or 'craft\\fields\\RadioButtons' %}
    	{% if matrixBlock[singleField.handle].value is not null %}
		    {% set allAttributes = allAttributes|merge([{
		    	class: className(singleField)|split('\\')[2]|lower,
		        label: singleField.name,
		        value: matrixBlock[singleField.handle].label,
		    }]) %}
	    {% endif %}

		{# multiselect or chexkboxes #}
    	{% case 'craft\\fields\\MultiSelect' or 'craft\\fields\\Checkboxes' %}
    	{% if matrixBlock[singleField.handle] is not empty %}
		    {% set allAttributes = allAttributes|merge([{
		    	class: className(singleField)|split('\\')[2]|lower,
		        label: singleField.name,
		        value: matrixBlock[singleField.handle]|map(option => option.label)|join(', '),
		    }]) %}
	    {% endif %}

	    {# url #}
    	{% case 'craft\\fields\\Url' %}
    	{% if matrixBlock[singleField.handle] is not empty %}
		    {% set allAttributes = allAttributes|merge([{
		    	class: className(singleField)|split('\\')[2]|lower,
		        label: singleField.name,
		        value: "<a href='#{matrixBlock[singleField.handle]}'>#{matrixBlock[singleField.handle]}</a>",
		    }]) %}
	    {% endif %}

	    {# email #}
    	{% case 'craft\\fields\\Email' %}
    	{% if matrixBlock[singleField.handle] is not empty %}
		    {% set allAttributes = allAttributes|merge([{
		    	class: className(singleField)|split('\\')[2]|lower,
		        label: singleField.name,
		        value: "<a href='mailto:#{matrixBlock[singleField.handle]}'>#{matrixBlock[singleField.handle]}</a>",
		    }]) %}
	    {% endif %}

		{# entries and categories #}
    	{% case 'craft\\fields\\Entries' or 'craft\\fields\\Categories' %}
    	{% if matrixBlock[singleField.handle].exists() %}
		    {% set allAttributes = allAttributes|merge([{
		    	class: className(singleField)|split('\\')[2]|lower,
		        label: singleField.name,
		        value: matrixBlock[singleField.handle].all()|map(singleElement => "<a href='#{singleElement.url}'>#{singleElement.title}</a>")|join(', '),
		    }]) %}
	    {% endif %}		


    {% endswitch %}
{% endfor %}
{% endif %}

{# additional attributes injected into component #}
{% if additionalAttributes is defined and additionalAttributes is not empty %}
	{% for additionalSingle in additionalAttributes %}
	    {% set allAttributes = allAttributes|merge([{
	        label: additionalSingle.label,
	        value: additionalSingle.value,
	    }]) %}    
	{% endfor %}
{% endif %}


{# output table #}
{% if allAttributes is not empty %}
<table class="attributes table is-hoverable is-bordered">
    <tbody>
	{% for singleAttribute in allAttributes %}
	    <tr class="{{singleAttribute.class is defined ? 'singleAttribute.class'~'-field'}}">
			<td class="attributes__label">{{singleAttribute.label}}</td>
			<td class="attributes__value">{{singleAttribute.value|raw}}</td>
		</tr>
	{% endfor %}
    </tbody>
</table>
{% endif %}

{% endapply %}

To use it, include it and pass into it variable containing matrix block that contains our attribute fields:

{% include 'path_to_attributes_component' with {
	matrixBlock: entry.matrixFieldHandle.one()
} only %}

Looping through matrix block fields #

So, how does it work? First, we need to gather and process our data and put it into an array. This array is named allAttributes and is defined at the beginning of the component. For each matrix block field, we get three things:

class #

A string containing CSS class created from the field PHP class name of the field. This class will be applied to table row, so we can style attributes taken from various fields differently.

label #

The attribute label is taken from the field name. Note that field names in Craft do not have multiple language versions. However, if your site has multiple locales, you can use a little trick to make them translatable. For example, if you use two languages - english and french, set your field name like this - "french name/english name". Then, you can output proper label like this:

{{currentSite.language == 'fr' ? singleField.name|split('/')[0] : singleField.name|split('/')[1] }}

What we did here, was using / in field name as a separator. Using split filter, we divide field name into two parts and then output either first or second part - depending on the language.

value #

Depending on the field type, value will be outputted in different ways. Here is the list of fields and how their values are outputted:

  • lightswitch - depending on switch state, lighswitchOn and lighswitchOff strings are used. These strings can contain regular text or things like font icons (like font-awesome). Thanks to the use of default Twig filter, you can pass variables into component that can overwrite both of these strings.
  • plain text - pretty straightforward - field value is outputted if the field is not empty.
  • number - same as plain text, but you can also set number unit of measurement, using field suffix setting. For example, if your field contains weight, set it to "kg". The unit of measurement will be appended to the field value.
  • dropdown and radio - if option was selected, its label will be outputted. For dropdown, an options with empty values will be ignored (because such options usually have a label like "select something").
  • multiselect and checkboxes - if options were selected, their labels will be outputted, separated by commas.
  • url - will be outputted as a link - if the field is not empty.
  • email - will be outputted as a mailto: link - if the field is not empty.
  • entries and categories - will be outputted as links - separated by commas if the field has multiple elements selected.

Missing fields #

You might notice that we skipped a few fields. That's because their implementation would be pretty arbitrary. For example - asset field might contain just images, in which case we would also need to define specific image transform. Or it might allow other types of files, so we might also need to define some logic for checking file type. This would be pretty site-specific - so I left implementation to developers.

Here's a list of omitted fields:

  • assets
  • color
  • date/time
  • table
  • tags
  • users

Additional attributes #

If data contained in the matrix block is not enough for us, we can always pass some content into attributes table manually. We can do this by injecting additionalAttributes array into our component. This array should contain a collection of objects and each of these objects should have label and value properties. You can also optionally add class for styling table row. For example:

{% include 'path_to_attributes_component' with {
	matrixBlock: entry.matrixFieldHandle.one(),
	additionalAttributes: [
		{
			label: 'shoe shape',
			value: 'strange shape',
		},
		{
			label: 'color of shoelaces',
			value: 'colorful',			
		}
	]
} only %}

You can also use table field to inject additional attributes into component. Such table field will need two columns - label and value (and optionally class):

{% include 'path_to_attributes_component' with {
	matrixBlock: entry.matrixFieldHandle.one(),
	additionalAttributes: entry.tableFieldHandle,
} only %}

additionalAttributes content will be appended to allAttributes array. If you want your additional attributes to appear at beginning of the array, just move code handling additional attributes before for loop that handles matrix block (but not before allAttributes array declaration).

Outputting attributes table #

Now that our allAttributes array is bursting with content, we can output it into HTML. To do this, we just use a simple for loop. Note that I added a few bulma CSS classes - for good measure.

Also note raw filter applied to singleAttribute.value - this variable might contain HTML, so we need use raw to make sure that HTML markup is not escaped into HTML entities.


TAGS:
#twig component
If you want to get latest updates on Craft CMS tutorials and components, follow me on Twitter or subscribe to RSS feed.
Quick links for this article:
Attributes table on github gists
Articles on blog:
  • Building reactive Craft Commerce product page with Sprig plugin
  • Dynamically generated PDF attachments for Freeform submissions
  • Using template hooks in Craft CMS
  • Alpine JS modal component for Craft CMS
  • Using template UI elements to extend Craft CMS control panel
  • Matrix within a Matrix - possible solutions for Craft CMS
  • Universal email template for Craft CMS
  • Creating attributes table from entry fields in Craft CMS
  • Namespacing forms in Craft CMS
  • Creating map-based navigation for Craft CMS
  • Placeholder image macro for Craft CMS
  • Building AJAX contact form with Craft CMS
  • Using incognito field plugin for Craft CMS
  • Email footer creator made with Craft CMS
  • Infinite scrolling and lazy loading with Craft CMS
  • Using Javascript in Twig templates with Craft CMS
  • Twig templating tips and tricks for Craft CMS
  • Basic SEO functionality for Craft CMS
  • Working with dates in Craft CMS templates
  • Working with SVG images in Craft CMS templates
  • Responsive and lazy-loaded youtube videos with Craft CMS
  • Debugging and inspecting Twig templates in Craft CMS
  • Creating article excerpts with Twig component in Craft CMS
  • Adding favicons to Craft CMS website
  • Truncating text with Twig macros in Craft CMS
  • Universal language switcher for Craft CMS
  • Read time macro for Craft CMS
  • Using attr() function to render HTML attributes in Craft CMS
  • Building dynamic, AJAX based pagination for Craft CMS
  • How to add Disqus comments to Craft CMS website
  • Ellipsis pagination component for Craft CMS
  • Converting email addresses into links using Twig macro
  • Breadcrumb created from URL for Craft CMS
  • Best developer-oriented Craft CMS plugins
  • Search autocomplete component for Craft CMS
  • RSS feed - template component for Craft CMS
  • Testing emails sent by Craft CMS using Mailtrap
  • Quick edit link - Twig component for Craft CMS
  • Filtering entries in control panel using Searchit plugin
  • Fetching routes into Twig templates in Craft CMS


Building reactive Craft Commerce product page with Sprig plugin

Dynamically generated PDF attachments for Freeform submissions

Using template hooks in Craft CMS

Alpine JS modal component for Craft CMS

Using template UI elements to extend Craft CMS control panel

Matrix within a Matrix - possible solutions for Craft CMS

Universal email template for Craft CMS

Namespacing forms in Craft CMS

Copyright ©2022 Piotr Pogorzelski