Ripple Framework

Creating content types

How to create new content types for the SDP platform

See key concepts - content types for an overview of what content types are and the core content types used within the SDP platform.

Anatomy

Content types are comprised of three main parts:

Creating a content type

The Nuxt Ripple CLI provides a simple way to add new content types, to get started just run the following command:

npx @dpc-sdp/nuxt-ripple-cli add content-type [DIRECTORY] --name {NAME} --createTests --cypressPath {CYPRESSPATH}

Where [DIRECTORY] is the location to output the content type scaffolding, {NAME} is the name of the new content type, and {CYPRESSPATH} is the relative path to be used for the cypress tests folder.

Using the CLI is the preferred way to create new content types as it will ensure the correct file structure and naming conventions are used. It will also scaffold the mapping object, server plugin, and Vue component for you.

The mapping object

The mapping object is responsible for supplying the data mapping and includes.

  • Data mapping: the data mapping is responsible for mapping the Tide API response data (i.e. Drupal JSON) into a clean format that can be used within the Vue component.
  • Includes: the includes are an array of fields that need to be added to the API request so Drupal knows what referenced entities to include in the API response, for more on Drupal includes see JSON API includes.

Below is an example mapping object.

import type { IRplTideModuleMapping } from '@dpc-sdp/ripple-tide-api/types'
import { tidePageBaseMapping, tidePageBaseIncludes } from '@dpc-sdp/nuxt-ripple/mapping'
import { getField, getImageFromField } from '@dpc-sdp/ripple-tide-api'

const tideMyConentTypeModule: IRplTideModuleMapping = {
  mapping: {
    // The base mapping is used to add common fields
    // See the tidePageBaseMapping function for details 
    ...tidePageBaseMapping({ withTopicTags: true }),
    
    content: 'field_my_content_type_content',
    
    // Can be nested as needed
    header: {
      intro: 'field_landing_page_intro_text',
    },
    
    // Use a function to transform the data
    files: (src) => getField(src, 'field_node_files').map((file) => ({
      name: file.name,
      url: file.field_media_file.url
    })),

    // Or when using helper functions
    image: (src) => getImageFromField(src, 'field_featured_image.field_media_image')
  },
  includes: [
    // The base includes are used to include common fields
    // See the tidePageBaseIncludes function for details 
    ...tidePageBaseIncludes({ withTopicTags: true }),
    
    'field_featured_image',
    'field_featured_image.field_media_image'
  ]
}

export default tideMyConentTypeModule

The server plugin

The server plugin is responsible for registering the new content type with @dpc-sdp/ripple-tide-api, this file needs to live within the server/plugins directory of the content type.

The content type can then be registered by calling the setContentType method on the Tide page API. This method takes two arguments, the first is the name of the content type, and the second is the mapping object. If you have used the CLI to create the content type this will already be done for you.

import type { NitroApp } from 'nitropack'
import { defineNitroPlugin } from 'nitropack/dist/runtime/plugin'
import tideMyContentTypeModule from '../../mapping'

export default defineNitroPlugin(async (nitroApp: NitroApp) => {
  nitroApp.tide?.pageApi.setContentType('my-content-type', tideMyContentTypeModule)
})

The Vue component

A Vue component is needed to render the content type. This component will receive a page prop, it's this prop that contains the mapped data from the Tide API response.

Important note: The component needs to be registered globally, this can be done by adding the component to a /components/global folder within the content type. Because this is global the name needs to be unique, it must also be prefixed with Tide i.e. TideMyContentType.

This component should use the TideBaseLayout component to render the shell of the site, from there slots are used to render the content types content in the right locations. If you're using the CLI it will take care of scaffolding a global component that uses TideBaseLayout for you.

Below is an example component using TideBaseLayout.

<template>
  <TideBaseLayout
    :site="site"
    :page="page"
    :siteSection="page.siteSection"
    :pageTitle="page.title"
    :pageLanguage="page.lang"
    :updatedDate="page.changed || page.created"
    :showContentRating="page.showContentRating"
  >
    <template #aboveHeader>
      <slot name="aboveHeader"></slot>
    </template>
    <template #primaryNav>
      <slot name="primaryNav"></slot>
    </template>
    <template #breadcrumbs>
      <slot name="breadcrumbs"></slot>
    </template>
    <template #aboveBody>
      <slot name="aboveBody"></slot>
    </template>
    <template #body>
      <!-- Add content within the relevant slot -->
      <TideMyContentTypeContent :content="page.content" />
    </template>
    <template #sidebar>
      <slot name="sidebar"></slot>
    </template>
    <template #footer>
      <slot name="footer"></slot>
    </template>
  </TideBaseLayout>
</template>

<script setup lang="ts">
import { TideSiteData } from '@dpc-sdp/ripple-tide-api/types'
import type { TideMediaPage } from '../../types'

interface Props {
  site: TideSiteData
  page: TideMediaPage
}

defineProps<Props>()
</script>

Using the content type

Content types are actually Nuxt layers so these are added in the same way as any other layer through the extends property of the main applications nuxt.config.ts file.

export default defineNuxtConfig({
  extends: [
    '@dpc-sdp/ripple-tide-news', // An npm installed package
    'github:dpc-sdp/ripple-tide-news#1.0.0', // A tagged git repository
    './layers/tide-my-content-type', // A local layer
  ]
})