diff --git a/CHANGELOG.md b/CHANGELOG.md index 446630f258..dc99eb1635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ Our versioning strategy is as follows: ### 🛠 Breaking Change -* Editing Integration Support: ([#1776](https://github.com/Sitecore/jss/pull/1776))([#1792](https://github.com/Sitecore/jss/pull/1792))([#1773](https://github.com/Sitecore/jss/pull/1773))([#1797](https://github.com/Sitecore/jss/pull/1797))([#1800](https://github.com/Sitecore/jss/pull/1800))([#1803](https://github.com/Sitecore/jss/pull/1803))([#1806](https://github.com/Sitecore/jss/pull/1806))([#1809](https://github.com/Sitecore/jss/pull/1809)) +* Editing Integration Support: ([#1776](https://github.com/Sitecore/jss/pull/1776))([#1792](https://github.com/Sitecore/jss/pull/1792))([#1773](https://github.com/Sitecore/jss/pull/1773))([#1797](https://github.com/Sitecore/jss/pull/1797))([#1800](https://github.com/Sitecore/jss/pull/1800))([#1803](https://github.com/Sitecore/jss/pull/1803))([#1806](https://github.com/Sitecore/jss/pull/1806))([#1809](https://github.com/Sitecore/jss/pull/1809))([#1816](https://github.com/Sitecore/jss/pull/1816)) * `[sitecore-jss-react]` Introduces `PlaceholderMetadata` component which supports the hydration of chromes on Pages by rendering the components and placeholders with required metadata. * `[sitecore-jss]` Chromes are hydrated based on the basis of new `editMode` property derived from LayoutData, which is defined as an enum consisting of `metadata` and `chromes`. * `ComponentConsumerProps` is removed. You might need to reuse `WithSitecoreContextProps` type. diff --git a/packages/sitecore-jss-react/src/components/Placeholder.test.tsx b/packages/sitecore-jss-react/src/components/Placeholder.test.tsx index 5b93097258..cbb3473ddb 100644 --- a/packages/sitecore-jss-react/src/components/Placeholder.test.tsx +++ b/packages/sitecore-jss-react/src/components/Placeholder.test.tsx @@ -28,7 +28,11 @@ import * as FEAASWrapper from './FEaaSWrapper'; import { HiddenRendering } from './HiddenRendering'; import { MissingComponent, MissingComponentProps } from './MissingComponent'; import { Placeholder } from './Placeholder'; -import { ComponentProps } from './PlaceholderCommon'; +import { + ComponentProps, + getDynamicPlaceholderPattern, + isDynamicPlaceholder, +} from './PlaceholderCommon'; import { SitecoreContext } from './SitecoreContext'; import { ComponentFactory } from './sharedTypes'; import { PlaceholderMetadata } from './PlaceholderMetadata'; @@ -814,6 +818,35 @@ describe('PlaceholderMetadata', () => { }, }; + const layoutDataForNestedDynamicPlaceholder = (rootPhKey: string) => ({ + sitecore: { + context: { + pageEditing: true, + editMode: EditMode.Metadata, + }, + route: { + name: 'main', + uid: 'root123', + placeholders: { + [rootPhKey]: [ + { + uid: 'nested123', + componentName: 'Header', + placeholders: { + logo: [ + { + uid: 'deep123', + componentName: 'Logo', + }, + ], + }, + }, + ], + }, + }, + }, + }); + const componentFactory: ComponentFactory = (componentName: string) => { const components = new Map(); @@ -930,7 +963,78 @@ describe('PlaceholderMetadata', () => { ].join('') ); }); + + it('should render dynamic placeholder', () => { + const phKey = 'container-1'; + const layoutData = layoutDataForNestedDynamicPlaceholder('container-{*}'); + const wrapper = mount( + + + + ); + + expect(wrapper.html()).to.equal( + [ + '', + '', + '
', + '', + '', + '
', + '', + '
', + '', + '', + ].join('') + ); + + expect(wrapper.find(PlaceholderMetadata).length).to.equal(4); + }); + + it('should render double digit dynamic placeholder', () => { + const phKey = 'container-1-2'; + const layoutData = layoutDataForNestedDynamicPlaceholder('container-1-{*}'); + const wrapper = mount( + + + + ); + + expect(wrapper.html()).to.equal( + [ + '', + '', + '
', + '', + '', + '
', + '', + '
', + '', + '', + ].join('') + ); + + expect(wrapper.find(PlaceholderMetadata).length).to.equal(4); + }); }); + +it('isDynamicPlaceholder', () => { + expect(isDynamicPlaceholder('container-{*}')).to.be.true; + expect(isDynamicPlaceholder('container-1-{*}')).to.be.true; + expect(isDynamicPlaceholder('container-1-2')).to.be.false; + expect(isDynamicPlaceholder('container-1')).to.be.false; + expect(isDynamicPlaceholder('container-1-2-3')).to.be.false; + expect(isDynamicPlaceholder('container-1-{*}-3')).to.be.true; +}); + +it('getDynamicPlaceholderPattern', () => { + expect(getDynamicPlaceholderPattern('container-{*}').test('container-1')).to.be.true; + expect(getDynamicPlaceholderPattern('container-{*}').test('container-1-2')).to.be.false; + expect(getDynamicPlaceholderPattern('container-1-{*}').test('container-1-2')).to.be.true; + expect(getDynamicPlaceholderPattern('container-1-{*}').test('container-1-2-3')).to.be.false; +}); + after(() => { (global as any).window.close(); }); diff --git a/packages/sitecore-jss-react/src/components/Placeholder.tsx b/packages/sitecore-jss-react/src/components/Placeholder.tsx index 8595a16470..75dd82bfd5 100644 --- a/packages/sitecore-jss-react/src/components/Placeholder.tsx +++ b/packages/sitecore-jss-react/src/components/Placeholder.tsx @@ -88,7 +88,8 @@ class PlaceholderComponent extends PlaceholderCommon const placeholderData = PlaceholderCommon.getPlaceholderDataFromRenderingData( renderingData, - this.props.name + this.props.name, + this.props.sitecoreContext?.editMode ); this.isEmpty = placeholderData.every((rendering: ComponentRendering | HtmlElementRendering) => diff --git a/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx b/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx index 2da6b0a835..749c334450 100644 --- a/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx +++ b/packages/sitecore-jss-react/src/components/PlaceholderCommon.tsx @@ -30,6 +30,22 @@ export type ComponentProps = { rendering: ComponentRendering; }; +/** + * Returns a regular expression pattern for a dynamic placeholder name. + * @param {string} placeholder Placeholder name with a dynamic segment (e.g. 'main-{*}') + * @returns Regular expression pattern for the dynamic segment + */ +export const getDynamicPlaceholderPattern = (placeholder: string) => { + return new RegExp(`^${placeholder.replace(/\{\*\}+/i, '\\d+')}$`); +}; + +/** + * Checks if the placeholder name is dynamic. + * @param {string} placeholder Placeholder name + * @returns True if the placeholder name is dynamic + */ +export const isDynamicPlaceholder = (placeholder: string) => placeholder.indexOf('{*}') !== -1; + export interface PlaceholderProps { [key: string]: unknown; /** Name of the placeholder to render. */ @@ -131,34 +147,43 @@ export class PlaceholderCommon extends React.Compone static getPlaceholderDataFromRenderingData( rendering: ComponentRendering | RouteData, - name: string + name: string, + editMode?: EditMode ) { let result; - /** [SXA] it needs for deleting dynamics placeholder when we set him number(props.name) of container. - from backend side we get common name of placeholder is called 'nameOfContainer-{*}' where '{*}' marker for replacing **/ + let phName = name.slice(); + + /** + * [Chromes Mode]: [SXA] it needs for deleting dynamics placeholder when we set him number(props.name) of container. + * from backend side we get common name of placeholder is called 'nameOfContainer-{*}' where '{*}' marker for replacing. + * [Metadata Mode]: We need to keep the raw placeholder name. e.g 'nameOfContainer-{*}' instead of 'nameOfContainer-1' + */ if (rendering?.placeholders) { Object.keys(rendering.placeholders).forEach((placeholder) => { - const patternPlaceholder = - placeholder.indexOf('{*}') !== -1 - ? new RegExp(`^${placeholder.replace(/\{\*\}+/i, '\\d+')}$`) - : null; - - if (patternPlaceholder && patternPlaceholder.test(name)) { - rendering.placeholders[name] = rendering.placeholders[placeholder]; - delete rendering.placeholders[placeholder]; + const patternPlaceholder = isDynamicPlaceholder(placeholder) + ? getDynamicPlaceholderPattern(placeholder) + : null; + + if (patternPlaceholder && patternPlaceholder.test(phName)) { + if (editMode === EditMode.Metadata) { + phName = placeholder; + } else { + rendering.placeholders[phName] = rendering.placeholders[placeholder]; + delete rendering.placeholders[placeholder]; + } } }); } if (rendering && rendering.placeholders && Object.keys(rendering.placeholders).length > 0) { - result = rendering.placeholders[name]; + result = rendering.placeholders[phName]; } else { result = null; } if (!result) { console.warn( - `Placeholder '${name}' was not found in the current rendering data`, + `Placeholder '${phName}' was not found in the current rendering data`, JSON.stringify(rendering, null, 2) ); @@ -294,7 +319,7 @@ export class PlaceholderCommon extends React.Compone // if editMode is equal to 'metadata' then emit shallow chromes for hydration in Pages if (this.props.sitecoreContext?.editMode === EditMode.Metadata) { return ( - + {rendered} ); @@ -308,8 +333,8 @@ export class PlaceholderCommon extends React.Compone return [ {transformedComponents} , diff --git a/packages/sitecore-jss-react/src/components/PlaceholderMetadata.test.tsx b/packages/sitecore-jss-react/src/components/PlaceholderMetadata.test.tsx index 5280baff42..50393bd063 100644 --- a/packages/sitecore-jss-react/src/components/PlaceholderMetadata.test.tsx +++ b/packages/sitecore-jss-react/src/components/PlaceholderMetadata.test.tsx @@ -7,7 +7,11 @@ describe('PlaceholderMetadata', () => { it('renders rendering code blocks for metadataType rendering', () => { const children =
; - const wrapper = shallow({children}); + const wrapper = shallow( + + {children} + + ); expect(wrapper.html()).to.equal( [ @@ -21,7 +25,14 @@ describe('PlaceholderMetadata', () => { it('renders placeholder code blocks when metadataType is placeholder', () => { const children =
; const wrapper = shallow( - + {children} ); @@ -34,4 +45,52 @@ describe('PlaceholderMetadata', () => { ].join('') ); }); + + it('renders placeholder code blocks when metadataType is dynamic placeholder', () => { + const children =
; + const wrapper = shallow( + + {children} + + ); + + expect(wrapper.html()).to.equal( + [ + '', + '
', + '', + ].join('') + ); + }); + + it('renders placeholder code blocks when metadataType is double digit dynamic placeholder', () => { + const children =
; + const wrapper = shallow( + + {children} + + ); + + expect(wrapper.html()).to.equal( + [ + '', + '
', + '', + ].join('') + ); + }); }); diff --git a/packages/sitecore-jss-react/src/components/PlaceholderMetadata.tsx b/packages/sitecore-jss-react/src/components/PlaceholderMetadata.tsx index bb060ba8f6..e08ab23b18 100644 --- a/packages/sitecore-jss-react/src/components/PlaceholderMetadata.tsx +++ b/packages/sitecore-jss-react/src/components/PlaceholderMetadata.tsx @@ -1,10 +1,12 @@ import React, { ReactNode } from 'react'; +import { ComponentRendering } from '@sitecore-jss/sitecore-jss/layout'; +import { getDynamicPlaceholderPattern, isDynamicPlaceholder } from './PlaceholderCommon'; /** * Props containing the component data to render. */ export interface PlaceholderMetadataProps { - uid: string; + rendering: ComponentRendering; placeholderName?: string; children?: ReactNode; } @@ -23,13 +25,13 @@ export type CodeBlockAttributes = { * or as a rendering to properly render the surrounding code blocks. * * @param {object} props The properties passed to the component. - * @param {string} props.uid A unique identifier for the component instance. + * @param {ComponentRendering} props.rendering The rendering data. * @param {string} [props.placeholderName] The name of the placeholder. * @param {JSX.Element} props.children The child components or elements to be wrapped by the metadata code blocks. * @returns {JSX.Element} A React fragment containing open and close code blocks surrounding the children elements. */ export const PlaceholderMetadata = ({ - uid, + rendering, placeholderName, children, }: PlaceholderMetadataProps): JSX.Element => { @@ -48,9 +50,33 @@ export const PlaceholderMetadata = ({ }; if (kind === 'open') { - attributes.id = - chrometype === 'placeholder' && placeholderName ? `${placeholderName}_${id}` : id; + if (chrometype === 'placeholder' && placeholderName) { + let phId = ''; + + for (const placeholder of Object.keys(rendering.placeholders)) { + if (placeholderName === placeholder) { + phId = `${placeholderName}_${id}`; + break; + } + + // Check if the placeholder is a dynamic placeholder + if (isDynamicPlaceholder(placeholder)) { + const pattern = getDynamicPlaceholderPattern(placeholder); + + // Check if the placeholder matches the dynamic placeholder pattern + if (pattern.test(placeholderName)) { + phId = placeholder; + break; + } + } + } + + attributes.id = phId; + } else { + attributes.id = id; + } } + return attributes; }; @@ -62,5 +88,5 @@ export const PlaceholderMetadata = ({ ); - return <>{renderComponent(uid, placeholderName)}; + return <>{renderComponent(rendering.uid, placeholderName)}; };