diff --git a/examples/nuxt-app/layers/example-data-driven-component/README.md b/examples/nuxt-app/layers/example-data-driven-component/README.md
new file mode 100644
index 000000000..f3ae29188
--- /dev/null
+++ b/examples/nuxt-app/layers/example-data-driven-component/README.md
@@ -0,0 +1,19 @@
+## About this layer
+
+An example of setting up a data driven component for testing purposes
+
+Data driven components can be registered in the Nuxt `app.config` file:
+
+```ts
+export default defineAppConfig({
+ ripple: {
+ dataDrivenComponents: {
+ // add key of field_data_driven_component and value of component name to render
+ // eg: find_a_council_map: 'VicCouncilLookup'
+ example_ddc: 'ExampleDDC'
+ }
+ }
+})
+```
+
+Note that the vue component (e.g. ExampleDDC) must be declared globally for this to work, see https://nuxt.com/docs/guide/directory-structure/components.
diff --git a/examples/nuxt-app/layers/example-data-driven-component/app.config.ts b/examples/nuxt-app/layers/example-data-driven-component/app.config.ts
new file mode 100644
index 000000000..e43f2b936
--- /dev/null
+++ b/examples/nuxt-app/layers/example-data-driven-component/app.config.ts
@@ -0,0 +1,12 @@
+export default defineAppConfig({
+ ripple: {
+ featureFlags: {
+ contentCollectionSearchConnector: 'elasticsearch'
+ },
+ dataDrivenComponents: {
+ // add key of field_data_driven_component and value of component name to render
+ // eg: find_a_council_map: 'VicCouncilLookup'
+ example_ddc: 'ExampleDDC'
+ }
+ }
+})
diff --git a/examples/nuxt-app/layers/example-data-driven-component/components/global/ExampleDDC.vue b/examples/nuxt-app/layers/example-data-driven-component/components/global/ExampleDDC.vue
new file mode 100644
index 000000000..350e0163d
--- /dev/null
+++ b/examples/nuxt-app/layers/example-data-driven-component/components/global/ExampleDDC.vue
@@ -0,0 +1,15 @@
+
+ {{ title }}
+
+ {{ testCustomProp }}
+
+
+
diff --git a/examples/nuxt-app/layers/example-data-driven-component/nuxt.config.ts b/examples/nuxt-app/layers/example-data-driven-component/nuxt.config.ts
new file mode 100644
index 000000000..f716d9f9d
--- /dev/null
+++ b/examples/nuxt-app/layers/example-data-driven-component/nuxt.config.ts
@@ -0,0 +1,3 @@
+import { defineNuxtConfig } from 'nuxt/config'
+
+export default defineNuxtConfig({})
diff --git a/examples/nuxt-app/nuxt.config.ts b/examples/nuxt-app/nuxt.config.ts
index b306f8f43..fd1c319c2 100644
--- a/examples/nuxt-app/nuxt.config.ts
+++ b/examples/nuxt-app/nuxt.config.ts
@@ -25,7 +25,8 @@ export default defineNuxtConfig({
'./layers/example-components',
'./layers/fixture-api',
'./layers/map-features',
- './layers/ripple-ui-forms-ext'
+ './layers/ripple-ui-forms-ext',
+ './layers/example-data-driven-component'
],
// Nuxt devtools
sourcemap: true,
diff --git a/examples/nuxt-app/test/features/landingpage/page-components.feature b/examples/nuxt-app/test/features/landingpage/page-components.feature
index 390611cee..e3c378f2a 100644
--- a/examples/nuxt-app/test/features/landingpage/page-components.feature
+++ b/examples/nuxt-app/test/features/landingpage/page-components.feature
@@ -209,3 +209,9 @@ Feature: Home page
Then the dataLayer should include the following events
| event | element_id | element_text | label | file_name | file_extension | type | component |
| file_download | page-component-1951 | Download it | Complex image | medium.png | png | image | rpl-media-embed |
+
+ @mockserver
+ Scenario: Page component - Data driven component
+ Then a custom data driven component with ID "3553540" should exist with title "Test data driven title" and have the properties
+ | description | testCustomProp |
+ | Test data driven desc | testCustomValue |
diff --git a/examples/nuxt-app/test/fixtures/landingpage/home.json b/examples/nuxt-app/test/fixtures/landingpage/home.json
index 793f0e3d4..2ec2bcd7d 100644
--- a/examples/nuxt-app/test/fixtures/landingpage/home.json
+++ b/examples/nuxt-app/test/fixtures/landingpage/home.json
@@ -981,6 +981,18 @@
"allowFullscreen": true,
"showTitle": true
}
+ },
+ {
+ "uuid": "0aa14648-604d-4aab-833a-bbce065433f4",
+ "component": "ExampleDDC",
+ "id": "3553540",
+ "props": {
+ "title": "Test data driven title",
+ "description": "
Test data driven desc
",
+ "field": "example_ddc",
+ "component": "ExampleDDC",
+ "testCustomProp": "testCustomValue"
+ }
}
],
"meta": {
diff --git a/packages/ripple-test-utils/step_definitions/components/data-driven-component.ts b/packages/ripple-test-utils/step_definitions/components/data-driven-component.ts
new file mode 100644
index 000000000..b859fe85c
--- /dev/null
+++ b/packages/ripple-test-utils/step_definitions/components/data-driven-component.ts
@@ -0,0 +1,22 @@
+import { Then, DataTable } from '@badeball/cypress-cucumber-preprocessor'
+
+Then(
+ 'a custom data driven component with ID {string} should exist with title {string} and have the properties',
+ (id: string, title: string, dataTable: DataTable) => {
+ const data = dataTable.hashes()[0]
+
+ cy.get(`[data-component-id="${id}"]`).as('component')
+ cy.get('@component').should('exist')
+ cy.get(`@component`).should(
+ 'have.attr',
+ 'data-component-type',
+ 'ExampleDDC'
+ )
+ cy.get('@component').within(() => {
+ cy.get(`h3`).should('have.text', title)
+ })
+
+ cy.get('@component').should('contain', data.description)
+ cy.get('@component').should('contain', data.testCustomProp)
+ }
+)
diff --git a/packages/ripple-test-utils/step_definitions/components/index.ts b/packages/ripple-test-utils/step_definitions/components/index.ts
index db59dde95..555fff06e 100644
--- a/packages/ripple-test-utils/step_definitions/components/index.ts
+++ b/packages/ripple-test-utils/step_definitions/components/index.ts
@@ -20,3 +20,4 @@ import './category-grid'
import './content-collection'
import './custom-collection'
import './social-share'
+import './data-driven-component'
diff --git a/packages/ripple-tide-landing-page/app.config.ts b/packages/ripple-tide-landing-page/app.config.ts
index b1cbeba1c..eedaf4ea1 100644
--- a/packages/ripple-tide-landing-page/app.config.ts
+++ b/packages/ripple-tide-landing-page/app.config.ts
@@ -2,6 +2,10 @@ export default defineAppConfig({
ripple: {
featureFlags: {
contentCollectionSearchConnector: 'elasticsearch'
+ },
+ dataDrivenComponents: {
+ // add key of field_data_driven_component and value of component name to render
+ // eg: find_a_council_map: 'VicCouncilLookup'
}
}
})
diff --git a/packages/ripple-tide-landing-page/components/global/TideLandingPage/DataDrivenCmpError.vue b/packages/ripple-tide-landing-page/components/global/TideLandingPage/DataDrivenCmpError.vue
new file mode 100644
index 000000000..1aa800412
--- /dev/null
+++ b/packages/ripple-tide-landing-page/components/global/TideLandingPage/DataDrivenCmpError.vue
@@ -0,0 +1,24 @@
+
+
+
+
Data driven component {{ component }} not found
+
Check the component mapping for {{ field }} in app.config
+
+
+
+
+
+
+
diff --git a/packages/ripple-tide-landing-page/mapping/components.ts b/packages/ripple-tide-landing-page/mapping/components.ts
index 0d50767ae..feb7dd00d 100644
--- a/packages/ripple-tide-landing-page/mapping/components.ts
+++ b/packages/ripple-tide-landing-page/mapping/components.ts
@@ -19,6 +19,7 @@ import complexImageMapping from './components/complex-image/complex-image-mappin
import dataTableMapping from './components/data-table/data-table-mapping'
import compactCardsMapping from './components/compact-cards/compact-cards-mapping'
import openFormsMapping from './components/openforms/openforms-mapping'
+import dataDrivenComponentMapping from './components/data-driven-component/data-driven-component-mapping'
const mappings: {
[key: string]: {
@@ -45,7 +46,8 @@ const mappings: {
'paragraph--complex_image': complexImageMapping,
'paragraph--data_table': dataTableMapping,
'paragraph--compact_card_collection': compactCardsMapping,
- 'paragraph--form_embed_openforms': openFormsMapping
+ 'paragraph--form_embed_openforms': openFormsMapping,
+ 'paragraph--data_driven_component': dataDrivenComponentMapping
}
// Add reusable include on whitelisted paragraph types
diff --git a/packages/ripple-tide-landing-page/mapping/components/data-driven-component/data-driven-component-mapping.ts b/packages/ripple-tide-landing-page/mapping/components/data-driven-component/data-driven-component-mapping.ts
new file mode 100644
index 000000000..b31cf498c
--- /dev/null
+++ b/packages/ripple-tide-landing-page/mapping/components/data-driven-component/data-driven-component-mapping.ts
@@ -0,0 +1,60 @@
+/* eslint-disable no-prototype-builtins */
+import type { TideDynamicPageComponent } from '@dpc-sdp/ripple-tide-api/types'
+import { logger } from '@dpc-sdp/ripple-tide-api'
+import { useAppConfig } from '#imports'
+
+export interface ITideDataDrivenComponent {
+ component?: string
+ description: string
+ title: string
+ field?: string
+ data?: unknown
+}
+
+interface IDDCAppConfig {
+ ripple?: {
+ dataDrivenComponents?: Record
+ }
+}
+
+export const dataDrivenComponentMapping = (
+ field: any
+): TideDynamicPageComponent => {
+ const getComponent = (field: any) => {
+ const appConfig = useAppConfig() as IDDCAppConfig
+ if (
+ appConfig?.ripple?.dataDrivenComponents?.hasOwnProperty(
+ field.field_data_driven_component
+ )
+ ) {
+ return appConfig.ripple.dataDrivenComponents[
+ field.field_data_driven_component
+ ]
+ }
+ return 'TideLandingPageDataDrivenCmpError'
+ }
+ let dataProps = {}
+ try {
+ dataProps = JSON.parse(field.field_configuration)
+ } catch (error) {
+ logger.error(`Error parsing data driven component extra data`, error)
+ }
+
+ return {
+ component: getComponent(field),
+ id: `${field.drupal_internal__id}`,
+ props: {
+ title: field.field_paragraph_title,
+ description: field.field_paragraph_body?.processed,
+ field: field.field_data_driven_component,
+ component: getComponent(field),
+ ...dataProps
+ }
+ }
+}
+
+export default {
+ includes: [],
+ mapping: dataDrivenComponentMapping,
+ contentTypes: ['landing_page']
+}