Last active
May 3, 2024 16:37
-
-
Save watershed/3746fb96d87a5a7c5e0ce9db48300928 to your computer and use it in GitHub Desktop.
Report all fields in a Craft instance
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{% 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> |
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).
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
The
fData
array can be easily enough printed as JSON withfData|json_encode
, and then prettified by passing it into something like: