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

Decoupled Drupal Days 2018: Drupal Unhitched

Decoupled Drupal Days 2018: Drupal Unhitched

The CMS in Decoupled Architectures

dirtystylus

August 17, 2018
Tweet

More Decks by dirtystylus

Other Decks in Technology

Transcript

  1. Drupal Unhitched
    The CMS in Decoupled Architectures
    Decoupled Drupal Days 2018

    View Slide

  2. Hi.
    Mark Llobrera
    Technology Director, Bluecadet Philadelphia
    @dirtystylus
    @bluecadet

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. Descriptive, Not Prescriptive
    I’m not telling you how to do this stuff. But I hope all of
    this helps.

    View Slide

  7. 3 Things

    View Slide

  8. 3 Things
    1. What is a decoupled CMS?

    View Slide

  9. 3 Things
    1. What is a decoupled CMS?
    2. Decoupled Scenarios

    View Slide

  10. 3 Things
    1. What is a decoupled CMS?
    2. Decoupled Scenarios
    3. Decoupling Drupal

    View Slide

  11. 1. What is a
    Decoupled CMS?

    View Slide

  12. What is a decoupled CMS?

    View Slide

  13. What is a decoupled CMS?
    •An architecture for websites and applications where
    the CMS is not used to render the user-facing site or
    application.

    View Slide

  14. Stop me if you’ve heard this
    one before

    View Slide

  15. Separating
    CONTENT
    from
    PRESENTATION

    View Slide

  16. 2. Decoupled Scenarios

    View Slide

  17. First, a flashback…

    View Slide

  18. View Slide

  19. Touchscreen Interactive +
    Static XML
    Penn Museum, Native American Voices

    View Slide

  20. “Authoring XML for 300+ objects
    by hand is…unwise.”

    View Slide

  21. “Authoring XML for 300+ objects
    by hand is…unwise.”
    — Me, after lots of pain

    View Slide

  22. CMS (Drupal 8) +
    Touchscreen Interactive (React) +
    JS Website (React)
    Smithsonian
    National Museum of the American Indian
    nmai.si.edu/americans

    View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. View Slide

  28. CMS (Drupal 8) +
    CMS-rendered Website +
    Digital Signage (React)
    Mann Center for the Performing Arts
    manncenter.org

    View Slide

  29. View Slide

  30. View Slide

  31. View Slide

  32. View Slide

  33. CMS +
    CMS-rendered Website +
    React components
    BRIC
    bricartsmedia.org

    View Slide

  34. View Slide

  35. View Slide

  36. CMS
    +
    Statically Published React (GatsbyJS)
    Outrider Foundation
    outrider.org

    View Slide

  37. View Slide

  38. View Slide

  39. View Slide

  40. View Slide

  41. CMS (Drupal 7) +
    CMS-rendered Website +
    Touch Wall (Cinder/C++) +
    2 Touchscreen Interactives (AngularJS) +
    iOS / Android Native Applications
    Smithsonian
    National Air and Space Museum
    airandspace.si.edu

    View Slide

  42. View Slide

  43. View Slide

  44. View Slide

  45. View Slide

  46. View Slide

  47. View Slide

  48. PROS
    and
    CONS

    View Slide

  49. Pros

    View Slide

  50. Pros
    •Back and Front End systems are swappable

    View Slide

  51. Pros
    •Back and Front End systems are swappable
    •Teams can divide and conquer

    View Slide

  52. Cons

    View Slide

  53. Cons
    •Larger tech stack (Drupal, JS, multiple environments)

    View Slide

  54. Cons
    •Larger tech stack (Drupal, JS, multiple environments)
    •Maintenance

    View Slide

  55. Cons
    •Larger tech stack (Drupal, JS, multiple environments)
    •Maintenance
    •For websites you lose built-in functionality of CMS:
    menus/routing/accessibility/SEO

    View Slide

  56. 3. Decoupling Drupal

    View Slide

  57. Start with Content

    View Slide

  58. Start with Content
    •Getting content ready to travel

    View Slide

  59. Start with Content
    •Getting content ready to travel
    •NO Presentation specific names

    View Slide

  60. Start with Content
    •Getting content ready to travel
    •NO Presentation specific names
    •Decouple your mind

    View Slide

  61. Start with Content
    •Getting content ready to travel
    •NO Presentation specific names
    •Decouple your mind
    •Content in many forms/faces

    View Slide

  62. Example: Two Content Types

    View Slide

  63. Example: Two Content Types
    •Author

    View Slide

  64. Example: Two Content Types
    •Author
    •Book

    View Slide

  65. Author

    View Slide

  66. Book

    View Slide

  67. HOW??
    There’s lots of ways:

    View Slide

  68. HOW??
    There’s lots of ways:
    •Core: RESTful Web Services

    View Slide

  69. HOW??
    There’s lots of ways:
    •Core: RESTful Web Services
    •Custom Controller

    View Slide

  70. HOW??
    There’s lots of ways:
    •Core: RESTful Web Services
    •Custom Controller
    •JSON API

    View Slide

  71. HOW??
    There’s lots of ways:
    •Core: RESTful Web Services
    •Custom Controller
    •JSON API
    •GraphQL

    View Slide

  72. RESTful Web Services
    Two basic approaches:

    View Slide

  73. RESTful Web Services
    Two basic approaches:
    •Core RESTful Services

    View Slide

  74. RESTful Web Services
    Two basic approaches:
    •Core RESTful Services
    •REST Exports

    View Slide

  75. Core RESTful Services

    View Slide

  76. Core RESTful Services
    •Core REST Module

    View Slide

  77. Core RESTful Services
    •Core REST Module
    •REST UI Module https://drupal.org/project/restui

    View Slide

  78. Request:
    /node/1?format=json

    View Slide

  79. {
    "nid": [
    {
    "value": 1
    }
    ],
    "uuid": [
    {
    "value": "3036ea2d-7502-4c64-8bc6-2e9ee3a0f897"
    }
    ],
    "vid": [
    {
    "value": 1
    }
    ],
    "langcode": [
    {
    "value": "en"
    }
    ],
    "type": [
    {
    "target_id": "book",
    "target_type": "node_type",
    "target_uuid": "a3c32842-68b2-46fd-b70e-d6ed9e71ee2d"
    }
    ],
    "revision_timestamp": [
    {
    "value": "2018-04-23T18:10:59+00:00",
    "format": "Y-m-d\\TH:i:sP"
    }
    ],
    "revision_uid": [
    {
    "target_id": 1,
    "target_type": "user",
    "target_uuid": "a7484c09-d013-402e-b070-46c0d84a7b28",
    "url": "/user/1"
    }
    ],
    "revision_log": [],
    "status": [
    {
    "value": true
    }
    ],
    "title": [
    {
    "value": "The Fifth Season"
    }
    ],
    "uid": [
    {
    "target_id": 1,
    "target_type": "user",
    "target_uuid": "a7484c09-d013-402e-b070-46c0d84a7b28",
    "url": "/user/1"
    }
    ],
    "created": [
    {
    "value": "2018-04-23T18:10:29+00:00",
    "format": "Y-m-d\\TH:i:sP"
    }
    ],
    "changed": [
    {
    "value": "2018-04-23T18:10:59+00:00",
    "format": "Y-m-d\\TH:i:sP"
    }
    ],
    "promote": [
    {
    "value": true
    }
    ],
    "sticky": [
    {
    "value": false
    }
    ],
    "default_langcode": [
    {
    "value": true
    }
    ],
    "revision_translation_affected": [
    {
    "value": true
    }
    ],
    "path": [
    {
    "alias": null,
    "pid": null,
    "langcode": "en"
    }
    ],
    "body": [
    {
    "value": "Part one of the Broken Earth trilogy.\r\n",
    "format": "basic_html",
    "processed": "Part one of the Broken Earth trilogy.",
    "summary": ""
    }
    ]
    }

    View Slide

  80. {
    "nid": [
    {
    "value": 1
    }
    ],
    "type": [
    {
    "target_id": "book",
    "target_type": "node_type",
    "target_uuid": "a3c32842-68b2-46fd-b70e-d6ed9e71ee2d"
    }
    ],
    "title": [
    {
    "value": "The Fifth Season"
    }
    ],
    "body": [
    {
    "value": "Part one of the Broken Earth trilogy.\r\n",
    "format": "basic_html",
    "processed": "Part one of the Broken Earth trilogy.",
    "summary": ""
    }
    ]
    }

    View Slide

  81. REST Exports

    View Slide

  82. REST Exports
    •Views! (! old friend)

    View Slide

  83. REST Exports
    •Views! (! old friend)
    •“Provide a REST Export” checkbox ✅

    View Slide

  84. View Slide

  85. View Slide

  86. View Slide

  87. [
    {
    "title": "Kim Stanley Robinson",
    "body": "Author of the Mars trilogy and New York 2140.\r\n",
    "field_books": [
    "2"
    ]
    },
    {
    "title": "N. K. Jemisin",
    "body": "Hugo Award-winning author of the Broken Earth trilogy.\r\n",
    "field_books": [
    "1"
    ]
    }
    ]

    View Slide

  88. Custom Controller

    View Slide

  89. Custom Controller

    View Slide

  90. Custom Controller
    •Routing (YML)

    View Slide

  91. Custom Controller
    •Routing (YML)
    •Controller Class

    View Slide

  92. Routing
    mann.season:
    path: 'api/season'
    defaults:
    _controller: \Drupal\mann_api\Controller\Season::api
    methods: [GET]
    requirements:
    _access: 'TRUE'

    View Slide


  93. $a_query = \Drupal::entityQuery('node');
    $a_query->condition('status', 1);
    $a_query->condition('type', 'event');
    $entity_ids = $a_query->execute();
    $events = Node::loadMultiple($entity_ids);
    foreach ($events as $event) {
    $item['id'] = (int) $event->nid->value;
    $date = $event->field_event_date->value;
    $item['date'] = $date;
    $item['title'] = $event->title->value;
    $item['display_title'] = $event->field_display_title->value;
    $image_data = MediaAssetService::getMediaPathSingle($event, 'field_featured_image');
    $item['image'] = urldecode($image_data['image']);
    $item['image_relative'] = urldecode($image_data['image_relative']);
    $data[] = $item;
    }
    $response = new JsonResponse(['data' => $data]);
    return $response;

    View Slide

  94. JSON API
    drupal.org/project/jsonapi

    View Slide

  95. Out of the box:
    /jsonapi/node/[Content Type]
    for example:
    /jsonapi/node/author

    View Slide

  96. {
    "data": [
    {
    "type": "node--author",
    "id": "88223a76-278d-4fc6-9945-0bdb56cc78d3",
    "attributes": {
    "nid": 2,
    "body": {
    "value": "Winner of the Hugo Award.\r\n",
    "format": "basic_html",
    "processed": "Winner of the Hugo Award.",
    "summary": ""
    },
    "field_display_name": {
    "value": "N. K. Jemisin",
    "format": "basic_html",
    "processed": "N. K. Jemisin"
    }
    },
    "relationships": {
    "field_books": {
    "data": [
    {
    "type": "node--book",
    "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f"
    }
    ],
    },
    }
    ],
    }

    View Slide

  97. {
    "attributes": {
    "nid": 2,
    "body": {
    "value": "Winner of the Hugo Award.\r\n",
    "format": "basic_html",
    "processed": "\Winner of the Hugo Award.",
    "summary": ""
    },
    "field_display_name": {
    "value": "N. K. Jemisin",
    "format": "basic_html",
    "processed": "N. K. Jemisin"
    }
    },
    }

    View Slide

  98. {
    "relationships": {
    "field_books": {
    "data": [
    {
    "type": "node--book",
    "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f"
    }
    ],
    },
    }

    View Slide

  99. {
    "relationships": {
    "field_books": {
    "data": [
    {
    "type": "node--book",
    "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f"
    }
    ],
    },
    }

    View Slide

  100. {
    "relationships": {
    "field_books": {
    "data": [
    {
    "type": "node--book",
    "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f"
    }
    ],
    },
    }

    View Slide

  101. Relationships are complicated
    /jsonapi/node/[Content Type]?include=[Field Name]
    for example (we want field_books):
    /jsonapi/node/author?include=field_books

    View Slide

  102. {
    "included": [
    {
    "type": "node--book",
    "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f",
    "attributes": {
    "nid": 1,
    "title": "The Fifth Season",
    "body": {
    "value": "Part one of the Broken Earth trilogy.\r\n",
    "format": "basic_html",
    "processed": "Part one of the Broken Earth trilogy.",
    "summary": ""
    }
    },
    }
    }

    View Slide

  103. {
    "included": [
    {
    "type": "node--book",
    "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f",
    "attributes": {
    "nid": 1,
    "title": "The Fifth Season",
    "body": {
    "value": "Part one of the Broken Earth trilogy.\r\n",
    "format": "basic_html",
    "processed": "Part one of the Broken Earth trilogy.",
    "summary": ""
    }
    },
    }
    }

    View Slide

  104. {
    "included": [
    {
    "type": "node--book",
    "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f",
    "attributes": {
    "nid": 1,
    "title": "The Fifth Season",
    "body": {
    "value": "Part one of the Broken Earth trilogy.\r\n",
    "format": "basic_html",
    "processed": "Part one of the Broken Earth trilogy.",
    "summary": ""
    }
    },
    }
    }

    View Slide

  105. {
    "included": [
    {
    "type": "node--book",
    "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f",
    "attributes": {
    "nid": 1,
    "title": "The Fifth Season",
    "body": {
    "value": "Part one of the Broken Earth trilogy.\r\n",
    "format": "basic_html",
    "processed": "Part one of the Broken Earth trilogy.",
    "summary": ""
    }
    },
    }
    }

    View Slide

  106. "id": "e8c31c68-4fe5-4046-ab3f-1cb42e1f314f"

    View Slide

  107. GraphQL
    github.com/drupal-graphql

    View Slide

  108. Installation Note

    View Slide

  109. Installation Note
    •Download the module (or use drush dl graphql)

    View Slide

  110. Installation Note
    •Download the module (or use drush dl graphql)
    •Run composer require webonyx/graphql-php at
    root level (error otherwise)

    View Slide

  111. Installation Note
    •Download the module (or use drush dl graphql)
    •Run composer require webonyx/graphql-php at
    root level (error otherwise)
    •Enable the module

    View Slide

  112. Explorer allows
    you to test out
    queries

    View Slide

  113. View Slide

  114. Auto-complete!

    View Slide

  115. View Slide

  116. Simple Query
    query {
    nodeQuery {
    entities {
    entityId,
    entityLabel
    }
    }
    }

    View Slide

  117. {
    "data": {
    "nodeQuery": {
    "entities": [
    {
    "entityId": "1",
    "entityLabel": "The Fifth Season"
    },
    {
    "entityId": "2",
    "entityLabel": "New York 2140"
    },
    {
    "entityId": "3",
    "entityLabel": "N. K. Jemisin"
    },
    {
    "entityId": "4",
    "entityLabel": "Kim Stanley Robinson"
    }
    ]
    }
    }
    }

    View Slide

  118. Node Query w/ Types (Author)
    query {
    nodeQuery {
    entities {
    ...on NodeAuthor {
    entityId
    title
    body {
    value
    }
    fieldDisplayName {
    value
    }
    fieldBooks {
    entity {
    entityId
    title
    }
    }
    }
    }
    }
    }

    View Slide

  119. Node Query w/ Types (Author)
    query {
    nodeQuery {
    entities {
    ...on NodeAuthor {
    entityId
    title
    body {
    value
    }
    fieldDisplayName {
    value
    }
    fieldBooks {
    entity {
    entityId
    title
    }
    }
    }
    }
    }
    }

    View Slide

  120. Node Query w/ Types (Author)
    query {
    nodeQuery {
    entities {
    ...on NodeAuthor {
    entityId
    title
    body {
    value
    }
    fieldDisplayName {
    value
    }
    fieldBooks {
    entity {
    entityId
    title
    }
    }
    }
    }
    }
    }

    View Slide

  121. Node Query w/ Types (Author)
    query {
    nodeQuery {
    entities {
    ...on NodeAuthor {
    entityId
    title
    body {
    value
    }
    fieldDisplayName {
    value
    }
    fieldBooks {
    entity {
    entityId
    title
    }
    }
    }
    }
    }
    }

    View Slide

  122. Node Query w/ Types (Author)
    query {
    nodeQuery {
    entities {
    ...on NodeAuthor {
    entityId
    title
    body {
    value
    }
    fieldDisplayName {
    value
    }
    fieldBooks {
    entity {
    entityId
    title
    }
    }
    }
    }
    }
    }

    View Slide

  123. References are Easy(ish)
    fieldBooks {
    entity {
    entityId
    title
    }
    }

    View Slide

  124. {
    "data": {
    "nodeQuery": {
    "entities": [
    {},
    {},
    {
    "entityId": "3",
    "title": "N. K. Jemisin",
    "body": {
    "value": "Hugo Award-winning author of the Broken Earth trilogy.\r\n"
    },
    "fieldDisplayName": {
    "value": "N. K. Jemisin"
    },
    "fieldBooks": [
    {
    "entity": {
    "entityId": "1",
    "title": "The Fifth Season"
    }
    }
    ]
    },

    ]
    }
    }
    }

    View Slide

  125. {
    "data": {
    "nodeQuery": {
    "entities": [
    {},
    {},
    {
    "entityId": "3",
    "title": "N. K. Jemisin",
    "body": {
    "value": "Hugo Award-winning author of the Broken Earth trilogy.\r\n"
    },
    "fieldDisplayName": {
    "value": "N. K. Jemisin"
    },
    "fieldBooks": [
    {
    "entity": {
    "entityId": "1",
    "title": "The Fifth Season"
    }
    }
    ]
    },

    ]
    }
    }
    }

    View Slide

  126. {
    "data": {
    "nodeQuery": {
    "entities": [
    {},
    {},
    {
    "entityId": "3",
    "title": "N. K. Jemisin",
    "body": {
    "value": "Hugo Award-winning author of the Broken Earth trilogy.\r\n"
    },
    "fieldDisplayName": {
    "value": "N. K. Jemisin"
    },
    "fieldBooks": [
    {
    "entity": {
    "entityId": "1",
    "title": "The Fifth Season"
    }
    }
    ]
    },

    ]
    }
    }
    }

    View Slide

  127. query {
    nodeQuery {
    entities {
    ...on NodeAuthor {
    entityId
    title
    body {
    value
    }
    fieldDisplayName {
    value
    }
    fieldBooks {
    entity {
    entityId
    title
    }
    }
    }
    ...on NodeBook {
    entityId
    title
    body {
    value
    }
    }
    }
    }
    }

    View Slide

  128. query {
    nodeQuery {
    entities {
    ...on NodeAuthor {
    entityId
    title
    body {
    value
    }
    fieldDisplayName {
    value
    }
    fieldBooks {
    entity {
    entityId
    title
    }
    }
    }
    ...on NodeBook {
    entityId
    title
    body {
    value
    }
    }
    }
    }
    }

    View Slide

  129. query {
    nodeQuery {
    entities {
    ...on NodeAuthor {
    entityId
    title
    body {
    value
    }
    fieldDisplayName {
    value
    }
    fieldBooks {
    entity {
    entityId
    title
    }
    }
    }
    ...on NodeBook {
    entityId
    title
    body {
    value
    }
    }
    }
    }
    }

    View Slide

  130. query {
    nodeQuery {
    entities {
    ...on NodeAuthor {
    entityId
    title
    body {
    value
    }
    fieldDisplayName {
    value
    }
    fieldBooks {
    entity {
    entityId
    title
    }
    }
    }
    ...on NodeBook {
    entityId
    title
    body {
    value
    }
    }
    }
    }
    }

    View Slide

  131. Filter by Node Type
    query {
    nodeQuery (filter: {conditions: [{
    field: "type"
    value: "author"
    operator: EQUAL
    }]
    } ) {

    }
    }

    View Slide

  132. Filter by Node Type
    query {
    nodeQuery (filter: {conditions: [{
    field: "type"
    value: "author"
    operator: EQUAL
    }]
    } ) {

    }
    }

    View Slide

  133. Filter by Node Type
    query {
    nodeQuery (filter: {conditions: [{
    field: "type"
    value: "author"
    operator: EQUAL
    }]
    } ) {

    }
    }

    View Slide

  134. Filter by Node Type
    query {
    nodeQuery (filter: {conditions: [{
    field: "type"
    value: "author"
    operator: EQUAL
    }]
    } ) {

    }
    }

    View Slide

  135. query {
    nodeQuery (filter: {conditions: [{
    field: "type"
    value: "author"
    operator: EQUAL
    }]
    } ) {
    entities {
    ...on NodeAuthor {
    entityId
    title
    body {
    value
    }
    fieldBooks {
    entity {
    title
    body {
    value
    }
    }
    }
    }
    }
    }
    }

    View Slide

  136. {
    "data": {
    "nodeQuery": {
    "entities": [
    {
    "entityId": "3",
    "title": "N. K. Jemisin",
    "body": {
    "value": "Hugo Award-winning author of the Broken Earth trilogy.\r\n"
    },
    "fieldBooks": [
    {
    "entity": {
    "title": "The Fifth Season",
    "body": {
    "value": "Part one of the Broken Earth trilogy.\r\n"
    }
    }
    }
    ]
    },

    View Slide

  137. Aliases and Multiple Queries
    Take a simple Image Media Bundle
    entities {
    entityLabel
    ... on NodeHotspot {
    fieldName,
    title,
    fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    url
    width
    height
    alt
    title
    }
    }
    }
    },
    }
    }

    View Slide

  138. entities {
    entityLabel
    ... on NodeHotspot {
    fieldName,
    title,
    fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    url
    width
    height
    alt
    title
    }
    }
    }
    },
    }
    }

    View Slide

  139. entities {
    entityLabel
    ... on NodeHotspot {
    fieldName,
    title,
    fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    url
    width
    height
    alt
    title
    }
    }
    }
    },
    }
    }

    View Slide

  140. entities {
    entityLabel
    ... on NodeHotspot {
    fieldName,
    title,
    fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    url
    width
    height
    alt
    title
    }
    }
    }
    },
    }
    }

    View Slide

  141. entities {
    entityLabel
    ... on NodeHotspot {
    fieldName,
    title,
    fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    url
    width
    height
    alt
    title
    }
    }
    }
    },
    }
    }

    View Slide

  142. entities {
    entityLabel
    ... on NodeHotspot {
    fieldName,
    title,
    fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    url
    width
    height
    alt
    title
    }
    }
    }
    },
    }
    }

    View Slide

  143. entities {
    entityLabel
    ... on NodeHotspot {
    fieldName,
    title,
    fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    url
    width
    height
    alt
    title
    }
    }
    }
    },
    }
    }

    View Slide

  144. This gives us the source image. What about derivatives?
    fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    derivative(style: THUMBNAIL) {
    url
    width
    height
    }
    url
    width
    height
    alt
    title
    }
    }
    }

    View Slide

  145. This gives us the source image. What about derivatives?
    fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    derivative(style: THUMBNAIL) {
    url
    width
    height
    }
    url
    width
    height
    alt
    title
    }
    }
    }

    View Slide

  146. This gives us the source image. What about derivatives?
    fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    derivative(style: THUMBNAIL) {
    url
    width
    height
    }
    url
    width
    height
    alt
    title
    }
    }
    }

    View Slide

  147. This gives us the source image. What about derivatives?
    fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    derivative(style: THUMBNAIL) {
    url
    width
    height
    }
    url
    width
    height
    alt
    title
    }
    }
    }

    View Slide

  148. What if we need multiple derivatives? Enter aliases.

    View Slide

  149. What if we need multiple derivatives? Enter aliases.
    An alias allows you to run multiple queries for the
    same field and map them to different names.

    View Slide

  150. fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    thumbnail:derivative(style: THUMBNAIL) {
    url
    width
    height
    }
    large:derivative(style: LARGE) {
    url
    width
    height
    }
    url
    width
    height
    alt
    title
    }
    }
    }

    View Slide

  151. fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    thumbnail:derivative(style: THUMBNAIL) {
    url
    width
    height
    }
    large:derivative(style: LARGE) {
    url
    width
    height
    }
    url
    width
    height
    alt
    title
    }
    }
    }

    View Slide

  152. fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    thumbnail:derivative(style: THUMBNAIL) {
    url
    width
    height
    }
    large:derivative(style: LARGE) {
    url
    width
    height
    }
    url
    width
    height
    alt
    title
    }
    }
    }

    View Slide

  153. fieldThumbnailImage {
    entity {
    ... on MediaImage {
    fieldMediaImage {
    thumbnail:derivative(style: THUMBNAIL) {
    url
    width
    height
    }
    large:derivative(style: LARGE) {
    url
    width
    height
    }
    url
    width
    height
    alt
    title
    }
    }
    }

    View Slide

  154. Bonus:
    Communication

    View Slide

  155. We’ve tried:

    View Slide

  156. We’ve tried:
    •Polling for changes (are we there yet?)

    View Slide

  157. We’ve tried:
    •Polling for changes (are we there yet?)
    •NodeJS (get a server in the middle,
    maybe Socket I/O)

    View Slide

  158. Enter Progressive Web Applications

    View Slide

  159. Enter Progressive Web Applications
    •CMS to Application communication is a use case

    View Slide

  160. Enter Progressive Web Applications
    •CMS to Application communication is a use case
    •PWAs have Service Workers and Push Notifications

    View Slide

  161. Web Push API

    View Slide

  162. Web Push API
    •Gives web applications the ability to receive messages
    pushed to them from a server

    View Slide

  163. Web Push API
    •Gives web applications the ability to receive messages
    pushed to them from a server
    •Requires a subscription to be activated, but once
    allowed, the subscription is saved by the browser

    View Slide

  164. Web Push API
    •Gives web applications the ability to receive messages
    pushed to them from a server
    •Requires a subscription to be activated, but once
    allowed, the subscription is saved by the browser
    •You can pass “messages”, but fundamentally you are
    passing data for your app or website to interpret as you
    see fit

    View Slide

  165. Web Push API
    •Gives web applications the ability to receive messages
    pushed to them from a server
    •Requires a subscription to be activated, but once
    allowed, the subscription is saved by the browser
    •You can pass “messages”, but fundamentally you are
    passing data for your app or website to interpret as you
    see fit
    •Browsers handle pushing the data through Service
    Workers, so a third party is not required

    View Slide

  166. Fine Print

    View Slide

  167. Fine Print
    •Limited browser support: ✅ Chrome, ✅ Firefox, "
    Safari (coming soon?)

    View Slide

  168. Fine Print
    •Limited browser support: ✅ Chrome, ✅ Firefox, "
    Safari (coming soon?)
    •Spec is considered experimental, so it may change in
    the future

    View Slide

  169. Fine Print
    •Limited browser support: ✅ Chrome, ✅ Firefox, "
    Safari (coming soon?)
    •Spec is considered experimental, so it may change in
    the future
    •Great for kiosks, but in the wild, you can’t guarantee
    that a user will subscribe

    View Slide

  170. Fine Print
    •Limited browser support: ✅ Chrome, ✅ Firefox, "
    Safari (coming soon?)
    •Spec is considered experimental, so it may change in
    the future
    •Great for kiosks, but in the wild, you can’t guarantee
    that a user will subscribe
    •Doesn’t help with non-web-based projects (Cinder/
    C++, Unity)

    View Slide

  171. View Slide

  172. Where to Next?

    View Slide

  173. Where to Next?
    •Two-way communication

    View Slide

  174. Where to Next?
    •Two-way communication
    •Electron wrappers for JS interactives

    View Slide

  175. Where to Next?
    •Two-way communication
    •Electron wrappers for JS interactives
    •Preview modes for interactives

    View Slide

  176. Takeaways

    View Slide

  177. Takeaways
    •Start with your Content, and make that portable

    View Slide

  178. Takeaways
    •Start with your Content, and make that portable
    •There’s many different ways to expose your content

    View Slide

  179. Takeaways
    •Start with your Content, and make that portable
    •There’s many different ways to expose your content
    •You can do this!

    View Slide

  180. THANK YOU.
    Mark Llobrera
    Technology Director, Bluecadet Philadelphia
    https://speakerdeck.com/mllobrera
    @dirtystylus
    @bluecadet

    View Slide