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

Structured content & JS Components – a match made in heaven

stefan judis
September 27, 2017

Structured content & JS Components – a match made in heaven

stefan judis

September 27, 2017
Tweet

More Decks by stefan judis

Other Decks in Technology

Transcript

  1. Structured content and JS Components
    @stefanjudis
    A match made in heaven

    View Slide

  2. Stefan Judis
    Frontend Developer, Occasional Teacher, Meetup Organizer
    ❤ Open Source, Performance and Accessibility ❤
    @stefanjudis

    View Slide

  3. View Slide

  4. In ❤ with
    Web Technology
    for seven years

    View Slide

  5. View Slide

  6. Brad Frost

    View Slide

  7. Atomic Design

    View Slide

  8. Atoms

    View Slide

  9. Atoms

    View Slide

  10. Molecules

    View Slide

  11. Molecules

    View Slide

  12. Organisms

    View Slide

  13. Organisms

    View Slide

  14. Templates

    View Slide

  15. Templates

    View Slide

  16. Building blocks
    in CSS

    View Slide

  17. Frontend Engineers
    needed more

    View Slide

  18. Component-driven JavaScript Frameworks

    View Slide

  19. Component-driven JavaScript Frameworks





    ...


    View Slide

  20. Component-driven JavaScript Frameworks

    View Slide

  21. Component-driven JavaScript Frameworks
    Virtual DOM implementation

    View Slide

  22. vdom
    What is this virtual DOM?

    View Slide

  23. - Virtual DOM in a nutshell -
    /** @jsx h */
    const meetup = (

    CTF Meetup
    Hello! "

    );

    View Slide

  24. - Virtual DOM in a nutshell -
    /** @jsx h */
    const meetup = (

    CTF Meetup
    Hello! "

    );
    const meetup = h(
    "div",
    null,
    h(
    "h1",
    { id: "headline" },
    "CTF Meetup"
    ),
    h(
    "p",
    null,
    "Hello! ""
    )
    );

    View Slide

  25. - Virtual DOM in a nutshell -
    /** @jsx h */
    const meetup = (

    CTF Meetup
    Hello! "

    );
    const meetup = h(
    "div",
    null,
    h(
    "h1",
    { id: "headline" },
    "CTF Meetup"
    ),
    h(
    "p",
    null,
    "Hello! ""
    )
    );

    View Slide

  26. - Virtual DOM in a nutshell -
    /** @jsx h */
    const meetup = (

    CTF Meetup
    Hello! "

    );
    const meetup = h(
    "div",
    null,
    h(
    "h1",
    { id: "headline" },
    "CTF Meetup"
    ),
    h(
    "p",
    null,
    "Hello! ""
    )
    );
    NodeName
    NodeName
    NodeName

    View Slide

  27. - Virtual DOM in a nutshell -
    /** @jsx h */
    const meetup = (

    CTF Meetup
    Hello! "

    );
    const meetup = h(
    "div",
    null,
    h(
    "h1",
    { id: "headline" },
    "CTF Meetup"
    ),
    h(
    "p",
    null,
    "Hello! ""
    )
    );
    NodeName
    NodeName
    NodeName
    Attributes
    Attributes
    Attributes

    View Slide

  28. - Virtual DOM in a nutshell -
    /** @jsx h */
    const meetup = (

    CTF Meetup
    Hello! "

    );
    const meetup = h(
    "div",
    null,
    h(
    "h1",
    { id: "headline" },
    "CTF Meetup"
    ),
    h(
    "p",
    null,
    "Hello! ""
    )
    );
    NodeName
    NodeName
    NodeName
    Attributes
    Attributes
    Attributes
    Children
    Children
    Children
    Children

    View Slide

  29. export function h(nodeName, attributes) {
    let children=EMPTY_CHILDREN, lastSimple, child, simple, i;
    for (i=arguments.length; i-- > 2; ) {
    stack.push(arguments[i]);
    }
    if (attributes && attributes.children!=null) {
    if (!stack.length) stack.push(attributes.children);
    delete attributes.children;
    }
    while (stack.length) {
    if ((child = stack.pop()) && child.pop!==undefined) {
    for (i=child.length; i--; ) stack.push(child[i]);
    }
    else {
    if (typeof child==='boolean') child = null;
    if ((simple = typeof nodeName!=='function')) {
    if (child==null) child = '';
    - Virtual DOM in a nutshell -
    github.com/developit/preact/blob/master/src/h.js

    View Slide

  30. children = [child];
    }
    else {
    children.push(child);
    }
    lastSimple = simple;
    }
    }
    let p = new VNode();
    p.nodeName = nodeName;
    p.children = children;
    p.attributes = attributes==null ? undefined : attributes;
    p.key = attributes==null ? undefined : attributes.key;
    // if a "vnode hook" is defined, pass every created VNode to it
    if (options.vnode!==undefined) options.vnode(p);
    return p;
    }
    - Virtual DOM in a nutshell -
    github.com/developit/preact/blob/master/src/h.js

    View Slide

  31. children = [child];
    }
    else {
    children.push(child);
    }
    lastSimple = simple;
    }
    }
    let p = new VNode();
    p.nodeName = nodeName;
    p.children = children;
    p.attributes = attributes==null ? undefined : attributes;
    p.key = attributes==null ? undefined : attributes.key;
    // if a "vnode hook" is defined, pass every created VNode to it
    if (options.vnode!==undefined) options.vnode(p);
    return p;
    }
    - Virtual DOM in a nutshell -
    github.com/developit/preact/blob/master/src/h.js

    View Slide

  32. - Virtual DOM in a nutshell -
    const meetup = h(
    "div",
    null,
    h(
    "h1",
    { id: "headline" },
    "CTF Meetup"
    ),
    h(
    "p",
    null,
    "Hello! ""
    )
    );

    View Slide

  33. - Virtual DOM in a nutshell -
    const meetup = h(
    "div",
    null,
    h(
    "h1",
    { id: "headline" },
    "CTF Meetup"
    ),
    h(
    "p",
    null,
    "Hello! ""
    )
    );
    {
    "nodeName": "div",
    "children": [
    {
    "nodeName": "h1",
    "children": [
    "CTF Meetup"
    ],
    "attributes": {
    "id": "headline"
    }
    },
    {
    "nodeName": "p",
    "children": [
    "Hello! ""
    ]
    }
    ]
    }

    View Slide

  34. - Virtual DOM in a nutshell -
    function render(vnode) {
    // Strings just convert to #text Nodes:
    if (vnode.split) return document.createTextNode(vnode);
    // create a DOM element with the nodeName of our VDOM element:
    let n = document.createElement(vnode.nodeName);
    // copy attributes onto the new node:
    let a = vnode.attributes || {};
    Object.keys(a).forEach( k => n.setAttribute(k, a[k]) );
    // render (build) and then append child nodes:
    (vnode.children || []).forEach( c => n.appendChild(render(c)) );
    return n;
    }

    View Slide

  35. It's "just" data!

    View Slide

  36. View Slide

  37. {
    "nodeName": "Page",
    "children": [
    {
    "nodeName": "h1",
    "children": [
    "Landing Page"
    ]
    },
    {
    "nodeName": "header",
    "children": [{
    "nodeName": "HeroImage",
    ...
    }]
    },
    {
    "nodeName": "main",
    "children": [{
    "nodeName": "Article",
    ...
    }]
    },
    ...
    ]
    }

    View Slide

  38. WTF is JSX
    The benefit of virtual DOM is that it is

    extremely lightweight.
    Small objects referring to other small objects,

    a structure composed by easily optimizable
    application logic.
    This also means it is not tied 

    to any rendering logic or slow DOM methods.

    View Slide

  39. The benefit of virtual DOM is that it is

    extremely lightweight.
    Small objects referring to other small objects,
    a structure composed by easily optimizable
    application logic.
    This also means it is not tied 

    to any rendering logic or slow DOM methods.

    WTF is JSX

    View Slide

  40. It's "just" data!
    This got me thinking...

    View Slide

  41. What if we could connect
    content types and components?

    View Slide

  42. View Slide

  43. View Slide

  44. View Slide

  45. View Slide

  46. View Slide

  47. {
    "sys": {
    "contentType": {
    "sys": {
    "id": "page"
    }
    }
    },
    "fields": {
    "title": "Landing Page",
    "header": [
    {
    "sys": {
    "contentType": {
    "sys": {
    "id": "heroImage"
    }
    }
    }
    }
    ]
    },
    ...
    }

    View Slide

  48. {
    "sys": {
    "contentType": {
    "sys": {
    "id": "page"
    }
    }
    },
    "fields": {
    "title": "Landing Page",
    "header": [
    {
    "sys": {
    "contentType": {
    "sys": {
    "id": "heroImage"
    }
    }
    }
    }
    ]
    },
    ...
    }
    ?
    Contentful response vDOM
    {
    "nodeName": "Page",
    "children": [
    {
    "nodeName": "h1",
    "children": [
    "Landing Page"
    ]
    },
    {
    "nodeName": "header",
    "children": [{
    "nodeName": "HeroImage",
    ...
    }]
    },
    {
    "nodeName": "main",
    "children": [{
    "nodeName": "Article",
    ...
    }]
    },
    ...
    ]
    }

    View Slide

  49. {
    "sys": {
    "contentType": {
    "sys": {
    "id": "page"
    }
    }
    },
    "fields": {
    "title": "Landing Page",
    "header": [
    {
    "sys": {
    "contentType": {
    "sys": {
    "id": "heroImage"
    }
    }
    }
    }
    ]
    },
    ...
    }
    ?
    Contentful response vDOM
    {
    "nodeName": "Page",
    "children": [
    {
    "nodeName": "h1",
    "children": [
    "Landing Page"
    ]
    },
    {
    "nodeName": "header",
    "children": [{
    "nodeName": "HeroImage",
    ...
    }]
    },
    {
    "nodeName": "main",
    "children": [{
    "nodeName": "Article",
    ...
    }]
    },
    ...
    ]
    }

    View Slide

  50. export default class Home extends Component {
    componentWillMount() {
    createClient({
    space: '...',
    accessToken: '...'
    }).getEntries({
    'sys.id': '1PYzQGJdgEG2Ycqweqqmk0'
    }).then(response => {
    this.setState({ page: response.items[0] });
    });
    }
    render(props, state) {
    return (

    {
    state.page && (

    )
    }

    );
    }
    }
    - Glue Components and Content Types together -
    routes/home/index.js

    View Slide

  51. export default class Home extends Component {
    componentWillMount() {
    createClient({
    space: '...',
    accessToken: '...'
    }).getEntries({
    'sys.id': '1PYzQGJdgEG2Ycqweqqmk0'
    }).then(response => {
    this.setState({ page: response.items[0] });
    });
    }
    render(props, state) {
    return (

    {
    state.page && (

    )
    }

    );
    }
    }
    - Glue Components and Content Types together -
    routes/home/index.js

    View Slide

  52. export default class Home extends Component {
    componentWillMount() {
    createClient({
    space: '...',
    accessToken: '...'
    }).getEntries({
    'sys.id': '1PYzQGJdgEG2Ycqweqqmk0'
    }).then(response => {
    this.setState({ page: response.items[0] });
    });
    }
    render(props, state) {
    return (

    {
    state.page && (

    )
    }

    );
    }
    }
    - Glue Components and Content Types together -
    routes/home/index.js

    View Slide

  53. export default class Home extends Component {
    componentWillMount() {
    createClient({
    space: '...',
    accessToken: '...'
    }).getEntries({
    'sys.id': '1PYzQGJdgEG2Ycqweqqmk0'
    }).then(response => {
    this.setState({ page: response.items[0] });
    });
    }
    render(props, state) {
    return (

    {
    state.page && (

    )
    }

    );
    }
    }
    - Glue Components and Content Types together -
    routes/home/index.js

    View Slide

  54. export default function CtfModule({ children, ...props }) {
    if (!props.entry) {
    return '';
    }
    const entries = props.entry instanceof Array ? props.entry : [ props.entry ];
    return h(
    'div', { class: 'ctf-modules' }, [
    entries.map(
    entry => h(
    ContentfulComponents[getComponentName(entry.sys.contentType.sys.id)],
    { fields: entry.fields }
    )
    )
    ]
    );
    }
    - Glue Components and Content Types together -
    components/contentful-module/index.js

    View Slide

  55. export default function CtfModule({ children, ...props }) {
    if (!props.entry) {
    return '';
    }
    const entries = props.entry instanceof Array ? props.entry : [ props.entry ];
    return h(
    'div', { class: 'ctf-modules' }, [
    entries.map(
    entry => h(
    ContentfulComponents[getComponentName(entry.sys.contentType.sys.id)],
    { fields: entry.fields }
    )
    )
    ]
    );
    }
    - Glue Components and Content Types together -
    components/contentful-module/index.js

    View Slide

  56. export default function CtfModule({ children, ...props }) {
    if (!props.entry) {
    return '';
    }
    const entries = props.entry instanceof Array ? props.entry : [ props.entry ];
    return h(
    'div', { class: 'ctf-modules' }, [
    entries.map(
    entry => h(
    ContentfulComponents[getComponentName(entry.sys.contentType.sys.id)],
    { fields: entry.fields }
    )
    )
    ]
    );
    }
    - Glue Components and Content Types together -
    components/contentful-module/index.js

    View Slide

  57. export default function CtfModule({ children, ...props }) {
    if (!props.entry) {
    return '';
    }
    const entries = props.entry instanceof Array ? props.entry : [ props.entry ];
    return h(
    'div', { class: 'ctf-modules' }, [
    entries.map(
    entry => h(
    ContentfulComponents[getComponentName(entry.sys.contentType.sys.id)],
    { fields: entry.fields }
    )
    )
    ]
    );
    }
    - Glue Components and Content Types together -
    components/contentful-module/index.js

    View Slide

  58. export default function CtfModule({ children, ...props }) {
    if (!props.entry) {
    return '';
    }
    const entries = props.entry instanceof Array ? props.entry : [ props.entry ];
    return h(
    'div', { class: 'ctf-modules' }, [
    entries.map(
    entry => h(
    ContentfulComponents[getComponentName(entry.sys.contentType.sys.id)],
    { fields: entry.fields }
    )
    )
    ]
    );
    }
    - Glue Components and Content Types together -
    components/contentful-module/index.js
    entry => h(
    HeroImage,
    { fields: entry.fields }
    )
    entry => h(
    Article,
    { fields: entry.fields }
    )
    entry => h(
    Person,
    { fields: entry.fields }
    )
    content type dependent components

    View Slide

  59. export default function Page({ children, ...props }) {
    return (

    { props.fields.header && (

    Header Area


    )}
    { props.fields.main && (

    Main Area


    )}
    { props.fields.header && (

    Footer Area


    )}

    );
    }
    - Glue Components and Content Types together -
    components/page/index.js

    View Slide

  60. export default function Page({ children, ...props }) {
    return (

    { props.fields.header && (

    Header Area


    )}
    { props.fields.main && (

    Main Area


    )}
    { props.fields.header && (

    Footer Area


    )}

    );
    }
    - Glue Components and Content Types together -
    components/page/index.js

    View Slide

  61. export default function Page({ children, ...props }) {
    return (

    { props.fields.header && (

    Header Area


    )}
    { props.fields.main && (

    Main Area


    )}
    { props.fields.header && (

    Footer Area


    )}

    );
    }
    - Glue Components and Content Types together -
    components/page/index.js

    View Slide

  62. - Glue Components and Content Types together -
    components/hero-image/index.js components/article/index.js components/person/index.js
    export default function HeroImage({ children, ...props }) {
    return (

    {props.fields.headline}


    );
    }
    export default function Article({ children, ...props }) {
    return (

    {props.fields.title}
    {props.fields.main}

    );
    }
    export default function Person({ children, ...props }) {
    return (

    {props.fields.name}


    );
    }
    HeroImage
    Article
    Person

    View Slide

  63. Header
    Page
    Main
    Footer
    Article
    HeroImage
    Person

    View Slide

  64. Header
    Page
    Main
    Person
    Footer
    Article
    HeroImage
    Person

    View Slide

  65. Header
    Person
    Page
    Main
    Footer
    Article
    HeroImage
    Person

    View Slide

  66. Header
    Page
    Main
    Footer
    HeroImage
    Person

    View Slide

  67. Previewing content
    can be hard...

    View Slide

  68. export default class Home extends Component {
    componentWillMount() {
    createClient({
    space: '...',
    accessToken: '...'
    }).getEntries({
    'sys.id': '1PYzQGJdgEG2Ycqweqqmk0'
    }).then(response => {
    this.setState({ page: response.items[0] });
    });
    }
    render(props, state) {
    return (

    {
    state.page && (

    )
    }

    );
    }
    }
    - Glue Components and Content Types together -
    routes/home/index.js

    View Slide

  69. export default class Home extends Component {
    componentWillMount() {
    createClient({
    space: '...',
    accessToken: '...'
    }).getEntries({
    'sys.id': this.props.matches.id
    }).then(response => {
    this.setState({ entry: response.items[0] });
    });
    }
    render(props, state) {
    return (

    {
    state.page && (
    Component preview

    )
    }

    );
    }
    }
    - Glue Components and Content Types together -
    routes/preview/index.js

    View Slide

  70. export default class Home extends Component {
    componentWillMount() {
    createClient({
    space: '...',
    accessToken: '...'
    }).getEntries({
    'sys.id': this.props.matches.id
    }).then(response => {
    this.setState({ entry: response.items[0] });
    });
    }
    render(props, state) {
    return (

    {
    state.page && (
    Component preview

    )
    }

    );
    }
    }
    CtfModule renders the particular
    components automatically.
    - Glue Components and Content Types together -
    routes/preview/index.js

    View Slide

  71. export default class Home extends Component {
    componentWillMount() {
    createClient({
    space: '...',
    accessToken: '...'
    }).getEntries({
    'sys.id': this.props.matches.id
    }).then(response => {
    this.setState({ entry: response.items[0] });
    });
    }
    render(props, state) {
    return (

    {
    state.page && (
    Component preview

    )
    }

    );
    }
    }
    CtfModule renders the particular
    components automatically.
    - Glue Components and Content Types together -
    routes/preview/index.js
    The components render
    data automatically.

    View Slide

  72. View Slide

  73. View Slide

  74. It's "just" data!
    This got me thinking...

    View Slide


  75. - New workflows? -

    View Slide

  76. - New workflows? -
    "Contentful UI components"?

    View Slide

  77. - New workflows? -

    View Slide

  78. - New workflows? -

    View Slide

  79. - New workflows? -

    View Slide

  80. So far it's just an idea in my head...

    View Slide

  81. Structured Data JS Components

    View Slide

  82. Thanks.
    @stefanjudis
    Slides
    ctfl.io/js-components

    View Slide