Using a Content Fragment API in React

In this blog post, we’ll use a simple React app to access content fragment JSON data exposed via a content fragment API. For this example, you will need to set up movie content fragments detailed within the AEM Content Fragments in the Wild and AEM Content Fragments as an API blog posts to generate the content fragment API that we will be using.

You can also simply clone the fragmentexamples project, install with “mvn clean install -PautoInstallPackage” and then set up content fragments by following these steps:

Create the fragmentexamples configuration

The first step is to create the configuration for our sample project, fragmentexamples. AEM configurations allow you to do many things such as editable templates, contextual site configurations, and content fragment configurations.

  1. Go to AEM Start > Tools > General > Configuration Browser > Create
  2. Enter “fragmentexamples” for the title
  3. Check the “Content Fragment Models” checkbox
  4. Click create
Create the fragmentexamples DAM Folder

Content fragments are stored in the AEM DAM and are simply treated as an asset like any image or pdf. We will want to create a DAM folder for storing all of our content fragments.

  1. Go to AEM Start > Assets > Files > Create > Folder
  2. Enter the title “fragmentexamples”
  3. Click create
Apply the fragmentexamples configuration to the fragmentexamples DAM folder

Next we need to apply our fragmentexamples configuration to our fragmentexamples DAM folder so that we can create content fragments with custom schemas within this folder.

  1. Go to AEM Start > Assets > Files
  2. Edit the properties of the fragmentexamples folder
  3. Go to the “Cloud Services” tab
  4. In the “Cloud Configuration” field select “fragmentexamples”
Create a “Movie” content fragment model

Now we are ready to create out model. In order to fulfill the requirements of the user story we need to display a list of movies.

  1. Go to AEM Start > Tools > Assets > Content Fragment Models > fragmentexamples > Create
  2. Set the title to “Movie” and then click “Open”.
  3. Add a single line text field with a field label of “Title” and a field name of “title”
  4. Add a single line text field with a field label of “Description and a field name of “description”
  5. Add a date and time field with a label of “Release Date” and a field name of “releaseDate”
  6. Add a content reference field with the label “Hero Image” and a field name of “heroImage”
Test Content Fragment API

Verify that your movie content fragments are being returned in JSON format by typing http://localhost:4502/bin/fragmentexamples/movies.json into your browser. Each movie content fragment should be listed as objects in movies.json:

React App Startup

For this example, we’ll be using a React app built originally via Create React App. React is a Javascript library primarily used for single-page web applications and native mobile applications. To learn more about React and how to quickly spin up a React app visit https://facebook.github.io/create-react-app/.

Clone React App

To get started, clone down the content-fragment-react-app repo:

  1. Run”git clone https://github.com/Dfeldbaum/content-fragment-react-app.git
Start React Server

This React app has several added scripts detailed within its package.json file for compiling CSS, JS and watching for changes to files.

  1. Run “npm start” which will initiate each script and launch a React environment on localhost: 3000

Content Fragment React App

For this example, we created a lightweight React/Webpack web application that fetches data from a content fragment API endpoint and displays AEM content fragment text and images from a content fragment React Component (<ContentFragment/>). Below are important files and components utilized within the app.

src/components/App.js

App.js is our app component which houses all other components and acts as our parent component. Within the component’s render( ) method we are generating HTML within a general “wrapper” div element that contains a <Header/> component, a <Footer/> component and view in the middle that is set by app component state. To learn more about state management in React visit React Component State. For the purposes of this app, state is simply toggled to the <ContentFragment/> component by way of a PickView( ) method:

import React, { Component } from 'react';
import { BrowserRouter, Link, Router } from 'react-router-dom';
import '../styles/App.css';
import Header from './Header/Header'
import Footer from './Footer/Footer'
import About from './About/About'
import ContentFragment from './ContentFragment/ContentFragment'


class App extends Component {

  constructor(props) {
    super(props);

    this.state = {
      progress: 'content-fragment',
    }
  }

  showContentFragments(){
    this.setState({progress:"content-fragment"})
  }

  showAbout(){
    this.setState({progress:"about"})
  }

  PickView(props){
    let progress = this.state.progress;

    if (progress == 'content-fragment'){
      return <ContentFragment/>
    }

    if (progress == 'about'){
      return <About/>
    }
  }

