Resolving Linked Content in Structs in the Headless Server - CMCC 12

Last updated 9 days ago

Learn how to resolve linked content in structs

LightbulbWhat you'll learn

  • Getting content from linked content in structs

Person reading a bookPrerequisites

  • A CoreMedia system with Headless Server

WristwatchTime matters

Reading time: 15 minutes

Person in front of a laptopShould I read this?

This guide is for Developers.

The settings struct field of the CMLinkable type provides access to settings of content elements. However when retrieving content items or lists of content items from those structs, these elements are not resolved. Instead, the corresponding content ID is returned.

The reason for this limitation is that the Headless Server doesn’t return a proper GraphQL type as response but instead simple JSON. Resolving content elements in these JSON responses would raise the question which fields of the content item should get displayed. Displaying all fields of a content item does not effectively work and undermines all of GraphQLs advantages. Furthermore changing these mechanisms requires considerably more extensive and project specific adaptions to the JSON parsing.

Solution

In order to solve the problem, you have to step away from the JSON responses and use a combination of the StructAdapter and the GraphQL type system.

Via the `StructAdapter`it is possible to retrieve the values in the specified struct. If described correctly in the GraphQL schema, GraphQL takes care of returning the proper fields for a given content element. For the type specification of the fields, the built-in types as well as custom ones may be used.

Linked Content Elements

In order to resolve linked lists, two components are needed.

  • The actual link list needs to be retrieved. This can be done via a new field in the GraphQL Schema and a corresponding fetch declaration.

  • The field needs to be declared in the actual type as well as in the interface. When doing this you should use the StructAdapter::getStructValue() Method of the StructAdapter. This is necessary for GraphQL to be able to interpret the results and use the correct fields.

An important note is that all the types which are supposed to get retrieved from the struct need to be defined as GraphQL types.

In this example, there is a LinkList of articles defined in the property customArticles in the localSettings of an CMArticle.

interface CMArticle @inherit(from: ["CMTeasable"]) {
  linkedArticles: [CMArticle]
}
type CMArticleImpl implements CMArticle @inherit(from: ["CMTeasableImpl"]) {
  linkedArticles: [CMArticle] @fetch(from: "@structAdapter.to(#root).getStructValue('localSettings',{'test', 'customArticles'}, null)")
}

This enables the linked content items to be queried like this:

{
  content {
    article(id: "15580") {
      id
      name
      linkedArticles {
        id
        name
        detailText
      }
    }
  }
}

Special case CMSettings:

Retrieving a (linked) result of the CMSettings type can be a special case, since the values you might want to retrieve from a CMSettings document can themselves be content items in structs which need to get resolved.

In this case you also need to repeat the same procedure: create a new field in the CMSettings interface to specify the structure of data to be returned by the field and specify in the CMSettings type how to retrieve the data for the type. When just accessing primitive values in CMSettings, you can use the existing settings field to retrieve these values.

interface CMArticle @inherit(from: ["CMTeasable"]) {
  customSettings: [MySetting]
}
type CMArticleImpl implements CMArticle @inherit(from: ["CMTeasableImpl"]) {
  customSettings: [MySetting] @fetch(from: "@structAdapter.to(#root).getStructValue('linkedSettings',{}, null)")
}
type MySetting @inherit(from: ["CMSettingsImpl"]){
  settingsList: [CMPicture] @fetch(from: "@structAdapter.to(#root).getStructValue('settings', {'linkedPictures'}, null)")
}

This makes the code of the query a bit more complex, since you also need to take care of this level:

{
  content {
    article(id: "15580") {
      id
      name
      customSettings {
        settingsList {
          id
          name
          uriTemplate
        }
      }
    }
  }
}

General Remarks

The previous examples focus on the more generic use-case of linked content items in structs. This is especially useful if there are other values in custom fields of the content.

If the values are just in settings, it is also possible to use the SettingsAdapter. This has the additional advantage of not needing to specify if the setting in question is in the local settings or linked settings.

It can be used to simplify, for example, the way to retrieve linked content items in linked settings. When you use the SettingsAdapter to resolve the value, you can directly retrieve the values without the additional step of custom fields in the CMSettings type:

interface CMArticle @inherit(from: ["CMTeasable"]) {
  customSettings: [CMPicture]
}
type CMArticleImpl implements CMArticle @inherit(from: ["CMTeasableImpl"]) {
  customSettings: [CMPicture] @fetch(from: "@settingsAdapter.to(#root).get({'linkedPictures'})")
}

And in the query:

{
  content {
    article(id: "15580") {
      id
      name
      customSettings {
        id
        name
        uriTemplate
      }
    }
  }
}

Another aspect relates to the specificity of the types. While it makes sense to specify (if known) the exact types of the results in the schema, you can use the full bandwidth of GraphQLs capabilities here. An example would be the usage of the generic Content_ type in combination with the GraphQL spread operator:

interface CMArticle @inherit(from: ["CMTeasable"]) {
  customSettings: [Content_]
}
type CMArticleImpl implements CMArticle @inherit(from: ["CMTeasableImpl"]) {
  customSettings: [Content_] @fetch(from: "@settingsAdapter.to(#root).get({'linkedContentItems'})")
}
{
  content {
    article(id: "15580") {
      id
      name
      customSettings {
        ... on CMPicture {
          id
          name
          uriTemplate
        }
        ... on CMArticle {
          id
          name
          detailText
        }
      }
    }
  }
}

This way, you can dynamically decide in the query which type is expected. This can also be combined with a parametrized field to specify the path to the properties in question, similar to the existing settings field in CMLinkable or CMSettings.

type CMArticleImpl implements CMArticle @inherit(from: ["CMTeasableImpl"]) {
  customSettings(path: [String]!): [Content_] @fetch(from: "@settingsAdapter.to(#root).get({#path})")
}

And this can be used in the query like that:

{
  content {
    article(id: "15580") {
      id
      name
      customSettings(path: ["linkedContentItems"]) {
        ... on CMPicture {
          id
          name
          uriTemplate
        }
        ... on CMArticle {
          id
          name
          detailText
        }
      }
    }
  }
}

When using this however you can not query multiple different properties at once like in the settings property of the CMSettings and CMLinkable types, since here you might get conflicts if their contents have different types.

Limitations

Of course, this approach only works if the fields in the structs actually have the correct types. When requesting a CMArticle from a CMPicture you will get errors about type incompatibility.

Copyright © 2024 CoreMedia GmbH, CoreMedia Corporation. All Rights Reserved.
Loading...