© Joe Attardi 2020
J. AttardiUsing Gatsby and Netlify CMShttps://doi.org/10.1007/978-1-4842-6297-9_12

12. Customizing the CMS

Joe Attardi1 
(1)
Billerica, MA, USA
 
The Netlify CMS application has a preview of the content as you are entering it. By default, this preview is minimalistic and not very useful. For example, Figure 12-1 shows the CMS preview of the menu page.
../images/502348_1_En_12_Chapter/502348_1_En_12_Fig1_HTML.jpg
Figure 12-1

The default preview functionality

It would be nice if we could preview what the actual menu page would look like with the data we have added. Netlify CMS allows us to do this with custom previews.

Customizing Netlify CMS

The gatsby-plugin-netlify-cms plugin gives us a hook for configuring the CMS application. To get started, we need to make a change to the plugin configuration. Currently, in gatsby-config.js, our plugin entry for this plugin is a simple string. We will need to change that to the object format with resolve and options properties.

Updating the plugin configuration

Open the file gatsby-config.js and update it with the code shown in Listing 12-1.
module.exports = {
  siteMetadata: {
    title: 'The Coffee Blog'
  },
  plugins: [
    {
      resolve: 'gatsby-plugin-netlify-cms',
      options: {
        modulePath: `${__dirname}/src/cms/cms.js`
      }
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        name: 'images',
        path: 'static/img'
      }
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        name: 'blog',
        path: 'src/blog'
      }
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        name: 'pageData',
        path: 'src/pageData'
      }
    },
    {
      resolve: 'gatsby-transformer-remark',
      options: {
        plugins: [
          'gatsby-remark-relative-images',
          'gatsby-remark-images'
        ]
      }
    },
    'gatsby-plugin-sharp',
    'gatsby-transformer-sharp'
  ]
};
Listing 12-1

The updated Gatsby configuration file

Here, we are adding an option called modulePath. This tells the CMS where to find customizations that we will create in this chapter. These customizations will be registered in the file src/cms/cms.js.

Adding a custom menu preview

Create a new directory called src/cms. All the CMS customizations will go inside this directory, including the cms.js module we specified in the preceding plugin configuration.

Refactoring the menu page

Currently, the menu page gets its data via a static query inside the component. In order for the preview to work, the page’s data must be passed to it from an external source. That’s currently not possible with the existing design.

Let’s refactor the menu page to use a page query. The results of the page query are passed to the page with a data prop. Later, when we render the preview page, we can pass CMS data into that data prop.

Open the file src/pages/menu.js and refactor it with the code shown in Listing 12-2.
import React from 'react';
import Layout from '../components/Layout';
import MenuCategory from '../components/MenuCategory';
import { graphql } from 'gatsby';
import styles from './menu.module.css';
export default function Menu({ data }) {
  return (
    <Layout>
      <div id={styles.main}>
        <h1>{data.markdownRemark.frontmatter.title}</h1>
        <div id={styles.menu}>
          {data.markdownRemark.frontmatter.categories.map(category => (
            <MenuCategory
              key={category.name}
              category={category} />
          ))}
        </div>
      </div>
    </Layout>
  );
}
export const query = graphql`
  {
    markdownRemark(frontmatter: { contentKey: { eq: "menu" } }) {
      frontmatter {
        title
        categories {
          name
          items {
            name
            description
            price
          }
        }
      }
    }
  }
`;
Listing 12-2

Using a page query instead of a static query

All we’ve done here is taken the GraphQL query from the static query and moved it to a page query, then added the data prop to the page component. The current menu page should still work as it did before.

Creating the preview component

Next, let’s create the preview component that will be used by the CMS. Create a new file src/cms/MenuPreview.js and add the code from Listing 12-3 .
import React from 'react';
import MenuPage from '../pages/menu';
export default function MenuPreview({ entry }) {
  const menu = entry.getIn(['data']).toJS();
  const data = {
    markdownRemark: {
      frontmatter: {
        ...menu
      }
    }
  };
  return <MenuPage data={data} />;
}
Listing 12-3

The menu preview component

