Skip to content

Instantly share code, notes, and snippets.

@watershed
Last active May 3, 2024 16:37
Show Gist options
  • Save watershed/3746fb96d87a5a7c5e0ce9db48300928 to your computer and use it in GitHub Desktop.
Save watershed/3746fb96d87a5a7c5e0ce9db48300928 to your computer and use it in GitHub Desktop.
Report all fields in a Craft instance
{% set fields = craft.app.fields.allFields() %}
{# Dependencies:
Use of `return`ing variables in macros, courtesy of Marion Newlevant's Twig Perversion:
https://plugins.craftcms.com/twig-perversion
#}
<style>
body {color: #333; font: 1rem/1.25 system-ui, sans-serif; margin: 2rem;}
table {border-collapse: collapse;}
th, td {padding: 0.25rem; border: 1px solid rgb(0 0 0 / 0.15);}
th:not([align]) {text-align: left;}
thead tr {background: hsl(0deg 0% 85%);}
tbody tr:first-child,
tr[data-type="Matrix"],
tr[data-type="blockField"] + tr:not([data-type="blockField"]) {border-top: 2px solid rgb(0 0 0 / 0.3);}
thead th {vertical-align: bottom;}
tbody tr > * {vertical-align: top;}
tr.stripe {background: hsl(0deg 0% 95%);}
tr[data-type="Matrix"] [data-col="handle"],
tr[data-type="Matrix"] [data-col="field_type"] {font-weight: bold;}
td[data-col*="handle"],
td[data-col="options"] {font-family: monospace;}
pre {white-space: pre-wrap;}
</style>
{# Compile a list of field option optgroups and values #}
{% macro getOptions(f) %}
{% set opts = [] %}
{% for opt in f.options %}
{% if opt.value is defined %}
{% set opts = opt.value ? opts|merge([ opt.value ]) : opts %}
{% else %}
{% set opts = opt.optgroup is defined ? opts|merge([ "optgroup:#{opt.optgroup}" ]) : opts %}
{% endif %}
{% endfor %}
{{ opts|join(', ') }}
{% endmacro %}
{# Write a table header or data element #}
{% macro tableCell(tagName, text, align, class, data) %}
{{ tag(tagName, {
align: align,
class: class,
data : data,
text : text
}) }}
{% endmacro %}
{# Get the field data for a set of Matrix fields #}
{% macro getBlockFields(fields, handle, n, cols) %}
{% set mData = [] %}
{% for f in fields %}
{% set nPoint = "#{n}.#{loop.index}" %}
{% set bType = null %}
{% if f.hasProperty('columnPrefix') %}
{% set bType = f.columnPrefix|replace('/field_(\\w+)_/', '$1')|ucfirst %}
{% endif %}
{% set data = _self.getFieldData(f, 'blockField', nPoint, cols, handle, bType) %}
{% set mData = mData|merge([ { blockField: data } ]) %}
{# <pre>{{ dump(bType) }}</pre> #}
{% endfor %}
{% return mData %}
{% endmacro %}
{# Get the field data scope defined by the cols array #}
{% macro getFieldData(f, type, n, cols, parent, bType) %}
{% set data = {} %}
{% for col in cols %}
{% set text = '' %}
{% switch col %}
{% case 'count' %}
{% set text = n %}
{% case 'block_type' %}
{% set text = bType ? bType : text %}
{% case 'field_type' %}
{% set text = type %}
{% case 'options' %}
{% set text = f.hasProperty('options') ?
_self.getOptions(f) :
text
%}
{% case 'parent_handle' %}
{% set text = parent ? parent : text %}
{% default %}
{% set text = f[col] %}
{% endswitch %}
{% set data = data|merge({ (col): text }) %}
{% endfor %}
{% return data %}
{% endmacro %}
{##### Set up an empty array, cols and alignment parameters #####}
{% set fData = [] %} {# Array for data 'row' #}
{% set cols = ['count', 'name', 'handle', 'field_type', 'parent_handle', 'block_type', 'options'] %}
{% set right = ['count', 'id'] %} {# Columns to be right aligned #}
{# Append to the fData array #}
{% for f in fields %}
{% set type = f.className|replace('/^.*\\\\/', '') %}
{% set data = _self.getFieldData(f, type, loop.index, cols) %}
{% set mData = type == 'Matrix' ?
_self.getBlockFields(f.blockTypeFields, f.handle, loop.index, cols) :
[]
%}
{% set fData = fData|merge([ { (type): data } ]) %}
{% set fData = mData|length ? fData|merge(mData) : fData %}
{% endfor %}
{# Write the results as a table #}
<table>
<thead>
<tr>
{% for col in cols %}
{% set text = col|replace('_', ' ')|ucfirst %}
{% set align = col in right ? 'right' : null %}
{{ _self.tableCell('th', text, align, null, {col: col}) }}
{% endfor %}
</tr>
</thead>
<tbody>
{% for node in fData %}
{% for type, data in node %}
{% set row = [] %}
{% for col, text in data %}
{% set align = col in right ? 'right' : null %}
{% set cell = _self.tableCell('td', text, align, null, {col: col}) %}
{% set row = row|merge([ cell ]) %}
{% endfor %}
{{ tag('tr', {data: {type: type}, html: row|join('\n')}) }}
{% endfor %}
{% endfor %}
</tbody>
</table>
<script>
const rows = document.querySelectorAll('tbody tr');
const getElem = (node, val, elem) => {
const selector = `[data-${node}="${val}"]`;
elem = elem ? elem.querySelector(selector) : document.querySelector(selector);
return elem;
}
const getVal = (row, col) => {
let val = getElem('col', col, row);
val = val ? val.textContent : null;
return val;
}
const addStripe = (row, stripe) => {
if (stripe) {
row.classList.add('stripe');
}
}
// Row striping which groups Matrix rows with their blockField rows
let stripe = true;
rows.forEach( (row, index) => {
const type = row.dataset.type;
if ( type === 'Matrix' ) {
const handle = getVal(row, 'handle');
row.dataset.handle = handle ? handle : '';
stripe = !stripe;
}
else if ( type === 'blockField' ) {
const handle = getVal(row, 'parent_handle');
if ( handle ) {
const parentRow = getElem('handle', handle);
stripe = parentRow.classList.contains('stripe');
}
}
else {
stripe = !stripe;
}
addStripe(row, stripe);
});
</script>
@watershed
Copy link
Author

watershed commented May 3, 2024

The fData array can be easily enough printed as JSON with fData|json_encode, and then prettified by passing it into something like:

const getPretty = (data) => {
    data = JSON.parse(data);
    data = typeof data === 'object' ? JSON.stringify(data, null, 4) : null;
    return data;
}

@thisisjamessmith
Copy link

This is great, can see how this will be useful. Shame about the Twig Perversion plugin dependency, though. Can't see a quick way around that, but might be worth just accepting some duplicate code instead. (You could solve with embeds, but then you lose the big benefit of a single file).

@watershed
Copy link
Author

Thanks James. Point taken. I just love being able to push stuff in and out of macros without having to print out the outcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment