Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#27 jsx layouts #29

Merged
merged 3 commits into from
Oct 9, 2017
Merged

Feature/#27 jsx layouts #29

merged 3 commits into from
Oct 9, 2017

Conversation

Quicksaver
Copy link
Contributor

Closes #27: resolves existing layout redundancy.

NOTE: this was branched out of #26, as of its creation still not merged, because it requires the webpack changes from there.

Overview

Any piece of layout code that can potentially be shared between pages, or even repeated inside the same page, should go in its own BesugoComponent in a .jsx file. This way, we won't have to copy-paste repeating layouts in every file, or even rewrite it for CMS previews.

BesugoComponent

A Class extension of React.Component, what matters most to us is its ability to parse JSX from any data supplied to it, and that it can reuse other components when rendering itself.

All such components should have the following basic structure to be built:

import React from 'react';
import BesugoComponent from 'Besugo';

class SomeThing extends BesugoComponent {
  // ...
}

SomeThing.initialize();
export default SomeThing;

Below is a structural overview of a BesugoComponent extended class.

constructor

constructor(props) {
  super(props);
}

Only mandatory if the component expects to be passed any props (i.e. data from attributes in html). Should always take the form shown above.

config

A static object that defines this component within our website.

  • (optional) tag: a string representing the placeholder tag passed in the HTML layout and which will be replaced by the actual layout of the component
  • (optional) categories: content categories that this component will preview in /admin

Example:

static get config() {
  return {
    tag: "Person",
    categories: [ "people", "people-pt" ]
  };
}

getData()

An optional method to fetch and return whatever data is needed to build the component. If in a CMS preview page, it should fetch the data from the methods supplied by the CMS global object; otherwise it would fetch from its props object.

Use this.isPreview() to check whether we're in a CMS preview content.

Example:

getData() {
  if(this.isPreview()) {
    const entry = this.props.entry;
    return {
      Title: entry.getIn(['data', 'title']),
      Content: this.props.widgetFor('body')
    };
  }
  return this.props;
}

In most cases, you would pass any necessary data as attributes of that placeholder, and they will be direct properties of the this.props object:

<Person
  firstname="Foo"
  lastname="Bar"
></Person>

resolves automatically to

this.props = {
  firstname: "Foo",
  lastname: "Bar"
}

If there's a need, you can pass more complicated data from hugo templates as children of the placeholder element, or as a JSON text inside it. The placeholder DOM object is given to the component as this.props.xplaceholder, so you can pass the data however you want as long as you fetch it appropriately.

Example hugo template:

<BlogPost
  title="{{ .Title }}"
  content="{{ htmlEscape .Content }}"
>
{{ range .Params.people }}
  {{ range where (where $.Site.RegularPages "Section" "people") ".Params.title" .person }}
    <BlogPostAuthor
      link="{{ .URL }}"
      title="{{ .Title }}"
    ></BlogPostAuthor>
  {{ end }}
{{ end }}
</BlogPost>

fetch in the component as:

getData() {
  const data = Object.assign({
    people: []
  }, this.props);

  if(this.props.xplaceholder) {
    const authors = this.props.xplaceholder.querySelectorAll('BlogPostAuthor');
    for(let i = 0; i < authors.length; i++) {
      let author = authors[i];
      let person = {
        link: author.getAttribute('link'),
        Title: author.getAttribute('title')
      };
      data.people.push(person);
    }
  }

  // "Content" comes pre-built with HTML markup already. We need to parse it so that it doesn't show up as simple text
  const parsed = new DOMParser().parseFromString(data.content, "text/html");
  data.content = ReactHtmlParser(parsed.documentElement.textContent);

  return data;
}

renderBlock()

Where the actual layout for this component goes; must return a single JSX object, so if necessary you must encapsulate the whole output in an outer div container or equivalent.

React elements have some syntax differences, the most important being:

  • class attributes should be defined as className instead;
  • style attributes expect a JS style object rather than a text string: style={ { background: 'red' } }

Example:

renderBlock() {
  const data = this.getData();
  return (
    <div>
      <div className="blog-post__header" style={{ backgroundImage: `url(${data.image})` }}>
        <div className="blog-post__header-title__wrapper">
          <h1 className="blog-post__header-title">{ data.title }</h1>
        </div>
      </div>
      <section className="layout-container--inner">
        { data.content }
      </section>
    </div>
  );
}

Although it's not necessary, when the component will only be used for direct output of a block and not for any content preview, you can define the layout in the render() method instead as you would in a typical React component.

You can also include other components within another component's layouts; don't forget to pass any data necessary to build it:

import React from 'react';
import BesugoComponent from 'Besugo';
import SocialIcons from 'partials/SocialIcons';

// ...

renderBlock() {
  const data = this.getData();

  return (
    <div className="profile__header-info">
      <h1 className="profile__header-info__title">{ data.Title }</h1>
      <SocialIcons section="profile" { ...data } />
    </div>
  );
}

renderPreview()

This is the layout for the preview area of this component. Typically, you will encapsulate the above renderBlock() within another JSX layout:

import React from 'react';
import BesugoComponent from 'Besugo';
import SVGElements from 'partials/SVGElements';
import TopHeader from 'partials/TopHeader';
import EndFooter from 'partials/EndFooter';

// ...

renderPreview() {
  return (
    <div id="cmsPreview">
      <SVGElements/>
      <TopHeader/>
      { this.renderBlock() }
      <EndFooter/>
    </div>
  );
}

@Quicksaver Quicksaver removed the request for review from nunoveloso October 9, 2017 11:08
@Quicksaver Quicksaver changed the title [MERGE AFTER #26] Feature/#27 jsx layouts Feature/#27 jsx layouts Oct 9, 2017
@jolidog jolidog merged commit 2a232c8 into develop Oct 9, 2017
@jolidog jolidog deleted the feature/#27_jsx_layouts branch October 9, 2017 11:17
jolidog added a commit that referenced this pull request Oct 9, 2017
…improvements

[MERGE AFTER #29] Feature/#32 preview relations improvements
@Quicksaver Quicksaver mentioned this pull request Dec 19, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Resolve redundancy of layouts between hugo and CMS previews
2 participants