This preview component renders our menu page, passing in the data from the CMS. The template receives an entry prop, which is a data structure containing all of the CMS data.

entry.getIn(['data']) gets us a collection of the entry data. Calling toJS() on this converts it to a plain JavaScript object. This gives us the raw CMS data. However, we can’t pass this directly to the menu page, because it is expecting a data structure resembling the structure of its GraphQL page query. Before we pass this data to the page, we have to wrap it in some other data so that it appears to be the result of the GraphQL query.

Finally, we return the menu page component, passing in the correctly formatted data.

Now that we’ve created the preview, let’s go back to src/cms/cms.js and register the preview with the CMS. Change the file so that it has the following content from Listing 12-4.
import CMS from 'netlify-cms-app';
import MenuPreview from './MenuPreview';
CMS.registerPreviewTemplate('menu', MenuPreview);
Listing 12-4

Registering the CMS preview

Opening the local CMS instance

To test these changes without needing to push to GitHub, we can actually access the CMS application locally at http://localhost:8000/admin. When you visit this page, you will again be prompted to log in with Netlify Identity.

When you click this button, however, you will see a new message. Before you can access the CMS locally, you have to point it to the URL of the live site. This is because the Git Gateway retrieves the CMS data via GitHub’s APIs and not through the local repository.
../images/502348_1_En_12_Chapter/502348_1_En_12_Fig2_HTML.jpg
Figure 12-2

The Development Settings dialog

Enter the full URL of your live site, for example, https://<your-site-name>.netlify.app, then click the “Set site’s URL” button. You will then be prompted to log in again. Log in with the same credentials you use on the live site, and you should be taken to the CMS application.

Once you have logged in, you will see the familiar CMS home page. Click “Pages,” then click “Menu.” Unfortunately, now you will see an error in the preview area.
../images/502348_1_En_12_Chapter/502348_1_En_12_Fig3_HTML.jpg
Figure 12-3

The CMS error message

If you look at the error details, you’ll see it is related to a StaticQuery. The query it is referring to is the one in the Layout component that queries for the site’s header background.

The preview components don’t go through the same build process that the Gatsby site pages do. Recall that the static query is executed at build time. Here, the browser tries to execute it at runtime on the page. The browser doesn’t know anything about GraphQL queries, so we get the error.

To fix this, we’ll have to do a little refactoring. Unfortunately, this means that our custom preview will not show the site header, as it is dependent on a static query. But we can still preview the menu page content with its styling.

Refactoring the menu page again

To make the menu page work in the custom preview, we’ll need to extract the page content into its own component that receives the page query data. The menu page component will be little more than a page query followed by the new component we’ll create, inside the Layout component.

Create a new file src/components/Menu.js and add the code from Listing 12-5.
import React from 'react';
import styles from './Menu.module.css';
import MenuCategory from './MenuCategory';
export default function Menu({ data }) {
  return (
    <div id={styles.main}>
      <h1>{data.markdownRemark.frontmatter.title}</h1>
      <div id={styles.menu}>
        {data.markdownRemark.frontmatter.categories.map(category => (
          <MenuCategory
            key={category.name}
            category={category} />
        ))}
      </div>
    </div>
  );
}
Listing 12-5

The new Menu component

This component contains all of the content of the menu page, encapsulated into a presentational component. We’ll use this component both from the menu page, where the query results will be passed in, and the preview, where the CMS data will be passed in.

Next, create a new file src/components/Menu.module.css and add the code from Listing 12-6.
#main {
  padding: 1rem;
}
#main h1 {
  margin: 0;
}
#menu {
  display: flex;
}
Listing 12-6

The CSS module for the Menu component

Finally, open the file src/pages/menu.js and make the changes indicated in Listing 12-7.
import React from 'react';
import Layout from '../components/Layout';
import Menu from '../components/Menu';
import { graphql } from 'gatsby';
export default function MenuPage({ data }) {
  return (
    <Layout>
      <Menu data={data} />
    </Layout>
  );
}
export const query = graphql`
  {
    markdownRemark(frontmatter: { contentKey: { eq: "menu" } }) {
      frontmatter {
        title
        categories {
          name
          items {
            name
            description
            price
          }
        }
      }
    }
  }
`;
Listing 12-7

