I'm not sure I understand your question 100%, but I'm assuming you're talking about a thoroughly modular approach to templating, and not about letting your end users/content editors actually edit the content model (i.e. create/edit Sections, fields, field layouts etc) – because the latter is not a good approach, in general.
Either way, this is a very open question, so this answer is kind of broad (or in any case, it just outlines one way to do it, focused on entries and fields).
Note that I haven't seen the Mijingo videos, but I think the answer to your use case is probably to go with a partial-heavy approach. Basically, a "partial" is an incomplete/modular template that you can include in another template.
First off, you might want to create a "catch-all" template (let's just assume your catch-all template is called templates/index.html
). Be sure to add the path to that template (which would just be index
) to the Entry Template setting for all relevant Sections, to make sure the same, catch-all template is always loaded for all entry URLs.
Then, assuming your site has three fields – heading
, image
and body
– you'd create a partial template for each of these fields. It's a good idea to store your partials in a folder prefixed with an underscore, to make sure Craft doesn't include them whenever it maps URL routes to template paths (these templates aren't standalone and should never be rendered on their own). So for the three fields in our example, you'd create these three templates:
/templates/_partials/heading.html
/templates/_partials/image.html
/templates/_partials/body.html
Here's how the heading.html
partial could look:
{% if element.heading %}
<h1 class="heading">{{ element.heading }}</h1>
{% endif %}
Inside your catch-all template, you'd then need to {% include %}
these partial templates, following some logic of your choice.
Here's an approach that basically loops through all fields for the current entry
's field layout, attempting to include a partial template for each field:
{% extends '_layout' %}{# Assuming this template extends a basic layout template #}
{% block content %}
{# Get all fields, indexed by field ID #}
{% set allFields = craft.fields.getAllFields('id') %}
{# Get the IDs for all fields in this entry's field layout #}
{% set fieldIds = entry.getFieldLayout().getFieldIds() %}
{# Loop through the entry fields' IDs; attempt to include template based on each field's handle #}
{% for fieldId in fieldIds %}
{% set field = allFields[fieldId] ?? null %}
{% if field and entry[field.handle] %}
{% include '_partials/'~field.handle ignore missing with { element: entry } %}
{% endif %}
{% endfor %}
{% endblock %}
A couple of things to note:
First, the above code builds the template path to each partial dynamically, by joining the root path ('_partials/'
) with each field's handle
property (using the tilde operator ~
). In other words, this solution hinges upon using the fields' handles for the partial templates' filenames. Note that its also possible to pass an array of template paths to the {% include %}
tag, in which case Craft will attempt to load the next template in that array whenever it fails the load the former. Here's how that could look:
{% include ['_partials/matrix/'~field.handle, '_partials/fields/'~field.handle] ignore missing with { element: entry } %}`
With the above, Craft would first look for the template /templates/_partials/matrix/title.html
, and if that file doesn't exist it'd look for /templates/_partials/fields/title.html
(before failing silently due to the ignore missing
directive).
Second, it might seem redundant to pull all fields from the FieldsService, but the alternative is to hit the database for every field in your entry's field layout, so this should actually be the most performant way to pull the current entry's actual FieldModels.
Third – the with
statement for the {% include %}
tag enables you to pass parameters to the included template. In this example, we're actually exposing the current entry to the included template as the more generic element
variable. This is just future proofing in case we want to use the same partials for other element types, e.g. categories (in which case you'd use with { element: category }
) – notice that in the example heading.html
template above, element.heading
is used over entry.heading
.
If you want to avoid variable pollution, you might pass the only
directive aswell, in which case only whatever parameters passed with with
will be made available to the included statement, nothing else:
{% include '_partials/'~field.handle with { element: entry } only %}
Of course, you can include other stuff in the object passed to with
, as well. For instance, you might want to render stuff differently if the current field is either first or last in the loop:
{% include '_partials/'~field.handle with { element: entry, isFirst: loop.first, isLast: loop.last } %}
{# In heading.html #}
<div class="field{{ isFirst ? ' is-first' }}{{ isLast ? ' is-last' }}">
<h2 class="heading">{{ element.heading }}</h2>
</div>
Going further; talking about Matrix fields and alluding to the fact that putting Matrix field partials in a separate templates folder might be a good idea – if you're going fully modular it might make sense to create separate templates for each Matrix block type, as well. Tons of ways to skin this cat, but one way to do it is to add a sub folder for every Matrix field, containing partials for each of the field's block types. For example, if you've got a Matrix field called contentBlocks
with the block types images
and text
you might structure it like this:
templates/_partials/contentBlocks.html
(the partial for the Matrix field itself)
templates/_partials/contentBlocks/images.html
(the partial for the images
block type)
templates/_partials/contentBlocks/text.html
(the partial for the text
block type)
Then, inside the contentBlocks.html
template, you could do something like this:
{% for block in element.contentBlocks %}
{% include '_partials/contentBlocks/'~block.type ignore missing with { block: block } only %}
{% endfor %}
In fact, you might not even need the contentBlocks.html
field template, and just do something like this in index.html
:
{% if field.type == 'Matrix' %}
{# Attempt to include the blocks for this Matrix field #}
{% for block in entry.field %}
{% include '_partials/'~field.handle~'/'~block.type ignore missing with { block: block } only %}
{% endfor %}
{% else %}
{# Non-Matrix fields #}
{% include '_partials/'~field.handle ignore missing with { element: entry } %}
{% endif %}
One final thing to note – with the above solution, your entries' fields' partials will be included in the order corresponding to the entry type's field layout (i.e. the first field in the layout will be included first). This may or may not be what you want (the order that makes sense inside the CP may not always correspond to what the design/front end should look like) but if you need more control over your fields' order, you could do something like this inside index.html
:
{% set fieldsToInclude = ['image', 'heading', 'body'] %}
{% for fieldHandle in fieldsToInclude %}
{% if entry['fieldHandle'] %}
{% include '_partials/'~fieldHandle ignore missing with { element: entry ) %}
{% endif %}
{% endfor %}
The above will actually be slightly more performant than the first example, because it avoids the few database queries involved in pulling the fields dynamically.
Of course, for optimal flexibility you'd probably go heavy on the Matrix fields, too – obviously, any Matrix block partials would render in their original order inside the Matrix fields, so with Matrix blocks your content editors will have complete control over where each piece of content actually renders.