Usage
Alokai integration for SmartEdit ships with a framework-agnostic Typescript SDK. It allows you to:
- fetch your pages from the SAP Commerce Cloud Pages API
Fetching pages
The getPageByIdAndUser()
method allows you to fetch the page data and CMS content slots using the pageLabel
/pageId
or the code
parameter
import { getSdk } from '@/sdk/sdk.server';
const sdk = getSdk();
const page = await sdk.smartedit.getPageByIdAndUser({
pageLabel: 'homepage',
});
getPageByIdAndUser
doesn't return the nested components of the page. You can fetch them using the getComponentsByIdsAndUser()
method
import { getSdk } from '@/sdk/sdk.server';
const sdk = getSdk();
const page = await sdk.smartedit.getComponentsByIdsAndUser({
componentIds: ['Component1', 'Component2'],
});
Initializing Live Preview
Use the initLivePreview()
method to allow SmartEdit to edit your application pages in real time by:
- injecting the webApplicationInjector script into your application's head,
- adding the SmartEdit classes to the
body
element as specified in the SmartEdit HTML markup contract, - handling component updates.
To allow the method to work correctly:
- create a /cx-preview route which SmartEdit can load initially to get redirected to a proper route in your app,
- fulfill the SmartEdit HTML markup contract by adding SmartEdit properties to slots and components,
- call the
initLivePreview()
method client-side and pass the raw, non-unified page object to it, - include a copy of the
webApplicationInjector
script in your project,
We suggest keeping the webApplicationInjector
script it in the /public
directory of your Next/Nuxt project. If you want to keep it somewhere else, provide the correct path to the file via the scriptSrc
prop,
Creating the preview route
Whenever you open up Live Preview window for a page in SmartEdit, it will first try to load your application's /cx-preview route. Make sure it does exist and handle redirect to a desired Storefront route there.
import { prepareConfig } from '@vue-storefront/sdk';
import type { SearchParams } from 'nuqs/server';
import { redirect } from '@/config/navigation';
import { getSdk } from '@/sdk/sdk.server';
/**
* CX Preview Page Props
*/
interface CxPreviewPageProps {
/**
* Raw parameters passed by Next.js to the top-level page component.
*/
params: {
/**
* The locale of the page.
*/
locale: string;
/**
* The slug of the page.
*/
slug?: string[];
};
/**
* Search parameters.
*/
searchParams: SearchParams;
}
/**
* Change the path to your example product path.
*
* Path is used in case when you want to create a shared CMS content for all products.
* In this case, as the `/product` path doesn't exist, we need to redirect to one of the product page,
* to be able to preview the CMS content in SmartEdit.
*/
const EXAMPLE_PRODUCT_PATH = '/product/45574-snowboard-ski-tool-toko-plexiklinge-3-mm-flexibel/45574';
export default async function CxPreviewPage({ searchParams }: CxPreviewPageProps) {
const sdk = getSdk();
const { cmsTicketId: cmsTicketIdParam } = searchParams;
if (!cmsTicketIdParam) {
redirect({ pathname: '/' });
return null;
}
const cmsTicketId = cmsTicketIdParam.toString();
const requestConfig = prepareConfig({ headers: { 'x-cms-ticket-id': cmsTicketId } });
const { data: page } = await sdk.smartedit.getPageWithUser({}, requestConfig);
let redirectPath = (page as any).label;
if (redirectPath === '/product') {
redirectPath = EXAMPLE_PRODUCT_PATH;
}
redirect({
pathname: `/${redirectPath}`,
query: { cmsTicketId },
});
return null;
}
Fulfilling HTML markup contract
When the /cx-preview
route performs a redirect, the destination route needs to be handled by a page component which fetches and renders SmartEdit data (including properties required by the HTML markup contract).
import { prepareConfig } from '@vue-storefront/sdk';
import { SearchParams } from 'nuqs/parsers';
import { getSdk } from '@/sdk/sdk.server';
import LivePreview from '@/components/live-preview';
/**
* CMS Dynamic Page props.
*/
interface DynamicPageProps {
/**
* Raw parameters passed by Next.js to the top-level page component.
*/
params: {
/**
* The locale of the page.
*/
locale: string;
/**
* The slug of the page.
*/
slug?: string[];
};
/**
* The search parameters of the page.
*/
searchParams: SearchParams;
}
export default async function DynamicPage(props: DynamicPageProps) {
const sdk = getSdk();
const { searchParams } = props;
const { cmsTicketId: cmsTicketIdParam } = searchParams;
const cmsTicketId = cmsTicketIdParam?.toString();
const requestConfig = prepareConfig({});
if (cmsTicketId) {
Object.assign(requestConfig, { headers: { 'x-cms-ticket-id': cmsTicketId } });
}
const { data: page } = await sdk.smartedit.getPageWithUser({ pageLabelOrId: '/your-page-path' }, requestConfig);
return (
<>
{/* The LivePreview component will be added in the next step */}
<LivePreview page={page} />
{
page.contentSlots?.contentSlot?.map((contentSlot) => (
<>
<div {...getSmartEditProps(contentSlot)}>
{
contentSlot.components?.component?.map((component) => (
<div {...getSmartEditProps(component)} key={component.uid}>
<pre>{JSON.stringify(component, null, 2)}</pre>
</div>
))
}
</div>
</>
))
}
</>
)
}
function getSmartEditProps(item: Record<string, any>) {
const { properties = {} } = item;
const { smartedit } = properties;
if (!smartedit) {
return {};
}
const { catalogVersionUuid, classes, componentId, componentType, componentUuid } = smartedit;
return {
className: classes,
'data-smartedit-catalog-version-uuid': catalogVersionUuid,
'data-smartedit-component-id': componentId,
'data-smartedit-component-type': componentType,
'data-smartedit-component-uuid': componentUuid,
};
}
Calling initLivePreview()
method
Connection with SmartEdit's Live Preview service is a purely client-side operation. Therefore, make sure you're calling the initLivePreview()
method via appropriate lifecycle methods / hooks. Executed on the server-side, it will not throw but will not yield the desired result either.
Create a client component which calls initLivePreview()
. Make sure it is imported and rendered in the page component from the previous step.
'use client';
import { useEffect } from 'react';
import { useSdk } from '@/sdk/alokai-context';
interface LivePreviewProps {
/**
* A SmartEdit page object.
*/
page: Record<string, any>;
}
/**
* Change these to match your SAP environment
*/
const ALLOWED_ORIGINS = ['backoffice.c1jvi8hu9a-vsfspzooa1-d1-public.model-t.cc.commerce.ondemand.com:443'];
export default function LivePreview(props: LivePreviewProps) {
const { page } = props;
const sdk = useSdk();
useEffect(() => {
sdk.smartedit.utils.initLivePreview({
allowedOrigins: ALLOWED_ORIGINS,
page,
});
}, []);
return null;
}
If you're experiencing CORS-related issues after calling the initLivePreview()
method, you're probably missing a proper ALLOWED_ORIGINS
configuration. Read the official SmartEdit documentation which explains the issue in more detail.