Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modular Architecture for Building Content Websites

Souvik Das Gupta
September 28, 2018

Modular Architecture for Building Content Websites

Craft is great at not making any assumptions about your content. It puts a bit more onus on you but in turn enables you to create a unique, hand-crafted information architecture for your website. However, the entire exercise from content modelling to templating can get daunting when working with a large content website. This talk will simplify the exercise by suggesting an approach that is loosely based on the OOUX methodology while keeping reuse and scale in mind. We’ll walk through the process right from identifying content objects to abstracting reusable patterns and finally organising Twig templates in Craft.

Presented at Dot All 2018 (Berlin): https://dotall.com/sessions/architecting-a-content-website

Souvik Das Gupta

September 28, 2018
Tweet

More Decks by Souvik Das Gupta

Other Decks in Design

Transcript

  1. Section Section Website Section Page Page Page Page Page Page

    Page Template Template Template n = templates
  2. Section Section Website Blog Section Page Page Post Page Page

    Page Post Template Template Template Post Post
  3. Section Section Website Blog Section Page Page Post Page Page

    Page Post Template Template Post Post Template Post Post Post Post Post Post Post Post Post Post Post Post Post Post Post Post Post Index
  4. Post Post Post Post Post Post Post Post Post Post

    Post Post Post Post Post Post Post Post Post Post Post Post Post Post Index Template Section Section Website Blog Section Page Page Page Page Page Template Template Post Post Post Post Post Post Post Post Post Post Post Post Post Post Post Post
  5. Post Post Post Post Post Post Post Post Post Post

    Post Post Post Post Post Post Post Post Post Post Post Post Post Post Index Template Section Section Website Blog Section Page Page Page Page Page Template Template Post Post Post Post Post Post Post Post Post Post Post Post Post Post Post Post Taxonomy Taxonomy Taxonomy Taxonomy
  6. Post Post Post Post Post Post Post Post Post Post

    Post Post Post Post Post Post Post Post Post Post Post Post Post Post Index Template Section Section Website Blog Section Page Page Page Page Page Template Template Post Post Post Post Post Post Post Post Post Post Post Post Post Post Post Post Taxonomy Taxonomy Taxonomy Taxonomy n = templates (page + index + taxonomy)
  7. “ – Tim Berners-Lee There are lots of reasons for

    modularity. The basic one is that one module 
 can evolve or be replaced without affecting 
 the others. www.w3.org/DesignIssues/Modularity.html
  8. “ – Tim Berners-Lee If the interfaces are clean, and

    there are no side effects, then a developer can redesign a module without having to deeply understand the neighboring modules. www.w3.org/DesignIssues/Modularity.html
  9. “ – Tim Berners-Lee The flip side is that a

    cleanly designed module designed as part of one system can be re-used in other systems. www.w3.org/DesignIssues/Modularity.html
  10. “ – Tim Berners-Lee There are lots of reasons for

    modularity. The basic one is that one module 
 can evolve or be replaced without affecting 
 the others. www.w3.org/DesignIssues/Modularity.html
  11. “ – Tim Berners-Lee There are lots of reasons for

    modularity. The basic one is that one module 
 can evolve or be replaced without affecting 
 the others. www.w3.org/DesignIssues/Modularity.html
  12. “ – Tim Berners-Lee If the interfaces are clean, and

    there are no side effects, then a developer can redesign a module without having to deeply understand the neighboring modules. www.w3.org/DesignIssues/Modularity.html
  13. “ – Tim Berners-Lee The flip side is that a

    cleanly designed module designed as part of one system can be re-used in other systems. www.w3.org/DesignIssues/Modularity.html
  14. Spon posals Talks Schedule JSFoo Speakers Talk 1 Talk 2

    Talk 3 Speaker 1 Speaker 2 Speaker 3 Track 1 Track 2
  15. Talks Schedule JSFoo Speakers Talk 1 Talk 2 Talk 3

    Speaker 1 Speaker 2 Speaker 3 Track 1 Track 2 Spon posals
  16. Talks Schedule JSFoo Speakers Talk 1 Talk 2 Talk 3

    Speaker 1 Speaker 2 Speaker 3 Track 1 Track 2 Spon posals
  17. Talks Schedule JSFoo Speakers Talk 1 Talk 2 Talk 3

    Speaker 1 Speaker 2 Speaker 3 Track 1 Track 2 Spon posals
  18. Talks Schedule JSFoo Speakers Talk 1 Talk 2 Talk 3

    Speaker 1 Speaker 2 Speaker 3 Track 1 Track 2 Spon posals
  19. Talks Schedule JSFoo Speakers Talk 1 Talk 2 Talk 3

    Speaker 1 Speaker 2 Speaker 3 Track 1 Track 2 Sponsors Workshops Proposals Past Speakers Featured Talks Venue Hotels Spon posals
  20. “ – Tom Brinck Contrast and consistency go hand in

    hand. Make similar things look similar. Make different things look different. Usability For The Web (2001)
  21. “ – Josh Clark Pull back from obsession with presentation.

    Start with content. [Designers] have to accept that your content will take many forms.… Seven Deadly Mobile Myths (2012)
  22. Structured Content Information or content that is organised in a

    predictable way and is usually classified with metadata.
  23. Content Modelling 1. Identify the objects (content types) 2. Define

    the objects (models) 3. Map relationships between objects
  24. 1. Asset 2. Category 3. Entry 4. Globals 5. MatrixBlock

    6. Tag 7. User craft\elements\Asset craft\elements\Category craft\elements\Entry craft\elements\GlobalSet craft\elements\MatrixBlock craft\elements\Tag craft\elements\User
  25. 1. Asset 2. Category 3. Entry 4. Globals 5. MatrixBlock

    6. Tag 7. User craft\elements\Asset craft\elements\Category craft\elements\Entry craft\elements\GlobalSet craft\elements\MatrixBlock craft\elements\Tag craft\elements\User
  26. Views 1. Are the representations of an object. 2. Are

    not tied to a visual design, but rather to a purpose behind the representation. 3. Stack, nest and cascade across all objects. 4. Can evolve (or be replaced) for specific objects.
  27. _components templates/ _layouts " details teasers ! ! snippets !

    default.twig default.twig default.twig " _views ! "
  28. Separation of Concerns Components Layouts Views Render
 a UI Component

    Render 
 an object Provide scaffolding
 for laying out the content
  29. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Speaker Page
  30. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Snippet(Talk)
  31. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Detail(Speaker)
  32. {% block detail_main %}
 <article class="detail">
 <header>
 {% block detail_header_content

    %}
 <h1>{{ entry.title }}</h1>
 {% endblock %}
 </header>
 
 {% block detail_body %}
 <main>
 {% block detail_body_content %}
 {% for component in entry.body.all() %}
 
 {% include '_components/' ~ component.type 
 with { self: component } only %}
 
 {% endfor %}
 {% endblock %}
 </main>
 {% endblock %}
 
 {% block detail_body_after %}{% endblock %}
 _views/details/default.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  33. {% block detail_main %}
 <article class="detail">
 <header>
 {% block detail_header_content

    %}
 <h1>{{ entry.title }}</h1>
 {% endblock %}
 </header>
 
 {% block detail_body %}
 <main>
 {% block detail_body_content %}
 {% for component in entry.body.all() %}
 
 {% include '_components/' ~ component.type 
 with { self: component } only %}
 
 {% endfor %}
 {% endblock %}
 </main>
 {% endblock %}
 
 {% block detail_body_after %}{% endblock %}
 _views/details/default.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  34. {% block detail_main %}
 <article class="detail">
 <header>
 {% block detail_header_content

    %}
 <h1>{{ entry.title }}</h1>
 {% endblock %}
 </header>
 
 {% block detail_body %}
 <main>
 {% block detail_body_content %}
 {% for component in entry.body.all() %}
 
 {% include '_components/' ~ component.type 
 with { self: component } only %}
 
 {% endfor %}
 {% endblock %}
 </main>
 {% endblock %}
 
 {% block detail_body_after %}{% endblock %}
 _views/details/default.twig 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
  35. {% extends '_layouts/fragment' %}
 
 
 
 {% block component_content

    %}
 <div class="{{ self.class ?? style ?? '' }}">
 {{ self.text }}
 </div>
 {% endblock %} _components/text.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  36. {% extends '_layouts/fragment' %}
 
 
 
 {% block component_content

    %}
 <div class="{{ self.class ?? style ?? '' }}">
 {{ self.text }}
 </div>
 {% endblock %} _components/text.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  37. {% extends '_layouts/fragment' %}
 
 
 
 {% block component_content

    %}
 <div class="{{ self.class ?? style ?? '' }}">
 {{ self.text }}
 </div>
 {% endblock %} _components/text.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  38. {% extends '_layouts/fragment' %}
 
 
 
 {% block component_content

    %}
 <div class="{{ self.class ?? style ?? '' }}">
 {{ self.text }}
 </div>
 {% endblock %} _components/text.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 How will a talk and a speaker view be made different?
  39. _components templates/ _layouts " details teasers ! ! snippets !

    default.twig default.twig default.twig " _views ! "
  40. _components ꔇ templates/ _layouts " details teasers ! ! snippets

    ! default.twig default.twig speakers.twig default.twig speakers.twig talks.twig " _views ! "
  41. {% extends '_views/details/default' %}
 
 
 
 {% block detail_header_content

    %}
 
 {# Photo #}
 {% set photo = entry.photo.one() %}
 {% include '_components/image' with {
 self: photo,
 style: 'round',
 } %}
 
 {# Name #}
 {{ parent() }} {# <h1> {{ entry.title }} </h1> #}
 
 <hr>
 
 {# Job Title and Twitter #}
 <p>{{ entry.jobTitle }}</p>
 <p>
 <a href="//twitter.com/{{ entry.twitter }}">
 _views/details/speakers.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  42. {% extends '_views/details/default' %}
 
 
 
 {% block detail_header_content

    %}
 
 {# Photo #}
 {% set photo = entry.photo.one() %}
 {% include '_components/image' with {
 self: photo,
 style: 'round',
 } %}
 
 {# Name #}
 {{ parent() }} {# <h1> {{ entry.title }} </h1> #}
 
 <hr>
 
 {# Job Title and Twitter #}
 <p>{{ entry.jobTitle }}</p>
 <p>
 <a href="//twitter.com/{{ entry.twitter }}">
 _views/details/speakers.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  43. {% extends '_views/details/default' %}
 
 
 
 {% block detail_header_content

    %}
 
 {# Photo #}
 {% set photo = entry.photo.one() %}
 {% include '_components/image' with {
 self: photo,
 style: 'round',
 } %}
 
 {# Name #}
 {{ parent() }} {# <h1> {{ entry.title }} </h1> #}
 
 <hr>
 
 {# Job Title and Twitter #}
 <p>{{ entry.jobTitle }}</p>
 <p>
 <a href="//twitter.com/{{ entry.twitter }}">
 _views/details/speakers.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  44. {% set photo = entry.photo.one() %}
 {% include '_components/image' with

    {
 self: photo,
 style: 'round',
 } %}
 
 {# Name #}
 {{ parent() }} {# <h1> {{ entry.title }} </h1> #}
 
 <hr>
 
 {# Job Title and Twitter #}
 <p>{{ entry.jobTitle }}</p>
 <p>
 <a href="//twitter.com/{{ entry.twitter }}">
 @{{ entry.twitter }}
 </a>
 </p>
 {% endblock %}
 
 
 
 _views/details/speakers.twig 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
  45. {% set photo = entry.photo.one() %}
 {% include '_components/image' with

    {
 self: photo,
 style: 'round',
 } %}
 
 {# Name #}
 {{ parent() }} {# <h1> {{ entry.title }} </h1> #}
 
 <hr>
 
 {# Job Title and Twitter #}
 <p>{{ entry.jobTitle }}</p>
 <p>
 <a href="//twitter.com/{{ entry.twitter }}">
 @{{ entry.twitter }}
 </a>
 </p>
 {% endblock %}
 
 
 
 _views/details/speakers.twig 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
  46. {% block detail_body_after %}
 
 {# 
 Talks by this

    Speaker 
 #}
 
 {% set talks = craft.entries({
 section: 'talks',
 relatedTo: entry,
 }).all() %}
 
 {% if talks|length %} 
 <section>
 <h2>Talks by {{ entry.firstName }}</h2>
 
 <ul>
 {% for talk in talks %}
 {% block related_item %}
 <li>
 {# Invoke a relevant view for the talk object #}
 </li>
 _views/details/speakers.twig 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
  47. {% block detail_body_after %}
 
 {# 
 Talks by this

    Speaker 
 #}
 
 {% set talks = craft.entries({
 section: 'talks',
 relatedTo: entry,
 }).all() %}
 
 {% if talks|length %} 
 <section>
 <h2>Talks by {{ entry.firstName }}</h2>
 
 <ul>
 {% for talk in talks %}
 {% block related_item %}
 <li>
 {# Invoke a relevant view for the talk object #}
 </li>
 _views/details/speakers.twig 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
  48. {% block detail_body_after %}
 
 {# 
 Talks by this

    Speaker 
 #}
 
 {% set talks = craft.entries({
 section: 'talks',
 relatedTo: entry,
 }).all() %}
 
 {% if talks|length %} 
 <section>
 <h2>Talks by {{ entry.firstName }}</h2>
 
 <ul>
 {% for talk in talks %}
 {% block related_item %}
 <li>
 {# Invoke a relevant view for the talk object #}
 </li>
 _views/details/speakers.twig 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
  49. {% block detail_body_after %}
 
 {# 
 Talks by this

    Speaker 
 #}
 
 {% set talks = craft.entries({
 section: 'talks',
 relatedTo: entry,
 }).all() %}
 
 {% if talks|length %} 
 <section>
 <h2>Talks by {{ entry.firstName }}</h2>
 
 <ul>
 {% for talk in talks %}
 {% block related_item %}
 <li>
 {# Invoke a relevant view for the talk object #}
 
 _views/details/speakers.twig 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
  50. _components ꔇ templates/ _layouts " details teasers ! ! snippets

    ! default.twig default.twig speakers.twig default.twig speakers.twig talks.twig " _views ! "
  51. Router 1. Target a desired view 2. Find the most

    specific template based on an order of precedence 3. Invoke the template
  52. {% block detail_body_after %}
 
 {# 
 Talks by this

    Speaker 
 #}
 
 {% set talks = craft.entries({
 section: 'talks',
 relatedTo: entry,
 }).all() %}
 
 {% if talks|length %} 
 <section>
 <h2>Talks by {{ entry.firstName }}</h2>
 
 <ul>
 {% for talk in talks %}
 {% block related_item %}
 <li>
 {% include ‘_routers/snippet’ with { entry: talk } %}
 </li>
 _views/details/speakers.twig 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
  53. {% block detail_body_after %}
 
 {# 
 Talks by this

    Speaker 
 #}
 
 {% set talks = craft.entries({
 section: 'talks',
 relatedTo: entry,
 }).all() %}
 
 {% if talks|length %} 
 <section>
 <h2>Talks by {{ entry.firstName }}</h2>
 
 <ul>
 {% for talk in talks %}
 {% block related_item %}
 <li>
 {% include ‘_routers/snippet’ with { entry: talk } %}
 </li>
 _views/details/speakers.twig 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
  54. {#
 Snippet Router
 #}
 
 {%- include [
 '_views/snippets/' ~

    entry.section ~ '/' ~ entry.type,
 '_views/snippets/' ~ entry.section ~ '/default',
 '_views/snippets/' ~ entry.section,
 '_views/snippets/default',
 ] -%}
 _routers/snippet.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  55. {#
 Snippet Router
 #}
 
 {%- include [
 '_views/snippets/' ~

    entry.section ~ '/' ~ entry.type,
 '_views/snippets/' ~ entry.section ~ '/default',
 '_views/snippets/' ~ entry.section,
 '_views/snippets/default',
 ] -%}
 _routers/snippet.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  56. {#
 Snippet Router
 #}
 
 {%- include [
 '_views/snippets/' ~

    entry.section ~ '/' ~ entry.type,
 '_views/snippets/' ~ entry.section ~ '/default',
 '_views/snippets/' ~ entry.section,
 '_views/snippets/default',
 ] -%}
 _routers/snippet.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  57. {#
 Snippet Router
 #}
 
 {%- include [
 '_views/snippets/' ~

    entry.section ~ '/' ~ entry.type,
 '_views/snippets/' ~ entry.section ~ '/default',
 '_views/snippets/' ~ entry.section,
 '_views/snippets/default',
 ] -%}
 _routers/snippet.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  58. {#
 Snippet Router
 #}
 
 {%- include [
 '_views/snippets/' ~

    entry.section ~ '/' ~ entry.type,
 '_views/snippets/' ~ entry.section ~ '/default',
 '_views/snippets/' ~ entry.section,
 '_views/snippets/default',
 ] -%}
 _routers/snippet.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  59. {% block snippet_main %} <p> {% block snippet_content %}
 <a

    rel="permalink" href="{{ entry.url }}">
 {{ entry.title }}
 </a> {% endblock %} </p>
 {% endblock %} _views/snippets/default.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  60. {% block snippet_main %} <p> {% block snippet_content %}
 <a

    rel="permalink" href="{{ entry.url }}">
 {{ entry.title }}
 </a> {% endblock %} </p>
 {% endblock %} _views/snippets/default.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  61. {% extends '_views/snippets/default' %}
 
 {% block snippet_content %}
 {{

    entry.year }} – {{ parent() }}
 {% endblock %} _views/snippets/talks.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  62. _components " _views ! templates/ _routers ! snippet.twig detail.twig teaser.twig

    page.twig teasers snippets details " " " _layouts "
  63. _components " _views ! templates/ _routers ! snippet.twig detail.twig teaser.twig

    page.twig teasers snippets details " " " _layouts "
  64. {% extends '_layouts/page' %}
 
 
 
 {% block page_main

    %}
 
 {% include '_routers/detail' with { entry: entry } %}
 
 {% endblock %}
 _routers/page.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  65. {% extends '_layouts/page' %}
 
 
 
 {% block page_main

    %}
 
 {% include '_routers/detail' with { entry: entry } %}
 
 {% endblock %}
 _routers/page.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  66. {% extends '_layouts/page' %}
 
 
 
 {% block page_main

    %}
 
 {% include '_routers/detail' with { entry: entry } %}
 
 {% endblock %}
 _routers/page.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  67. {#
 Detail Router
 #}
 
 {%- include [
 '_views/details/' ~

    entry.section ~ '/' ~ entry.type,
 '_views/details/' ~ entry.section ~ '/default',
 '_views/details/' ~ entry.section,
 '_views/details/default',
 ] -%} _routers/detail.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  68. {#
 Detail Router
 #}
 
 {%- include [
 '_views/details/' ~

    entry.section ~ '/' ~ entry.type,
 '_views/details/' ~ entry.section ~ '/default',
 '_views/details/' ~ entry.section,
 '_views/details/default',
 ] -%} _routers/detail.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  69. {#
 Detail Router
 #}
 
 {%- include [
 '_views/details/' ~

    entry.section ~ '/' ~ entry.type,
 '_views/details/' ~ entry.section ~ '/default',
 '_views/details/' ~ entry.section,
 '_views/details/default',
 ] -%} _routers/detail.twig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 What if we want to make the related talks a bit more engaging?
  70. {% block detail_body_after %}
 
 {# 
 Talks by this

    Speaker 
 #}
 
 {% set talks = craft.entries({
 section: 'talks',
 relatedTo: entry,
 }).all() %}
 
 {% if talks|length %} 
 <section>
 <h2>Talks by {{ entry.firstName }}</h2>
 
 <ul>
 {% for talk in talks %}
 {% block related_item %}
 <li>
 {% include '_routers/snippet' with { entry: talk } %} 
 </li>
 _views/details/speakers.twig 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
  71. {% block detail_body_after %}
 
 {# 
 Talks by this

    Speaker 
 #}
 
 {% set talks = craft.entries({
 section: 'talks',
 relatedTo: entry,
 }).all() %}
 
 {% if talks|length %} 
 <section>
 <h2>Talks by {{ entry.firstName }}</h2>
 
 <ul>
 {% for talk in talks %}
 {% block related_item %}
 <li>
 {% include ‘_routers/teaser' with { entry: talk } %}
 </li>
 _views/details/speakers.twig 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
  72. Recap 1. Content Modelling 3. Translate Modules
 into Templates 2.

    Identify Representations
 and Modules Objects Views Views (Objects) Routers +