  render() {
    return (
      <div className="wrapper">
          <Header 
            showContentFragments={this.showContentFragments.bind(this)} 
            showAbout={this.showAbout.bind(this)}
          />

          {this.PickView()} 

          <Footer/>
      </div>
    )
  }
}

export default App;
src/components/ContentFragment.js

In the <ContentFragment/> component, which is being imported into our app component we are using the JavaScript fetch( ) method to make an HTTPS GET request to the “http://localhost:4502/bin/fragmentexamples/movies.json” endpoint. More specifically, we are executing the fetch( ) method within the component’s componentDidMount( ) lifecycle method. This means we are instructing the component to make our GET request right before the component is mounted onto the page.

import React, { Component } from 'react';

class ContentFragment extends Component {

  constructor(props) {
    super(props);

    this.state = {
      items: [],
      isLoaded: false,
    }
  }

  componentDidMount() {
    fetch('/bin/fragmentexamples/movies.json', {
      method: 'GET',
      credentials: 'include',
      headers: {
        'Connection': 'Keep-Alive',
        'Accept': 'application/json',
        'Authorization': 'Basic YWRtaW46YWRtaW4=',
        'Content-Type': 'application/json'
      }
    })
      .then(res => res.json())  // res is result from api, convert to json format
      .then(json => {  // take json and set json data to state.items
        this.setState({
          isLoaded: true,
          items: json,
        })
      })
    }

  render() {
    let { isLoaded, items } = this.state  // access items from state in render()

    if (!isLoaded) {
      return <div className="content-fragment">Loading...</div>;
    }

    else {
      return (
        <div className="content-fragment">
          <ul>
            {items.map(item => ( // loop each obj from api result 
                <li key={item.modelTitle}>
                  <div className="content-fragment__text-container">
                    <p><span>Title:</span> {item.title}</p>
                    <p><span>Description:</span> {item.description}</p>
                    <p><span>Release Date:</span> 
                      {new Intl.DateTimeFormat('en-US', { 
                        year: 'numeric', 
                        month: 'long', 
                        day: '2-digit' 
                        }).format(item.releaseDate)}                
                    </p>
                  </div>

                  <div className="content-fragment__img-container">
                    <img src={"/../img" + item.image}/> 
                  </div>
                </li> 
            ))} 
          </ul> 
        </div>
      )
    }
  }
}

export default ContentFragment;

Before the fetch, two properties are added to the state object – an empty “items” array and an isLoaded property with a boolean value of “false”:

this.state = {
  items: [],
  isLoaded: false,
}

If the response from the API is successful, the data is converted to JSON and the JSON objects are added to the items array (stored in “this.state.items”), which can then be used in React expressions within the render( ) markup. If the API request fails for any reason the isLoading property (“this.state.isLoading”) toggles to false and a <div> with “Loading…” text is displayed:

render() {
  let { isLoaded, items } = this.state  // access items from state in render()

  if (!isLoaded) {
    return <div className="content-fragment">Loading...</div>;
  }

  else {
    return (
      <div className="content-fragment">
        <ul>
          {items.map(item => ( // loop each obj from api result 
              <li key={item.modelTitle}>
                <div className="content-fragment__text-container">
                  <p><span>Title:</span> {item.title}</p>
                  <p><span>Description:</span> {item.description}</p>
                  <p><span>Release Date:</span> 
                    {new Intl.DateTimeFormat('en-US', { 
                      year: 'numeric', 
                      month: 'long', 
                      day: '2-digit' 
                      }).format(item.releaseDate)}                
                  </p>
                </div>

                <div className="content-fragment__img-container">
                  <img src={"/../img" + item.image}/> 
                </div>
              </li> 
          ))} 
        </ul> 
      </div>
    )
  }
}
Notes & Considerations:
  1. All image files from AEM DAM must also be added to the public/img/content/dam/fragmentexamples directory within the React app codebase to be displayed in the React app.
  2. This app utilizes the HTTP Proxy Middleware NPM package to establish a localhost:4502 proxy server to handle API requests. This was necessary to prevent Cross Origin Resource Sharing (CORS) issues.

Leave a Reply

avatar
  Subscribe  
Notify of