The new menu page

This page component no longer has any styling, so you can delete the file src/pages/menu.module.css.

At this point, the menu page should look exactly the same as it did before our refactoring. Go to http://localhost:8000/menu and make sure before continuing.

Updating the preview component

Now, let’s update the preview component to use the new Menu component. Open the file src/cms/MenuPreview.js and make the changes in Listing 12-8.
import React from 'react';
import '../global.css';
import Menu from '../components/Menu';
export default function MenuPreview({ entry }) {
  const menu = entry.getIn(['data']).toJS();
  const data = {
    markdownRemark: {
      frontmatter: {
        ...menu
      }
    }
  };
  return <Menu data={data} />;
}
Listing 12-8

The updated MenuPreview component

Previewing the menu data

Now we’re ready to test the preview again. Open the local CMS application, and click “Pages,” then click “Menu.” This time, the preview area should have a preview of how the menu looks on the live site – minus the site header, since we are unable to run the static query required by the header.
../images/502348_1_En_12_Chapter/502348_1_En_12_Fig4_HTML.jpg
Figure 12-4

The CMS interface showing the editing controls alongside the preview

This is a live preview. This means that as you are making changes, the preview is updated immediately. The preview functionality is important for the site’s maintainability. The CMS provides the ability for the coffee shop management to update the menu without writing code, but without the custom preview, they won’t be able to see what the updated menu will look like to users of the site.

However, there’s still one problem we have to solve. Let’s try adding a new menu category now. As we type the name of the category, it will appear in real time in the preview window. Click the “Add category” button.
../images/502348_1_En_12_Chapter/502348_1_En_12_Fig5_HTML.jpg
Figure 12-5

The new error

You’ll see that another error is thrown as soon as you click the button.

If you check the stack trace in the console, you’ll see the error is with the call to map in the MenuCategory component.
../images/502348_1_En_12_Chapter/502348_1_En_12_Fig6_HTML.jpg
Figure 12-6

The error stack trace

In the MenuCategory component, we call map on the category’s items array in order to render each individual menu item. This is the root cause of the problem. When you click the “New category” button, the preview immediately tries to render the updated data.

At the moment when the category is first created, it does not have an items array yet – it is undefined. We call map on an undefined value, which throws the error.

The fix is quite simple. We just need to add a condition so that we only call map if the items array actually exists. Open the file src/components/MenuCategory.js, and make the changes indicated in Listing 12-9.
import React from 'react';
import styles from './MenuCategory.module.css';
export default function MenuCategory({ category }) {
  return (
    <div className={styles.category}>
      <h2>{category.name}</h2>
      <ul>
        {category.items && category.items.map(item => (
          <li key={item.name}>
            <div className={styles.name}>{item.name}</div>
            <div className={styles.description}>{item.description}</div>
            <div>{item.price}</div>
          </li>
        ))}
      </ul>
    </div>
  );
}
Listing 12-9

The updated MenuCategory component

With this change, the call to map will now only occur if category.items is defined. This will fix the issue. We can test this out by refreshing the CMS page. You may be prompted to restore from a local backup. You can click “Cancel” to bypass this.

Now that we’ve refreshed the page, click “Add category” again. A new editor widget appears for the new category, which is currently blank. You might have also noticed that a new blank space appeared to the right of the items in the preview.

For the new category name, type “Bakery”. You’ll notice that as you type, the menu preview is instantly updated.
../images/502348_1_En_12_Chapter/502348_1_En_12_Fig7_HTML.jpg
Figure 12-7

The live preview while adding a new category

Now that the live preview of the menu is working, let’s make a commit and push to GitHub.
git add .
git commit -m "Add menu preview"
git push origin master

Summary

In this chapter, we
  • Created a module to customize the CMS

  • Created a live preview component for the menu page

  • Refactored the menu page so that it works with the live preview

  • Fixed a few errors along the way

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.57.219