Content Fragments

Activity: Create Queries

Query to get list of all Press Releases:

query allReleases {
  pressReleaseList{
    items{
      _id
      _path
      title
    }
  }
}

Query to get specific press release by path:

query releaseByPath($path: String!) {
    pressReleaseByPath(_path: $path) {
  		item {
  		  _path
  		  _id
  		  _variation
  		  title
  		  slug
  		  date
        content {
          plaintext
        }
  		}
    }
}

Activity: Dedicated Content Fragment Block

Block JSON

{
    "definitions": [
        {
            "title": "Content Fragment",
            "id": "content-fragment",
            "plugins": {
                "xwalk": {
                    "page": {
                        "resourceType": "core/franklin/components/block/v1/block",
                        "template": {
                            "name": "Content Fragment",
                            "model": "content-fragment"
                        }
                    }
                }
            }
        }
    ],
    "models": [
        {
            "id": "content-fragment",
            "fields": [
                {
                    "component": "aem-content-fragment",
                    "name": "path",
                    "value": "",
                    "label": "Content Fragment path",
                    "variationName": "contentFragmentVariation",
                    "validation": {
                        "rootPath": "/content/dam/www-site"
                    }
                }
            ]
        }
    ],
    "filters": []
}

Block JS

function showEmpty(block) {
  const emptyMessage = 'No content fragment selected. Use the Universal Editor to select a content fragment.';
  block.innerHTML = `<div class="content-fragment-empty">${emptyMessage}</div>`;
}

function showError(block, message) {
  block.innerHTML = `<div class="content-fragment-error">Error: ${message}</div>`;
}

function getAEMHost() {
  let host;
  if (window.location.hostname.endsWith('adobeaemcloud.com')) {
    host = 'https://author-p219104-e2263158.adobeaemcloud.com';
  } else {
    host = 'https://publish-p219104-e2263158.adobeaemcloud.com';
    // or actual public facing domain
  }
  // Remove trailing slash if present
  if (host.endsWith('/')) {
    host = host.slice(0, -1);
  }
  return host;
}

function createDisplay(contentfragment) {
  const cfDiv = document.createElement('div');

  const prHeading = document.createElement('h1');
  prHeading.innerHTML = contentfragment.data.pressReleaseByPath.item.title;

  const prDate = document.createElement('p');
  prDate.innerHTML = contentfragment.data.pressReleaseByPath.item.date;

  const prContent = document.createElement('div');
  prContent.innerHTML = contentfragment.data.pressReleaseByPath.item.content.plaintext;

  cfDiv.appendChild(prHeading);
  cfDiv.appendChild(prDate);
  cfDiv.appendChild(prContent);

  return cfDiv;
}

export default async function decorate(block) {
  // Get the content fragment path from the UE generated content in the DOM
  const cfPath = block.querySelector('a')?.textContent;
  if (!cfPath) {
    showEmpty(block);
    return;
  }

  try {
    const gqlUrl = `${getAEMHost()}/graphql/execute.json/www-site/pr-by-path;path=${cfPath}`;

    const response = await fetch(gqlUrl);
    const contentFragment = await response.json();
    if (!contentFragment) {
      showError(block, 'Content fragment not found');
    }
    block.replaceChildren(createDisplay(contentFragment));
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error('Content Fragment block error:', error);
    showError(block, 'Failed to load content fragment');
  }
}

Block CSS

/* Error State */
.content-fragment-error {
  text-align: center;
  padding: 4rem 2rem;
  color: var(--color-error, #dc3545);
  font-size: 1.1rem;
  border: 1px solid var(--color-error, #dc3545);
  border-radius: 8px;
  background: rgb(220 53 69 / 10%);
}

/* Empty State */
.content-fragment-empty {
  text-align: center;
  padding: 4rem 2rem;
  color: var(--text-color);
  font-size: 1.1rem;
  font-style: italic;
  border: 2px dashed var(--color-gray-300);
  border-radius: 8px;
  background: var(--color-gray-50);
}

Activity: Press Release List & Detail

Block JSON

{
    "definitions": [
        {
            "title": "Press Releases",
            "id": "press-releases",
            "plugins": {
                "xwalk": {
                    "page": {
                        "resourceType": "core/franklin/components/block/v1/block",
                        "template": {
                            "name": "Press Releases",
                            "model": "press-releases"
                        }
                    }
                }
            }
        }
    ],
    "models": [],
    "filters": []
}

Block JS

function getAEMHost() {
  let host;
  if (window.location.hostname.endsWith('adobeaemcloud.com')) {
    host = 'https://author-p219104-e2263158.adobeaemcloud.com';
  } else {
    host = 'https://publish-p219104-e2263158.adobeaemcloud.com';
    // or actual public facing domain
  }
  // Remove trailing slash if present
  if (host.endsWith('/')) {
    host = host.slice(0, -1);
  }
  return host;
}

async function loadFragment(cfPath) {
  const gqlUrl = `${getAEMHost()}/graphql/execute.json/www-site/pr-by-path;path=${cfPath}`;
  const response = await fetch(gqlUrl);
  const contentFragment = await response.json();
  const cfDiv = document.createElement('div');

  const prHeading = document.createElement('h1');
  prHeading.innerHTML = contentFragment.data.pressReleaseByPath.item.title;

  const prDate = document.createElement('p');
  prDate.innerHTML = contentFragment.data.pressReleaseByPath.item.date;

  const prContent = document.createElement('div');
  prContent.innerHTML = contentFragment.data.pressReleaseByPath.item.content.plaintext;

  cfDiv.appendChild(prHeading);
  cfDiv.appendChild(prDate);
  cfDiv.appendChild(prContent);

  return cfDiv;
}

function createDisplay(contentfragment) {
  const cfDiv = document.createElement('div');

  const listDiv = document.createElement('div');
  listDiv.classList.add('pr-list');
  const detailDiv = document.createElement('div');
  detailDiv.classList.add('pr-detail');

  contentfragment.data.pressReleaseList.items.forEach((pr) => {
    const a = document.createElement('a');
    // eslint-disable-next-line no-underscore-dangle
    a.href = pr._path;
    a.innerText = pr.title;

    a.addEventListener('click', async (e) => {
      e.preventDefault();
      // eslint-disable-next-line no-underscore-dangle
      detailDiv.replaceChildren(await loadFragment(pr._path));
    });
    listDiv.appendChild(a);
  });

  cfDiv.appendChild(listDiv);
  cfDiv.appendChild(detailDiv);

  return cfDiv;
}

export default async function decorate(block) {
  try {
    const gqlUrl = `${getAEMHost()}/graphql/execute.json/www-site/pr-all`;
    const response = await fetch(gqlUrl);
    const contentFragment = await response.json();
    block.replaceChildren(createDisplay(contentFragment));
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error('Content Fragment block error:', error);
  }
}

Block CSS

.press-releases {
    border: 1px solid black;


    a {
        display: block;
        margin-bottom: 1.2em;
    }

    & > div {
        display: flex;
        flex-direction: row;

        .pr-list {
            width: 30%;
        }

        .pr-detail {
            width: 70%;

            h1 {
                margin-top: 0;
            }
        }
    }
}

An Overview of the Concepts and Best Practices of Working with Content Fragments

Content Fragments

Working with Content Fragments (Assets - Content Fragments)

Content Fragment Models (Assets - Content Fragments)

Managing Content Fragments

Managing Content Fragment Models

Publishing AEM Content Fragments to Edge Delivery Services

JSON2HTML for Edge Delivery Services