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 full-size slide

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

    View full-size slide

  3. In ❤ with
    Web Technology
    for seven years

    View full-size slide

  4. Atomic Design

    View full-size slide

  5. Building blocks
    in CSS

    View full-size slide

  6. Frontend Engineers
    needed more

    View full-size slide

  7. Component-driven JavaScript Frameworks

    View full-size slide

  8. Component-driven JavaScript Frameworks





    ...


    View full-size slide

  9. Component-driven JavaScript Frameworks

    View full-size slide

  10. Component-driven JavaScript Frameworks
    Virtual DOM implementation

    View full-size slide

  11. vdom
    What is this virtual DOM?

    View full-size slide

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

    CTF Meetup
    Hello! "

    );

    View full-size slide

  13. - 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 full-size slide

  14. - 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 full-size slide

  15. - 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 full-size slide

  16. - 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 full-size slide

  17. - 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 full-size slide

  18. 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 full-size slide

  19. 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 full-size slide

  20. 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 full-size slide

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

    View full-size slide

  22. - 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 full-size slide

  23. - 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 full-size slide

  24. It's "just" data!

    View full-size slide

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

    View full-size slide

  26. 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 full-size slide

  27. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  31. {
    "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 full-size slide

  32. {
    "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 full-size slide

  33. 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 full-size slide

  34. 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 full-size slide

  35. 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 full-size slide

  36. 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 full-size slide

  37. 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 full-size slide

  38. 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 full-size slide

  39. 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 full-size slide

  40. 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 full-size slide

  41. 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 full-size slide

  42. 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 full-size slide

  43. 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 full-size slide

  44. 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 full-size slide

  45. - 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 full-size slide

  46. Header
    Page
    Main
    Footer
    Article
    HeroImage
    Person

    View full-size slide

  47. Header
    Page
    Main
    Person
    Footer
    Article
    HeroImage
    Person

    View full-size slide

  48. Header
    Person
    Page
    Main
    Footer
    Article
    HeroImage
    Person

    View full-size slide

  49. Header
    Page
    Main
    Footer
    HeroImage
    Person

    View full-size slide

  50. Previewing content
    can be hard...

    View full-size 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 full-size slide

  52. 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 full-size slide

  53. 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 full-size slide

  54. 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 full-size slide

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

    View full-size slide


  56. - New workflows? -

    View full-size slide

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

    View full-size slide

  58. - New workflows? -

    View full-size slide

  59. - New workflows? -

    View full-size slide

  60. - New workflows? -

    View full-size slide

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

    View full-size slide

  62. Structured Data JS Components

    View full-size slide

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

    View full-size slide