Skip to content

Commit

Permalink
docs: add custom element page (#840)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdanilowicz committed May 6, 2024
1 parent 18d8528 commit 823aa9b
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/eighty-islands-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"docs": minor
---

Add custom CMS element page
5 changes: 5 additions & 0 deletions .changeset/flat-eggs-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopware-pwa/composables-next": minor
---

Return `componentNameToResolve` in resolveCmsComponent function
4 changes: 4 additions & 0 deletions apps/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export const sidebar = [
text: "CMS",
link: "/getting-started/cms/",
items: [
{
text: "Custom Elements",
link: "/getting-started/cms/custom-elements",
},
{ text: "Content Pages", link: "/getting-started/cms/content-pages" },
{
text: "Customize Components",
Expand Down
137 changes: 137 additions & 0 deletions apps/docs/src/getting-started/cms/custom-elements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
head:
- - meta
- name: og:title
content: Custom Elements (CMS)
- - meta
- name: og:description
content: "In this chapter you will learn how to add custom elements"
- - meta
- name: og:image
content: "https://frontends-og-image.vercel.app/Custom%20Elements.png?fontSize=120px"
nav:
position: 10
---

# Custom Elements (CMS)

:::warning
This tutorial is a continuation of example from the backend part. That can be found [here](https://developer.shopware.com/docs/guides/plugins/plugins/content/cms/add-cms-element.html)
:::

All custom CMS elements created in the backend require corresponding implementations in the frontend application.

The CMS package utilizes the [resolveComponent](https://vuejs.org/api/render-function#resolvecomponent) method from Vue to identify the component returned by the backend API.
Therefore, the only requirement is to globally register the component with the appropriate name.

## Registration

### Demo store

The demo store utilizes Nuxt 3, which by default registers all components globally. For optimal application structure, we recommend adding the components to the `/components/cms` directory.

### Vue apps

[Global registration](https://vuejs.org/guide/components/registration#global-registration) in Vue apps

```ts
import CmsBlockCustomBlock from "./components/cms/CmsElementDailymotion.vue";

app.component("CmsElementDailymotion", CmsBlockCustomBlock);
```

## Naming

The component is searched in the global component register by its name.

[Resolving component in CMS package](https://github.com/shopware/frontends/blob/main/packages/composables/src/index.ts#L74)

```js
const componentNameToResolve = pascalCase(`Cms-${type}-${componentName}`);
const resolvedComponent = resolveComponent(componentNameToResolve);
```

Component name must be the same as it was registered in the backed.

```ts{3}
Shopware.Service('cmsService').registerCmsElement({
...
name: 'dailymotion',
...
});
```

Lets create new component `components/cms/element/CmsElementDailymotion.vue`

```vue
// components/cms/element/CmsElementDailymotion.vue
<script setup lang="ts">
import type { CmsSlot } from "@shopware-pwa/types";
type CmsElementDailymotion = CmsSlot & {
type: "dailymotion" | typeof String;
slot: typeof String;
config: CmsElementDailymotionConfig;
translated: {
config: CmsElementDailymotionConfig;
};
};
type CmsElementDailymotionConfig = {
dailyUrl: {
value: string;
source: "static";
};
};
const props = defineProps<{
content: CmsElementDailymotion;
}>();
</script>
<template>
<div>
<h2>Element!</h2>
<div class="sw-cms-el-dailymotion">
<div class="sw-cms-el-dailymotion-iframe-wrapper">
<iframe
frameborder="0"
type="text/html"
width="100%"
height="100%"
:src="props.content.config.dailyUrl.value"
>
</iframe>
</div>
</div>
</div>
</template>
```

### Reading config

Component settings are passed via props. The declared `defaultConfig` can be accessed through the `props.content.config` property.

The following is an example of how to convert the backend registration config to a TypeScript type.

```ts{4-9}
Shopware.Service('cmsService').registerCmsElement({
...
name: 'dailymotion',
defaultConfig: {
dailyUrl: {
source: 'static',
value: ''
}
}
...
});
```

```ts
type CmsElementDailymotionConfig = {
dailyUrl: {
value: string;
source: "static";
};
};
```
12 changes: 8 additions & 4 deletions packages/cms-base/components/public/cms/CmsGenericBlock.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ const props = defineProps<{
}>();
const DynamicRender = () => {
const { resolvedComponent, componentName, isResolved } = resolveCmsComponent(
props.content,
);
const {
resolvedComponent,
componentName,
isResolved,
componentNameToResolve,
} = resolveCmsComponent(props.content);
if (resolvedComponent) {
if (!isResolved)
Expand Down Expand Up @@ -50,7 +53,8 @@ const DynamicRender = () => {
}),
);
}
return h("div", {}, "Loading...");
console.error("Component not resolved: " + componentNameToResolve);
return h("div", {}, "");
};
</script>

Expand Down
12 changes: 8 additions & 4 deletions packages/cms-base/components/public/cms/CmsGenericElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ const props = defineProps<{
}>();
const DynamicRender = () => {
const { resolvedComponent, componentName, isResolved } = resolveCmsComponent(
props.content,
);
const {
resolvedComponent,
componentName,
isResolved,
componentNameToResolve,
} = resolveCmsComponent(props.content);
if (resolvedComponent) {
if (!isResolved)
return h("div", {}, "Problem resolving component: " + componentName);
Expand All @@ -25,7 +28,8 @@ const DynamicRender = () => {
class: cssClasses,
});
}
return h("div", {}, "Loading...");
console.error("Component not resolved: " + componentNameToResolve);
return h("div", {}, "");
};
</script>

Expand Down
2 changes: 2 additions & 0 deletions packages/composables/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,15 @@ export function resolveCmsComponent(content: CmsSection | CmsBlock | CmsSlot) {

return {
componentName,
componentNameToResolve,
isResolved: resolvedComponent !== componentName,
resolvedComponent:
typeof resolvedComponent !== "string" ? resolvedComponent : undefined,
};
} catch (e) {
return {
componentName,
componentNameToResolve,
resolvedComponent: undefined,
resolved: false,
error: (e as Error).message,
Expand Down

0 comments on commit 823aa9b

Please sign in to comment.