<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: vuejstest</title>
    <description>The latest articles on DEV Community by vuejstest (@vuejstest_9d7a4f2fbe7631a).</description>
    <link>https://dev.to/vuejstest_9d7a4f2fbe7631a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2993061%2F5cb96ffc-ff42-49aa-ade7-687f8c7f1b87.png</url>
      <title>DEV Community: vuejstest</title>
      <link>https://dev.to/vuejstest_9d7a4f2fbe7631a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vuejstest_9d7a4f2fbe7631a"/>
    <language>en</language>
    <item>
      <title>custom row task step by step</title>
      <dc:creator>vuejstest</dc:creator>
      <pubDate>Wed, 28 May 2025 18:59:59 +0000</pubDate>
      <link>https://dev.to/vuejstest_9d7a4f2fbe7631a/custom-row-task-step-by-step-4h9j</link>
      <guid>https://dev.to/vuejstest_9d7a4f2fbe7631a/custom-row-task-step-by-step-4h9j</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/dataresolver
CustomImageCmsElementResolver

&amp;lt;?php
declare(strict_types=1);

namespace CmsTheme2\DataResolver;

use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
use Shopware\Core\Content\Cms\DataResolver\Element\AbstractCmsElementResolver;
use Shopware\Core\Content\Cms\DataResolver\Element\ElementDataCollection;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
use Shopware\Core\Content\Cms\DataResolver\CriteriaCollection;
use Shopware\Core\Content\Media\MediaDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Content\Cms\DataResolver\FieldConfig;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\EntityResolverContext;
use Shopware\Core\Content\Cms\SalesChannel\Struct\ImageStruct;
use Shopware\Core\Content\Media\MediaEntity;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Shopware\Core\Framework\Uuid\Uuid;

class CustomImageCmsElementResolver extends AbstractCmsElementResolver
{
    public function getType(): string
    {
        return 'custom-image';
    }
    public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
    {
        $config = $slot-&amp;gt;getFieldConfig();
        $imageConfig = $config-&amp;gt;get('media');
        $backgroundImageConfig = $config-&amp;gt;get('mobileMedia');

        $ids = [];
        if (!$imageConfig || $imageConfig-&amp;gt;isMapped() || $imageConfig-&amp;gt;getValue() === null) {
            //
        } else {
            array_push($ids, $imageConfig-&amp;gt;getValue());
        }

        if (!$backgroundImageConfig || $backgroundImageConfig-&amp;gt;isMapped() || $backgroundImageConfig-&amp;gt;getValue() === null) {
            //
        } else {
            array_push($ids, $backgroundImageConfig-&amp;gt;getValue());
        }
        if (count($ids) === 0) {
            return null;
        }
        $criteria = new Criteria($ids);
        $criteriaCollection = new CriteriaCollection();
        $criteriaCollection-&amp;gt;add('media_' . $slot-&amp;gt;getUniqueIdentifier(), MediaDefinition::class, $criteria);
        return $criteriaCollection;
    }
    public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
    {
        $config = $slot-&amp;gt;getFieldConfig();
        $data = new ArrayEntity();
        $data-&amp;gt;setUniqueIdentifier(Uuid::randomHex());
        $slot-&amp;gt;setData($data);
        $image = new ImageStruct();
        $backgroundImage = new ImageStruct();
        $imageConfig = $config-&amp;gt;get('media');
        $backgroundImageConfig = $config-&amp;gt;get('mobileMedia');
        if ($imageConfig &amp;amp;&amp;amp; $imageConfig-&amp;gt;getValue()) {
            $this-&amp;gt;addMediaEntity($slot, $image, $result, $imageConfig, $resolverContext);
        }
        $data-&amp;gt;set('media', $image);

        if ($backgroundImageConfig &amp;amp;&amp;amp; $backgroundImageConfig-&amp;gt;getValue()) {
            $this-&amp;gt;addMediaEntity($slot, $backgroundImage, $result, $backgroundImageConfig, $resolverContext);
        }
        $data-&amp;gt;set('mobileMedia', $backgroundImage);
    }

    private function addMediaEntity(
        CmsSlotEntity         $slot,
        ImageStruct           $image,
        ElementDataCollection $result,
        FieldConfig           $config,
        ResolverContext       $resolverContext
    ): void
    {
        if ($config-&amp;gt;isMapped() &amp;amp;&amp;amp; $resolverContext instanceof EntityResolverContext) {
            /** @var MediaEntity|null $media */
            $media = $this-&amp;gt;resolveEntityValue($resolverContext-&amp;gt;getEntity(), $config-&amp;gt;getValue());

            if ($media !== null) {
                $image-&amp;gt;setMediaId($media-&amp;gt;getUniqueIdentifier());
                $image-&amp;gt;setMedia($media);
            }
        }

        if ($config-&amp;gt;isStatic()) {
            $image-&amp;gt;setMediaId($config-&amp;gt;getValue());
            $searchResult = $result-&amp;gt;get('media_' . $slot-&amp;gt;getUniqueIdentifier());
            if (!$searchResult) {
                return;
            }

            /** @var MediaEntity|null $media */
            $media = $searchResult-&amp;gt;get($config-&amp;gt;getValue());

            if (!$media) {
                return;
            }
            $image-&amp;gt;setMedia($media);
        }
    }
}

res
app
 admin/src
  module/sw-cms
   blocks/text-image
    custom-image-text-gallery
      component
        index

import template from './sw-cms-block-custom-image-text-gallery.html.twig';
import './sw-cms-block-custom-image-text-gallery.scss';

Shopware.Component.register('sw-cms-block-custom-image-text-gallery', template, {
    template,

    compatConfig: Shopware.compatConfig,
});
twig

{% block sw_cms_block_custom_image_text_gallery %}
    &amp;lt;div class="sw-cms-block-image-text-gallery"&amp;gt;
        &amp;lt;div class="sw-cms-block-image-text-gallery__left"&amp;gt;
            &amp;lt;slot name="left-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="left-text"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="left-button"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class="sw-cms-block-image-text-gallery__center-left"&amp;gt;
            &amp;lt;slot name="center-left-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-left-text"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-left-button"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class="sw-cms-block-image-text-gallery__center-right"&amp;gt;
            &amp;lt;slot name="center-right-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-right-text"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-right-button"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class="sw-cms-block-image-text-gallery__right"&amp;gt;
            &amp;lt;slot name="right-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="right-text"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="right-button"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}
scss

.sw-cms-block-image-text-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(195px, 1fr));
  align-content: stretch;
  align-items: start;
  grid-gap: 40px;

  .sw-cms-el-text {
    padding: 20px;
  }

  .sw-cms-el-image.is--cover {
    min-height: 100px;
  }

  .sw-cms-block-image-text-gallery__left,
  .sw-cms-block-image-text-gallery__center-left,
  .sw-cms-block-image-text-gallery__center-right,
  .sw-cms-block-image-text-gallery__right {
    box-shadow: 0 0 4px 0 rgba(0, 0, 0, 20%);
  }
}
.sw-cms-block-image-text-gallery-shop-button {
  display: grid;
  align-items: center;
}

      preview
index

import template from './sw-cms-preview-custom-image-text-gallery.html.twig';
import './sw-cms-preview-custom-image-text-gallery.scss';

Shopware.Component.register("sw-cms-preview-custom-image-text-gallery", {
    template,
    compatConfig: Shopware.compatConfig,
    computed: {
        assetFilter() {
            return Shopware.Filter.getByName('asset');
        },
    },
});

twig

{% block sw_cms_block_custom_image_text_gallery_preview %}
    &amp;lt;div class="sw-cms-preview-custom-image-text-gallery"&amp;gt;
        &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__left"&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__image"&amp;gt;
                &amp;lt;img :src="assetFilter('/administration/static/img/cms/preview_camera_small.jpg')" alt=""&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__text"&amp;gt;
                &amp;lt;h2&amp;gt;block 1&amp;lt;/h2&amp;gt;
                &amp;lt;p&amp;gt;this is block1&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-slot sw-cms-slot-left-button"&amp;gt;
                &amp;lt;div class="sw-cms-el-buy-button"&amp;gt;
                    &amp;lt;div class="sw-cms-el-buy-button__actions"&amp;gt;
                        &amp;lt;a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small"&amp;gt;Shop&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="sw-cms-slot__preview-overlay"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__center-left"&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__image"&amp;gt;
                &amp;lt;img :src="assetFilter('/administration/static/img/cms/preview_plant_small.jpg')" alt=""&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__text"&amp;gt;
                &amp;lt;h2&amp;gt;block 2&amp;lt;/h2&amp;gt;
                &amp;lt;p&amp;gt;this is block2&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-slot sw-cms-slot-center-left-button"&amp;gt;
                &amp;lt;div class="sw-cms-el-buy-button"&amp;gt;
                    &amp;lt;div class="sw-cms-el-buy-button__actions"&amp;gt;
                        &amp;lt;a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small"&amp;gt;Shop&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="sw-cms-slot__preview-overlay"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__center-right"&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__image"&amp;gt;
                &amp;lt;img :src="assetFilter('/administration/static/img/cms/preview_glasses_small.jpg')" alt=""&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__text"&amp;gt;
                &amp;lt;h2&amp;gt;block 3&amp;lt;/h2&amp;gt;
                &amp;lt;p&amp;gt;this is block3&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-slot sw-cms-slot-center-right-button"&amp;gt;
                &amp;lt;div class="sw-cms-el-buy-button"&amp;gt;
                    &amp;lt;div class="sw-cms-el-buy-button__actions"&amp;gt;
                        &amp;lt;a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small"&amp;gt;Shop&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="sw-cms-slot__preview-overlay"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__right"&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__image"&amp;gt;
                &amp;lt;img :src="assetFilter('/administration/static/img/cms/preview_glasses_small.jpg')" alt=""&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__text"&amp;gt;
                &amp;lt;h2&amp;gt;block 4&amp;lt;/h2&amp;gt;
                &amp;lt;p&amp;gt;this is block4&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-slot sw-cms-slot-right-button"&amp;gt;
                &amp;lt;div class="sw-cms-el-buy-button"&amp;gt;
                    &amp;lt;div class="sw-cms-el-buy-button__actions"&amp;gt;
                        &amp;lt;a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small"&amp;gt;Shop&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="sw-cms-slot__preview-overlay"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}

scss

.sw-cms-preview-custom-image-text-gallery {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-gap: 10px;
  padding: 15px;

  .sw-cms-preview-custom-image-text-gallery__left,
  .sw-cms-preview-custom-image-text-gallery__center-left,
  .sw-cms-preview-custom-image-text-gallery__center-right,
  .sw-cms-preview-custom-image-text-gallery__right {
    box-shadow: 0 0 3px 0 rgba(0, 0, 0, 20%);
  }

  .sw-cms-preview-custom-image-text-gallery__image {
    height: 60px;
  }

  .sw-cms-preview-custom-image-text-gallery__text {
    padding: 8px;
    text-align: center;
  }

  img {
    display: block;
    object-fit: cover;
    width: 100%;
    height: 100%;
  }
}
.sw-cms-block-image-text-gallery-shop-button {
  display: grid;
  align-items: center;
  .sw-button {
    height: 30px;
    width: auto;
    padding: 25px;
  }
  .sw-cms-el-buy-button__buy-action{
    font-size: smaller;
  }
}

      index.js

import CMS from '../../../constant/sw-cms.constant';
import "./preview";
import "./component";

Shopware.Service("cmsService").registerCmsBlock({
    name: "custom-image-text-gallery",
    label: "Custom Image Text Gallery Block",
    category: "text-image",
    component: "sw-cms-block-custom-image-text-gallery",
    previewComponent: "sw-cms-preview-custom-image-text-gallery",
    defaultConfig: {
        marginBottom: "20px",
        marginTop: "20px",
        marginLeft: "20px",
        marginRight: "20px",
        sizingMode: "boxed",
    },
    slots: {
        "left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block 1&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block1&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_glasses_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block 2&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block2&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "center-left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_plant_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block3&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block3&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "center-right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block 4&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block4&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },
    },
});

    image-text-gallery/custom-image-text-gallery
index.js

import CMS from '../../../constant/sw-cms.constant';
import "./preview";
import "./component";

Shopware.Service("cmsService").registerCmsBlock({
    name: "custom-image-text-gallery",
    label: "Custom Image Text Gallery Block",
    category: "text-image",
    component: "sw-cms-block-custom-image-text-gallery",
    previewComponent: "sw-cms-preview-custom-image-text-gallery",
    defaultConfig: {
        marginBottom: "20px",
        marginTop: "20px",
        marginLeft: "20px",
        marginRight: "20px",
        sizingMode: "boxed",
    },
    slots: {
        "left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block 1&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block1&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_glasses_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block 2&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block2&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "center-left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_plant_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block 3&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block3&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "center-right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block 4&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block4&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },
    },
});

    text-image-text
component
index.js

import template from './sw-cms-block-text-image-text.html.twig';
import './sw-cms-block-text-image-text.scss';

Shopware.Component.register('sw-cms-block-text-image-text', {
    template,

    compatConfig: Shopware.compatConfig,
});
twig

{% block sw_cms_block_text_image_text %}
    &amp;lt;div class="sw-cms-block-text-image-text"&amp;gt;
        &amp;lt;div class="sw-cms-block-text-image-text__text-left"&amp;gt;
            &amp;lt;slot name="left"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class="sw-cms-block-text-image-text__image"&amp;gt;
            &amp;lt;slot name="center"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class="sw-cms-block-text-image-text__text-right"&amp;gt;
            &amp;lt;slot name="right"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}
scss

.sw-cms-block-text-image-text {
  display: flex;
  flex-wrap: wrap;

  .sw-cms-block-text-image-text__text-left,
  .sw-cms-block-text-image-text__text-right {
    flex-grow: 2;
    flex-shrink: 0;
    flex-basis: 300px;
  }
  .sw-cms-block-text-image-text__image {
    flex-grow: 1;
    flex-shrink: 0;
    flex-basis: 240px;
  }
  .sw-cms-el-image.is--cover {
    min-height: 340px;
  }
  .sw-cms-el-text {
    padding: 30px;
  }
}

preview
inddex.js

import template from './sw-cms-preview-text-image-text.html.twig';
import './sw-cms-preview-text-image-text.scss';

Shopware.Component.register('sw-cms-preview-text-image-text', {
    template,

    compatConfig: Shopware.compatConfig,

    computed: {
        assetFilter() {
            return Shopware.Filter.getByName('asset');
        },
    },
});

twig

{% block sw_cms_block_text_image_text_preview %}
    &amp;lt;div class="sw-cms-preview-text-image-text"&amp;gt;
        &amp;lt;div class="sw-cms-preview-text-image-text__text-left"&amp;gt;
            &amp;lt;h2&amp;gt;Hello&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;world&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;img :src="assetFilter('/administration/static/img/cms/preview_glasses_small.jpg')"
                alt=""&amp;gt;
        &amp;lt;div class="sw-cms-preview-text-image-text__text-right"&amp;gt;
            &amp;lt;h2&amp;gt;Hello&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;world&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}

scss

.sw-cms-preview-text-image-text {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  padding: 15px;

  .sw-cms-preview-text-image-text__text-left,
  .sw-cms-preview-text-image-text__text-right {
    padding: 15px;
    text-align: center;
    height: 120px;
  }

  img {
    display: block;
    object-fit: cover;
    width: 100%;
    height: 100%;
  }
}

index.js

import CMS from "../../../constant/sw-cms.constant";
import "./preview";
import "./component";

Shopware.Service("cmsService").registerCmsBlock({
    name: "text-image-text",
    label: "Custom text-image-text",
    category: "text-image",
    component: "sw-cms-block-text-image-text",
    previewComponent: "sw-cms-preview-text-image-text",
    defaultConfig: {
        marginBottom: "20px",
        marginTop: "20px",
        marginLeft: "20px",
        marginRight: "20px",
        sizingMode: "boxed",
    },
    slots: {
        left: {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
                        &amp;lt;h2 style="text-align: center;"&amp;gt;Hello&amp;lt;/h2&amp;gt;
                        &amp;lt;p style="text-align: center;"&amp;gt;world&amp;lt;/p&amp;gt;
                        `.trim(),
                    },
                },
            },
        },
        center: {
            type: "custom-image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: CMS.MEDIA.previewGlasses,
                        source: "default",
                    },
                },
            },
        },
        right: {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
                        &amp;lt;h2 style="text-align: center;"&amp;gt;hello&amp;lt;/h2&amp;gt;
                        &amp;lt;p style="text-align: center;"&amp;gt;world&amp;lt;/p&amp;gt;
                        `.trim(),
                    },
                },
            },
        },
    },
});

   constant
sw-cms.constant.ts

const uniqueSlotsKebab = [
    'buy-box',
    'product-description-reviews',
    'cross-selling',
];

/**
 * @sw-package discovery
 */
// eslint-disable-next-line sw-deprecation-rules/private-feature-declarations
export default Object.freeze({
    REQUIRED_FIELD_ERROR_CODE: 'c1051bb4-d103-4f74-8988-acbcafc7fdc3',
    PAGE_TYPES: {
        SHOP: 'page',
        LANDING: 'landingpage',
        LISTING: 'product_list',
        PRODUCT_DETAIL: 'product_detail',
    },
    TYPE_MAPPING_ENTITIES: {
        product_detail: {
            entity: 'product',
            mode: 'single',
        },
        product_list: {
            entity: 'category',
            mode: 'single',
        },
    },
    UNIQUE_SLOTS: uniqueSlotsKebab.map((slotName) =&amp;gt; slotName.replace(/-./g, (char) =&amp;gt; char.toUpperCase()[1])),
    UNIQUE_SLOTS_KEBAB: uniqueSlotsKebab,
    SLOT_POSITIONS: {
        left: 0,
        'left-image': 100,
        'left-top': 200,
        'left-text': 300,
        'left-bottom': 400,
        'center-left': 1000,
        center: 1100,
        'center-image': 1200,
        'center-top': 1300,
        'center-text': 1400,
        'center-bottom': 1500,
        'center-right': 1600,
        right: 2000,
        'right-image': 2100,
        'right-top': 2200,
        'right-text': 2300,
        'right-bottom': 2400,
        content: 3000,
        image: 3100,
        video: 3200,
        imageSlider: 3300,
        default: 5000,
    },
    MEDIA: {
        previewCamera: 'bundles/administration/static/img/cms/preview_camera_large.jpg',
        previewMountain: 'bundles/administration/static/img/cms/preview_mountain_large.jpg',
        previewPlant: 'bundles/administration/static/img/cms/preview_plant_large.jpg',
        previewGlasses: 'bundles/administration/static/img/cms/preview_glasses_large.jpg',
        SMALL:{
            previewCamera: 'framework/assets/default/cms/preview_camera_small.jpg',
            previewMountain: 'framework/assets/default/cms/preview_mountain_small.jpg',
            previewPlant: 'framework/assets/default/cms/preview_plant_small.jpg',
            previewGlasses: 'framework/assets/default/cms/preview_glasses_small.jpg',
        }
    },
});

   elements
custom-image
componeny
index.js

import CMS from '../../../constant/sw-cms.constant';
import template from './sw-cms-el-custom-image.html.twig';
import './sw-cms-el-custom-image.scss';

const { Component, Mixin, Filter } = Shopware;

Component.register('sw-cms-el-custom-image', {
    template,

    mixins: [
        Mixin.getByName('cms-element'),
    ],

    computed: {
        mediaUrl() {
            const fallBackImageFileName = CMS.MEDIA.previewPlant.slice(CMS.MEDIA.previewPlant.lastIndexOf('/') + 1);
            const staticFallBackImage = this.assetFilter(`administration/static/img/cms/${fallBackImageFileName}`);
            const elemData = this.element?.data?.media || {};
            const elemConfig = this.element?.config?.media || {};

            if (elemConfig.source === 'mapped') {
                const demoMedia = this.getDemoValue(elemConfig.value);
                if (demoMedia?.url) {
                    return demoMedia.url;
                }
                return staticFallBackImage;
            }

            if (elemConfig.source === 'default' &amp;amp;&amp;amp; typeof elemConfig.value === 'string') {
                const fileName = elemConfig.value.slice(elemConfig.value.lastIndexOf('/') + 1);
                return this.assetFilter(`/administration/static/img/cms/${fileName}`);
            }

            if (elemData?.id) {
                return this.element.data.media.url;
            }

            if (elemData?.url) {
                return elemData.url;
            }

            return staticFallBackImage;
        },

        assetFilter() {
            return Filter.getByName('asset');
        },

        mediaConfigValue() {
            return this.element?.config?.media?.value;
        },
    },

    watch: {
        'cmsPageState.currentDemoEntity': {
            handler() {
                this.updateDemoValue(this.mediaConfigValue);
            },
        },

        mediaConfigValue(value) {
            this.updateDemoValue(value);
        },
    },

    created() {
        this.createdComponent();
    },

    methods: {
        createdComponent() {
            this.initElementConfig('custom-image');
            this.initElementData('custom-image');
        },
        updateDemoValue(value) {
            const mediaId = this.element?.data?.media?.id;
            const isSourceStatic = this.element?.config?.media?.source === 'static';

            if (isSourceStatic &amp;amp;&amp;amp; mediaId &amp;amp;&amp;amp; value !== mediaId) {
                this.element.config.media.value = mediaId;
            }
        },
    },
});

twig

{% block sw_cms_element_custom_image %}
    &amp;lt;div class="sw-cms-el-custom-image"&amp;gt;
        {% block sw_cms_element_custom_image_content %}
            &amp;lt;img :src="mediaUrl" alt=""&amp;gt;
        {% endblock %}
    &amp;lt;/div&amp;gt;
{% endblock %}

scss

.sw-cms-el-custom-image {
    display: flex;
    max-width: 100%;

    img {
        display: block;
        max-width: 100%;
    }

    &amp;amp;.is--cover {
        height: 100%;
        width: 100%;
        position: relative;
        margin: auto;
        border-radius: 50%;
        background-clip: padding-box;
        overflow: hidden;

        img {
            object-fit: cover;
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
        }
    }

    &amp;amp;.is--stretch {
        img {
            width: 100%;
        }
    }
}

config
index.js
import template from './sw-cms-el-config-custom-image.html.twig';
import './sw-cms-el-config-custom-image.scss';

const { Component, Mixin } = Shopware;

Component.register('sw-cms-el-config-custom-image', {
    template,

    compatConfig: Shopware.compatConfig,

    inject: ['repositoryFactory'],

    emits: ['element-update'],

    mixins: [
        Mixin.getByName('cms-element'),
    ],

    data() {
        return {
            mediaModalIsOpen: false,
            initialFolderId: null,

        };
    },
    computed: {
        mediaRepository() {
            return this.repositoryFactory.create('media');

        },
        uploadTag() {
            return `cms-element-media-config-${this.element.id}`;
        },
        previewSource() {
            if (this.element?.data?.media?.id) {
                return this.element.data.media;
            }
            return this.element.config.media.value;
        },
    },
    created() {
        this.createdComponent();
    },

    methods: {
        createdComponent() {
            this.initElementConfig('image');
        },

        async onImageUpload({ targetId }) {
            const mediaEntity = await this.mediaRepository.get(targetId);

            this.element.config.media.value = mediaEntity.id;
            this.element.config.media.source = 'static';

            this.updateElementData(mediaEntity);

            this.$emit('element-update', this.element);
        },

        onImageRemove() {
            this.element.config.media.value = null;

            this.updateElementData();

            this.$emit('element-update', this.element);
        },

        onCloseModal() {
            this.mediaModalIsOpen = false;
        },

        onSelectionChanges(mediaEntity) {
            const media = mediaEntity[0];
            this.element.config.media.value = media.id;
            this.element.config.media.source = 'static';

            this.updateElementData(media);

            this.$emit('element-update', this.element);
        },

        updateElementData(media = null) {
            const mediaId = media === null ? null : media.id;
            if (!this.element.data) {
                if (this.isCompatEnabled('INSTANCE_SET')) {
                    this.$set(this.element, 'data', { mediaId, media });
                } else {
                    this.element.data = { mediaId, media };
                }
                return;
            }
            if (this.isCompatEnabled('INSTANCE_SET')) {
                this.$set(this.element.data, 'mediaId', mediaId);
                this.$set(this.element.data, 'media', media);
            } else {
                this.element.data.mediaId = mediaId;
                this.element.data.media = media;
            }
        },

        onOpenMediaModal() {
            this.mediaModalIsOpen = true;
        },

        onChangeMinHeight(value) {
            this.element.config.minHeight.value = value === null ? '' : value;

            this.$emit('element-update', this.element);
        },

        onChangeDisplayMode() {
            this.$emit('element-update', this.element);
        },

        onChangeIsDecorative(value) {
            this.element.config.isDecorative.value = value;

            this.$emit('element-update', this.element);
        },
    },
});
twig


{% block sw_cms_element_image_config %}
    &amp;lt;div class="sw-cms-el-config-image"&amp;gt;
        {% block sw_cms_element_image_config_media_upload %}
            &amp;lt;sw-cms-mapping-field v-model:config="element.config.media" :label="$tc('sw-cms.elements.image.label')" value-types="entity" entity="media"&amp;gt;
                &amp;lt;sw-media-upload-v2
                        variant="regular"
                        :upload-tag="uploadTag"
                        :source="previewSource"
                        :allow-multi-select="false"
                        :default-folder="cmsPageState.pageEntityName"
                        :caption="$tc('sw-cms.elements.general.config.caption.mediaUpload')"
                        @media-upload-sidebar-open="onOpenMediaModal"
                        @media-upload-remove-image="onImageRemove"/&amp;gt;

                &amp;lt;template #preview="{ demoValue }"&amp;gt;
                    &amp;lt;div class="sw-cms-el-config-image__mapping-preview"&amp;gt;
                        &amp;lt;img v-if="demoValue.url" :src="demoValue.url" alt=""&amp;gt;
                        &amp;lt;sw-alert v-else class="sw-cms-el-config-image__preview-info" variant="info"&amp;gt;
                            {{ $tc('sw-cms.detail.label.mappingEmptyPreview') }}
                        &amp;lt;/sw-alert&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/template&amp;gt;
            &amp;lt;/sw-cms-mapping-field&amp;gt;
            &amp;lt;sw-upload-listener
                    :upload-tag="uploadTag"
                    auto-upload @media-upload-finish="onImageUpload"/&amp;gt;
        {% endblock %}
        {% block sw_cms_element_image_config_display_mode %}
            &amp;lt;sw-select-field v-model:value="element.config.displayMode.value"
                             class="sw-cms-el-config-image__display-mode"
                             :label="$tc('sw-cms.elements.general.config.label.displayMode')"
                             :help-text="$tc('sw-cms.elements.general.config.helpText.displayMode')"
                             @update:value="onChangeDisplayMode"&amp;gt;
                &amp;lt;option value="standard"&amp;gt;
                    {{ $tc('sw-cms.elements.general.config.label.displayModeStandard') }}
                &amp;lt;/option&amp;gt;
                &amp;lt;option value="stretch"&amp;gt;
                    {{ $tc('sw-cms.elements.general.config.label.displayModeStretch') }}
                &amp;lt;/option&amp;gt;
                &amp;lt;option value="cover"&amp;gt;
                    {{ $tc('sw-cms.elements.general.config.label.displayModeCover') }}
                &amp;lt;/option&amp;gt;
            &amp;lt;/sw-select-field&amp;gt;
        {% endblock %}

        &amp;lt;template v-if="element.config.displayMode.value === 'cover'"&amp;gt;
            {% block sw_cms_element_image_config_min_height %}
                &amp;lt;sw-text-field v-model:value="element.config.minHeight.value" :label="$tc('sw-cms.elements.image.config.label.minHeight')" :placeholder="$tc('sw-cms.elements.image.config.placeholder.enterMinHeight')" @update:value="onChangeMinHeight"/&amp;gt;
            {% endblock %}
        &amp;lt;/template&amp;gt;

        {% block sw_cms_element_image_config_vertical_align %}
            &amp;lt;sw-select-field v-model:value="element.config.verticalAlign.value" :label="$tc('sw-cms.elements.general.config.label.verticalAlign')" :placeholder="$tc('sw-cms.elements.general.config.label.verticalAlign')" :disabled="['cover'].includes(element.config.displayMode.value)"&amp;gt;
                &amp;lt;option value="flex-start"&amp;gt;
                    {{ $tc('sw-cms.elements.general.config.label.verticalAlignTop') }}
                &amp;lt;/option&amp;gt;
                &amp;lt;option value="center"&amp;gt;
                    {{ $tc('sw-cms.elements.general.config.label.verticalAlignCenter') }}
                &amp;lt;/option&amp;gt;
                &amp;lt;option value="flex-end"&amp;gt;
                    {{ $tc('sw-cms.elements.general.config.label.verticalAlignBottom') }}
                &amp;lt;/option&amp;gt;
            &amp;lt;/sw-select-field&amp;gt;
        {% endblock %}

        {% block sw_cms_element_image_config_horizontal_align %}
            &amp;lt;sw-select-field
                    v-model:value="element.config.horizontalAlign.value"
                    :label="$tc('sw-cms.elements.general.config.label.horizontalAlign')"
                    :placeholder="$tc('sw-cms.elements.general.config.label.horizontalAlign')"
                    :disabled="element.config.displayMode.value === 'cover'"&amp;gt;
                &amp;lt;option value="flex-start"&amp;gt;
                    {{ $tc('sw-cms.elements.general.config.label.horizontalAlignLeft') }}
                &amp;lt;/option&amp;gt;
                &amp;lt;option value="center"&amp;gt;
                    {{ $tc('sw-cms.elements.general.config.label.horizontalAlignCenter') }}
                &amp;lt;/option&amp;gt;
                &amp;lt;option value="flex-end"&amp;gt;
                    {{ $tc('sw-cms.elements.general.config.label.horizontalAlignRight') }}
                &amp;lt;/option&amp;gt;
            &amp;lt;/sw-select-field&amp;gt;
        {% endblock %}

        {% block sw_cms_element_image_config_link %}
            &amp;lt;div class="sw-cms-el-config-image__link"&amp;gt;
                &amp;lt;sw-dynamic-url-field
                        v-model:value="element.config.url.value"/&amp;gt;
                &amp;lt;sw-switch-field
                        v-model:value="element.config.newTab.value"
                        class="sw-cms-el-config-image__link-tab"
                        :label="$tc('sw-cms.elements.image.config.label.linkNewTab')"/&amp;gt;
            &amp;lt;/div&amp;gt;
        {% endblock %}

        &amp;lt;mt-switch class="sw-cms-el-config-image__is-decorative" :checked="element.config.isDecorative.value" :label="$tc('sw-cms.elements.image.config.label.isDecorative')" @change="onChangeIsDecorative"/&amp;gt;

        {% block sw_cms_element_image_config_media_modal %}
            &amp;lt;sw-media-modal-v2
                    v-if="mediaModalIsOpen"
                    variant="full"
                    :caption="$tc('sw-cms.elements.general.config.caption.mediaUpload')"
                    :entity-context="cmsPageState.entityName"
                    :allow-multi-select="false"
                    :initial-folder-id="cmsPageState.defaultMediaFolderId"
                    @media-upload-remove-image="onImageRemove"
                    @media-modal-selection-change="onSelectionChanges"
                    @modal-close="onCloseModal"/&amp;gt;
        {% endblock %}
    &amp;lt;/div&amp;gt;
{% endblock %}
scs

.sw-cms-el-config-image {
    .sw-media-upload-v2 {
        margin-bottom: 22px;

        .sw-media-upload-v2__header {
            display: none;
        }
    }

    .sw-cms-el-config-image__mapping-preview {
        display: grid;
        width: 100%;
        height: 200px;
        justify-items: center;
        align-items: start;

        img {
            width: 100%;
            height: 100%;
            object-fit: contain;
            overflow: hidden;
        }

        .sw-cms-el-config-image__preview-info {
            width: 100%;
        }
    }
}

preview
index.js

import template from "./sw-cms-el-preview-custom-image.html.twig";
import "./sw-cms-el-preview-custom-image.scss";

const { Component } = Shopware;

Component.register("sw-cms-el-preview-custom-image", {
    template,

    computed: {
        assetFilter() {
            return Shopware.Filter.getByName("asset");
        },
    },
});
twig

{% block sw_cms_element_image_preview %}
    &amp;lt;div class="sw-cms-el-preview-image"&amp;gt;
        &amp;lt;img :src="assetFilter('/administration/static/img/cms/preview_mountain_small.jpg')" alt=""&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}
scss

.sw-cms-el-preview-image {
    width: 100%;
    height: 100%;
    overflow: hidden;

    img {
        margin: 0;
        display: block;
        width: 110%;
    }
}

index.js

import './component';
import './config';
import './preview';

Shopware.Service('cmsService').registerCmsElement({
    name: 'custom-image',
    label: 'Custom Image',
    component: 'sw-cms-el-custom-image',
    configComponent: 'sw-cms-el-config-custom-image',
    previewComponent: 'sw-cms-el-preview-custom-image',
    defaultConfig: {
        media: {
            source: 'static',
            value: null,
            required: true,
            entity: {
                name: 'media',
            },
        },
        url: {
            source: 'static',
            value: null,
        },
    },
});

   snippet
de

{
  "sw-cms": {
    "elements": {
      "general": {
        "config": {
          "caption": {
            "mediaUpload": "Upload an image"
          },
          "label": {
            "verticalAlign": "Vertical align",
            "alignment": "Alignment",
            "verticalAlignTop": "Top",
            "verticalAlignCenter": "Center",
            "verticalAlignBottom": "Bottom",
            "displayMode": "Display mode",
            "displayModeStandard": "Original",
            "displayModeCover": "Cropped",
            "displayModeStretch": "Scaled",
            "displayModeContain": "Fixed height",
            "autoSlide": "Automatic transition",
            "autoplayTimeout": "Delay",
            "speed": "Animation duration",
            "horizontalAlign": "Horizontal align",
            "horizontalAlignLeft": "Left",
            "horizontalAlignCenter": "Center",
            "horizontalAlignRight": "Right"
          },
          "helpText": {
            "autoSlide": "Temporarily disabled due to lack of accessibility.",
            "autoplayTimeout": "The millisecond duration for how long the slide should stay still.",
            "speed": "The millisecond duration of the sliding animation.",
            "displayMode": "&amp;lt;strong&amp;gt;Original:&amp;lt;/strong&amp;gt; The image is displayed in its original resolution or shrunk down to current CMS page's width.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&amp;lt;strong&amp;gt;Scaled:&amp;lt;/strong&amp;gt; The image is scaled to the CMS page's width without any height restrictions.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&amp;lt;strong&amp;gt;Cropped:&amp;lt;/strong&amp;gt; The image is scaled to the current CMS page's width. Any vertical overflow is cropped to the configured maximum height."
          },
          "tab": {
            "content": "Content",
            "settings": "Settings",
            "options": "Options"
          },
          "infoText": {
            "listingElement": "This element's product data is automatically loaded by the category associated with this layout. The content just shows a sample preview."
          }
        },
        "switch": {
          "groups": {
            "all": "All elements",
            "favorites": "Favourite elements"
          }
        }
      },

      "buyButton": {
        "label": "Buy Button",
        "infoText": {
          "tooltipSettingDisabled": "This element's product data is automatically loaded by the product associated with this layout. The content just shows a sample preview."
        },
        "config": {
          "label": {
            "buttonText": "Button Text",
            "redirectLink": "Redirect Link"
          },
          "placeholder": {
            "buttonText": "Enter Button Text ...",
            "redirectLink": "/#"
          }
        },
        "component": {
          "label": {
            "actionBuy": "Add to cart",
            "taxInfo": "Prices incl. VAT plus shipping costs",
            "deliveryShippingFree": "Free shipping",
            "deliveryTime": "Available, delivery time {name}",
            "variants": "Variants",
            "productNumber": "Product number:"
          }
        }
      }
    },
    "blocks": {
      "textImage": {
        "centerText": {
          "label": "Three columns, boxed images &amp;amp; text"
        },
        "imageText": {
          "label": "Two columns, boxed image &amp;amp; text"
        },
        "imageTextBubble": {
          "label": "Three columns, captioned &amp;amp; rounded"
        },
        "imageTextCover": {
          "label": "Two columns, full-sized image &amp;amp; text"
        },
        "imageTextGallery": {
          "label": "Three columns, image/text cards"
        },
        "imageTextRow": {
          "label": "Three columns, captioned images"
        },
        "textOnImage": {
          "label": "Hero image"
        }
      }
    }
  }
}
en

{
  "sw-cms": {
    "elements": {
      "general": {
        "config": {
          "caption": {
            "mediaUpload": "Upload an image"
          },
          "label": {
            "verticalAlign": "Vertical align",
            "alignment": "Alignment",
            "verticalAlignTop": "Top",
            "verticalAlignCenter": "Center",
            "verticalAlignBottom": "Bottom",
            "displayMode": "Display mode",
            "displayModeStandard": "Original",
            "displayModeCover": "Cropped",
            "displayModeStretch": "Scaled",
            "displayModeContain": "Fixed height",
            "autoSlide": "Automatic transition",
            "autoplayTimeout": "Delay",
            "speed": "Animation duration",
            "horizontalAlign": "Horizontal align",
            "horizontalAlignLeft": "Left",
            "horizontalAlignCenter": "Center",
            "horizontalAlignRight": "Right"
          },
          "helpText": {
            "autoSlide": "Temporarily disabled due to lack of accessibility.",
            "autoplayTimeout": "The millisecond duration for how long the slide should stay still.",
            "speed": "The millisecond duration of the sliding animation.",
            "displayMode": "&amp;lt;strong&amp;gt;Original:&amp;lt;/strong&amp;gt; The image is displayed in its original resolution or shrunk down to current CMS page's width.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&amp;lt;strong&amp;gt;Scaled:&amp;lt;/strong&amp;gt; The image is scaled to the CMS page's width without any height restrictions.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&amp;lt;strong&amp;gt;Cropped:&amp;lt;/strong&amp;gt; The image is scaled to the current CMS page's width. Any vertical overflow is cropped to the configured maximum height."
          },
          "tab": {
            "content": "Content",
            "settings": "Settings",
            "options": "Options"
          },
          "infoText": {
            "listingElement": "This element's product data is automatically loaded by the category associated with this layout. The content just shows a sample preview."
          }
        },
        "switch": {
          "groups": {
            "all": "All elements",
            "favorites": "Favourite elements"
          }
        }
      },

      "buyButton": {
        "label": "Buy Button",
        "infoText": {
          "tooltipSettingDisabled": "This element's product data is automatically loaded by the product associated with this layout. The content just shows a sample preview."
        },
        "config": {
          "label": {
            "buttonText": "Button Text",
            "redirectLink": "Redirect Link"
          },
          "placeholder": {
            "buttonText": "Enter Button Text ...",
            "redirectLink": "/#"
          }
        },
        "component": {
          "label": {
            "actionBuy": "Add to cart",
            "taxInfo": "Prices incl. VAT plus shipping costs",
            "deliveryShippingFree": "Free shipping",
            "deliveryTime": "Available, delivery time {name}",
            "variants": "Variants",
            "productNumber": "Product number:"
          }
        }
      }
    },
    "blocks": {
      "textImage": {
        "centerText": {
          "label": "Three columns, boxed images &amp;amp; text"
        },
        "imageText": {
          "label": "Two columns, boxed image &amp;amp; text"
        },
        "imageTextBubble": {
          "label": "Three columns, captioned &amp;amp; rounded"
        },
        "imageTextCover": {
          "label": "Two columns, full-sized image &amp;amp; text"
        },
        "imageTextGallery": {
          "label": "Three columns, image/text cards"
        },
        "imageTextRow": {
          "label": "Three columns, captioned images"
        },
        "textOnImage": {
          "label": "Hero image"
        }
      }
    }
  }
}

  main.js

import './module/sw-cms/blocks/text-image/text-image-text';
import './module/sw-cms/elements/custom-image';
import './module/sw-cms/blocks/text-image/custom-image-text-gallery';
import './module/sw-cms/elements/buy-button';

 storefront keep it as it is
config
services.xmml

&amp;lt;?xml version="1.0"?&amp;gt;

&amp;lt;container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"&amp;gt;
    &amp;lt;services&amp;gt;
        &amp;lt;service id="CmsTheme2\DataResolver\CustomImageCmsElementResolver"
                 class="CmsTheme2\DataResolver\CustomImageCmsElementResolver"&amp;gt;
            &amp;lt;tag name="shopware.cms.data_resolver" /&amp;gt;
        &amp;lt;/service&amp;gt;
        &amp;lt;service id="CmsTheme2\DataResolver\CustomImageTextGalleryCmsElementResolver"
                 class="CmsTheme2\DataResolver\CustomImageTextGalleryCmsElementResolver"&amp;gt;
            &amp;lt;tag name="shopware.cms.data_resolver" /&amp;gt;
        &amp;lt;/service&amp;gt;
    &amp;lt;/services&amp;gt;
&amp;lt;/container&amp;gt;

public/admin keep it as it is

views/storefront
block
custom-block-text-image-text.html.twig

{% block block_text_image_text %}
{#    {{ dump('test') }}#}
    {% set columns = 3 %}

    {% block block_left %}
        {% set element = block.slots.getSlot('left') %}

        &amp;lt;div class="col-md-4" data-cms-element-id="{{ element.id }}"&amp;gt;
            {% block block_left_inner %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}

    {% block block_center %}
        {% set element = block.slots.getSlot('center') %}

        &amp;lt;div class="col-md-4" data-cms-element-id="{{ element.id }}"&amp;gt;
            {% block block_center_center_inner %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}

    {% block block_right %}
        {% set element = block.slots.getSlot('right') %}

        &amp;lt;div class="col-md-4" data-cms-element-id="{{ element.id }}"&amp;gt;
            {% block block_right_inner %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}
{% endblock %}

element
cms-element-custorm-image.html.twig

{% block sw_cms_el_custom_image %}
{#    {{ dump(element.data.media.media.url) }}#}
    &amp;lt;div class="cms-element-custom-image"&amp;gt;
        &amp;lt;a href="{{ element.config.url.value }}"&amp;gt;
            &amp;lt;img src="{{ element.data.media.media.url }}"/&amp;gt;
        &amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>cms custom column task step by step</title>
      <dc:creator>vuejstest</dc:creator>
      <pubDate>Wed, 28 May 2025 18:17:49 +0000</pubDate>
      <link>https://dev.to/vuejstest_9d7a4f2fbe7631a/cms-custom-column-task-step-by-step-1o3m</link>
      <guid>https://dev.to/vuejstest_9d7a4f2fbe7631a/cms-custom-column-task-step-by-step-1o3m</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cmstheme
src
1.dataesolver
    CustomImageTextGalleryCmsElementResolver

&amp;lt;?php
declare(strict_types=1);

namespace CmsTheme\DataResolver;

use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
use Shopware\Core\Content\Cms\DataResolver\Element\AbstractCmsElementResolver;
use Shopware\Core\Content\Cms\DataResolver\Element\ElementDataCollection;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
use Shopware\Core\Content\Cms\DataResolver\CriteriaCollection;
use Shopware\Core\Content\Media\MediaDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Content\Cms\DataResolver\FieldConfig;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\EntityResolverContext;
use Shopware\Core\Content\Cms\SalesChannel\Struct\ImageStruct;
use Shopware\Core\Content\Media\MediaEntity;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Shopware\Core\Framework\Uuid\Uuid;

class CustomImageTextGalleryCmsElementResolver extends AbstractCmsElementResolver
{
    public function getType(): string
    {
        return 'buy-button';
    }

    public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
    {
        $config = $slot-&amp;gt;getFieldConfig();
        $imageConfig = $config-&amp;gt;get('media');
        $backgroundImageConfig = $config-&amp;gt;get('mobileMedia');

        $ids = [];
        if (!$imageConfig || $imageConfig-&amp;gt;isMapped() || $imageConfig-&amp;gt;getValue() === null) {
            //
        } else {
            array_push($ids, $imageConfig-&amp;gt;getValue());
        }

        if (!$backgroundImageConfig || $backgroundImageConfig-&amp;gt;isMapped() || $backgroundImageConfig-&amp;gt;getValue() === null) {
            //
        } else {
            array_push($ids, $backgroundImageConfig-&amp;gt;getValue());
        }

        if (count($ids) === 0) {
            return null;
        }

        $criteria = new Criteria($ids);

        $criteriaCollection = new CriteriaCollection();
        $criteriaCollection-&amp;gt;add('media_' . $slot-&amp;gt;getUniqueIdentifier(), MediaDefinition::class, $criteria);

        return $criteriaCollection;
    }

    public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
    {
        $config = $slot-&amp;gt;getFieldConfig();
        $data = new ArrayEntity();
        $data-&amp;gt;setUniqueIdentifier(Uuid::randomHex());
        $slot-&amp;gt;setData($data);

        $image = new ImageStruct();
        $backgroundImage = new ImageStruct();

        $imageConfig = $config-&amp;gt;get('media');
        $backgroundImageConfig = $config-&amp;gt;get('mobileMedia');

        if ($imageConfig &amp;amp;&amp;amp; $imageConfig-&amp;gt;getValue()) {
            $this-&amp;gt;addMediaEntity($slot, $image, $result, $imageConfig, $resolverContext);
        }
        $data-&amp;gt;set('media', $image);

        if ($backgroundImageConfig &amp;amp;&amp;amp; $backgroundImageConfig-&amp;gt;getValue()) {
            $this-&amp;gt;addMediaEntity($slot, $backgroundImage, $result, $backgroundImageConfig, $resolverContext);
        }
        $data-&amp;gt;set('mobileMedia', $backgroundImage);
    }

    private function addMediaEntity(
        CmsSlotEntity         $slot,
        ImageStruct           $image,
        ElementDataCollection $result,
        FieldConfig           $config,
        ResolverContext       $resolverContext
    ): void
    {
        if ($config-&amp;gt;isMapped() &amp;amp;&amp;amp; $resolverContext instanceof EntityResolverContext) {
            /** @var MediaEntity|null $media */
            $media = $this-&amp;gt;resolveEntityValue($resolverContext-&amp;gt;getEntity(), $config-&amp;gt;getValue());

            if ($media !== null) {
                $image-&amp;gt;setMediaId($media-&amp;gt;getUniqueIdentifier());
                $image-&amp;gt;setMedia($media);
            }
        }

        if ($config-&amp;gt;isStatic()) {
            $image-&amp;gt;setMediaId($config-&amp;gt;getValue());

            $searchResult = $result-&amp;gt;get('media_' . $slot-&amp;gt;getUniqueIdentifier());
            if (!$searchResult) {
                return;
            }

            /** @var MediaEntity|null $media */
            $media = $searchResult-&amp;gt;get($config-&amp;gt;getValue());
            if (!$media) {
                return;
            }

            $image-&amp;gt;setMedia($media);
        }
    }
}

2.res
app
    admin/src
    module/sw-cms
        blocks/text-image
            custom-image-text-gallery
component
index.js

import template from "./sw-cms-block-custom-image-text-gallery.html.twig";
import "./sw-cms-block-custom-image-text-gallery.scss";

Shopware.Component.register("sw-cms-block-custom-image-text-gallery", {
    template,

    compatConfig: Shopware.compatConfig,
});

twig

{% block sw_cms_block_custom_image_text_gallery %}
    &amp;lt;div class="sw-cms-block-image-text-gallery"&amp;gt;
        &amp;lt;div class="sw-cms-block-image-text-gallery__left"&amp;gt;
            &amp;lt;slot name="left-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="left-text"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="left-button"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class="sw-cms-block-image-text-gallery__center-left"&amp;gt;
            &amp;lt;slot name="center-left-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-left-text"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-left-button"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class="sw-cms-block-image-text-gallery__center-right"&amp;gt;
            &amp;lt;slot name="center-right-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-right-text"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-right-button"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class="sw-cms-block-image-text-gallery__right"&amp;gt;
            &amp;lt;slot name="right-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="right-text"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="right-button"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}
scss

.sw-cms-block-image-text-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(195px, 1fr));
  align-content: stretch;
  align-items: start;
  grid-gap: 40px;

  .sw-cms-el-text {
    padding: 20px;
  }
  .sw-cms-el-image.is--cover {
    min-height: 20px;
  }
  .sw-cms-block-image-text-gallery__left,
  .sw-cms-block-image-text-gallery__center-left,
  .sw-cms-block-image-text-gallery__center-right,
  .sw-cms-block-image-text-gallery__right {
    box-shadow: 0 0 4px 0 rgba(0, 0, 0%, 20%);
  }
}
.sw-cms-block-image-text-gallery-shop-button {
  display: grid;
  align-items: center;
}

preview
index.js

import template from './sw-cms-preview-custom-image-text-gallery.html.twig';
import './sw-cms-preview-custom-image-text-gallery.scss';

Shopware.Component.register("sw-cms-preview-custom-image-text-gallery", {
    template,
    compatConfig: Shopware.compatConfig,
    computed: {
        assetFilter() {
            return Shopware.Filter.getByName('asset');
        },
    },
});

twig

{% block sw_cms_block_custom_image_text_gallery_preview %}
    &amp;lt;div class="sw-cms-preview-custom-image-text-gallery"&amp;gt;
        &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__left"&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__image"&amp;gt;
                &amp;lt;img :src="assetFilter('/administration/static/img/cms/preview_plant_small.jpg')" alt=""&amp;gt;

            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__text"&amp;gt;
                &amp;lt;h2&amp;gt;block1&amp;lt;/h2&amp;gt;
                &amp;lt;p&amp;gt;this is block1.&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-slot sw-cms-slot-left-button"&amp;gt;
                &amp;lt;div class="sw-cms-el-buy-button"&amp;gt;
                    &amp;lt;div class="sw-cms-el-buy-button__actions"&amp;gt;
                        &amp;lt;a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small"&amp;gt;Shop&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="sw-cms-slot__preview-overlay"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__center-left"&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__image"&amp;gt;
                &amp;lt;img :src="assetFilter('/administration/static/img/cms/preview_glasses_small.jpg')" alt=""&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__text"&amp;gt;
                &amp;lt;h2&amp;gt;block2&amp;lt;/h2&amp;gt;
                &amp;lt;p&amp;gt;this is block2&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-slot sw-cms-slot-center-left-button"&amp;gt;
                &amp;lt;div class="sw-cms-el-buy-button"&amp;gt;
                    &amp;lt;div class="sw-cms-el-buy-button__actions"&amp;gt;
                        &amp;lt;a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small"&amp;gt;Shop&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="sw-cms-slot__preview-overlay"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__center-right"&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__image"&amp;gt;
                &amp;lt;img :src="assetFilter('/administration/static/img/cms/preview_plant_small.jpg')" alt=""&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__text"&amp;gt;
                &amp;lt;h2&amp;gt;block3&amp;lt;/h2&amp;gt;
                &amp;lt;p&amp;gt;this is block3&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-slot sw-cms-slot-center-right-button"&amp;gt;
                &amp;lt;div class="sw-cms-el-buy-button"&amp;gt;
                    &amp;lt;div class="sw-cms-el-buy-button__actions"&amp;gt;
                        &amp;lt;a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small"&amp;gt;Shop&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="sw-cms-slot__preview-overlay"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__right"&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__image"&amp;gt;
                &amp;lt;img :src="assetFilter('/administration/static/img/cms/preview_camera_small.jpg')" alt=""&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-preview-custom-image-text-gallery__text"&amp;gt;
                &amp;lt;h2&amp;gt;block4&amp;lt;/h2&amp;gt;
                &amp;lt;p&amp;gt;this is block4&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-slot sw-cms-slot-right-button"&amp;gt;
                &amp;lt;div class="sw-cms-el-buy-button"&amp;gt;
                    &amp;lt;div class="sw-cms-el-buy-button__actions"&amp;gt;
                        &amp;lt;a class="sw-cms-el-buy-button__buy-action sw-button sw-button--small"&amp;gt;Shop&amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div class="sw-cms-slot__preview-overlay"&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}

scss

.sw-cms-preview-custom-image-text-gallery {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-gap: 10px;
  padding: 15px;

  .sw-cms-preview-custom-image-text-gallery__left,
  .sw-cms-preview-custom-image-text-gallery__center-left,
  .sw-cms-preview-custom-image-text-gallery__center-right,
  .sw-cms-preview-custom-image-text-gallery__right {
    box-shadow: 0 0 3px 0 rgba(0, 0, 0, 20%);
  }

  .sw-cms-preview-custom-image-text-gallery__image {
    height: 60px;
  }

  .sw-cms-preview-custom-image-text-gallery__text {
    padding: 8px;
    text-align: center;
  }

  img {
    display: block;
    object-fit: cover;
    width: 100%;
    height: 100%;
  }
}

.sw-cms-block-image-text-gallery-shop-button {
  display: grid;
  align-items: center;
  .sw-button {
    height: 30px;
    width: auto;
    padding: 25px;
  }
  .sw-cms-el-buy-button__buy-action{
    font-size: smaller;
  }
}

index.js

import CMS from '../../../constant/sw-cms.constant';
import "./preview";
import "./component";


Shopware.Service("cmsService").registerCmsBlock({
    name: "custom-image-text-gallery",
    label: "CMS image-text-button block",
    category: "text-image",
    component: "sw-cms-block-custom-image-text-gallery",
    previewComponent: "sw-cms-preview-custom-image-text-gallery",
    defaultConfig: {
        marginBottom: "20px",
        marginTop: "20px",
        marginLeft: "20px",
        marginRight: "20px",
        sizingMode: "boxed",
    },
    slots: {
        "left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_plant_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block 1&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block1&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_glasses_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block2&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block2&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "center-left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_plant_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block3&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block3&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "center-right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block 4&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block 4&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },
    },
});

            image-text-gallery/custom-image-text-gallery
index.js

import CMS from '../../../constant/sw-cms.constant';
import "./preview";
import "./component";


Shopware.Service("cmsService").registerCmsBlock({
    name: "custom-image-text-gallery",
    label: "CMS image-text-button block",
    category: "text-image",
    component: "sw-cms-block-custom-image-text-gallery",
    previewComponent: "sw-cms-preview-custom-image-text-gallery",
    defaultConfig: {
        marginBottom: "20px",
        marginTop: "20px",
        marginLeft: "20px",
        marginRight: "20px",
        sizingMode: "boxed",
    },
    slots: {
        "left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_plant_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block 1&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block1&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-left-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_glasses_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-left-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block2&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block2&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "center-left-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "center-right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_plant_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "center-right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block3&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block3&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "center-right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },

        "right-image": {
            type: "image",
            default: {
                config: {
                    displayMode: { source: "static", value: "cover" },
                },
                data: {
                    media: {
                        value: "bundles/administration/static/img/cms/preview_camera_large.jpg",
                        source: "default",
                    },
                },
            },
        },
        "right-text": {
            type: "text",
            default: {
                config: {
                    content: {
                        source: "static",
                        value: `
              &amp;lt;h2 style="text-align: center;"&amp;gt;block 4&amp;lt;/h2&amp;gt;
              &amp;lt;p style="text-align: center;"&amp;gt;this is block 4&amp;lt;/p&amp;gt;
            `.trim(),
                    },
                },
            },
        },
        "right-button": {
            type: "buy-button",
            default: {
                config: {
                    name: {
                        source: "static",
                        value: "Shop",
                        required: true,
                    },
                    link: {
                        source: "static",
                        value: null,
                    },
                },
            },
        },
    },
});

        constant
sw-cms.constant.ts

const uniqueSlotsKebab = [
    'buy-box',
    'product-description-reviews',
    'cross-selling',
];

/**
 * @sw-package discovery
 */
// eslint-disable-next-line sw-deprecation-rules/private-feature-declarations
export default Object.freeze({
    REQUIRED_FIELD_ERROR_CODE: 'c1051bb4-d103-4f74-8988-acbcafc7fdc3',
    PAGE_TYPES: {
        SHOP: 'page',
        LANDING: 'landingpage',
        LISTING: 'product_list',
        PRODUCT_DETAIL: 'product_detail',
    },
    TYPE_MAPPING_ENTITIES: {
        product_detail: {
            entity: 'product',
            mode: 'single',
        },
        product_list: {
            entity: 'category',
            mode: 'single',
        },
    },
    UNIQUE_SLOTS: uniqueSlotsKebab.map((slotName) =&amp;gt; slotName.replace(/-./g, (char) =&amp;gt; char.toUpperCase()[1])),
    UNIQUE_SLOTS_KEBAB: uniqueSlotsKebab,
    SLOT_POSITIONS: {
        left: 0,
        'left-image': 100,
        'left-top': 200,
        'left-text': 300,
        'left-bottom': 400,
        'center-left': 1000,
        center: 1100,
        'center-image': 1200,
        'center-top': 1300,
        'center-text': 1400,
        'center-bottom': 1500,
        'center-right': 1600,
        right: 2000,
        'right-image': 2100,
        'right-top': 2200,
        'right-text': 2300,
        'right-bottom': 2400,
        content: 3000,
        image: 3100,
        video: 3200,
        imageSlider: 3300,
        default: 5000,
    },
    MEDIA: {
        previewCamera: 'bundles/administration/static/img/cms/preview_camera_large.jpg',
        previewMountain: 'bundles/administration/static/img/cms/preview_mountain_large.jpg',
        previewPlant: 'bundles/administration/static/img/cms/preview_plant_large.jpg',
        previewGlasses: 'bundles/administration/static/img/cms/preview_glasses_large.jpg',
        SMALL:{
            previewCamera: 'framework/assets/default/cms/preview_camera_small.jpg',
            previewMountain: 'framework/assets/default/cms/preview_mountain_small.jpg',
            previewPlant: 'framework/assets/default/cms/preview_plant_small.jpg',
            previewGlasses: 'framework/assets/default/cms/preview_glasses_small.jpg',
        }
    },
});


        elements/buy-button
component
index.js

import template from "./sw-cms-el-buy-button.html.twig";
import "./sw-cms-el-buy-button.scss";

const { Mixin } = Shopware;

Shopware.Component.register("sw-cms-el-buy-button", {
    template,

    compatConfig: Shopware.compatConfig,

    mixins: [Mixin.getByName("cms-element"), Mixin.getByName("placeholder")],

    computed: {
        name() {
            return this.element?.config?.name?.value ?? "Shop";
        },
        redirectTo() {
            return this.element.config?.link?.value ?? null;
        },
    },

created(){
        this.createdComponent();
},
    methods: {
        createdComponent() {
            this.initElementConfig("buy-button");
            this.initElementData("buy-button");
        },
    },
});

twig

{% block sw_cms_el_buy_button %}
    {#    {{ dump('test') }}#}
    &amp;lt;div class="sw-cms-el-buy-button"&amp;gt;
        {% block sw_cms_element_buy_button_form_action %}
            &amp;lt;div class="sw-cms-el-buy-button__actions"&amp;gt;
                &amp;lt;a href="{{ redirectTo }}" class="sw-cms-el-buy-button__buy-action"&amp;gt;
                    {{ name }}
                &amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
        {% endblock %}
    &amp;lt;/div&amp;gt;
{% endblock %}

scss

.sw-cms-el-buy-button {
  display: flex;
  justify-content: flex-start;
  flex-direction: column;
  height: 100%;
  width: 100%;
  pointer-events: none;
  font-size: smaller;
  color: darkgrey;

  &amp;amp;__placeholder {
    background-color: gray;
    height: 10px;
    border-radius: 10px;
    margin-bottom: 16px;
    max-width: 150px;

    &amp;amp;:nth-of-type(1) {
      height: 26px;
      max-width: 130px;
    }

    &amp;amp;:nth-of-type(2) {
      max-width: 100px;
      margin-bottom: 10px;
    }

    &amp;amp;:nth-of-type(3) {
      margin-bottom: 32px;
    }

    &amp;amp;:nth-of-type(4) {
      height: 16px;
      max-width: 300px;
    }

    &amp;amp;:nth-of-type(6) {
      margin-top: 16px;
    }
  }

  &amp;amp;__form {
    display: inline-flex;
    width: 100%;
  }

  &amp;amp;__quantity.sw-block-field {
    flex: 1;
    max-width: 150px;
    margin-bottom: 0;

    .sw-block-field__block {
      position: relative;
    }

    select {
      padding: 9px;
    }
  }

  &amp;amp;__variant-title {
    font-weight: bold;
  }

  &amp;amp;__variants {
    display: flex;
    justify-content: space-between;
    margin: 10px 0 24px;
    height: 36px;
    max-width: 260px;
  }

  &amp;amp;__variant {
    width: 80px;
    margin-right: 8px;
    background: gray;

    &amp;amp;:last-of-type {
      margin-right: 0;
    }
  }

  &amp;amp;__icon {
    display: flex;
    flex-direction: column;
    position: absolute;
    right: 10px;
    top: calc(50% - 15px);
    color: darkgrey;

    .icon--regular-chevron-up-xxs {
      width: 16px;
      height: 16px;
      padding-top: 5px;
      padding-right: 4px;
      padding-bottom: 6px;
      padding-left: 4px;
    }

    .icon--regular-chevron-down-xxs {
      width: 16px;
      height: 16px;
      padding-top: 6px;
      padding-right: 4px;
      padding-bottom: 5px;
      padding-left: 4px;
    }
  }

  &amp;amp;__actions {
    flex: 2;
    margin-left: 8px;
    max-width: 300px;
  }

  &amp;amp;__buy-action {
    display: block;
    line-height: 40px;
    font-size: small;
    font-weight: bold;
    text-align: center;
    text-decoration: none;
    color: white;
    background: gray;
    border-radius: 10px;
  }

  &amp;amp;__price {
    font-size: 26px;
    font-weight: bold;
    margin-bottom: 16px;
  }

  &amp;amp;__tax-info {
    margin-bottom: 10px;
  }

  &amp;amp;__shipping-info {
    margin-bottom: 32px;
    margin-left: 14px;
  }

  &amp;amp;__product-number-title {
    font-weight: bold;
  }

  &amp;amp;__product-number {
    margin-top: 16px;
  }
}

config
index.js

import template from "./sw-cms-el-config-buy-button.html.twig";
import "./sw-cms-el-config-buy-button.scss";
const { Mixin } = Shopware;
Shopware.Component.register("sw-cms-el-config-buy-button", {
    template,
    compatConfig: Shopware.compatConfig,

    emits: ["element-update"],

    mixins: [Mixin.getByName("cms-element")],

    created() {
        this.createdComponent();
    },

    methods: {
createdComponent(){
            this.initElementConfig("buy-button");
},
        onChange() {
            this.$emit("element-update", this.element);
        },
    },

});

twig

{% block sw_cms_element_buy_button_config %}
    &amp;lt;div class="sw-cms-el-config-buy-button"&amp;gt;
        {% block sw_cms_element_buy_button_config_product_select %}
            &amp;lt;sw-text-field v-model:value="element.config.name.value"
                           :label="$tc('sw-cms.elements.buyButton.config.label.buttonText')"
                           :placeholder="$tc('sw-cms.elements.buyButton.config.placeholder.buttonText')"
                           name="name"
                           validation="required" required
                           @update:value="onChange" /&amp;gt;

            &amp;lt;sw-text-field v-model:value="element.config.link.value"
                           :label="$tc('sw-cms.elements.buyButton.config.label.redirectLink')"
                           :placeholder="$tc('sw-cms.elements.buyButton.config.placeholder.redirectLink')"
                           name="link"
                           validation="required" required
                           @update:value="onChange" /&amp;gt;
        {% endblock %}
    &amp;lt;/div&amp;gt;
{% endblock %}

scss

.sw-cms-el-config-buy-button {
  &amp;amp;__alignment.sw-block-field,
  .sw-entity-single-select {
    margin-bottom: 0;
  }

  .sw-entity-single-select__selection {
    white-space: nowrap;
    display: flex;
    align-items: center;
  }

  .sw-entity-single-select__selection-text {
    vertical-align: middle;
    padding-right: 16px;
    width: 100%;
  }
}

preview
index.js

import template from "./sw-cms-el-preview-buy-button.html.twig";
import "./sw-cms-el-preview-buy-button.scss";

Shopware.Component.register("sw-cms-el-preview-buy-button", {
    template,
    compatConfig: Shopware.compatConfig,
});

twig

{% block sw_cms_element_buy_button_preview %}
&amp;lt;div class="sw-cms-el-preview-buy-button"&amp;gt;
    &amp;lt;div class="sw-cms-el-preview-buy-button__action"&amp;gt;shop
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}

scss

.sw-cms-el-preview-buy-button {
  height: 100%;
  width: 100%;
  padding: 0 10px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;

  &amp;amp;__price {
    font-size: smaller;
  }

  &amp;amp;__placeholders {
    display: grid;
    grid-template-columns: 10px 1fr;
    grid-gap: 4px;
    margin-top: 4px;

    &amp;amp; &amp;gt; .sw-cms-el-preview-buy-button__placeholder {
      width: 60px;
      margin: 0;

      &amp;amp;:nth-child(odd) {
        width: 8px;
        margin-right: 4px;
      }
    }
  }

  &amp;amp;__placeholder {
    height: 8px;
    border-radius: 50% 50%;
    background-color: gray;
  }

  &amp;amp;__action {
    background: gray;
    color: darkgrey;
    border-radius: 50% 50%;
    text-align: center;
    font-size: smaller;
    line-height: 26px;
    width: 100%;
    margin-top: 4px;
    align-self: flex-end;
  }
}

index.js

import './preview';
import './component';
import './config';

/**
 * @private
 * @sw-package discovery
 */
Shopware.Service("cmsService").registerCmsElement({
    name:"buy-button",
    label:"sw-cms.elements.buyButton.label",
    component:"sw-cms-el-buy-button",
    configComponent:"sw-cms-el-config-buy-button",
    previewComponent:"sw-cms-el-preview-buy-button",
    disabledConfigInfoTextKey:
        "sw-cms.elements.buyButton.infoText.tooltipSettingDisabled",
    defaultConfig:{
        name:{
            source:"static",
            value:"shop",
            required:true,
        },
        link:{
            source:"static",
            value:null,
        }
    },
    collect:Shopware.Service("cmsService").getCollectFunction(),
});

        snippet
de

{
  "sw-cms": {
    "elements": {
      "general": {
        "config": {
          "caption": {
            "mediaUpload": "Hochladen ein Bild"
          },
          "label": {
            "verticalAlign": "Vertikal ausrichten",
            "alignment": "Ausrichtung",
            "verticalAlignTop": "Spitze",
            "verticalAlignCenter": "Center",
            "verticalAlignBottom": "Unten",
            "displayMode": "Anzeige Modus",
            "displayModeStandard": "Original",
            "displayModeCover": "Beschnitten",
            "displayModeStretch": "Skaliert",
            "displayModeContain": "Behoben Höhe",
            "autoSlide": "Automatisch Übergang",
            "autoplayTimeout": "Verzögerung",
            "speed": "Animation Dauer",
            "horizontalAlign": "Horizontal ausrichten",
            "horizontalAlignLeft": "Links",
            "horizontalAlignCenter": "Center",
            "horizontalAlignRight": "Rechts"
          },
          "helpText": {
            "autoSlide": "Vorübergehend unbenutzt",
            "autoplayTimeout": "Dauer sollen bleiben Trotzdem",
            "speed": "gleiten Geschwindigkeit",
            "displayMode": "Bild angezeigt"
          },
          "tab": {
            "content": "Inhalt",
            "settings": "Einstellungen",
            "options": "Optionen"
          },
          "infoText": {
            "listingElement": "Probe Layout"
          }
        },
        "switch": {
          "groups": {
            "all": "Alle elements",
            "favorites": "Favorit Elemente"
          }
        }
      },
      "buyButton": {
        "label": "Buy Button",
        "infoText": {
          "tooltipSettingDisabled": "Probe Vorschau"
        },
        "config": {
          "label": {
            "buttonText": "Taste Text",
            "redirectLink": "Umleiten Link"
          },
          "placeholder": {
            "buttonText": "Eingeben Taste Text",
            "redirectLink": "/#"
          }
        },
        "component": {
          "label": {
            "actionBuy": "Add to cart",
            "taxInfo": "Steuerinformationen",
            "deliveryShippingFree": "Kostenloser Versand",
            "deliveryTime": "Verfügbar, Lieferzeit {name}",
            "variants": "Varianten",
            "productNumber": "Produktnummer:"
          }
        }
      }
    },
    "blocks": {
      "textImage": {
        "centerText": {
          "label": "Drei Spalten, Bilder und Text in Kästen"
        },
        "imageText": {
          "label": "Zwei Spalten, eingerahmtes Bild und Text"
        },
        "imageTextBubble": {
          "label": "Dreispaltig, beschriftet &amp;amp; gerundet"
        },
        "imageTextCover": {
          "label": "Zwei Spalten, Bild und Text in voller Größe"
        },
        "imageTextGallery": {
          "label": "Dreispaltig, Bild-/Textkarten"
        },
        "imageTextRow": {
          "label": "Dreispaltige, beschriftete Bilder"
        },
        "textOnImage": {
          "label": "Heldenbild"
        }
      }
    }
  }
}

en

{
  "sw-cms": {
    "elements": {
      "general": {
        "config": {
          "caption": {
            "mediaUpload": "Upload an image"
          },
          "label": {
            "verticalAlign": "Vertical align",
            "alignment": "Alignment",
            "verticalAlignTop": "Top",
            "verticalAlignCenter": "Center",
            "verticalAlignBottom": "Bottom",
            "displayMode": "Display mode",
            "displayModeStandard": "Original",
            "displayModeCover": "Cropped",
            "displayModeStretch": "Scaled",
            "displayModeContain": "Fixed height",
            "autoSlide": "Automatic transition",
            "autoplayTimeout": "Delay",
            "speed": "Animation duration",
            "horizontalAlign": "Horizontal align",
            "horizontalAlignLeft": "Left",
            "horizontalAlignCenter": "Center",
            "horizontalAlignRight": "Right"
          },
          "helpText": {
            "autoSlide": "Temporily unused",
            "autoplayTimeout": "duration should stay still",
            "speed": "sliding speed",
            "displayMode": "image displayed"
          },

          "tab": {
            "content": "Content",
            "settings": "Settings",
            "options": "Options"
          },
          "infoText": {
            "listingElement": "sample layout"
          }
        },
        "switch": {
          "groups": {
            "all": "All elements",
            "favorites": "Favourite elements"
          }
        }
      },
      "buyButton": {
        "label": "Buy Button",
        "infoText": {
          "tooltipSettingDisabled": "sample preview"
        },
        "config": {
          "label": {
            "buttonText": "Button Text",
            "redirectLink": "Redirect Link"
          },
          "placeholder": {
            "buttonText": "Enter Button Text",
            "redirectLink": "/#"
          }
        },
        "component": {
          "label": {
            "actionBuy": "Add to cart",
            "taxInfo": "tax info",
            "deliveryShippingFree": "Free shipping",
            "deliveryTime": "Available, delivery time {name}",
            "variants": "Variants",
            "productNumber": "Product number:"
          }
        }
      }
    },
    "blocks": {
      "textImage": {
        "centerText": {
          "label": "Three columns, boxed images &amp;amp; text"
        },
        "imageText": {
          "label": "Two columns, boxed image &amp;amp; text"
        },
        "imageTextBubble": {
          "label": "Three columns, captioned &amp;amp; rounded"
        },
        "imageTextCover": {
          "label": "Two columns, full-sized image &amp;amp; text"
        },
        "imageTextGallery": {
          "label": "Three columns, image/text cards"
        },
        "imageTextRow": {
          "label": "Three columns, captioned images"
        },
        "textOnImage": {
          "label": "Hero image"
        }
      }
    }
  }
}

main.js

import './module/sw-cms/blocks/text-image/custom-image-text-gallery';
import './module/sw-cms/elements/buy-button';

    in storefront no change keep as it it
dist/storefront/js/cms-theme
src

config
services.xml

&amp;lt;?xml version="1.0"?&amp;gt;

&amp;lt;container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"&amp;gt;

    &amp;lt;services&amp;gt;
        &amp;lt;service id="CmsTheme\DataResolver\CustomImageTextGalleryCmsElementResolver"
                 class="CmsTheme\DataResolver\CustomImageTextGalleryCmsElementResolver"&amp;gt;
            &amp;lt;tag name="shopware.cms.data_resolver" /&amp;gt;
        &amp;lt;/service&amp;gt;
    &amp;lt;/services&amp;gt;
&amp;lt;/container&amp;gt;

in public/admin no change

views/storefront
block
cms-block-custom-image-text-gallery.html.twig

{% block block_image_four_row %}
    {% set columns = 4 %}
    {% block block_image_four_row_left %}
        &amp;lt;div class="col-md-3"&amp;gt;
            {% block block_image_four_row_left_image %}
                {% set element = block.slots.getSlot('left-image') %}
                {% block block_image_four_column_left_inner_image %}
                    {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                {% endblock %}
            {% endblock %}
            {% block block_image_four_row_left_text %}
                {% set element = block.slots.getSlot('left-text') %}
                {% block block_image_four_column_left_inner_text %}
                    {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                {% endblock %}
            {% endblock %}
            {% block block_image_four_row_left_button %}
                {% set element = block.slots.getSlot('left-button') %}
                {% block block_image_four_column_left_inner_button %}
                    {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                {% endblock %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}

    {% block block_image_text_row_center_left %}
        &amp;lt;div class="col-md-3"&amp;gt;
            {% block block_image_text_row_center_left_image %}
                {% set element = block.slots.getSlot('center-left-image') %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
            {% block block_image_text_row_center_left_text %}
                {% set element = block.slots.getSlot('center-left-text') %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
            {% block block_image_four_row_center_left_button %}
                {% set element = block.slots.getSlot('center-left-button') %}
                {% block block_image_four_column_center_left_inner_button %}
                    {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                {% endblock %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}

    {% block block_image_text_row_center_right %}
        &amp;lt;div class="col-md-3"&amp;gt;
            {% block block_image_text_row_center_right_image %}
                {% set element = block.slots.getSlot('center-right-image') %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
            {% block block_image_text_row_center_right_text %}
                {% set element = block.slots.getSlot('center-right-text') %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
            {% block block_image_four_row_center_right_button %}
                {% set element = block.slots.getSlot('center-right-button') %}
                {% block block_image_four_column_center_right_inner_button %}
                    {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                {% endblock %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}

    {% block block_image_text_row_right %}
        &amp;lt;div class="col-md-3"&amp;gt;
            {% block block_image_text_row_right_image %}
                {% set element = block.slots.getSlot('right-image') %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
            {% block block_image_text_row_right_text %}
                {% set element = block.slots.getSlot('right-text') %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
            {% block block_image_four_row_right_button %}
                {% set element = block.slots.getSlot('right-button') %}
                {% block block_image_four_column_right_inner_button %}
                    {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                {% endblock %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}
{% endblock %}

element
cms-element-buy-button.html.twig
{% block sw_cms_el_buy_button %}

    &amp;lt;div class="sw-cms-el-buy-button"&amp;gt;
        {% block sw_cms_element_buy_button_form_action %}
{#            {{ dump('test') }}#}
            &amp;lt;div class="sw-cms-el-buy-button__actions"&amp;gt;
                    &amp;lt;a href="{{ element.config.link.value }}" class="sw-cms-el-buy-button__buy-action"&amp;gt;
                        {{ element.config.name.value}}
                    &amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
        {% endblock %}
    &amp;lt;/div&amp;gt;
{% endblock %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>how to create definition step by step</title>
      <dc:creator>vuejstest</dc:creator>
      <pubDate>Wed, 28 May 2025 17:57:15 +0000</pubDate>
      <link>https://dev.to/vuejstest_9d7a4f2fbe7631a/how-to-create-definition-step-by-step-ml1</link>
      <guid>https://dev.to/vuejstest_9d7a4f2fbe7631a/how-to-create-definition-step-by-step-ml1</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eventcrud
src/Core/Content
Event
    Aggregate
EventTranslationDefinition

&amp;lt;?php declare(strict_types=1);

namespace EventTask\Core\Content\Event\Aggregate;


use EventTask\Core\Content\Event\EventDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityTranslationDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
use Shopware\Core\Framework\DataAbstractionLayer\Field\LongTextField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;

class EventTranslationDefinition extends EntityTranslationDefinition
{
    const ENTITY_NAME = 'event_translation';

    public function getEntityName(): string
    {
        return self::ENTITY_NAME;
    }
    public function getEntityClass(): string{
        return EventTranslationEntity::class;
    }
    public function getCollectionClass(): string
    {
        return EventTranslationCollection::class;
    }
    protected function getParentDefinitionClass(): string
    {
        return EventDefinition::class;
    }

    protected function defineFields(): FieldCollection
    {
        return new FieldCollection([
            (new StringField('name', 'name'))-&amp;gt;addFlags(new Required()),
            (new LongTextField('description', 'description'))-&amp;gt;addFlags(new Required()),
        ]);
    }
}

    EventDefinition

&amp;lt;?php declare(strict_types=1);

namespace EventTask\Core\Content\Event;

use EventTask\Core\Content\Event\Aggregate\EventTranslationDefinition;
use EventTask\Core\Content\EventCategory\EventCategoryDefinition;
use EventTask\Core\Content\EventCategoryMapping\EventCategoryMappingDefinition;

//use EventTask\Core\Content\EventCustomerMapping\EventCustomerMappingDefinition;
use Shopware\Core\Checkout\Customer\CustomerDefinition;
use Shopware\Core\Content\Category\CategoryDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Field\BoolField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\DateField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToManyAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToOneAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ReferenceVersionField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslatedField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslationsAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;

class EventDefinition extends EntityDefinition
{

    public const ENTITY_NAME = 'event';

    public function getEntityName(): string
    {
        return self::ENTITY_NAME;
    }

    public function getCollectionClass(): string
    {
        return EventCollection::class;
    }

    public function getEntityClass(): string
    {
        return EventEntity::class;
    }

    protected function defineFields(): FieldCollection
    {
        return new FieldCollection([
            (new IdField('id', 'id'))-&amp;gt;addFlags(new PrimaryKey(), new Required()),
            new TranslatedField('name'),
            new TranslatedField('description'),
            (new BoolField('active', 'active'))-&amp;gt;addFlags(new Required()),
            (new DateField('event_date', 'eventDate'))-&amp;gt;addFlags(new Required()),
            new FkField('organized_by_id', 'organizedById', CustomerDefinition::class),
            new ManyToOneAssociationField('organizedBy', 'organized_by_id', CustomerDefinition::class, 'id', false),

            new ManyToManyAssociationField(
                'eventCategories',
                EventCategoryDefinition::class,
                EventCategoryMappingDefinition::class,
                'event_id',
                'event_category_id'
            ),
            (new TranslationsAssociationField(EventTranslationDefinition::class, 'event_id'))-&amp;gt;addFlags(new Required()),
        ]);
    }
}

EventCategory
Aggregate
EventCategoryTranslationDefitiion

&amp;lt;?php declare(strict_types=1);

namespace EventTask\Core\Content\EventCategory\Aggregate;

use EventTask\Core\Content\EventCategory\EventCategoryDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityTranslationDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;

class EventCategoryTranslationDefinition extends EntityTranslationDefinition
{
    const ENTITY_NAME = 'event_category_translation';

    public function getEntityName(): string
    {
        return self::ENTITY_NAME;
    }
    public function getEntityClass(): string{
        return EventCategoryTranslationEntity::class;
    }
    public function getCollectionClass(): string
    {
        return EventCategoryTranslationCollection::class;
    }
    protected function getParentDefinitionClass(): string
    {
        return EventCategoryDefinition::class;
    }

    protected function defineFields(): FieldCollection
    {
        return new FieldCollection([
            (new StringField('name', 'name'))-&amp;gt;addFlags(new Required()),
        ]);
    }
}

EventCategoryDefinition

&amp;lt;?php declare(strict_types=1);

namespace EventTask\Core\Content\EventCategory;

use EventTask\Core\Content\Event\EventDefinition;
use EventTask\Core\Content\EventCategory\Aggregate\EventCategoryTranslationDefinition;
use EventTask\Core\Content\EventCategoryMapping\EventCategoryMappingDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToManyAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslatedField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslationsAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;

class EventCategoryDefinition extends EntityDefinition
{
    public const ENTITY_NAME = 'event_category';

    public function getEntityName(): string
    {
        return self::ENTITY_NAME;
    }
    public function getEntityClass(): string{
        return EventCategoryEntity::class;
    }
    public function getCollectionClass(): string
    {
        return EventCategoryCollection::class;
    }
    protected function defineFields(): FieldCollection
    {
        return new FieldCollection([
            (new IdField('id', 'id'))-&amp;gt;addFlags(new PrimaryKey(), new Required()),
            new TranslatedField('name'),

            new ManyToManyAssociationField(
                'events',
                EventDefinition::class,
                EventCategoryMappingDefinition::class,
                'event_category_id',
                'event_id'
            ),
            (new TranslationsAssociationField(EventCategoryTranslationDefinition::class, 'event_category_id'))-&amp;gt;addFlags(new Required()),
        ]);
    }
}

EventCategoryMapping

&amp;lt;?php declare(strict_types=1);

namespace EventTask\Core\Content\EventCategoryMapping;


use EventTask\Core\Content\Event\EventDefinition;
use EventTask\Core\Content\EventCategory\EventCategoryDefinition;
use Shopware\Core\Content\Category\CategoryDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToOneAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ReferenceVersionField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
use Shopware\Core\Framework\DataAbstractionLayer\MappingEntityDefinition;

class EventCategoryMappingDefinition extends MappingEntityDefinition
{
    public const ENTITY_NAME = 'event_category_event';

    public function getEntityName(): string
    {
        return self::ENTITY_NAME;
    }
    protected function defineFields(): FieldCollection
    {

        return new FieldCollection([
            (new FkField('event_category_id', 'eventCategoryId', EventCategoryDefinition::class))-&amp;gt;addFlags(new PrimaryKey(), new Required()),
            (new FkField('event_id', 'eventId', EventDefinition::class))-&amp;gt;addFlags(new PrimaryKey(), new Required()),
            (new ReferenceVersionField(EventCategoryDefinition::class))-&amp;gt;addFlags(new PrimaryKey(), new Required()),
            (new ReferenceVersionField(EventDefinition::class))-&amp;gt;addFlags(new PrimaryKey(), new Required()),
            new ManyToOneAssociationField('event', 'event_id', EventDefinition::class, 'id'),
            new ManyToOneAssociationField('eventCategory', 'event_category_id', EventCategoryDefinition::class, 'id'),
        ]);

    }
}

Extension
CategoryExtension
&amp;lt;?php declare(strict_types=1);

namespace EventTask\Core\Content\Extension;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;

use EventTask\Core\Content\Event\EventDefinition;
use EventTask\Core\Content\EventCategoryMapping\EventCategoryMappingDefinition;
use Shopware\Core\Content\Category\CategoryDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToManyAssociationField;

class CategoryExtension extends EntityExtension
{
    public function extendFields(FieldCollection $collection): void
    {
        $collection-&amp;gt;add(
            new ManyToManyAssociationField(
                'events',
                EventDefinition::class,
                EventCategoryMappingDefinition::class,
                'event_category_id',
                'event_id'
            )
        );
    }

    public function getDefinitionClass(): string
    {
        return CategoryDefinition::class;
    }
}
CustomerExtension

&amp;lt;?php declare(strict_types=1);

namespace EventTask\Core\Content\Extension;

use EventTask\Core\Content\Event\EventDefinition;
use Shopware\Core\Checkout\Customer\CustomerDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToManyAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;

class CustomerExtension extends EntityExtension
{
    public function extendFields(FieldCollection $collection): void
    {
        $collection-&amp;gt;add(
                        new OneToManyAssociationField(
                            'customerId',
                            EventDefinition::class,
                            'organized_by_id',
                        )
        );
    }

    public function getDefinitionClass(): string
    {
        return CustomerDefinition::class;
    }
}
LanguageExtension

&amp;lt;?php declare(strict_types=1);

namespace EventTask\Core\Content\Extension;


use EventTask\Core\Content\Event\Aggregate\EventTranslationDefinition;
use EventTask\Core\Content\EventCategory\Aggregate\EventCategoryTranslationDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToManyAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
use Shopware\Core\System\Language\LanguageDefinition;


class LanguageExtension extends EntityExtension
{
    public function extendFields(FieldCollection $collection): void
    {
        $collection-&amp;gt;add(
            new OneToManyAssociationField(
                'eventTransId',
                EventTranslationDefinition::class,
                'event_id',
            )
        );
        $collection-&amp;gt;add(
            new OneToManyAssociationField(
                'eventCategoryTransId',
                EventCategoryTranslationDefinition::class,
                'event_category_id'
            )
        );
    }

    public function getDefinitionClass(): string
    {
        return LanguageDefinition::class;
    }
}

Migration

Resources
app/admin/src
    module
        sw-event-category
            page
                sw-event-category-detail
                    index.js

import template from "./sw-event-category-detail.html.twig";

const { Mixin } = Shopware;
const { Criteria } = Shopware.Data;

export default {
    template,
    compatConfig: Shopware.compatConfig,

    inject: ["repositoryFactory"],
    mixins: [
        Mixin.getByName("placeholder"),
        Mixin.getByName("notification"),
        Mixin.getByName("discard-detail-page-changes")("eventCategory"),
    ],

    data() {
        return {
            eventCategory: null,
            repository: null,
            isLoading: false,
            processSuccess: false,
        };
    },
    props: {
        eventCategoryId: {
            type: String,
            required: false,
            default: null,
        },
    },
    metaInfo() {
        return {
            title: this.$createTitle(),
        };
    },
    watch: {
        eventCategoryId() {
            this.createdComponent();
        },
    },
    created() {
        this.createdComponent();
    },
    methods: {
        createdComponent() {
            this.repository = this.repositoryFactory.create("event_category");
            if (this.eventCategoryId) {
                this.getCategory();
                return;
            }
            Shopware.State.commit("context/resetLanguageToDefault");
            this.eventCategory = this.repository.create();
        },
        abortOnLanguageChange() {
            return this.repository.hasChanges(this.eventCategory);
        },

        saveOnLanguageChange() {
            return this.onClickSave();
        },

        onChangeLanguage(languageId) {
            this.isLoading = true;
            Shopware.State.commit("context/setApiLanguageId", languageId);
            this.getCategory();
        },
        getCategory() {
            Shopware.ExtensionAPI.publishData({
                id: "sw-event-category-detail__event_category",
                path: "event-category",
                scope: this,
            });
            const criteria = new Criteria();
            criteria.addAssociation("translations");

            this.repository
                .get(this.eventCategoryId, Shopware.Context.api, criteria)
                .then((entity) =&amp;gt; {
                    console.log("entity", entity);
                    this.eventCategory = entity;
                });
        },
        onClickSave() {

            this.isLoading = true;
            this.repository
                .save(this.eventCategory)
                .then(() =&amp;gt; {
                    this.isLoading = false;
                    this.processSuccess = true;
                    this.createNotificationSuccess({
                        title: this.$tc("success"),
                        message: this.$tc("success", 0, {
                            name: this.eventCategory.name,
                        }),
                    });
                    if (this.eventCategoryId === null) {
                        this.$router.push({
                            name: "detail",
                            params: { id: this.eventCategory.id },
                        });
                        return;
                    }
                    this.getCategory();
                })
        },
        saveFinish() {
            this.processSuccess = false;
        },
    },
};

                    sw-event-category-detail.html.twig

{% block sw_event_category_detail %}
    &amp;lt;sw-page class="sw-event-detail"&amp;gt;
        {% block sw_product_list_language_switch %}
            &amp;lt;template #language-switch&amp;gt;
                &amp;lt;sw-language-switch :disabled="eventCategoryId == null || undefined" :save-changes-function="saveOnLanguageChange" :abort-change-function="abortOnLanguageChange" @on-change="onChangeLanguage"/&amp;gt;
            &amp;lt;/template&amp;gt;
        {% endblock %}
        &amp;lt;template #smart-bar-actions&amp;gt;
            &amp;lt;sw-button :router-link="{name: 'sw.event.category.index'}"&amp;gt;
                {{$tc('cancel')}}
            &amp;lt;/sw-button&amp;gt;
            &amp;lt;sw-button-process
                    :isLoading="isLoading"
                    :process-success="process"
                    variant="primary"
                    @process-finish="saveFinish"
                    @click="onClickSave"
            &amp;gt;
                {{ $tc('save') }}
            &amp;lt;/sw-button-process&amp;gt;
        &amp;lt;/template&amp;gt;
        &amp;lt;template #content&amp;gt;
            &amp;lt;sw-card-view&amp;gt;
                &amp;lt;sw-card v-if="eventCategory"&amp;gt;
                    &amp;lt;sw-text-field
                            v-model:value="eventCategory.name"
                            :placeholder="$tc('sw-event-category.detail.placeholderName')"
                            :label="$tc('sw-event-category.detail.labelName')"
                            name="name"
                            validation="required"
                            required
                    &amp;gt;
                    &amp;lt;/sw-text-field&amp;gt;
                &amp;lt;/sw-card&amp;gt;
            &amp;lt;/sw-card-view&amp;gt;
        &amp;lt;/template&amp;gt;
    &amp;lt;/sw-page&amp;gt;
{% endblock %}

                sw-event-category-list
                    index.js

import template from "./sw-event-category-list.html.twig";

const { Mixin } = Shopware;
const { Criteria } = Shopware.Data;

export default {
    template,
    inject: ["repositoryFactory"],
    data() {
        return {
            eventCategory: null,
            repository: null,
            isLoading: false,
            total: 0,
        };
    },
    metaInfo() {
        return {
            title: this.$createTitle(),
        };
    },
    computed: {
        columns() {
            return this.getColumns();
        },
    },
    created() {
        this.createComponent();
    },
    methods: {
        createComponent() {
            this.repository = this.repositoryFactory.create("event_category");
            this.getList();
        },
        getList() {
            this.isLoading = true;
            return this.repository
                .search(new Criteria(), Shopware.Context.api)
                .then((result) =&amp;gt; {
                    this.eventCategory = result;
                    this.total = result.total;
                    this.isLoading = false;
                })
                .catch(() =&amp;gt; {
                    this.isLoading = false;
                });
        },

        onChangeLanguage(languageId) {
            Shopware.State.commit("context/setApiLanguageId", languageId);
            this.getList();
        },
        getColumns() {
            return [
                {
                    property: "name",
                    label: "sw-event-category.list.columnName",
                    routerLink: "sw.event.category.detail",
                    inlineEdit: "string",
                    allowResize: true,
                    primary: true,
                },
                {
                    property: 'createdAt',
                    label: 'createdAt',
                    allowResize: true
                },
                {
                    property: 'updatedAt',
                    label: 'updatedAt',
                    allowResize: true
                }
            ];
        },
    },
};

                    sw-event-category-list.html.twig

{% block sw_event_category_list %}
    &amp;lt;sw-page class="sw-event-list"&amp;gt;
        {% block sw_event_category_list_search_bar %}
            &amp;lt;template #search-bar&amp;gt;
                &amp;lt;sw-search-bar initial-search-type="event" :initial-search="term" @search="onSearch"/&amp;gt;
            &amp;lt;/template&amp;gt;
        {% endblock %}

        {% block sw_event_category_list_smart_bar_header %}
            &amp;lt;template #smart-bar-header&amp;gt;
                {% block sw_event_category_list_smart_bar_header_title %}
                    &amp;lt;h2&amp;gt;
                        {% block sw_event_category_list_smart_bar_header_title_text %}
                            {{ $tc('sw-event-category.general.mainMenuItemGeneral') }}
                        {% endblock %}

                        {% block sw_event_category_list_smart_bar_header_amount %}
                            &amp;lt;span v-if="!isLoading" class="sw-page__smart-bar-amount"&amp;gt;
                                ({{ total }})
                            &amp;lt;/span&amp;gt;
                        {% endblock %}
                    &amp;lt;/h2&amp;gt;
                {% endblock %}
            &amp;lt;/template&amp;gt;
        {% endblock %}

        {% block sw_event_category_list_smart_bar_actions %}
            &amp;lt;template #smart-bar-actions&amp;gt;
                &amp;lt;sw-button
                        variant="primary"
                        :router-link="{ name: 'sw.event.category.create' }"
                &amp;gt;
                    &amp;lt;p&amp;gt;
                        {{ $tc('sw-event-category.list.buttonAddEvent') }}
                    &amp;lt;/p&amp;gt;
                &amp;lt;/sw-button&amp;gt;
            &amp;lt;/template&amp;gt;
        {% endblock %}
        {% block sw_event_list_language_switch %}
            &amp;lt;template #language-switch&amp;gt;
                &amp;lt;sw-language-switch @on-change="onChangeLanguage"/&amp;gt;
            &amp;lt;/template&amp;gt;
        {% endblock %}
        &amp;lt;template #content&amp;gt;
            {% block sw_event_category_list_content %}
                &amp;lt;sw-entity-listing
                        :items="eventCategory"
                        :repository="repository"
                        :showselection="false"
                        :columns="columns"
                        :is-loading="isLoading"
                        :allow-edit="true"
                        :allow-inline-edit="true"
                        :allow-delete="true"&amp;gt;
                &amp;lt;/sw-entity-listing&amp;gt;
            {% endblock %}
        &amp;lt;/template&amp;gt;
    &amp;lt;/sw-page&amp;gt;
{% endblock %}


            snippet
de

{
  "sw-event-category": {
    "general": {
      "mainMenuItemList": "Veranstaltungskategorie",
      "mainMenuItemGeneral": "Veranstaltungskategorie",
      "somethingWentWrong": "etwas ist schief gelaufen",
      "cancel": "Stornieren",
      "save": "Speichern"
    },
    "list": {
      "buttonAddEvent": "Ereigniskategorie hinzufügen",
      "columnName": "Name",
      "columnLink": "Webseite"
    },
    "detail": {
      "labelName": "Name",
      "labelDescription": "Beschreibung",
      "labelLink": "Webseite",
      "placeholderName": "Geben Sie den Namen der Ereigniskategorie ein ...",
      "titleSaveSuccess": "Erfolg",
      "messageSaveSuccess": "Event-Kategorie „{name}“ wurde gespeichert.",
      "errorNameRequired" : "Name ist erforderlich"

    }
  }
}

en

{
  "sw-event-category": {
    "general": {
      "mainMenuItemList": "Event category",
      "mainMenuItemGeneral": "Event category",
      "somethingWentWrong": "something went wrong",
      "cancel": "Cancel",
      "save": "Save"
    },
    "list": {
      "buttonAddEvent": "Add Event category",
      "columnName": "Name",
      "columnLink": "Website"
    },
    "detail": {
      "labelName": "Name",
      "labelDescription": "Description",
      "labelLink": "Website",
      "placeholderName": "Enter Event category name...",
      "titleSaveSuccess": "Success",
      "messageSaveSuccess": "Event category \"{name}\" has been saved.",
      "errorNameRequired" : "Name is required"
    }
  }
}

            index.js

import deDE from "./snippet/de-DE.json";
import enGB from "./snippet/en-GB.json";
const { Module } = Shopware;
Shopware.Component.register("sw-event-category-list", () =&amp;gt;
    import("./page/sw-event-category-list")
);
Shopware.Component.register("sw-event-category-detail", () =&amp;gt;
    import("./page/sw-event-category-detail")
);
Module.register("sw-event-category", {
    type: "plugin",
    name: "Event Category",
    title: "Event Category",
    description: "Event Category",
    color: "#176aaf",
    snippets: {
        "de-DE": deDE,
        "en-GB": enGB,
    },
    routes: {
        index: {
            component: "sw-event-category-list",
            path: "index",
            name: "sw.event.category.index",
        },
        create: {
            component: "sw-event-category-detail",
            path: "create",
            name: "sw.event.category.detail",
            meta: {
                parentPath: "sw.event.category.index",
                privilege: "event-category.creator",
            },
        },
        detail: {
            component: "sw-event-category-detail",
            path: "detail/:id",
            name: "sw.event.category.detail",
            meta: {
                parentPath: "sw.event.category.index",
                privilege: "event-category.viewer",
            },
            props: {
                default(route) {
                    return {
                        eventCategoryId: route.params.id,
                    };
                },
            },
        },
    },
    navigation: [
        {
            path: "sw.event.category.index",
            label: "sw-event-category.general.mainMenuItemList",
            id: "sw-event-category",
            parent: "sw-catalogue",
            color: "#176aaf",
            position: 100,
        },
    ],
});

        sw-event
page
    sw-event-detail
    index.js

import template from "./sw-event-detail.html.twig";

const {Mixin} = Shopware;
const {Criteria} = Shopware.Data;

export default {
    template,
    inject: ["repositoryFactory"],
    mixins: [
        Mixin.getByName("placeholder"),
        Mixin.getByName("notification"),
        Mixin.getByName("discard-detail-page-changes")("eventCategory"),
    ],

    data() {
        return {
            event: null,
            eventCategory: null,
            customer: null,
            eventRepository: null,
            eventCategoryRepository: null,
            customerRepository: null,
            isLoading: false,
            processSuccess: false,
            eventCategoryOptions: null,
        };
    },
    props: {
        eventId: {
            type: String,
            required: false,
            default: null,
        },
    },
    computed: {
        customerCriteria() {
            const criteria = new Criteria();
            return criteria;
        },
        customerId: {
            get() {
                return this.customer ? this.customer.id : '';
            },

            set(customerId) {
                if (this.customer) this.customer.id = customerId;
            },
        },

    },
    watch: {
        eventId() {
            this.createdComponent();
        },
    },
    metaInfo() {
        return {
            title: this.$createTitle(),
        };
    },
    created() {
        this.createdComponent();
    },
    methods: {
        createdComponent() {
            this.eventRepository = this.repositoryFactory.create("event");
            this.eventCategoryRepository =
                this.repositoryFactory.create("event_category");
            this.customerRepository = this.repositoryFactory.create("customer");
            this.getEventCategory();
            this.getCustomer();

            if (this.eventId) {
                this.getEvent();
                return;
            }
            Shopware.State.commit("context/resetLanguageToDefault");
            this.event = this.eventRepository.create();
        },

        getEvent() {
            const criteria = new Criteria();
            criteria.addAssociation("eventCategories");
            criteria.addAssociation("organizedBy");

            this.eventRepository
                .get(this.$route.params.id, Shopware.Context.api, criteria)
                .then((entity) =&amp;gt; {
                    this.event = entity;
                });
        },
        getEventCategory() {
            this.eventCategoryRepository
                .search(new Criteria(), Shopware.Context.api)
                .then((result) =&amp;gt; {
                    this.eventCategoryOptions = result;
                });
        },

        getCustomer() {
            this.customerRepository
                .search(new Criteria(), Shopware.Context.api)
                .then((result) =&amp;gt; {
                    this.customer = result;
                    // console.log(result);

                });
        },
        abortOnLanguageChange() {
            return this.eventRepository.hasChanges(this.event);
        },

        saveOnLanguageChange() {
            return this.onClickSave();
        },

        onChangeLanguage(languageId) {
            this.isLoading = true;
            Shopware.State.commit("context/setApiLanguageId", languageId);
            this.getEvent();
            this.getEventCategory();
            this.getCustomer();
        },
        onClickSave() {
            this.isLoading = true;
            this.eventRepository
                .save(this.event)
                .then(() =&amp;gt; {
                    this.isLoading = false;
                    this.processSuccess = true;
                    this.createNotificationSuccess({
                        title: this.$tc("success"),
                        message: this.$tc("success", 0, {
                            name: this.event.name,
                        }),
                    });
                    if (this.eventId === null) {
                        this.$router.push({
                            name: "sw.event.detail",
                            params: {id: this.event.id},
                        });
                        return;
                    }
                    this.getEvent();
                })
                .catch((exception) =&amp;gt; {
                    this.isLoading = false;
                    this.createNotificationError({
                        title: "exception",
                        message: exception,
                    });
                });
        },
        saveFinish() {
            this.processSuccess = false;
        },
    },
};

sw-event-detail.html.twig

{% block sw_event_detail %}
    &amp;lt;sw-page class="sw-event-detail"&amp;gt;
        {% block sw_product_list_language_switch %}
            &amp;lt;template #language-switch&amp;gt;
                &amp;lt;sw-language-switch
                        :disabled="eventId == null || undefined"
                        :save-changes-function="saveOnLanguageChange"
                        :abort-change-function="abortOnLanguageChange"
                        @on-change="onChangeLanguage"
                /&amp;gt;
            &amp;lt;/template&amp;gt;
        {% endblock %}
        &amp;lt;template #smart-bar-actions&amp;gt;
            &amp;lt;sw-button :router-link="{name: 'sw.event.index'}"&amp;gt;
                {{$tc('cancel')}}
            &amp;lt;/sw-button&amp;gt;
            &amp;lt;sw-button-process
                    :isloading="isLoading"
                    :processsuccess="processSuccess"
                    variant="primary"
                    @process-finish="saveFinish"
                    @click="onClickSave"&amp;gt;
                {{$tc('save')}}
            &amp;lt;/sw-button-process&amp;gt;
        &amp;lt;/template&amp;gt;
        &amp;lt;template #content&amp;gt;
            &amp;lt;sw-card-view&amp;gt;
                &amp;lt;sw-card v-if="event"&amp;gt;
                    &amp;lt;sw-text-field
                            v-model:value="event.name"
                            :placeholder="$tc('Name')"
                            :label="$tc('Name')"
                            name="name"
                            validation="required"
                            required
                    /&amp;gt;
                    &amp;lt;sw-textarea-field
                            v-model:value="event.description"
                            :placeholder="$tc('description')"
                            :label="$tc('Description')"
                            name="name"
                            validation="required"
                    &amp;gt;
                    &amp;lt;/sw-textarea-field&amp;gt;
                    &amp;lt;sw-datepicker
                            v-model:value="event.eventDate"
                            :label="$tc('Event date')"
                            name="event_date"
                    &amp;gt;
                    &amp;lt;/sw-datepicker&amp;gt;

                    &amp;lt;sw-switch-field
                            :label="$tc('Status')"
                            v-model:value="event.active"
                            name="active"
                    /&amp;gt;
                    &amp;lt;sw-entity-multi-select
                            :label="$tc('Category')"
                            :placeholder="$tc('Category')"
                            entity-name="event_category"
                            v-model:entityCollection="event.eventCategories"
                    &amp;gt;
                    &amp;lt;/sw-entity-multi-select&amp;gt;

                    &amp;lt;sw-entity-single-select
                            entity="customer"
                            :criteria="customerCriteria"
                            label-property="firstName"
                            :value="event.organizedById"
                            :label="$tc('Organized By')"
                            :placeholder="$tc('Organized By')"
                            show-clearable-button
                            @update:value="event.organizedById=$event"
                    /&amp;gt;
                &amp;lt;/sw-card&amp;gt;
            &amp;lt;/sw-card-view&amp;gt;
        &amp;lt;/template&amp;gt;
    &amp;lt;/sw-page&amp;gt;
{% endblock %}

    sw-event-list
index.js

import template from "./sw-event-list.html.twig";

const { Mixin } = Shopware;
const { Criteria } = Shopware.Data;

export default {
    template,
    inject: ["repositoryFactory"],
    data() {
        return {
            event: null,
            repository: null,
            isLoading: false,
            total: 0,
        };
    },
    metaInfo() {
        return {
            title: this.$createTitle(),
        };
    },
    computed: {
        columns() {
            return this.getColumns();
        },
    },
    created() {
        this.createComponent();
    },
    methods: {
        createComponent() {
            this.isLoading = true;
            this.repository = this.repositoryFactory.create("event");
            this.getList();
        },
        getList() {
            this.isLoading = true;
            return this.repository
                .search(new Criteria(), Shopware.Context.api)
                .then((result) =&amp;gt; {
                    this.event = result;
                    this.total = result.total;
                    this.isLoading = false;
                })
                .catch(() =&amp;gt; {
                    this.isLoading = false;
                });
        },
        onChangeLanguage(languageId) {
            this.isLoading = true;
            Shopware.State.commit('context/setApiLanguageId', languageId);
            this.getList();
        },
        getColumns() {
            return [
                {
                    property: "name",
                    label: this.$tc("Name"),
                    routerLink: "sw.event.detail",
                    inlineEdit: "string",
                    allowResize: true,
                    primary: true,
                },
                {
                    property: "description",
                    label: this.$tc("Description"),
                    inlineEdit: "string",
                    allowResize: true,
                    primary: false,
                },
                {
                    property: "active",
                    label: this.$tc("Active"),
                    inlineEdit: "boolean",
                    allowResize: true,
                    primary: false,
                    align: "center",
                    dataIndex: "active",
                    format: (value) =&amp;gt; {
                        return value ? "Active" : "Inactive";
                    },
                    cellComponent: "sw-boolean-badge",
                },
                { property: 'eventDate', label: 'event.fields.eventDate', allowResize: true },
            ];
        },
    },
};

sw-event-list.html.twig

{% block sw_event_list %}
    &amp;lt;sw-page class="sw-event-list"&amp;gt;

        {% block sw_event_list_search_bar %}
            &amp;lt;template #search-bar&amp;gt;
                &amp;lt;sw-search-bar initial-search-type="event" :initial-search="term" @search="onSearch"/&amp;gt;
            &amp;lt;/template&amp;gt;
        {% endblock %}

        {% block sw_event_list_smart_bar_header %}
            &amp;lt;template #smart-bar-header&amp;gt;
                {% block sw_event_list_smart_bar_header_title %}
                    &amp;lt;h2&amp;gt;
                        {% block sw_event_list_smart_bar_header_title_text %}
                            {{ $tc('title') }}
                        {% endblock %}

                        {% block sw_event_list_smart_bar_header_amount %}
                            &amp;lt;span v-if="!isLoading" class="sw-page__smart-bar-amount"&amp;gt;
                                ({{ total }})
                            &amp;lt;/span&amp;gt;
                        {% endblock %}
                    &amp;lt;/h2&amp;gt;
                {% endblock %}
            &amp;lt;/template&amp;gt;
        {% endblock %}
        {% block sw_event_list_smart_bar_actions %}
            &amp;lt;template #smart-bar-actions&amp;gt;
                &amp;lt;sw-button variant="primary" :router-link="{ name: 'sw.event.create' }"&amp;gt;
                    &amp;lt;p&amp;gt;
                        {{$tc('Create new Event')}}
                    &amp;lt;/p&amp;gt;
                &amp;lt;/sw-button&amp;gt;
            &amp;lt;/template&amp;gt;
        {% endblock %}
        {% block sw_event_list_language_switch %}
            &amp;lt;template #language-switch&amp;gt;
                &amp;lt;sw-language-switch @on-change="onChangeLanguage"/&amp;gt;
            &amp;lt;/template&amp;gt;
        {% endblock %}
        &amp;lt;template #content&amp;gt;
            {% block sw_event_list_content %}
                &amp;lt;sw-entity-listing
                        :items="event"
                        :repository="repository"
                        :showselection="false"
                        :columns="columns"
                        :is-loading="isLoading"
                        :allow-edit="true"
                        :allow-inline-edit="true"
                        :allow-delete="true"&amp;gt;
                &amp;lt;/sw-entity-listing&amp;gt;
            {% endblock %}
        &amp;lt;/template&amp;gt;
    &amp;lt;/sw-page&amp;gt;
{% endblock %}

snippet
de
{
  "event": {
    "module": {
      "name": "Event",
      "description": "Event verwalten",
      "label": "Event"
    },
    "general": {
      "mainMenuItemGeneral": "Event",
      "descriptionTextModule": "Allgemein",
      "createButton": "Neuen Event erstellen",
      "cancelButton": "Abbrechen",
      "saveButton": "Speichern Sie",
      "createTitle": "Event erstellen",
      "editTitle": "Event bearbeiten"
    },
    "fields": {
      "name": "Name",
      "description": "Beschreibung",
      "active": "Aktiv",
      "eventDate": "Ereignisdatum",
      "categories": "Event-Kategorien",
      "organizedBy": "organisiert von"
    },
    "list": {
      "title": "Events"
    }
  }
}
en

{
  "event": {
    "module": {
      "name": "Event",
      "description": "Manage event",
      "label": "Event"
    },
    "general": {
      "mainMenuItemGeneral": "Event",
      "descriptionTextModule": "Generally",
      "createButton": "Create new event",
      "cancelButton": "Cancel",
      "saveButton": "Save",
      "createTitle": "Create event",
      "editTitle": "Edit event"
    },
    "fields": {
      "name": "Name",
      "description": "Description",
      "active": "Active",
      "eventDate": "Event date",
      "categories": "Event categories",
      "organizedBy": "organized by"
    },
    "list": {
      "title": "Events"
    }
  }
}

index.js
import deDE from "./snippet/de-DE.json";
import enGB from "./snippet/en-GB.json";
const { Module } = Shopware;
Shopware.Component.register("sw-event-list", () =&amp;gt;
    import("./page/sw-event-list")
);
Shopware.Component.register("sw-event-detail", () =&amp;gt;
    import("./page/sw-event-detail")
);
Module.register("sw-event", {
    title: "Event",
    name: "Event",
    description: "Event",
    color: "#176aaf",
    snippets: {
        "de-DE": deDE,
        "en-GB": enGB,
    },
    routes: {
        index: {
            component: "sw-event-list",
            path: "index",
            name: "sw.event.index",
        },
        create: {
            component: 'sw-event-detail',
            path: 'create',
            name: "sw.event.detail",

            meta: {
                parentPath: 'sw.event.index',
                privilege: 'event.creator',
            },
        },
        detail: {
            component: 'sw-event-detail',
            path: 'detail/:id',
            name: "sw.event.detail",
            meta: {
                parentPath: 'sw.event.index',
                privilege: 'event.viewer',
            },
            props: {
                default(route) {
                    return {
                        eventId: route.params.id,
                    };
                },
            },
        },
    },
    navigation: [
        {
            path: "sw.event.index",
            label: "Event",
            id: "sw-event",
            parent: "sw-catalogue",
            color: "#176aaf",
            position: 100,
        },
    ],
});
    main.js

import './module/sw-event';
import './module/sw-event-category';

config
services.xml

&amp;lt;?xml version="1.0" ?&amp;gt;

&amp;lt;container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"&amp;gt;

    &amp;lt;services&amp;gt;
        &amp;lt;service id="EventTask\Core\Content\Event\EventDefinition"&amp;gt;
            &amp;lt;tag name="shopware.entity.definition" entity="event" /&amp;gt;
        &amp;lt;/service&amp;gt;
        &amp;lt;service id="EventTask\Core\Content\EventCategory\EventCategoryDefinition"&amp;gt;
            &amp;lt;tag name="shopware.entity.definition" entity="event_category" /&amp;gt;
        &amp;lt;/service&amp;gt;
        &amp;lt;service id="EventTask\Core\Content\EventCategoryMapping\EventCategoryMappingDefinition"&amp;gt;
            &amp;lt;tag name="shopware.entity.definition" entity="event_category_event" /&amp;gt;
        &amp;lt;/service&amp;gt;
        &amp;lt;service id="EventTask\Core\Content\Extension\CategoryExtension"&amp;gt;
            &amp;lt;tag name="shopware.entity.extension" /&amp;gt;
        &amp;lt;/service&amp;gt;
        &amp;lt;service id="EventTask\Core\Content\Extension\CustomerExtension"&amp;gt;
            &amp;lt;tag name="shopware.entity.extension" /&amp;gt;
        &amp;lt;/service&amp;gt;
        &amp;lt;service id="EventTask\Core\Content\Extension\LanguageExtension"&amp;gt;
            &amp;lt;tag name="shopware.entity.extension" /&amp;gt;
        &amp;lt;/service&amp;gt;
        &amp;lt;service id="EventTask\Core\Content\Event\Aggregate\EventTranslationDefinition"&amp;gt;
            &amp;lt;tag name="shopware.entity.definition" entity="event_translation" /&amp;gt;
        &amp;lt;/service&amp;gt;
        &amp;lt;service id="EventTask\Core\Content\EventCategory\Aggregate\EventCategoryTranslationDefinition"&amp;gt;
            &amp;lt;tag name="shopware.entity.definition" entity="event_category_translation" /&amp;gt;
        &amp;lt;/service&amp;gt;
    &amp;lt;/services&amp;gt;
&amp;lt;/container&amp;gt;

public-by default

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>cmselement</title>
      <dc:creator>vuejstest</dc:creator>
      <pubDate>Wed, 28 May 2025 17:24:07 +0000</pubDate>
      <link>https://dev.to/vuejstest_9d7a4f2fbe7631a/cmselement-5a3n</link>
      <guid>https://dev.to/vuejstest_9d7a4f2fbe7631a/cmselement-5a3n</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CmsElement

src
    resources
        app/admin/src
            module/sw-cms
                1.blocks/text-image/element-text
                    component
                        index.js

import template from './sw-cms-block-element-text.html.twig';
import './sw-cms-block-element-text.scss';

export default {
    template,
};
twig
{% block cms_block_element %}
    &amp;lt;div class="sw-cms-block-element-text"&amp;gt;
            &amp;lt;div class="sw-cms-column"&amp;gt;
                &amp;lt;slot name="left"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class="sw-cms-column"&amp;gt;
                &amp;lt;slot name="right"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}
scss

.sw-cms-block-element-text {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  grid-gap: 40px;
  align-items: start;
  padding: 20px;

  .sw-cms-column {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
}

                    preview
index.js
import template from './sw-cms-preview-element-text.html.twig';
import './sw-cms-preview-element-text.scss';

export default {
    template,
};
twig
{% block cms_preview_element %}
&amp;lt;div class="sw-cms-preview-element-text"&amp;gt;
        &amp;lt;div class="sw-cms-preview-column"&amp;gt;
            &amp;lt;div class="sw-cms-preview-left"&amp;gt;
                {{ $tc('element-text.general.textLabel') }}&amp;lt;input type="text" class="preview-field"&amp;gt;
                {{ $tc('element-text.general.urlLabel') }}&amp;lt;input  type="text" class="preview-field"&amp;gt;
            &amp;lt;/div&amp;gt;

        &amp;lt;/div&amp;gt;

        &amp;lt;div class="sw-cms-preview-column"&amp;gt;
            &amp;lt;div class="sw-cms-preview-right"&amp;gt;
                {{ $tc('element-text.general.textLabel') }}&amp;lt;input type="text" class="preview-field"&amp;gt;
                {{ $tc('element-text.general.urlLabel') }}&amp;lt;input  type="text" class="preview-field"&amp;gt;
            &amp;lt;/div&amp;gt;

        &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
scss
.sw-cms-preview-element-text {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-gap: 20px;
  padding: 15px;

  .preview-field {
    height: auto;
    max-height: 40px;
    overflow: hidden;
    width: 120px
  }
  .sw-cms-preview-column {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 8px;

    p {
      margin: 0;
      font-size: 13px;
    }
  }
}
                    snippet
de
{
  "element-text": {
    "module": {
      "label": "Element-Text"
    },
    "general": {
      "textLabel": "Text",
      "urlLabel": "Url-Link"
    }
  }
}
en
{
  "element-text": {
    "module": {
      "label": "Element Text"
    },
    "general": {
      "textLabel": "Text",
      "urlLabel": "Url Link"
    }
  }
}
                    index.js

Shopware.Component.register('sw-cms-preview-element-text', () =&amp;gt; import('./preview'));

Shopware.Component.register('sw-cms-block-element-text', () =&amp;gt; import('./component'));

Shopware.Service('cmsService').registerCmsBlock({
    name: 'element-text',
    label: 'element-text.module.label',
    category: 'customCategory',
    component: 'sw-cms-block-element-text',
    previewComponent: 'sw-cms-preview-element-text',
    defaultConfig: {
        marginBottom: '20px',
        marginTop: '20px',
        marginLeft: '20px',
        marginRight: '20px',
        sizingMode: 'boxed',
    },
    slots: {
        'left':{
            type: 'custom-element',
            default: {
                config: {
                    text: {
                        source: 'static',
                        value: 'Hello World 1'
                    },
                    url: {
                        source: 'static',
                        value: 'https://google.com/'
                    }
                }
            }
        },

        'right':{
            type: 'custom-element',
            default: {
                config: {
                    text: {
                        source: 'static',
                        value: 'Hello World 2'
                    },
                    url: {
                        source: 'static',
                        value: 'https://google.com/'
                    }
                }
            }
        },


    },
});
                2.component/sw-cms-sidebar
                    index.js
                        const { Component } = Shopware;
import template from './sw-cms-sidebar.html.twig';

Component.override('sw-cms-sidebar', {
    template
});
                    sw-cms-sidebar.html.twig
{% block sw_cms_sidebar_block_overview_category_options %}
    {% parent %}
    &amp;lt;option value="customCategory"&amp;gt;Custom Category&amp;lt;/option&amp;gt;
{% endblock %}

                3.elements/custom-element
                    component
                        index.js
import template from './sw-cms-el-custom-element.html.twig';
const { Mixin } = Shopware;
const { Component } = Shopware;

Component.register('sw-cms-el-custom-element', {
    template,

    mixins: [
        Mixin.getByName('cms-element')
    ],

    created() {
        this.createdComponent();
    },

    methods:{
        createdComponent() {
            this.initElementConfig('custom-element');
        }
    },
});

                        twig

{% block cms_custom_element %}
&amp;lt;div class="sw-cms-el-custom-element"&amp;gt;
        {% block sw_cms_element_custom_element_content %}
            &amp;lt;div v-for="(item, index) in element.config.items.value" :key="index"&amp;gt;
                    &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;{{ $tc('custom-element.general.textLabel') }}:&amp;lt;/strong&amp;gt; {{ item.text }}&amp;lt;/p&amp;gt;
                    &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;{{ $tc('custom-element.general.urlLabel') }}:&amp;lt;/strong&amp;gt; {{ item.url }}&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
        {% endblock %}
&amp;lt;/div&amp;gt;
{% endblock %}

                        scss
.sw-cms-el-custom-element {
    display: flex;
    max-width: 100%;
}
                    config
index.js
import template from './sw-cms-el-config-custom-element.html.twig';
const { Mixin } = Shopware;
const { Component } = Shopware;

Component.register('sw-cms-el-config-custom-element', {
    template,

    mixins: [
        Mixin.getByName('cms-element')
    ],

    created() {
        this.createdComponent();
    },

    methods: {
        createdComponent() {
            this.initElementConfig('custom-element');
            if (!this.element.config.items.value) {
                this.$set(this.element.config.items, 'value', [
                    { text: 'Hello World!', url: 'https://google.com/' }
                ]);
            }
        },

        addField() {
            this.element.config.items.value.push({ text: '', url: '' });
            this.onConfigUpdate();
        },

        removeField(index) {
            this.element.config.items.value.splice(index, 1);
            this.onConfigUpdate();
        },

        onConfigUpdate() {
            this.$emit('element-update', this.element);
        }
    }
});
twig
{% block cms_config_custom_element %}
&amp;lt;div&amp;gt;
    &amp;lt;div v-for="(item, index) in element.config.items.value" :key="index" class="sw-field"&amp;gt;
        &amp;lt;sw-text-field
                    label="Text"
                    v-model:value="item.text"
                    @input="onConfigUpdate"
                    style="margin-bottom: 10px;"
        &amp;gt;
        &amp;lt;/sw-text-field&amp;gt;
        &amp;lt;sw-text-field
                    label="URL"
                    v-model:value="item.url"
                    @input="onConfigUpdate"
                    style="margin-bottom: 10px;"
        &amp;gt;
        &amp;lt;/sw-text-field&amp;gt;
        &amp;lt;sw-button @click="removeField(index)" variant="danger" size="small"&amp;gt;Remove&amp;lt;/sw-button&amp;gt;
    &amp;lt;/div&amp;gt;
        &amp;lt;sw-button @click="addField" variant="primary" size="small"&amp;gt;Add More Element&amp;lt;/sw-button&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}
                    preview
index.js
import template from './sw-cms-el-preview-custom-element.html.twig';
import './sw-cms-el-preview-custom-element.scss';

const { Component } = Shopware;

Component.register('sw-cms-el-preview-custom-element', {
    template,

});
twig

{% block cms_preview_custom_element %}
&amp;lt;div class="sw-cms-el-preview-custom-element"&amp;gt;
    {{ $tc('custom-element.general.textLabel') }}&amp;lt;input type="text" class="preview-field"&amp;gt;
    {{ $tc('custom-element.general.urlLabel') }}:&amp;lt;input  type="text" class="preview-field"&amp;gt;
&amp;lt;/div&amp;gt;
{% endblock %}

scss

.sw-cms-el-preview-custom-element {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    gap: 10px;

    .preview-field {
        height: auto;
        max-height: 40px;
        overflow: hidden;
    }
}

                    snippet
de
{
  "custom-element": {
    "module": {
      "label": "Benutzerdefiniertes Element"
    },
    "general": {
      "textLabel": "Text",
      "urlLabel": "Url Link"
    }
  }
}
en

{
  "custom-element": {
    "module": {
      "label": "Custom Element"
    },
    "general": {
      "textLabel": "Text",
      "urlLabel": "Url Link"
    }
  }
}

                    index.js
import './component';
import './config';
import './preview';

Shopware.Service('cmsService').registerCmsElement({
    name: 'custom-element',
    label: 'custom-element.module.label',
    component: 'sw-cms-el-custom-element',
    configComponent: 'sw-cms-el-config-custom-element',
    previewComponent: 'sw-cms-el-preview-custom-element',
    defaultConfig: {
        items: {
            source: 'static',
            value: [
                {
                    text: 'Hello World! 1',
                    url: 'https://google.com/'
                },
                {
                    text: 'Hello World! 2',
                    url: 'https://google.com/'
                },
                {
                    text: 'Hello World! 3',
                    url: 'https://google.com/'
                }
            ]
        }
    }
});
            main.js

import './module/sw-cms/blocks/text-image/element-text';
import './module/sw-cms/elements/custom-element'
import './module/sw-cms/component/sw-cms-sidebar';

        config
services.xml
&amp;lt;?xml version="1.0" ?&amp;gt;

&amp;lt;container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"&amp;gt;

    &amp;lt;services&amp;gt;

    &amp;lt;/services&amp;gt;
&amp;lt;/container&amp;gt;
        public
            admin
                css
                    cms-element.css
.sw-cms-el-preview-custom-element{width:100%;height:100%;display:flex;flex-direction:column;gap:10px}.sw-cms-el-preview-custom-element .preview-field{height:auto;max-height:40px;overflow:hidden}
                js
cms-element.js

!function(){var e,t,n,o,r,i,s,l,a={672:function(){},974:function(e,t,n){Shopware.Component.register("sw-cms-preview-element-text",()=&amp;gt;n.e(172).then(n.bind(n,172))),Shopware.Component.register("sw-cms-block-element-text",()=&amp;gt;n.e(913).then(n.bind(n,913))),Shopware.Service("cmsService").registerCmsBlock({name:"element-text",label:"element-text.module.label",category:"customCategory",component:"sw-cms-block-element-text",previewComponent:"sw-cms-preview-element-text",defaultConfig:{marginBottom:"20px",marginTop:"20px",marginLeft:"20px",marginRight:"20px",sizingMode:"boxed"},slots:{left:{type:"custom-element",default:{config:{text:{source:"static",value:"Hello World 1"},url:{source:"static",value:"https://google.com/"}}}},right:{type:"custom-element",default:{config:{text:{source:"static",value:"Hello World 2"},url:{source:"static",value:"https://google.com/"}}}}}})},825:function(e,t,n){var o=n(672);o.__esModule&amp;amp;&amp;amp;(o=o.default),"string"==typeof o&amp;amp;&amp;amp;(o=[[e.id,o,""]]),o.locals&amp;amp;&amp;amp;(e.exports=o.locals),(0,n(534).A)("5f5d52a7",o,!0,{})},534:function(e,t,n){"use strict";function o(e,t){for(var n=[],o={},r=0;r&amp;lt;t.length;r++){var i=t[r],s=i[0],l={id:e+":"+r,css:i[1],media:i[2],sourceMap:i[3]};o[s]?o[s].parts.push(l):n.push(o[s]={id:s,parts:[l]})}return n}n.d(t,{A:function(){return g}});var r,i="undefined"!=typeof document;if("undefined"!=typeof DEBUG&amp;amp;&amp;amp;DEBUG&amp;amp;&amp;amp;!i)throw Error("vue-style-loader cannot be used in a non-browser environment. Use { target: 'node' } in your Webpack config to indicate a server-rendering environment.");var s={},l=i&amp;amp;&amp;amp;(document.head||document.getElementsByTagName("head")[0]),a=null,c=0,m=!1,u=function(){},d=null,p="data-vue-ssr-id",f="undefined"!=typeof navigator&amp;amp;&amp;amp;/msie [6-9]\b/.test(navigator.userAgent.toLowerCase());function g(e,t,n,r){m=n,d=r||{};var i=o(e,t);return v(i),function(t){for(var n=[],r=0;r&amp;lt;i.length;r++){var l=s[i[r].id];l.refs--,n.push(l)}t?v(i=o(e,t)):i=[];for(var r=0;r&amp;lt;n.length;r++){var l=n[r];if(0===l.refs){for(var a=0;a&amp;lt;l.parts.length;a++)l.parts[a]();delete s[l.id]}}}}function v(e){for(var t=0;t&amp;lt;e.length;t++){var n=e[t],o=s[n.id];if(o){o.refs++;for(var r=0;r&amp;lt;o.parts.length;r++)o.parts[r](n.parts[r]);for(;r&amp;lt;n.parts.length;r++)o.parts.push(b(n.parts[r]));o.parts.length&amp;gt;n.parts.length&amp;amp;&amp;amp;(o.parts.length=n.parts.length)}else{for(var i=[],r=0;r&amp;lt;n.parts.length;r++)i.push(b(n.parts[r]));s[n.id]={id:n.id,refs:1,parts:i}}}}function h(){var e=document.createElement("style");return e.type="text/css",l.appendChild(e),e}function b(e){var t,n,o=document.querySelector("style["+p+'~="'+e.id+'"]');if(o){if(m)return u;o.parentNode.removeChild(o)}if(f){var r=c++;t=y.bind(null,o=a||(a=h()),r,!1),n=y.bind(null,o,r,!0)}else t=x.bind(null,o=h()),n=function(){o.parentNode.removeChild(o)};return t(e),function(o){o?(o.css!==e.css||o.media!==e.media||o.sourceMap!==e.sourceMap)&amp;amp;&amp;amp;t(e=o):n()}}var w=(r=[],function(e,t){return r[e]=t,r.filter(Boolean).join("\n")});function y(e,t,n,o){var r=n?"":o.css;if(e.styleSheet)e.styleSheet.cssText=w(t,r);else{var i=document.createTextNode(r),s=e.childNodes;s[t]&amp;amp;&amp;amp;e.removeChild(s[t]),s.length?e.insertBefore(i,s[t]):e.appendChild(i)}}function x(e,t){var n=t.css,o=t.media,r=t.sourceMap;if(o&amp;amp;&amp;amp;e.setAttribute("media",o),d.ssrId&amp;amp;&amp;amp;e.setAttribute(p,t.id),r&amp;amp;&amp;amp;(n+="\n/*# sourceURL="+r.sources[0]+" */",n+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(r))))+" */"),e.styleSheet)e.styleSheet.cssText=n;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(n))}}}},c={};function m(e){var t=c[e];if(void 0!==t)return t.exports;var n=c[e]={id:e,exports:{}};return a[e](n,n.exports,m),n.exports}m.m=a,m.d=function(e,t){for(var n in t)m.o(t,n)&amp;amp;&amp;amp;!m.o(e,n)&amp;amp;&amp;amp;Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},m.f={},m.e=function(e){return Promise.all(Object.keys(m.f).reduce(function(t,n){return m.f[n](e,t),t},[]))},m.u=function(e){return"static/js/"+({172:"8bccec7180d799c37ea4",913:"da257c08312450b2823e"})[e]+".js"},m.miniCssF=function(e){return"static/css/"+(853===e?"cms-element":e)+".css"},m.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},e={},t="cms-element:",m.l=function(n,o,r,i){if(e[n]){e[n].push(o);return}if(void 0!==r)for(var s,l,a=document.getElementsByTagName("script"),c=0;c&amp;lt;a.length;c++){var u=a[c];if(u.getAttribute("src")==n||u.getAttribute("data-webpack")==t+r){s=u;break}}s||(l=!0,(s=document.createElement("script")).charset="utf-8",s.timeout=120,m.nc&amp;amp;&amp;amp;s.setAttribute("nonce",m.nc),s.setAttribute("data-webpack",t+r),s.src=n),e[n]=[o];var d=function(t,o){s.onerror=s.onload=null,clearTimeout(p);var r=e[n];if(delete e[n],s.parentNode&amp;amp;&amp;amp;s.parentNode.removeChild(s),r&amp;amp;&amp;amp;r.forEach(function(e){return e(o)}),t)return t(o)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:s}),12e4);s.onerror=d.bind(null,s.onerror),s.onload=d.bind(null,s.onload),l&amp;amp;&amp;amp;document.head.appendChild(s)},m.r=function(e){"undefined"!=typeof Symbol&amp;amp;&amp;amp;Symbol.toStringTag&amp;amp;&amp;amp;Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},m.p="bundles/cmselement/",n=function(e,t,n,o){var r=document.createElement("link");return r.rel="stylesheet",r.type="text/css",r.onerror=r.onload=function(i){if(r.onerror=r.onload=null,"load"===i.type)n();else{var s=i&amp;amp;&amp;amp;("load"===i.type?"missing":i.type),l=i&amp;amp;&amp;amp;i.target&amp;amp;&amp;amp;i.target.href||t,a=Error("Loading CSS chunk "+e+" failed.\n("+l+")");a.code="CSS_CHUNK_LOAD_FAILED",a.type=s,a.request=l,r.parentNode.removeChild(r),o(a)}},r.href=t,document.head.appendChild(r),r},o=function(e,t){for(var n=document.getElementsByTagName("link"),o=0;o&amp;lt;n.length;o++){var r=n[o],i=r.getAttribute("data-href")||r.getAttribute("href");if("stylesheet"===r.rel&amp;amp;&amp;amp;(i===e||i===t))return r}for(var s=document.getElementsByTagName("style"),o=0;o&amp;lt;s.length;o++){var r=s[o],i=r.getAttribute("data-href");if(i===e||i===t)return r}},r={853:0},m.f.miniCss=function(e,t){r[e]?t.push(r[e]):0!==r[e]&amp;amp;&amp;amp;({172:1,913:1})[e]&amp;amp;&amp;amp;t.push(r[e]=new Promise(function(t,r){var i=m.miniCssF(e),s=m.p+i;if(o(i,s))return t();n(e,s,t,r)}).then(function(){r[e]=0},function(t){throw delete r[e],t}))},i={853:0},m.f.j=function(e,t){var n=m.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else{var o=new Promise(function(t,o){n=i[e]=[t,o]});t.push(n[2]=o);var r=m.p+m.u(e),s=Error();m.l(r,function(t){if(m.o(i,e)&amp;amp;&amp;amp;(0!==(n=i[e])&amp;amp;&amp;amp;(i[e]=void 0),n)){var o=t&amp;amp;&amp;amp;("load"===t.type?"missing":t.type),r=t&amp;amp;&amp;amp;t.target&amp;amp;&amp;amp;t.target.src;s.message="Loading chunk "+e+" failed.\n("+o+": "+r+")",s.name="ChunkLoadError",s.type=o,s.request=r,n[1](s)}},"chunk-"+e,e)}}},s=function(e,t){var n,o,r=t[0],s=t[1],l=t[2],a=0;if(r.some(function(e){return 0!==i[e]})){for(n in s)m.o(s,n)&amp;amp;&amp;amp;(m.m[n]=s[n]);l&amp;amp;&amp;amp;l(m)}for(e&amp;amp;&amp;amp;e(t);a&amp;lt;r.length;a++)o=r[a],m.o(i,o)&amp;amp;&amp;amp;i[o]&amp;amp;&amp;amp;i[o][0](),i[o]=0},(l=window["webpackJsonpPlugincms-element"]=window["webpackJsonpPlugincms-element"]||[]).forEach(s.bind(null,0)),l.push=s.bind(null,l.push.bind(l)),window?.__sw__?.assetPath&amp;amp;&amp;amp;(m.p=window.__sw__.assetPath+"/bundles/cmselement/"),function(){"use strict";m(974);let{Mixin:e}=Shopware,{Component:t}=Shopware;t.register("sw-cms-el-custom-element",{template:'{% block cms_custom_element %}\n&amp;lt;div class="sw-cms-el-custom-element"&amp;gt;\n        {% block sw_cms_element_custom_element_content %}\n            &amp;lt;div v-for="(item, index) in element.config.items.value" :key="index"&amp;gt;\n                    &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;{{ $tc(\'custom-element.general.textLabel\') }}:&amp;lt;/strong&amp;gt; {{ item.text }}&amp;lt;/p&amp;gt;\n                    &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;{{ $tc(\'custom-element.general.urlLabel\') }}:&amp;lt;/strong&amp;gt; {{ item.url }}&amp;lt;/p&amp;gt;\n            &amp;lt;/div&amp;gt;\n        {% endblock %}\n&amp;lt;/div&amp;gt;\n{% endblock %}\n',mixins:[e.getByName("cms-element")],created(){this.createdComponent()},methods:{createdComponent(){this.initElementConfig("custom-element")}}});let{Mixin:n}=Shopware,{Component:o}=Shopware;o.register("sw-cms-el-config-custom-element",{template:'{% block cms_config_custom_element %}\n&amp;lt;div&amp;gt;\n    &amp;lt;div v-for="(item, index) in element.config.items.value" :key="index" class="sw-field"&amp;gt;\n        &amp;lt;sw-text-field\n                    label="Text"\n                    v-model:value="item.text"\n                    @input="onConfigUpdate"\n                    style="margin-bottom: 10px;"\n        &amp;gt;\n        &amp;lt;/sw-text-field&amp;gt;\n        &amp;lt;sw-text-field\n                    label="URL"\n                    v-model:value="item.url"\n                    @input="onConfigUpdate"\n                    style="margin-bottom: 10px;"\n        &amp;gt;\n        &amp;lt;/sw-text-field&amp;gt;\n        &amp;lt;sw-button @click="removeField(index)" variant="danger" size="small"&amp;gt;Remove&amp;lt;/sw-button&amp;gt;\n    &amp;lt;/div&amp;gt;\n        &amp;lt;sw-button @click="addField" variant="primary" size="small"&amp;gt;Add More Element&amp;lt;/sw-button&amp;gt;\n&amp;lt;/div&amp;gt;\n{% endblock %}',mixins:[n.getByName("cms-element")],created(){this.createdComponent()},methods:{createdComponent(){this.initElementConfig("custom-element"),this.element.config.items.value||this.$set(this.element.config.items,"value",[{text:"Hello World!",url:"https://google.com/"}])},addField(){this.element.config.items.value.push({text:"",url:""}),this.onConfigUpdate()},removeField(e){this.element.config.items.value.splice(e,1),this.onConfigUpdate()},onConfigUpdate(){this.$emit("element-update",this.element)}}}),m(825);let{Component:r}=Shopware;r.register("sw-cms-el-preview-custom-element",{template:'{% block cms_preview_custom_element %}\n&amp;lt;div class="sw-cms-el-preview-custom-element"&amp;gt;\n    {{ $tc(\'custom-element.general.textLabel\') }}&amp;lt;input type="text" class="preview-field"&amp;gt;\n    {{ $tc(\'custom-element.general.urlLabel\') }}:&amp;lt;input  type="text" class="preview-field"&amp;gt;\n&amp;lt;/div&amp;gt;\n{% endblock %}\n'}),Shopware.Service("cmsService").registerCmsElement({name:"custom-element",label:"custom-element.module.label",component:"sw-cms-el-custom-element",configComponent:"sw-cms-el-config-custom-element",previewComponent:"sw-cms-el-preview-custom-element",defaultConfig:{items:{source:"static",value:[{text:"Hello World! 1",url:"https://google.com/"},{text:"Hello World! 2",url:"https://google.com/"},{text:"Hello World! 3",url:"https://google.com/"}]}}});let{Component:i}=Shopware;i.override("sw-cms-sidebar",{template:'{% block sw_cms_sidebar_block_overview_category_options %}\n    {% parent %}\n    &amp;lt;option value="customCategory"&amp;gt;Custom Category&amp;lt;/option&amp;gt;\n{% endblock %}'})}()}();
            static
        views/storefront
block
    cms-block-element-text.html.twig
{% block block_element_text %}
    {% set columns = 2 %}

    {% block block_element_left %}
        &amp;lt;div class="col-md-6"&amp;gt;
            {% set element = block.slots.getSlot('left') %}
            {% block block_element_left_inner %}
                &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                    {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                &amp;lt;/div&amp;gt;
            {% endblock %}

        &amp;lt;/div&amp;gt;
    {% endblock %}

    {% block block_element_right %}
        &amp;lt;div class="col-md-6"&amp;gt;
            {% set element = block.slots.getSlot('right') %}
            {% block block_element_right_inner %}
                &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                    {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                &amp;lt;/div&amp;gt;
            {% endblock %}

        &amp;lt;/div&amp;gt;
    {% endblock %}
{% endblock %}
element
cms-element-custom-element.twig
{% block cms_element_custom_element %}
    &amp;lt;div class="cms-element-custom-element"&amp;gt;
        &amp;lt;div class="custom-grid"&amp;gt;
            {% for item in element.config.items.value %}
                &amp;lt;div class="custom-item"&amp;gt;
                    {% if item.text is not empty %}
                        &amp;lt;strong&amp;gt;Text:&amp;lt;/strong&amp;gt; &amp;lt;a class="custom-text" href="{{ item.url }}"&amp;gt;{{ item.text }}&amp;lt;/a&amp;gt;
                    {% endif %}
                    {% if item.url is not empty %}
                        &amp;lt;div class="custom-url"&amp;gt;
                            &amp;lt;strong&amp;gt;URL:&amp;lt;/strong&amp;gt;
                            &amp;lt;a href="{{ item.url }}" target="_blank" rel="noopener noreferrer"&amp;gt;
                                {{ item.url }}
                            &amp;lt;/a&amp;gt;
                        &amp;lt;/div&amp;gt;
                    {% endif %}
                &amp;lt;/div&amp;gt;
            {% endfor %}
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}
    cmselement.php
tests
TestBootstrap.php
&amp;lt;?php declare(strict_types=1);

use Shopware\Core\TestBootstrapper;

$loader = (new TestBootstrapper())
    -&amp;gt;addCallingPlugin()
    -&amp;gt;addActivePlugins('CmsElement')
    -&amp;gt;setForceInstallPlugins(true)
    -&amp;gt;bootstrap()
    -&amp;gt;getClassLoader();

$loader-&amp;gt;addPsr4('CmsElement\\Tests\\', __DIR__);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>blogcms</title>
      <dc:creator>vuejstest</dc:creator>
      <pubDate>Wed, 28 May 2025 17:22:29 +0000</pubDate>
      <link>https://dev.to/vuejstest_9d7a4f2fbe7631a/blogcms-4e78</link>
      <guid>https://dev.to/vuejstest_9d7a4f2fbe7631a/blogcms-4e78</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;blog
src
    dataresolver
CustomImageCmsElementResolver

&amp;lt;?php declare(strict_types=1);

namespace Blog\DataResolver;

use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
use Shopware\Core\Content\Cms\DataResolver\CriteriaCollection;
use Shopware\Core\Content\Cms\DataResolver\Element\AbstractCmsElementResolver;
use Shopware\Core\Content\Cms\DataResolver\Element\ElementDataCollection;
use Shopware\Core\Content\Cms\DataResolver\FieldConfig;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\EntityResolverContext;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
use Shopware\Core\Content\Cms\SalesChannel\Struct\ImageStruct;
use Shopware\Core\Content\Media\MediaDefinition;
use Shopware\Core\Content\Media\MediaEntity;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Shopware\Core\Framework\Uuid\Uuid;

class CustomImageCmsElementResolver extends AbstractCmsElementResolver
{
    public function getType(): string
    {
        return 'custom-image';
    }

    public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
    {
        $config = $slot-&amp;gt;getFieldConfig();

        $imageConfig = $config-&amp;gt;get('media');

        if (!$imageConfig || $imageConfig-&amp;gt;isMapped()) {
            return null;
        }

        $mediaId = $imageConfig-&amp;gt;getStringValue();

        if (empty($mediaId)) {
            return null;
        }

        $criteria = new Criteria([$mediaId]);
        $collection = new CriteriaCollection();
        $collection-&amp;gt;add('media_' . $slot-&amp;gt;getUniqueIdentifier(), MediaDefinition::class, $criteria);

        return $collection;
    }

    public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
    {
        $config = $slot-&amp;gt;getFieldConfig();
        $data = new ArrayEntity();
        $data-&amp;gt;setUniqueIdentifier(Uuid::randomHex());
        $slot-&amp;gt;setData($data);

        $image = new ImageStruct();
        $imageConfig = $config-&amp;gt;get('media');

        if ($imageConfig) {
            $this-&amp;gt;addMediaEntity($slot, $image, $result, $imageConfig, $resolverContext);
        }

        $data-&amp;gt;set('media', $image);


        $urlConfig = $config-&amp;gt;get('url');
        if ($urlConfig) {
            $data-&amp;gt;set('url', $urlConfig-&amp;gt;getStringValue());
        }
    }

    private function addMediaEntity(
        CmsSlotEntity $slot,
        ImageStruct $image,
        ElementDataCollection $result,
        FieldConfig $config,
        ResolverContext $resolverContext
    ): void {
        if ($config-&amp;gt;isMapped() &amp;amp;&amp;amp; $resolverContext instanceof EntityResolverContext) {

            $media = $this-&amp;gt;resolveEntityValue($resolverContext-&amp;gt;getEntity(), $config-&amp;gt;getValue());
            if ($media !== null) {
                $image-&amp;gt;setMediaId($media-&amp;gt;getUniqueIdentifier());
                $image-&amp;gt;setMedia($media);
            }
        }

        if ($config-&amp;gt;isStatic()) {
            $mediaId = $config-&amp;gt;getStringValue();
            $image-&amp;gt;setMediaId($mediaId);

            $searchResult = $result-&amp;gt;get('media_' . $slot-&amp;gt;getUniqueIdentifier());
            if ($searchResult) {

                $media = $searchResult-&amp;gt;get($mediaId);
                if ($media !== null) {
                    $image-&amp;gt;setMedia($media);
                }
            }
        }
    }
}
resources
    app
        admin/src
            module/sw-cms
                blocks/text-image
                    image-four-row
                        component
index.js

import template from './sw-cms-block-image-four-row.html.twig';
import './sw-cms-block-image-four-row.scss';

export default {
    template,
};
twig

{% block sw_cms_block_image_four_row %}
    &amp;lt;div class="sw-cms-block-image-four-row"&amp;gt;
        &amp;lt;div class="sw-cms-column"&amp;gt;
            &amp;lt;slot name="left-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="left-button"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="left-text"&amp;gt;&amp;lt;/slot&amp;gt;

        &amp;lt;/div&amp;gt;
        &amp;lt;div class="sw-cms-column"&amp;gt;
            &amp;lt;slot name="center-left-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-left-button"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-left-text"&amp;gt;&amp;lt;/slot&amp;gt;

        &amp;lt;/div&amp;gt;
        &amp;lt;div class="sw-cms-column"&amp;gt;
            &amp;lt;slot name="center-right-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-right-button"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="center-right-text"&amp;gt;&amp;lt;/slot&amp;gt;

        &amp;lt;/div&amp;gt;
        &amp;lt;div class="sw-cms-column"&amp;gt;
            &amp;lt;slot name="right-image"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="right-button"&amp;gt;&amp;lt;/slot&amp;gt;
            &amp;lt;slot name="right-text"&amp;gt;&amp;lt;/slot&amp;gt;

        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}
scss

.sw-cms-block-image-four-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  grid-gap: 40px;
  align-items: start;
  padding: 20px;


  .sw-cms-column {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;

    .sw-cms-el-image.is--cover {
      width: 100%;
      min-height: 200px;
    }

    .sw-cms-el-text {
      padding-top: 10px;
    }

    .sw-cms-el-button {
      margin-top: 10px;
    }
  }
}

                        preview
index.js

import template from './sw-cms-preview-image-four-row.html.twig';
import './sw-cms-preview-image-four-row.scss';

export default {
    template,

    computed: {
        assetFilter() {
            return Shopware.Filter.getByName('asset');
        },
    },
};
twig

import template from './sw-cms-preview-image-four-row.html.twig';
import './sw-cms-preview-image-four-row.scss';

export default {
    template,

    computed: {
        assetFilter() {
            return Shopware.Filter.getByName('asset');
        },
    },
};
scss

.sw-cms-preview-image-four-row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-gap: 20px;
  padding: 15px;

  .sw-cms-preview-column {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 8px;

    .sw-cms-preview-image-text-row__image {
      width: 100%;
      height: 80px;
      overflow: hidden;
    }

    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    .sw-cms-preview-button {
      padding: 5px 10px;
      background: #189eff;
      color: #fff;
      font-size: 12px;
      border-radius: 3px;
    }

    p {
      margin: 0;
      font-size: 13px;
    }
  }
}

                        index.js

import CMS from '../../../constant/sw-cms.constant';

Shopware.Component.register('sw-cms-preview-image-four-row', () =&amp;gt; import('./preview'));

Shopware.Component.register('sw-cms-block-image-four-row', () =&amp;gt; import('./component'));

Shopware.Service('cmsService').registerCmsBlock({
    name: 'image-four-row',
    label: 'Image Four Row',
    category: 'text-image',
    component: 'sw-cms-block-image-four-row',
    previewComponent: 'sw-cms-preview-image-four-row',
    defaultConfig: {
        marginBottom: '20px',
        marginTop: '20px',
        marginLeft: '20px',
        marginRight: '20px',
        sizingMode: 'boxed',
    },
    slots: {
        'left-button':{
            type: 'button',
            default: {
                config: {
                    title: {
                        source: 'static',
                        value: 'Click Me'
                    },
                    url: {
                        source: 'static',
                        value: 'https://example.com'
                    }
                }
            }
        },
        'center-left-button':{
            type: 'button',
            default: {
                config: {
                    title: {
                        source: 'static',
                        value: 'Click Me'
                    },
                    url: {
                        source: 'static',
                        value: 'https://example.com'
                    }
                }
            }
        },

        'center-right-button':{
            type: 'button',
            default: {
                config: {
                    title: {
                        source: 'static',
                        value: 'Click Me'
                    },
                    url: {
                        source: 'static',
                        value: 'https://example.com'
                    }
                }
            }
        },

        'right-button':{
            type: 'button',
            default: {
                config: {
                    title: {
                        source: 'static',
                        value: 'Click Me'
                    },
                    url: {
                        source: 'static',
                        value: 'https://example.com'
                    }
                }
            }
        },

        'left-image': {
            type: 'image',
            default: {
                config: {
                    displayMode: { source: 'static', value: 'cover' },
                },
                data: {
                    media: {
                        value: CMS.MEDIA.previewCamera,
                        source: 'default',
                    },
                },
            },
        },
        'center-left-image': {
            type: 'image',
            default: {
                config: {
                    displayMode: { source: 'static', value: 'cover' },
                },
                data: {
                    media: {
                        value: CMS.MEDIA.previewPlant,
                        source: 'default',
                    },
                },
            },
        },
        'center-right-image': {
            type: 'image',
            default: {
                config: {
                    displayMode: { source: 'static', value: 'cover' },
                },
                data: {
                    media: {
                        value: CMS.MEDIA.previewGlasses,
                        source: 'default',
                    },
                },
            },
        },
        'right-image': {
            type: 'image',
            default: {
                config: {
                    displayMode: { source: 'static', value: 'cover' },
                },
                data: {
                    media: {
                        value: CMS.MEDIA.previewMountain,
                        source: 'default',
                    },
                },
            },
        },
        'left-text': {
            type: 'text',
            default: {
                config: {
                    content: {
                        source: 'static',
                        value: `
                        &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim(),
                    },
                },
            },
        },
        'center-left-text': {
            type: 'text',
            default: {
                config: {
                    content: {
                        source: 'static',
                        value: `
                        &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim(),
                    },
                },
            },
        },
        'center-right-text': {
            type: 'text',
            default: {
                config: {
                    content: {
                        source: 'static',
                        value: `
                        &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim(),
                    },
                },
            },
        },
        'right-text': {
            type: 'text',
            default: {
                config: {
                    content: {
                        source: 'static',
                        value: `
                        &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim(),
                    },
                },
            },
        },
    },
});

                    text-image-text
                    component
index.js

import template from './sw-cms-block-text-image-text.html.twig';
import './sw-cms-block-text-image-text.scss';

export default {
    template,
};
twig

{% block sw_cms_block_center_text %}
    &amp;lt;div class="sw-cms-block-center-text"&amp;gt;
        &amp;lt;div class="sw-cms-block-center-text__image-left"&amp;gt;
            &amp;lt;slot name="left-text"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div class="sw-cms-block-center-text__text"&amp;gt;
            &amp;lt;slot name="center-image"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div class="sw-cms-block-center-text__image-right"&amp;gt;
            &amp;lt;slot name="right-text"&amp;gt;&amp;lt;/slot&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}
scss

.sw-cms-block-center-text {
  display: flex;
  flex-wrap: wrap;

  .sw-cms-block-center-text__text {
    flex-grow: 2;
    flex-shrink: 0;
    flex-basis: 300px;
  }

  .sw-cms-block-center-text__image-left,
  .sw-cms-block-center-text__image-right {
    flex-grow: 1;
    flex-shrink: 0;
    flex-basis: 240px;
  }

  .sw-cms-el-image.is--cover {
    min-height: 340px;
  }

  .sw-cms-el-text {
    padding: 30px;
  }
}

                    preview
index.js

import template from './sw-cms-preview-text-image-text.html.twig';
import './sw-cms-preview-text-image-text.scss';

export default {
    template,

    computed: {
        assetFilter() {
            return Shopware.Filter.getByName('asset');
        },
    },
};
twig

{% block sw_cms_block_center_image_preview %}
    &amp;lt;div class="sw-cms-preview-center-text"&amp;gt;
        &amp;lt;div class="sw-cms-preview-center-text__text"&amp;gt;
            &amp;lt;h2&amp;gt;Lorem ipsum&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;img
                :src="assetFilter('/administration/static/img/cms/preview_plant_small.jpg')"
                alt=""
        &amp;gt;
        &amp;lt;div class="sw-cms-preview-center-text__text"&amp;gt;
            &amp;lt;h2&amp;gt;Lorem ipsum&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr.&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;

    &amp;lt;/div&amp;gt;
{% endblock %}
scss

.sw-cms-preview-center-text {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  padding: 15px;

  .sw-cms-preview-center-text__text {
    padding: 15px;
    text-align: center;
    height: fit-content;
  }

  img {
    display: block;
    object-fit: cover;
    width: 100%;
    height: 100%;
  }
}

                    index.js

import CMS from '../../../constant/sw-cms.constant';

Shopware.Component.register('sw-cms-preview-text-image-text', () =&amp;gt; import('./preview'));

Shopware.Component.register('sw-cms-block-text-image-text', () =&amp;gt; import('./component'));

Shopware.Service('cmsService').registerCmsBlock({
    name: 'text-image-text',
    label: 'Text Image Text',
    category: 'text-image',
    component: 'sw-cms-block-text-image-text',
    previewComponent: 'sw-cms-preview-text-image-text',
    defaultConfig: {
        marginBottom: '20px',
        marginTop: '20px',
        marginLeft: '20px',
        marginRight: '20px',
        sizingMode: 'boxed',
    },
    slots: {
        'left-text': {
            type: 'text',
            default: {
                config: {
                    content: {
                        source: 'static',
                        value: `
                        &amp;lt;h2 style="text-align: center;"&amp;gt;Lorem Ipsum dolor sit amet&amp;lt;/h2&amp;gt;
                        &amp;lt;p style="text-align: center;"&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim(),
                    },
                },
            },
        },
        'center-image': {
            type: 'custom-image',
            default: {
                config: {
                    displayMode: { source: 'static', value: 'cover' },
                },
                data: {
                    media: {
                        value: null,
                        source: 'default',
                    },
                },
            },
        },
        'right-text': {
            type: 'text',
            default: {
                config: {
                    content: {
                        source: 'static',
                        value: `
                        &amp;lt;h2 style="text-align: center;"&amp;gt;Lorem Ipsum dolor sit amet&amp;lt;/h2&amp;gt;
                        &amp;lt;p style="text-align: center;"&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim(),
                    },
                },
            },
        },

    }
});

                constant

const uniqueSlotsKebab = [
    'buy-box',
    'product-description-reviews',
    'cross-selling',
];

/**
 * @sw-package discovery
 */
// eslint-disable-next-line sw-deprecation-rules/private-feature-declarations
export default Object.freeze({
    REQUIRED_FIELD_ERROR_CODE: 'c1051bb4-d103-4f74-8988-acbcafc7fdc3',
    PAGE_TYPES: {
        SHOP: 'page',
        LANDING: 'landingpage',
        LISTING: 'product_list',
        PRODUCT_DETAIL: 'product_detail',
    },
    TYPE_MAPPING_ENTITIES: {
        product_detail: {
            entity: 'product',
            mode: 'single',
        },
        product_list: {
            entity: 'category',
            mode: 'single',
        },
    },
    UNIQUE_SLOTS: uniqueSlotsKebab.map((slotName) =&amp;gt; slotName.replace(/-./g, (char) =&amp;gt; char.toUpperCase()[1])),
    UNIQUE_SLOTS_KEBAB: uniqueSlotsKebab,
    SLOT_POSITIONS: {
        left: 0,
        'left-image': 100,
        'left-top': 200,
        'left-text': 300,
        'left-bottom': 400,
        'center-left': 1000,
        center: 1100,
        'center-image': 1200,
        'center-top': 1300,
        'center-text': 1400,
        'center-bottom': 1500,
        'center-right': 1600,
        right: 2000,
        'right-image': 2100,
        'right-top': 2200,
        'right-text': 2300,
        'right-bottom': 2400,
        content: 3000,
        image: 3100,
        video: 3200,
        imageSlider: 3300,
        default: 5000,
    },
    MEDIA: {
        previewCamera: 'bundles/administration/static/img/cms/preview_camera_large.jpg',
        previewMountain: 'bundles/administration/static/img/cms/preview_mountain_large.jpg',
        previewPlant: 'bundles/administration/static/img/cms/preview_plant_large.jpg',
        previewGlasses: 'bundles/administration/static/img/cms/preview_glasses_large.jpg',
        SMALL:{
            previewCamera: 'framework/assets/default/cms/preview_camera_small.jpg',
            previewMountain: 'framework/assets/default/cms/preview_mountain_small.jpg',
            previewPlant: 'framework/assets/default/cms/preview_plant_small.jpg',
            previewGlasses: 'framework/assets/default/cms/preview_glasses_small.jpg',
        }
    },
});

                elements
button
component
index.js

import template from './sw-cms-el-button.html.twig';
const { Mixin } = Shopware;
export default {
    template,

    mixins: [
        Mixin.getByName('cms-element')
    ],

    created() {
        this.createdComponent();
    },

    methods:{
        createdComponent() {
            this.initElementConfig('button');
        }
    },

    };
twig

&amp;lt;div class="sw-cms-el-button"&amp;gt;
    &amp;lt;sw-button class="btn btn-primary" :href="element.config.url.value" target="_blank"&amp;gt;
        {{ element.config.title.value }}
    &amp;lt;/sw-button&amp;gt;
&amp;lt;/div&amp;gt;

config
index.js

import template from './sw-cms-el-config-button.html.twig';
const { Mixin } = Shopware;
export default {
    template,

    mixins: [
        Mixin.getByName('cms-element')
    ],

    created() {
        this.createdComponent();
    },

    methods:{
        createdComponent() {
            this.initElementConfig('button');
        },
        onConfigUpdate() {
            this.$emit('element-update', this.element);
        }

    }
};
twig

&amp;lt;div&amp;gt;
    &amp;lt;sw-text-field
            label="Button Title"
            v-model:value="element.config.title.value"
            @input="onConfigUpdate"
    &amp;gt;&amp;lt;/sw-text-field&amp;gt;
    &amp;lt;sw-text-field
            label="Button URL"
            v-model:value="element.config.url.value"
            @input="onConfigUpdate"
    &amp;gt;&amp;lt;/sw-text-field&amp;gt;
&amp;lt;/div&amp;gt;

preview
index.js

import template from './sw-cms-el-preview-button.html.twig';

export default {
    template,

    props: {
        element: {
            type: Object,
            required: true
        }
    }
};
twig

&amp;lt;div&amp;gt;
    &amp;lt;sw-button class="btn btn-outline-secondary"&amp;gt;
        Button
    &amp;lt;/sw-button&amp;gt;
&amp;lt;/div&amp;gt;

index.js


Shopware.Component.register('sw-cms-el-preview-button', () =&amp;gt; import('./preview'));

Shopware.Component.register('sw-cms-el-config-button', () =&amp;gt; import('./config'));

Shopware.Component.register('sw-cms-el-button', () =&amp;gt; import('./component'));

Shopware.Service('cmsService').registerCmsElement({
    name: 'button',
    label: 'Button',
    component: 'sw-cms-el-button',
    configComponent: 'sw-cms-el-config-button',
    previewComponent: 'sw-cms-el-preview-button',
    defaultConfig: {
        title: {
            source: 'static',
            value: 'Click Me'
        },
        url: {
            source: 'static',
            value: 'https://example.com'
        }
    }
});

custom-image
component
index.js

import CMS from '../../../constant/sw-cms.constant';
import template from './sw-cms-el-custom-image.html.twig';
import './sw-cms-el-custom-image.scss';

const { Component, Mixin, Filter } = Shopware;

Component.register('sw-cms-el-custom-image', {
    template,

    mixins: [
        Mixin.getByName('cms-element'),
    ],

    computed: {
        mediaUrl() {
            const fallBackImageFileName = CMS.MEDIA.previewPlant.slice(CMS.MEDIA.previewPlant.lastIndexOf('/') + 1);
            const staticFallBackImage = this.assetFilter(`administration/static/img/cms/${fallBackImageFileName}`);
            const elemData = this.element?.data?.media || {};
            const elemConfig = this.element?.config?.media || {};

            if (elemConfig.source === 'mapped') {
                const demoMedia = this.getDemoValue(elemConfig.value);
                if (demoMedia?.url) {
                    return demoMedia.url;
                }
                return staticFallBackImage;
            }

            if (elemConfig.source === 'default' &amp;amp;&amp;amp; typeof elemConfig.value === 'string') {
                const fileName = elemConfig.value.slice(elemConfig.value.lastIndexOf('/') + 1);
                return this.assetFilter(`/administration/static/img/cms/${fileName}`);
            }

            if (elemData?.id) {
                return this.element.data.media.url;
            }

            if (elemData?.url) {
                return elemData.url;
            }

            return staticFallBackImage;
        },

        assetFilter() {
            return Filter.getByName('asset');
        },

        mediaConfigValue() {
            return this.element?.config?.media?.value;
        },
    },

    watch: {
        'cmsPageState.currentDemoEntity': {
            handler() {
                this.updateDemoValue(this.mediaConfigValue);
            },
        },

        mediaConfigValue(value) {
            this.updateDemoValue(value);
        },
    },

    created() {
        this.createdComponent();
    },

    methods: {
        createdComponent() {
            this.initElementConfig('custom-image');
            this.initElementData('custom-image');
        },
        updateDemoValue(value) {
            const mediaId = this.element?.data?.media?.id;
            const isSourceStatic = this.element?.config?.media?.source === 'static';

            if (isSourceStatic &amp;amp;&amp;amp; mediaId &amp;amp;&amp;amp; value !== mediaId) {
                this.element.config.media.value = mediaId;
            }
        },
    },
});
twig

{% block sw_cms_element_custom_image %}
&amp;lt;div
    class="sw-cms-el-custom-image"
&amp;gt;

    {% block sw_cms_element_custom_image_content %}
    &amp;lt;img
        :src="mediaUrl"
        alt=""
    &amp;gt;
    {% endblock %}
&amp;lt;/div&amp;gt;
{% endblock %}
scss


.sw-cms-el-custom-image {
    display: flex;
    max-width: 100%;

    img {
        display: block;
        max-width: 100%;
    }

    &amp;amp;.is--cover {
        height: 100%;
        width: 100%;
        position: relative;
        margin: auto;
        border-radius: 50%;
        background-clip: padding-box;
        overflow: hidden;

        img {
            object-fit: cover;
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
        }
    }

    &amp;amp;.is--stretch {
        img {
            width: 100%;
        }
    }
}

config
index.js

import template from './sw-cms-el-config-custom-image.html.twig';
import './sw-cms-el-config-custom-image.scss';

const { Component, Mixin } = Shopware;

Component.register('sw-cms-el-config-custom-image', {
    template,

    inject: ['repositoryFactory'],

    emits: ['element-update'],

    mixins: [
        Mixin.getByName('cms-element'),
    ],

    data() {
        return {
            mediaModalIsOpen: false,
            initialFolderId: null,
        };
    },

    computed: {
        mediaRepository() {
            return this.repositoryFactory.create('media');
        },

        uploadTag() {
            return `cms-element-media-config-${this.element.id}`;
        },

        previewSource() {
            if (this.element.data &amp;amp;&amp;amp; this.element.data.media &amp;amp;&amp;amp; this.element.data.media.id) {
                return this.element.data.media;
            }

            return this.element.config.media.value;
        },
    },

    created() {
        this.createdComponent();
    },

    methods: {
        createdComponent() {
            this.initElementConfig('image');
        },

        async onImageUpload({ targetId }) {
            const mediaEntity = await this.mediaRepository.get(targetId);

            this.element.config.media.value = mediaEntity.id;
            this.element.config.media.source = 'static';

            this.updateElementData(mediaEntity);

            this.$emit('element-update', this.element);
        },

        onImageRemove() {
            this.element.config.media.value = null;

            this.updateElementData();

            this.$emit('element-update', this.element);
        },

        onCloseModal() {
            this.mediaModalIsOpen = false;
        },

        onSelectionChanges(mediaEntity) {
            const media = mediaEntity[0];
            this.element.config.media.value = media.id;
            this.element.config.media.source = 'static';

            this.updateElementData(media);

            this.$emit('element-update', this.element);
        },

        updateElementData(media = null) {
            const mediaId = media === null ? null : media.id;
            if (!this.element.data) {
                this.$set(this.element, 'data', { mediaId, media });
            } else {
                this.$set(this.element.data, 'mediaId', mediaId);
                this.$set(this.element.data, 'media', media);
            }
        },

        onOpenMediaModal() {
            this.mediaModalIsOpen = true;
        },

    },
});
twig


{% block sw_cms_element_custom_image_config %}
&amp;lt;div class="sw-cms-el-config-custom-image"&amp;gt;

    {% block sw_cms_element_image_config_media_upload %}
        &amp;lt;sw-cms-mapping-field
                v-model:config="element.config.media"
                :label="$tc('Image Upload')"
                value-types="entity"
                entity="media"
        &amp;gt;
            &amp;lt;sw-media-upload-v2
                    variant="regular"
                    :upload-tag="uploadTag"
                    :source="previewSource"
                    :allow-multi-select="false"
                    :default-folder="cmsPageState.pageEntityName"
                    :caption="$tc('sw-cms.elements.general.config.caption.mediaUpload')"
                    @media-upload-sidebar-open="onOpenMediaModal"
                    @media-upload-remove-image="onImageRemove"
            &amp;gt;
            &amp;lt;/sw-media-upload-v2&amp;gt;

            &amp;lt;template #preview="{ demoValue }"&amp;gt;
                &amp;lt;div class="sw-cms-el-config-image__mapping-preview"&amp;gt;
                    &amp;lt;img
                            v-if="demoValue.url"
                            :src="demoValue.url"
                            alt=""
                    &amp;gt;
                    &amp;lt;sw-alert
                            v-else
                            class="sw-cms-el-config-image__preview-info"
                            variant="info"
                    &amp;gt;
                        {{ $tc('sw-cms.detail.label.mappingEmptyPreview') }}
                    &amp;lt;/sw-alert&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/template&amp;gt;
        &amp;lt;/sw-cms-mapping-field&amp;gt;

        &amp;lt;sw-upload-listener
                :upload-tag="uploadTag"
                auto-upload
                @media-upload-finish="onImageUpload"
        &amp;gt;
        &amp;lt;/sw-upload-listener&amp;gt;
    {% endblock %}

    {% block sw_cms_element_image_config_link %}
        &amp;lt;div class="sw-cms-el-config-image__link"&amp;gt;
            &amp;lt;sw-dynamic-url-field
                    v-model:value="element.config.url.value"
            &amp;gt;
            &amp;lt;/sw-dynamic-url-field&amp;gt;

        &amp;lt;/div&amp;gt;
    {% endblock %}

    {% block sw_cms_element_custom_image_config_media_modal %}
    &amp;lt;sw-media-modal-v2
        v-if="mediaModalIsOpen"
        variant="regular"
        :caption=""
        :entity-context="cmsPageState.entityName"
        :allow-multi-select="false"
        :initial-folder-id="cmsPageState.defaultMediaFolderId"
        @media-upload-remove-image="onImageRemove"
        @media-modal-selection-change="onSelectionChanges"
        @modal-close="onCloseModal"
    &amp;gt;
    &amp;lt;/sw-media-modal-v2&amp;gt;
    {% endblock %}
&amp;lt;/div&amp;gt;
{% endblock %}
scss

.sw-cms-el-config-custom-image {
    .sw-media-upload-v2 {
        margin-bottom: 22px;

        .sw-media-upload-v2__header {
            display: none;
        }
    }

    .sw-cms-el-config-image__mapping-preview {
        display: grid;
        width: 100%;
        height: 200px;
        justify-items: center;
        align-items: start;

        img {
            width: 100%;
            height: 100%;
            object-fit: contain;
            overflow: hidden;
        }

        .sw-cms-el-config-image__preview-info {
            width: 100%;
        }
    }
}

preview
index.js

import template from './sw-cms-el-preview-custom-image.html.twig';
import './sw-cms-el-preview-custom-image.scss';

const { Component } = Shopware;

Component.register('sw-cms-el-preview-custom-image', {
    template,

    computed: {
        assetFilter() {
            return Shopware.Filter.getByName('asset');
        },
    },
});

twig

{% block sw_cms_element_image_preview %}
    &amp;lt;div class="sw-cms-el-preview-image"&amp;gt;
        &amp;lt;img
                :src="assetFilter('/administration/static/img/cms/preview_mountain_small.jpg')"
                alt=""
        &amp;gt;
    &amp;lt;/div&amp;gt;
{% endblock %}

scss

.sw-cms-el-preview-image {
    width: 100%;
    height: 100%;
    overflow: hidden;

    img {
        margin: 0;
        display: block;
        width: 110%;
    }
}

index.js

import './component';
import './config';
import './preview';

Shopware.Service('cmsService').registerCmsElement({
    name: 'custom-image',
    label: 'Custom Image',
    component: 'sw-cms-el-custom-image',
    configComponent: 'sw-cms-el-config-custom-image',
    previewComponent: 'sw-cms-el-preview-custom-image',
    defaultConfig: {
        media: {
            source: 'static',
            value: null,
            required: true,
            entity: {
                name: 'media',
            },
        },
        url: {
            source: 'static',
            value: null,
        },
    },
});

ict-image
component
index.js

import CMS from '../../../constant/sw-cms.constant';
import template from './sw-cms-el-ict-image.html.twig';
import './sw-cms-el-ict-image.scss';

const { Component, Mixin, Filter } = Shopware;

Component.register('sw-cms-el-ict-image', {
    template,

    mixins: [
        Mixin.getByName('cms-element'),
    ],

    computed: {
        displayModeClass() {
            if (this.element.config.displayMode.value === 'standard') {
                return null;
            }

            return `is--${this.element.config.displayMode.value}`;
        },

        styles() {
            return {
                'min-height': this.element.config.displayMode.value === 'cover' &amp;amp;&amp;amp;
                              this.element.config.minHeight.value &amp;amp;&amp;amp;
                              this.element.config.minHeight.value !== 0 ? this.element.config.minHeight.value : '340px',
            };
        },

        imgStyles() {
            return {
                'align-self': this.element.config.verticalAlign.value || null,
            };
        },

        mediaUrl() {
            const fallBackImageFileName = CMS.MEDIA.previewMountain.slice(CMS.MEDIA.previewMountain.lastIndexOf('/') + 1);
            const staticFallBackImage = this.assetFilter(`administration/static/img/cms/${fallBackImageFileName}`);
            const elemData = this.element.data.media;
            const elemConfig = this.element.config.media;

            if (elemConfig.source === 'mapped') {
                const demoMedia = this.getDemoValue(elemConfig.value);

                if (demoMedia?.url) {
                    return demoMedia.url;
                }

                return staticFallBackImage;
            }

            if (elemConfig.source === 'default') {
                // use only the filename
                const fileName = elemConfig.value.slice(elemConfig.value.lastIndexOf('/') + 1);
                return this.assetFilter(`/administration/static/img/cms/${fileName}`);
            }

            if (elemData?.id) {
                return this.element.data.media.url;
            }

            if (elemData?.url) {
                return this.assetFilter(elemConfig.url);
            }

            return staticFallBackImage;
        },


        assetFilter() {
            return Filter.getByName('asset');
        },

        mediaConfigValue() {
            return this.element?.config?.sliderItems?.value;
        },
    },

    watch: {
        cmsPageState: {
            deep: true,
            handler() {
                this.$forceUpdate();
            },
        },

        mediaConfigValue(value) {
            const mediaId = this.element?.data?.media?.id;
            const isSourceStatic = this.element?.config?.media?.source === 'static';

            if (isSourceStatic &amp;amp;&amp;amp; mediaId &amp;amp;&amp;amp; value !== mediaId) {
                this.element.config.media.value = mediaId;
            }
        },
    },

    created() {
        this.createdComponent();
    },

    methods: {
        createdComponent() {
            this.initElementConfig('ict-image');
            this.initElementData('ict-image');
        },
    },
});
twig

{% block sw_cms_element_ict_image %}
&amp;lt;div
    class="sw-cms-el-ict-image"
    :class="displayModeClass"
    :style="styles"
&amp;gt;
    {% block sw_cms_element_ict_image_content %}
    &amp;lt;img
        :src="mediaUrl"
        :style="imgStyles"
        alt=""
    &amp;gt;
    {% endblock %}
&amp;lt;/div&amp;gt;
{% endblock %}
scss

@import "~scss/variables";

.sw-cms-el-ict-image {
    display: flex;
    max-width: 100%;

    img {
        display: block;
        max-width: 100%;
    }

    &amp;amp;.is--cover {
        height: 100%;
        width: 100%;
        position: relative;
        margin: auto;
        border-radius: 50%;
        background-clip: padding-box;
        overflow: hidden;

        img {
            object-fit: cover;
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
        }
    }

    &amp;amp;.is--stretch {
        img {
            width: 100%;
        }
    }
}
sw-cms-el-image.spec.vue3.js

/**
 * @package buyers-experience
 */
import { mount } from '@vue/test-utils_v3';
import 'src/module/sw-cms/mixin/sw-cms-element.mixin';

const mediaDataMock = {
    id: '1',
    url: 'http://shopware.com/image1.jpg',
};

async function createWrapper() {
    return mount(await wrapTestComponent('sw-cms-el-image', {
        sync: true,
    }), {
        global: {
            provide: {
                cmsService: {
                    getCmsBlockRegistry: () =&amp;gt; {
                        return {};
                    },
                    getCmsElementRegistry: () =&amp;gt; {
                        return { image: {} };
                    },
                    getPropertyByMappingPath: () =&amp;gt; {
                        return {};
                    },
                },
            },
        },
        props: {
            element: {
                config: {},
                data: {},
            },
            defaultConfig: {
                media: {
                    source: 'static',
                    value: null,
                    required: true,
                    entity: {
                        name: 'media',
                    },
                },
                displayMode: {
                    source: 'static',
                    value: 'standard',
                },
                url: {
                    source: 'static',
                    value: null,
                },
                newTab: {
                    source: 'static',
                    value: false,
                },
                minHeight: {
                    source: 'static',
                    value: '340px',
                },
                verticalAlign: {
                    source: 'static',
                    value: null,
                },
                horizontalAlign: {
                    source: 'static',
                    value: null,
                },
            },
        },
        data() {
            return {
                cmsPageState: {
                    currentPage: {
                        type: 'ladingpage',
                    },
                },
            };
        },
    });
}

describe('src/module/sw-cms/elements/ict-image/component', () =&amp;gt; {
    it('should show default image if there is no config value', async () =&amp;gt; {
        const wrapper = await createWrapper();

        const img = wrapper.find('img');
        expect(img.attributes('src'))
            .toBe(wrapper.vm.assetFilter('administration/static/img/cms/preview_mountain_large.jpg'));
    });

    it('should show media source regarding to media data', async () =&amp;gt; {
        const wrapper = await createWrapper();

        await wrapper.setProps({
            element: {
                config: {
                    ...wrapper.props().element.config,
                    media: {
                        source: 'static',
                        value: '1',
                    },
                },
                data: {
                    media: mediaDataMock,
                },
            },
        });

        const img = wrapper.find('img');
        expect(img.attributes('src')).toBe(mediaDataMock.url);
    });

    it('should show default image if demo value is undefined', async () =&amp;gt; {
        const wrapper = await createWrapper();

        await wrapper.setProps({
            element: {
                config: {
                    ...wrapper.props().element.config,
                    media: {
                        source: 'mapped',
                        value: 'category.media',
                    },
                },
                data: mediaDataMock,
            },
        });

        const img = wrapper.find('img');
        expect(img.attributes('src'))
            .toBe(wrapper.vm.assetFilter('administration/static/img/cms/preview_mountain_large.jpg'));
    });
});

config
index.js

import template from './sw-cms-el-config-ict-image.html.twig';
import './sw-cms-el-config-ict-image.scss';

const { Component, Mixin } = Shopware;

Component.register('sw-cms-el-config-ict-image', {
    template,

    inject: ['repositoryFactory'],

    mixins: [
        Mixin.getByName('cms-element'),
    ],

    data() {
        return {
            mediaModalIsOpen: false,
            initialFolderId: null,
        };
    },

    computed: {
        mediaRepository() {
            return this.repositoryFactory.create('media');
        },

        uploadTag() {
            return `cms-element-media-config-${this.element.id}`;
        },

        previewSource() {
            if (this.element.data &amp;amp;&amp;amp; this.element.data.media &amp;amp;&amp;amp; this.element.data.media.id) {
                return this.element.data.media;
            }

            return this.element.config.media.value;
        },
    },

    created() {
        this.createdComponent();
    },

    methods: {
        createdComponent() {
            this.initElementConfig('image');
        },

        async onImageUpload({ targetId }) {
            const mediaEntity = await this.mediaRepository.get(targetId);

            this.element.config.media.value = mediaEntity.id;
            this.element.config.media.source = 'static';

            this.updateElementData(mediaEntity);

            this.$emit('element-update', this.element);
        },

        onImageRemove() {
            this.element.config.media.value = null;

            this.updateElementData();

            this.$emit('element-update', this.element);
        },

        onCloseModal() {
            this.mediaModalIsOpen = false;
        },

        onSelectionChanges(mediaEntity) {
            const media = mediaEntity[0];
            this.element.config.media.value = media.id;
            this.element.config.media.source = 'static';

            this.updateElementData(media);

            this.$emit('element-update', this.element);
        },

        updateElementData(media = null) {
            const mediaId = media === null ? null : media.id;
            if (!this.element.data) {
                this.$set(this.element, 'data', { mediaId, media });
            } else {
                this.$set(this.element.data, 'mediaId', mediaId);
                this.$set(this.element.data, 'media', media);
            }
        },

        onOpenMediaModal() {
            this.mediaModalIsOpen = true;
        },

        onChangeMinHeight(value) {
            this.element.config.minHeight.value = value === null ? '' : value;

            this.$emit('element-update', this.element);
        },
        onChangeButtonField(value) {
            this.element.config.buttonField.value = value === null ? '' : value;

            this.$emit('element-update', this.element);
        },
        onChangeDisplayMode(value) {
            if (value === 'cover') {
                this.element.config.verticalAlign.value = null;
            }

            this.$emit('element-update', this.element);
        },
    },
});
twig

&amp;lt;!-- eslint-disable-next-line sw-deprecation-rules/no-twigjs-blocks --&amp;gt;
{% block sw_cms_element_ict_image_config %}
&amp;lt;div class="sw-cms-el-config-ict-image"&amp;gt;

    {% block sw_cms_element_image_config_media_upload %}
        &amp;lt;sw-cms-mapping-field
                v-model:config="element.config.media"
                :label="$tc('sw-cms.elements.image.label')"
                value-types="entity"
                entity="media"
        &amp;gt;
            &amp;lt;sw-media-upload-v2
                    variant="regular"
                    :upload-tag="uploadTag"
                    :source="previewSource"
                    :allow-multi-select="false"
                    :default-folder="cmsPageState.pageEntityName"
                    :caption="$tc('sw-cms.elements.general.config.caption.mediaUpload')"
                    @media-upload-sidebar-open="onOpenMediaModal"
                    @media-upload-remove-image="onImageRemove"
            /&amp;gt;

            &amp;lt;template #preview="{ demoValue }"&amp;gt;
                &amp;lt;div class="sw-cms-el-config-image__mapping-preview"&amp;gt;
                    &amp;lt;img
                            v-if="demoValue.url"
                            :src="demoValue.url"
                            alt=""
                    &amp;gt;
                    &amp;lt;sw-alert
                            v-else
                            class="sw-cms-el-config-image__preview-info"
                            variant="info"
                    &amp;gt;
                        {{ $tc('sw-cms.detail.label.mappingEmptyPreview') }}
                    &amp;lt;/sw-alert&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/template&amp;gt;
        &amp;lt;/sw-cms-mapping-field&amp;gt;

        &amp;lt;sw-upload-listener
                :upload-tag="uploadTag"
                auto-upload
                @media-upload-finish="onImageUpload"
        /&amp;gt;
    {% endblock %}

    &amp;lt;!-- eslint-disable-next-line sw-deprecation-rules/no-twigjs-blocks --&amp;gt;
    {% block sw_cms_element_image_config_display_mode %}
        &amp;lt;sw-select-field
                v-model:value="element.config.displayMode.value"
                class="sw-cms-el-config-image__display-mode"
                :label="$tc('sw-cms.elements.general.config.label.displayMode')"
                @update:value="onChangeDisplayMode"
        &amp;gt;
            &amp;lt;option value="standard"&amp;gt;
                {{ $tc('sw-cms.elements.general.config.label.displayModeStandard') }}
            &amp;lt;/option&amp;gt;
            &amp;lt;option value="cover"&amp;gt;
                {{ $tc('sw-cms.elements.general.config.label.displayModeCover') }}
            &amp;lt;/option&amp;gt;
            &amp;lt;option value="stretch"&amp;gt;
                {{ $tc('sw-cms.elements.general.config.label.displayModeStretch') }}
            &amp;lt;/option&amp;gt;
        &amp;lt;/sw-select-field&amp;gt;
    {% endblock %}

    &amp;lt;template v-if="element.config.displayMode.value === 'cover'"&amp;gt;
        &amp;lt;!-- eslint-disable-next-line sw-deprecation-rules/no-twigjs-blocks --&amp;gt;
        {% block sw_cms_element_ict_image_config_min_height %}
        &amp;lt;sw-text-field
            v-model="element.config.minHeight.value"
            :label="$tc('sw-cms.elements.image.config.label.minHeight')"
            :placeholder="$tc('sw-cms.elements.image.config.placeholder.enterMinHeight')"
            @input="onChangeMinHeight"
        /&amp;gt;
        {% endblock %}
    &amp;lt;/template&amp;gt;

    &amp;lt;!-- eslint-disable-next-line sw-deprecation-rules/no-twigjs-blocks --&amp;gt;
    {% block sw_cms_element_ict_image_config_vertical_align %}
    &amp;lt;sw-select-field
        v-model="element.config.verticalAlign.value"
        :label="$tc('sw-cms.elements.general.config.label.verticalAlign')"
        :placeholder="$tc('sw-cms.elements.general.config.label.verticalAlign')"
        :disabled="element.config.displayMode.value === 'cover'"
    &amp;gt;
        &amp;lt;option value="flex-start"&amp;gt;
            {{ $tc('sw-cms.elements.general.config.label.verticalAlignTop') }}
        &amp;lt;/option&amp;gt;
        &amp;lt;option value="center"&amp;gt;
            {{ $tc('sw-cms.elements.general.config.label.verticalAlignCenter') }}
        &amp;lt;/option&amp;gt;
        &amp;lt;option value="flex-end"&amp;gt;
            {{ $tc('sw-cms.elements.general.config.label.verticalAlignBottom') }}
        &amp;lt;/option&amp;gt;
    &amp;lt;/sw-select-field&amp;gt;
    {% endblock %}

    {% block sw_cms_element_image_config_button %}
        &amp;lt;sw-text-field
            v-model:value="element.config.buttonField.value"
            :label="$tc('sw-cms.elements.image.config.label.buttonField')"
            :placeholder="$tc('sw-cms.elements.image.config.placeholder.buttonField')"
            {% if VUE3 %}
            @update:value="onChangeButtonField"
            {% else %}
            @input="onChangeButtonField"
            {% endif %}
        /&amp;gt;
    {% endblock %}
    &amp;lt;!-- eslint-disable-next-line sw-deprecation-rules/no-twigjs-blocks --&amp;gt;
    {% block sw_cms_element_image_config_link %}
        &amp;lt;div class="sw-cms-el-config-image__link"&amp;gt;
            &amp;lt;sw-dynamic-url-field
                    v-model:value="element.config.url.value"
            /&amp;gt;
            &amp;lt;sw-switch-field
                    v-model:value="element.config.newTab.value"
                    class="sw-cms-el-config-image__link-tab"
                    :label="$tc('sw-cms.elements.image.config.label.linkNewTab')"
            /&amp;gt;
        &amp;lt;/div&amp;gt;
    {% endblock %}

    &amp;lt;!-- eslint-disable-next-line sw-deprecation-rules/no-twigjs-blocks --&amp;gt;
    {% block sw_cms_element_ict_image_config_media_modal %}
    &amp;lt;sw-media-modal-v2
        v-if="mediaModalIsOpen"
        variant="regular"
        :caption="$tc('sw-cms.elements.general.config.caption.mediaUpload')"
        :entity-context="cmsPageState.entityName"
        :allow-multi-select="false"
        :initial-folder-id="cmsPageState.defaultMediaFolderId"
        @media-upload-remove-image="onImageRemove"
        @media-modal-selection-change="onSelectionChanges"
        @modal-close="onCloseModal"
    /&amp;gt;
    {% endblock %}
&amp;lt;/div&amp;gt;
{% endblock %}
scss

.sw-cms-el-config-ict-image {
    .sw-media-upload-v2 {
        margin-bottom: 22px;

        .sw-media-upload-v2__header {
            display: none;
        }
    }

    .sw-cms-el-config-ict-image__mapping-preview {
        display: grid;
        width: 100%;
        height: 200px;
        justify-items: center;
        align-items: start;

        img {
            width: 100%;
            height: 100%;
            object-fit: contain;
            overflow: hidden;
        }

        .sw-cms-el-config-ict-image__preview-info {
            width: 100%;
        }
    }
}
sw-cms-el-config-ict-image.spc.js

``
import { shallowMount } from '@vue/test-utils';
import 'src/module/sw-cms/mixin/sw-cms-element.mixin';
import 'src/module/sw-cms/elements/ict-image/config';
import 'src/module/sw-cms/component/sw-cms-mapping-field';
import 'src/app/component/form/sw-dynamic-url-field';

async function createWrapper() {
    return shallowMount(await Shopware.Component.build('sw-cms-el-config-ict-image'), {
        provide: {
            cmsService: {
                getCmsBlockRegistry: () =&amp;gt; {
                    return {};
                },
                getCmsElementRegistry: () =&amp;gt; {
                    return { image: {} };
                }
            },
            repositoryFactory: {
                create: () =&amp;gt; {
                    return {
                        search: () =&amp;gt; Promise.resolve()
                    };
                }
            }
        },
        stubs: {
            'sw-field': true,
            'sw-select-field': {
                template: '&amp;lt;select class="sw-select-field" :value="value" @change="$emit(\'change\', $event.target.value)"&amp;gt;&amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;&amp;lt;/select&amp;gt;',
                props: ['value', 'options']
            },
            'sw-text-field': true,
            'sw-cms-mapping-field': await Shopware.Component.build('sw-cms-mapping-field'),
            'sw-media-upload-v2': true,
            'sw-upload-listener': true,
            'sw-dynamic-url-field': true,
        },
        propsData: {
            element: {
                config: {
                    media: {
                        source: 'static',
                        value: null,
                        required: true,
                        entity: {
                            name: 'media'
                        }
                    },
                    displayMode: {
                        source: 'static',
                        value: 'standard'
                    },
                    url: {
                        source: 'static',
                        value: null
                    },
                    newTab: {
                        source: 'static',
                        value: false
                    },
                    minHeight: {
                        source: 'static',
                        value: '340px'
                    },
                    verticalAlign: {
                        source: 'static',
                        value: null
                    }
                },
                data: {}
            },
            defaultConfig: {}
        },
        data() {
            return {
                cmsPageState: {
                    currentPage: {
                        type: 'ladingpage'
                    }
                }
            };
        }
    });
}

describe('src/module/sw-cms/elements/ict-image/config', () =&amp;gt; {
    beforeAll(() =&amp;gt; {
        Shopware.State.registerModule('cmsPageState', {
            namespaced: true,
            state: {
                currentMappingTypes: {},
                currentDemoEntity: null
            },
            mutations: {
                setCurrentDemoEntity(state, entity) {
                    state.currentDemoEntity = entity;
                }
            }
        });
    });

    it('should be a Vue.js component', async () =&amp;gt; {
        const wrapper = await createWrapper();

        expect(wrapper.vm).toBeTruthy();
    });

    it('should keep minHeight value when changing display mode', async () =&amp;gt; {
        const wrapper = await createWrapper('settings');
        const displayModeSelect = wrapper.find('.sw-cms-el-config-ict-image__display-mode');

        await displayModeSelect.setValue('cover');

        expect(wrapper.vm.element.config.minHeight.value).toBe('340px');

        await displayModeSelect.setValue('standard');

        // Should still have the previous value
        expect(wrapper.vm.element.config.minHeight.value).toBe('340px');
    });
});


preview
index.js

import template from './sw-cms-el-preview-ict-image.html.twig';
import './sw-cms-el-preview-ict-image.scss';

const { Component } = Shopware;

/**
 * @private since v6.5.0
 * @package content
 */
Component.register('sw-cms-el-preview-ict-image', {
    template,
});
twig

&amp;lt;!-- eslint-disable-next-line sw-deprecation-rules/no-twigjs-blocks --&amp;gt;
{% block sw_cms_element_ict_image_preview %}
    &amp;lt;div class="sw-cms-el-preview-ict-image"&amp;gt;
        &amp;lt;img
            :src="'/administration/static/img/cms/preview_mountain_small.jpg' | asset"
            alt=""
        &amp;gt;
    &amp;lt;/div&amp;gt;

{% endblock %}
scss

.sw-cms-el-preview-ict-image {
    width: 100%;
    height: 100%;
    overflow: hidden;

    img {
        margin: 0;
        display: block;
        width: 110%;
    }
}

index.js
import './component';
import './config';
import './preview';

Shopware.Service('cmsService').registerCmsElement({
    name: 'ict-image',
    label: 'sw-cms.elements.ictImages.label',
    component: 'sw-cms-el-ict-image',
    configComponent: 'sw-cms-el-config-ict-image',
    previewComponent: 'sw-cms-el-preview-ict-image',
    defaultConfig: {
        buttonField: {
            source: 'static',
            value: null,
        },
        media: {
            source: 'static',
            value: null,
            required: true,
            entity: {
                name: 'media',
            },
        },
        displayMode: {
            source: 'static',
            value: 'standard',
        },
        url: {
            source: 'static',
            value: null,
        },
        newTab: {
            source: 'static',
            value: false,
        },
        minHeight: {
            source: 'static',
            value: '340px',
        },
        verticalAlign: {
            source: 'static',
            value: null,
        },
    },
});
            src/res/app/admin/src/main.js


import './module/sw-cms/blocks/text-image/image-four-row';
import './module/sw-cms/blocks/text-image/text-image-text'
import './module/sw-cms/elements/button';
import './module/sw-cms/elements/custom-image'

        res/app/storefront
dist/storefront/js/blog
src


res/config
services.xml

&amp;lt;?xml version="1.0" ?&amp;gt;

    &amp;lt;container xmlns="http://symfony.com/schema/dic/services"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"&amp;gt;

        &amp;lt;services&amp;gt;
            &amp;lt;service id="Blog\DataResolver\CustomImageCmsElementResolver"&amp;gt;
                &amp;lt;argument type="service" id="media.repository"/&amp;gt;
                &amp;lt;tag name="shopware.cms.data_resolver" /&amp;gt;
            &amp;lt;/service&amp;gt;
        &amp;lt;/services&amp;gt;
    &amp;lt;/container&amp;gt;

    res/public
admin
css/blog.css
.sw-cms-el-custom-image{display:flex;max-width:100%}.sw-cms-el-custom-image img{display:block;max-width:100%}.sw-cms-el-custom-image.is--cover{height:100%;width:100%;position:relative;margin:auto;border-radius:50%;background-clip:padding-box;overflow:hidden}.sw-cms-el-custom-image.is--cover img{object-fit:cover;width:100%;height:100%;position:absolute;top:0;left:0;right:0;bottom:0}.sw-cms-el-custom-image.is--stretch img{width:100%}
.sw-cms-el-config-custom-image .sw-media-upload-v2{margin-bottom:22px}.sw-cms-el-config-custom-image .sw-media-upload-v2 .sw-media-upload-v2__header{display:none}.sw-cms-el-config-custom-image .sw-cms-el-config-image__mapping-preview{display:grid;width:100%;height:200px;justify-items:center;align-items:start}.sw-cms-el-config-custom-image .sw-cms-el-config-image__mapping-preview img{width:100%;height:100%;object-fit:contain;overflow:hidden}.sw-cms-el-config-custom-image .sw-cms-el-config-image__mapping-preview .sw-cms-el-config-image__preview-info{width:100%}
.sw-cms-el-preview-image{width:100%;height:100%;overflow:hidden}.sw-cms-el-preview-image img{margin:0;display:block;width:110%}
js/blog.js

!function(){var e,t,a,n,i,o,s,r,l={382:function(){},732:function(){},152:function(){},603:function(e,t,a){Shopware.Component.register("sw-cms-el-preview-button",()=&amp;gt;a.e(957).then(a.bind(a,957))),Shopware.Component.register("sw-cms-el-config-button",()=&amp;gt;a.e(28).then(a.bind(a,28))),Shopware.Component.register("sw-cms-el-button",()=&amp;gt;a.e(228).then(a.bind(a,228))),Shopware.Service("cmsService").registerCmsElement({name:"button",label:"Button",component:"sw-cms-el-button",configComponent:"sw-cms-el-config-button",previewComponent:"sw-cms-el-preview-button",defaultConfig:{title:{source:"static",value:"Click Me"},url:{source:"static",value:"https://example.com"}}})},947:function(e,t,a){var n=a(382);n.__esModule&amp;amp;&amp;amp;(n=n.default),"string"==typeof n&amp;amp;&amp;amp;(n=[[e.id,n,""]]),n.locals&amp;amp;&amp;amp;(e.exports=n.locals),(0,a(534).A)("01fdda4d",n,!0,{})},645:function(e,t,a){var n=a(732);n.__esModule&amp;amp;&amp;amp;(n=n.default),"string"==typeof n&amp;amp;&amp;amp;(n=[[e.id,n,""]]),n.locals&amp;amp;&amp;amp;(e.exports=n.locals),(0,a(534).A)("f6c38710",n,!0,{})},321:function(e,t,a){var n=a(152);n.__esModule&amp;amp;&amp;amp;(n=n.default),"string"==typeof n&amp;amp;&amp;amp;(n=[[e.id,n,""]]),n.locals&amp;amp;&amp;amp;(e.exports=n.locals),(0,a(534).A)("30779790",n,!0,{})},534:function(e,t,a){"use strict";function n(e,t){for(var a=[],n={},i=0;i&amp;lt;t.length;i++){var o=t[i],s=o[0],r={id:e+":"+i,css:o[1],media:o[2],sourceMap:o[3]};n[s]?n[s].parts.push(r):a.push(n[s]={id:s,parts:[r]})}return a}a.d(t,{A:function(){return f}});var i,o="undefined"!=typeof document;if("undefined"!=typeof DEBUG&amp;amp;&amp;amp;DEBUG&amp;amp;&amp;amp;!o)throw Error("vue-style-loader cannot be used in a non-browser environment. Use { target: 'node' } in your Webpack config to indicate a server-rendering environment.");var s={},r=o&amp;amp;&amp;amp;(document.head||document.getElementsByTagName("head")[0]),l=null,m=0,u=!1,c=function(){},d=null,p="data-vue-ssr-id",g="undefined"!=typeof navigator&amp;amp;&amp;amp;/msie [6-9]\b/.test(navigator.userAgent.toLowerCase());function f(e,t,a,i){u=a,d=i||{};var o=n(e,t);return v(o),function(t){for(var a=[],i=0;i&amp;lt;o.length;i++){var r=s[o[i].id];r.refs--,a.push(r)}t?v(o=n(e,t)):o=[];for(var i=0;i&amp;lt;a.length;i++){var r=a[i];if(0===r.refs){for(var l=0;l&amp;lt;r.parts.length;l++)r.parts[l]();delete s[r.id]}}}}function v(e){for(var t=0;t&amp;lt;e.length;t++){var a=e[t],n=s[a.id];if(n){n.refs++;for(var i=0;i&amp;lt;n.parts.length;i++)n.parts[i](a.parts[i]);for(;i&amp;lt;a.parts.length;i++)n.parts.push(w(a.parts[i]));n.parts.length&amp;gt;a.parts.length&amp;amp;&amp;amp;(n.parts.length=a.parts.length)}else{for(var o=[],i=0;i&amp;lt;a.parts.length;i++)o.push(w(a.parts[i]));s[a.id]={id:a.id,refs:1,parts:o}}}}function h(){var e=document.createElement("style");return e.type="text/css",r.appendChild(e),e}function w(e){var t,a,n=document.querySelector("style["+p+'~="'+e.id+'"]');if(n){if(u)return c;n.parentNode.removeChild(n)}if(g){var i=m++;t=y.bind(null,n=l||(l=h()),i,!1),a=y.bind(null,n,i,!0)}else t=_.bind(null,n=h()),a=function(){n.parentNode.removeChild(n)};return t(e),function(n){n?(n.css!==e.css||n.media!==e.media||n.sourceMap!==e.sourceMap)&amp;amp;&amp;amp;t(e=n):a()}}var b=(i=[],function(e,t){return i[e]=t,i.filter(Boolean).join("\n")});function y(e,t,a,n){var i=a?"":n.css;if(e.styleSheet)e.styleSheet.cssText=b(t,i);else{var o=document.createTextNode(i),s=e.childNodes;s[t]&amp;amp;&amp;amp;e.removeChild(s[t]),s.length?e.insertBefore(o,s[t]):e.appendChild(o)}}function _(e,t){var a=t.css,n=t.media,i=t.sourceMap;if(n&amp;amp;&amp;amp;e.setAttribute("media",n),d.ssrId&amp;amp;&amp;amp;e.setAttribute(p,t.id),i&amp;amp;&amp;amp;(a+="\n/*# sourceURL="+i.sources[0]+" */",a+="\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(i))))+" */"),e.styleSheet)e.styleSheet.cssText=a;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(a))}}}},m={};function u(e){var t=m[e];if(void 0!==t)return t.exports;var a=m[e]={id:e,exports:{}};return l[e](a,a.exports,u),a.exports}u.m=l,u.d=function(e,t){for(var a in t)u.o(t,a)&amp;amp;&amp;amp;!u.o(e,a)&amp;amp;&amp;amp;Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},u.f={},u.e=function(e){return Promise.all(Object.keys(u.f).reduce(function(t,a){return u.f[a](e,t),t},[]))},u.u=function(e){return"static/js/"+({28:"39d760685715ec6a99d2",164:"5b3fd61caa0397bb014a",228:"7c633bcf232f94b9a375",426:"e3faeb57da0380c8eaf9",600:"a3ee68d413ea5b0bd592",878:"496ba786e5ecddbe8a42",957:"57b56d0405f24c550bc0"})[e]+".js"},u.miniCssF=function(e){return"static/css/"+(19===e?"blog":e)+".css"},u.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},e={},t="blog:",u.l=function(a,n,i,o){if(e[a]){e[a].push(n);return}if(void 0!==i)for(var s,r,l=document.getElementsByTagName("script"),m=0;m&amp;lt;l.length;m++){var c=l[m];if(c.getAttribute("src")==a||c.getAttribute("data-webpack")==t+i){s=c;break}}s||(r=!0,(s=document.createElement("script")).charset="utf-8",s.timeout=120,u.nc&amp;amp;&amp;amp;s.setAttribute("nonce",u.nc),s.setAttribute("data-webpack",t+i),s.src=a),e[a]=[n];var d=function(t,n){s.onerror=s.onload=null,clearTimeout(p);var i=e[a];if(delete e[a],s.parentNode&amp;amp;&amp;amp;s.parentNode.removeChild(s),i&amp;amp;&amp;amp;i.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:s}),12e4);s.onerror=d.bind(null,s.onerror),s.onload=d.bind(null,s.onload),r&amp;amp;&amp;amp;document.head.appendChild(s)},u.r=function(e){"undefined"!=typeof Symbol&amp;amp;&amp;amp;Symbol.toStringTag&amp;amp;&amp;amp;Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},u.p="bundles/blog/",a=function(e,t,a,n){var i=document.createElement("link");return i.rel="stylesheet",i.type="text/css",i.onerror=i.onload=function(o){if(i.onerror=i.onload=null,"load"===o.type)a();else{var s=o&amp;amp;&amp;amp;("load"===o.type?"missing":o.type),r=o&amp;amp;&amp;amp;o.target&amp;amp;&amp;amp;o.target.href||t,l=Error("Loading CSS chunk "+e+" failed.\n("+r+")");l.code="CSS_CHUNK_LOAD_FAILED",l.type=s,l.request=r,i.parentNode.removeChild(i),n(l)}},i.href=t,document.head.appendChild(i),i},n=function(e,t){for(var a=document.getElementsByTagName("link"),n=0;n&amp;lt;a.length;n++){var i=a[n],o=i.getAttribute("data-href")||i.getAttribute("href");if("stylesheet"===i.rel&amp;amp;&amp;amp;(o===e||o===t))return i}for(var s=document.getElementsByTagName("style"),n=0;n&amp;lt;s.length;n++){var i=s[n],o=i.getAttribute("data-href");if(o===e||o===t)return i}},i={19:0},u.f.miniCss=function(e,t){i[e]?t.push(i[e]):0!==i[e]&amp;amp;&amp;amp;({164:1,426:1,600:1,878:1})[e]&amp;amp;&amp;amp;t.push(i[e]=new Promise(function(t,i){var o=u.miniCssF(e),s=u.p+o;if(n(o,s))return t();a(e,s,t,i)}).then(function(){i[e]=0},function(t){throw delete i[e],t}))},o={19:0},u.f.j=function(e,t){var a=u.o(o,e)?o[e]:void 0;if(0!==a){if(a)t.push(a[2]);else{var n=new Promise(function(t,n){a=o[e]=[t,n]});t.push(a[2]=n);var i=u.p+u.u(e),s=Error();u.l(i,function(t){if(u.o(o,e)&amp;amp;&amp;amp;(0!==(a=o[e])&amp;amp;&amp;amp;(o[e]=void 0),a)){var n=t&amp;amp;&amp;amp;("load"===t.type?"missing":t.type),i=t&amp;amp;&amp;amp;t.target&amp;amp;&amp;amp;t.target.src;s.message="Loading chunk "+e+" failed.\n("+n+": "+i+")",s.name="ChunkLoadError",s.type=n,s.request=i,a[1](s)}},"chunk-"+e,e)}}},s=function(e,t){var a,n,i=t[0],s=t[1],r=t[2],l=0;if(i.some(function(e){return 0!==o[e]})){for(a in s)u.o(s,a)&amp;amp;&amp;amp;(u.m[a]=s[a]);r&amp;amp;&amp;amp;r(u)}for(e&amp;amp;&amp;amp;e(t);l&amp;lt;i.length;l++)n=i[l],u.o(o,n)&amp;amp;&amp;amp;o[n]&amp;amp;&amp;amp;o[n][0](),o[n]=0},(r=window.webpackJsonpPluginblog=window.webpackJsonpPluginblog||[]).forEach(s.bind(null,0)),r.push=s.bind(null,r.push.bind(r)),window?.__sw__?.assetPath&amp;amp;&amp;amp;(u.p=window.__sw__.assetPath+"/bundles/blog/"),function(){"use strict";let e=["buy-box","product-description-reviews","cross-selling"];var t=Object.freeze({REQUIRED_FIELD_ERROR_CODE:"c1051bb4-d103-4f74-8988-acbcafc7fdc3",PAGE_TYPES:{SHOP:"page",LANDING:"landingpage",LISTING:"product_list",PRODUCT_DETAIL:"product_detail"},TYPE_MAPPING_ENTITIES:{product_detail:{entity:"product",mode:"single"},product_list:{entity:"category",mode:"single"}},UNIQUE_SLOTS:e.map(e=&amp;gt;e.replace(/-./g,e=&amp;gt;e.toUpperCase()[1])),UNIQUE_SLOTS_KEBAB:e,SLOT_POSITIONS:{left:0,"left-image":100,"left-top":200,"left-text":300,"left-bottom":400,"center-left":1e3,center:1100,"center-image":1200,"center-top":1300,"center-text":1400,"center-bottom":1500,"center-right":1600,right:2e3,"right-image":2100,"right-top":2200,"right-text":2300,"right-bottom":2400,content:3e3,image:3100,video:3200,imageSlider:3300,default:5e3},MEDIA:{previewCamera:"bundles/administration/static/img/cms/preview_camera_large.jpg",previewMountain:"bundles/administration/static/img/cms/preview_mountain_large.jpg",previewPlant:"bundles/administration/static/img/cms/preview_plant_large.jpg",previewGlasses:"bundles/administration/static/img/cms/preview_glasses_large.jpg",SMALL:{previewCamera:"framework/assets/default/cms/preview_camera_small.jpg",previewMountain:"framework/assets/default/cms/preview_mountain_small.jpg",previewPlant:"framework/assets/default/cms/preview_plant_small.jpg",previewGlasses:"framework/assets/default/cms/preview_glasses_small.jpg"}}});Shopware.Component.register("sw-cms-preview-image-four-row",()=&amp;gt;u.e(164).then(u.bind(u,164))),Shopware.Component.register("sw-cms-block-image-four-row",()=&amp;gt;u.e(600).then(u.bind(u,600))),Shopware.Service("cmsService").registerCmsBlock({name:"image-four-row",label:"Image Four Row",category:"text-image",component:"sw-cms-block-image-four-row",previewComponent:"sw-cms-preview-image-four-row",defaultConfig:{marginBottom:"20px",marginTop:"20px",marginLeft:"20px",marginRight:"20px",sizingMode:"boxed"},slots:{"left-button":{type:"button",default:{config:{title:{source:"static",value:"Click Me"},url:{source:"static",value:"https://example.com"}}}},"center-left-button":{type:"button",default:{config:{title:{source:"static",value:"Click Me"},url:{source:"static",value:"https://example.com"}}}},"center-right-button":{type:"button",default:{config:{title:{source:"static",value:"Click Me"},url:{source:"static",value:"https://example.com"}}}},"right-button":{type:"button",default:{config:{title:{source:"static",value:"Click Me"},url:{source:"static",value:"https://example.com"}}}},"left-image":{type:"image",default:{config:{displayMode:{source:"static",value:"cover"}},data:{media:{value:t.MEDIA.previewCamera,source:"default"}}}},"center-left-image":{type:"image",default:{config:{displayMode:{source:"static",value:"cover"}},data:{media:{value:t.MEDIA.previewPlant,source:"default"}}}},"center-right-image":{type:"image",default:{config:{displayMode:{source:"static",value:"cover"}},data:{media:{value:t.MEDIA.previewGlasses,source:"default"}}}},"right-image":{type:"image",default:{config:{displayMode:{source:"static",value:"cover"}},data:{media:{value:t.MEDIA.previewMountain,source:"default"}}}},"left-text":{type:"text",default:{config:{content:{source:"static",value:`
                        &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim()}}}},"center-left-text":{type:"text",default:{config:{content:{source:"static",value:`
                        &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim()}}}},"center-right-text":{type:"text",default:{config:{content:{source:"static",value:`
                        &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim()}}}},"right-text":{type:"text",default:{config:{content:{source:"static",value:`
                        &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim()}}}}}}),Shopware.Component.register("sw-cms-preview-text-image-text",()=&amp;gt;u.e(878).then(u.bind(u,878))),Shopware.Component.register("sw-cms-block-text-image-text",()=&amp;gt;u.e(426).then(u.bind(u,426))),Shopware.Service("cmsService").registerCmsBlock({name:"text-image-text",label:"Text Image Text",category:"text-image",component:"sw-cms-block-text-image-text",previewComponent:"sw-cms-preview-text-image-text",defaultConfig:{marginBottom:"20px",marginTop:"20px",marginLeft:"20px",marginRight:"20px",sizingMode:"boxed"},slots:{"left-text":{type:"text",default:{config:{content:{source:"static",value:`
                        &amp;lt;h2 style="text-align: center;"&amp;gt;Lorem Ipsum dolor sit amet&amp;lt;/h2&amp;gt;
                        &amp;lt;p style="text-align: center;"&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim()}}}},"center-image":{type:"custom-image",default:{config:{displayMode:{source:"static",value:"cover"}},data:{media:{value:null,source:"default"}}}},"right-text":{type:"text",default:{config:{content:{source:"static",value:`
                        &amp;lt;h2 style="text-align: center;"&amp;gt;Lorem Ipsum dolor sit amet&amp;lt;/h2&amp;gt;
                        &amp;lt;p style="text-align: center;"&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
                        sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
                        sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
                        Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
                        `.trim()}}}}}}),u(603),u(947);let{Component:a,Mixin:n,Filter:i}=Shopware;a.register("sw-cms-el-custom-image",{template:'{% block sw_cms_element_custom_image %}\n&amp;lt;div\n    class="sw-cms-el-custom-image"\n&amp;gt;\n\n    {% block sw_cms_element_custom_image_content %}\n    &amp;lt;img\n        :src="mediaUrl"\n        alt=""\n    &amp;gt;\n    {% endblock %}\n&amp;lt;/div&amp;gt;\n{% endblock %}\n',mixins:[n.getByName("cms-element")],computed:{mediaUrl(){let e=t.MEDIA.previewPlant.slice(t.MEDIA.previewPlant.lastIndexOf("/")+1),a=this.assetFilter(`administration/static/img/cms/${e}`),n=this.element?.data?.media||{},i=this.element?.config?.media||{};if("mapped"===i.source){let e=this.getDemoValue(i.value);return e?.url?e.url:a}if("default"===i.source&amp;amp;&amp;amp;"string"==typeof i.value){let e=i.value.slice(i.value.lastIndexOf("/")+1);return this.assetFilter(`/administration/static/img/cms/${e}`)}return n?.id?this.element.data.media.url:n?.url?n.url:a},assetFilter(){return i.getByName("asset")},mediaConfigValue(){return this.element?.config?.media?.value}},watch:{"cmsPageState.currentDemoEntity":{handler(){this.updateDemoValue(this.mediaConfigValue)}},mediaConfigValue(e){this.updateDemoValue(e)}},created(){this.createdComponent()},methods:{createdComponent(){this.initElementConfig("custom-image"),this.initElementData("custom-image")},updateDemoValue(e){let t=this.element?.data?.media?.id;this.element?.config?.media?.source==="static"&amp;amp;&amp;amp;t&amp;amp;&amp;amp;e!==t&amp;amp;&amp;amp;(this.element.config.media.value=t)}}}),u(645);let{Component:o,Mixin:s}=Shopware;o.register("sw-cms-el-config-custom-image",{template:'\n{% block sw_cms_element_custom_image_config %}\n&amp;lt;div class="sw-cms-el-config-custom-image"&amp;gt;\n\n    {% block sw_cms_element_image_config_media_upload %}\n        &amp;lt;sw-cms-mapping-field\n                v-model:config="element.config.media"\n                :label="$tc(\'Image Upload\')"\n                value-types="entity"\n                entity="media"\n        &amp;gt;\n            &amp;lt;sw-media-upload-v2\n                    variant="regular"\n                    :upload-tag="uploadTag"\n                    :source="previewSource"\n                    :allow-multi-select="false"\n                    :default-folder="cmsPageState.pageEntityName"\n                    :caption="$tc(\'sw-cms.elements.general.config.caption.mediaUpload\')"\n                    @media-upload-sidebar-open="onOpenMediaModal"\n                    @media-upload-remove-image="onImageRemove"\n            &amp;gt;\n            &amp;lt;/sw-media-upload-v2&amp;gt;\n\n            &amp;lt;template #preview="{ demoValue }"&amp;gt;\n                &amp;lt;div class="sw-cms-el-config-image__mapping-preview"&amp;gt;\n                    &amp;lt;img\n                            v-if="demoValue.url"\n                            :src="demoValue.url"\n                            alt=""\n                    &amp;gt;\n                    &amp;lt;sw-alert\n                            v-else\n                            class="sw-cms-el-config-image__preview-info"\n                            variant="info"\n                    &amp;gt;\n                        {{ $tc(\'sw-cms.detail.label.mappingEmptyPreview\') }}\n                    &amp;lt;/sw-alert&amp;gt;\n                &amp;lt;/div&amp;gt;\n            &amp;lt;/template&amp;gt;\n        &amp;lt;/sw-cms-mapping-field&amp;gt;\n\n        &amp;lt;sw-upload-listener\n                :upload-tag="uploadTag"\n                auto-upload\n                @media-upload-finish="onImageUpload"\n        &amp;gt;\n        &amp;lt;/sw-upload-listener&amp;gt;\n    {% endblock %}\n\n    {% block sw_cms_element_image_config_link %}\n        &amp;lt;div class="sw-cms-el-config-image__link"&amp;gt;\n            &amp;lt;sw-dynamic-url-field\n                    v-model:value="element.config.url.value"\n            &amp;gt;\n            &amp;lt;/sw-dynamic-url-field&amp;gt;\n\n        &amp;lt;/div&amp;gt;\n    {% endblock %}\n\n    {% block sw_cms_element_custom_image_config_media_modal %}\n    &amp;lt;sw-media-modal-v2\n        v-if="mediaModalIsOpen"\n        variant="regular"\n        :caption=""\n        :entity-context="cmsPageState.entityName"\n        :allow-multi-select="false"\n        :initial-folder-id="cmsPageState.defaultMediaFolderId"\n        @media-upload-remove-image="onImageRemove"\n        @media-modal-selection-change="onSelectionChanges"\n        @modal-close="onCloseModal"\n    &amp;gt;\n    &amp;lt;/sw-media-modal-v2&amp;gt;\n    {% endblock %}\n&amp;lt;/div&amp;gt;\n{% endblock %}\n',inject:["repositoryFactory"],emits:["element-update"],mixins:[s.getByName("cms-element")],data(){return{mediaModalIsOpen:!1,initialFolderId:null}},computed:{mediaRepository(){return this.repositoryFactory.create("media")},uploadTag(){return`cms-element-media-config-${this.element.id}`},previewSource(){return this.element.data&amp;amp;&amp;amp;this.element.data.media&amp;amp;&amp;amp;this.element.data.media.id?this.element.data.media:this.element.config.media.value}},created(){this.createdComponent()},methods:{createdComponent(){this.initElementConfig("image")},async onImageUpload({targetId:e}){let t=await this.mediaRepository.get(e);this.element.config.media.value=t.id,this.element.config.media.source="static",this.updateElementData(t),this.$emit("element-update",this.element)},onImageRemove(){this.element.config.media.value=null,this.updateElementData(),this.$emit("element-update",this.element)},onCloseModal(){this.mediaModalIsOpen=!1},onSelectionChanges(e){let t=e[0];this.element.config.media.value=t.id,this.element.config.media.source="static",this.updateElementData(t),this.$emit("element-update",this.element)},updateElementData(e=null){let t=null===e?null:e.id;this.element.data?(this.$set(this.element.data,"mediaId",t),this.$set(this.element.data,"media",e)):this.$set(this.element,"data",{mediaId:t,media:e})},onOpenMediaModal(){this.mediaModalIsOpen=!0}}}),u(321);let{Component:r}=Shopware;r.register("sw-cms-el-preview-custom-image",{template:'{% block sw_cms_element_image_preview %}\n    &amp;lt;div class="sw-cms-el-preview-image"&amp;gt;\n        &amp;lt;img\n                :src="assetFilter(\'/administration/static/img/cms/preview_mountain_small.jpg\')"\n                alt=""\n        &amp;gt;\n    &amp;lt;/div&amp;gt;\n{% endblock %}\n',computed:{assetFilter(){return Shopware.Filter.getByName("asset")}}}),Shopware.Service("cmsService").registerCmsElement({name:"custom-image",label:"Custom Image",component:"sw-cms-el-custom-image",configComponent:"sw-cms-el-config-custom-image",previewComponent:"sw-cms-el-preview-custom-image",defaultConfig:{media:{source:"static",value:null,required:!0,entity:{name:"media"}},url:{source:"static",value:null}}})}()}();

static
    res/views/storefront
block
cms-block-image-four-row.html.twig

{% block block_image_four_row %}
    {% set columns = 4 %}

    {% block block_image_four_row_left %}
        &amp;lt;div class="col-md-3"&amp;gt;
            {% block block_image_four_row_left_image %}
                {% set element = block.slots.getSlot('left-image') %}
                    {% block block_image_four_column_left_inner_image %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                    {% endblock %}
            {% endblock %}

            {% block block_image_four_row_left_button %}
                {% set element = block.slots.getSlot('left-button') %}
                    {% block block_image_four_column_left_inner_button %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                    {% endblock %}
            {% endblock %}

            {% block block_image_four_row_left_text %}
                {% set element = block.slots.getSlot('left-text') %}
                    {% block block_image_four_column_left_inner_text %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                    {% endblock %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}


    {% block block_image_text_row_center_left %}
        &amp;lt;div class="col-md-3"&amp;gt;
            {% block block_image_text_row_center_left_image %}
                {% set element = block.slots.getSlot('center-left-image') %}
                    {% block block_image_four_column_center_left_inner_image %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                    {% endblock%}
                {% endblock %}

            {% block block_image_four_row_center_left_button %}
                {% set element = block.slots.getSlot('center-left-button') %}
                    {% block block_image_four_column_center_left_inner_button %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                {% endblock %}
            {% endblock %}

            {% block block_image_text_row_center_left_text %}
                {% set element = block.slots.getSlot('center-left-text') %}
                    {% block block_image_four_column_center_left_inner_text %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                    {% endblock %}
                {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}

    {% block block_image_text_row_center_right %}
        &amp;lt;div class="col-md-3"&amp;gt;
            {% block block_image_text_row_center_right_image %}
                {% set element = block.slots.getSlot('center-right-image') %}
                    {% block block_image_four_column_center_right_inner_image %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                    {% endblock %}
                {% endblock %}

            {% block block_image_four_row_center_right_button %}
                {% set element = block.slots.getSlot('center-right-button') %}
                    {% block block_image_four_column_center_right_inner_button %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                    {% endblock %}
            {% endblock %}

            {% block block_image_text_row_center_right_text %}
                {% set element = block.slots.getSlot('center-right-text') %}
                    {% block block_image_four_column_center_right_inner_text %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                    {% endblock %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}

    {% block block_image_text_row_right %}
        &amp;lt;div class="col-md-3"&amp;gt;
            {% block block_image_text_row_right_image %}
                {% set element = block.slots.getSlot('right-image') %}
                    {% block block_image_four_column_right_inner_image %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                    {% endblock %}
            {% endblock %}

            {% block block_image_four_row_right_button %}
                {% set element = block.slots.getSlot('right-button') %}
                    {% block block_image_four_column_right_inner_button %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                    {% endblock %}
            {% endblock %}

            {% block block_image_text_row_right_text %}
                {% set element = block.slots.getSlot('right-text') %}
                    {% block block_image_four_column_right_inner_text %}
                        &amp;lt;div data-cms-element-id="{{ element.id }}"&amp;gt;
                            {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
                        &amp;lt;/div&amp;gt;
                    {% endblock %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}
{% endblock %}
cms-block-text-image-text.html.twig

{% block block_text_image_text %}
    {% set columns = 3 %}

    {% block block_left_text %}
        {% set element = block.slots.getSlot('left-text') %}

        &amp;lt;div class="col-md-4" data-cms-element-id="{{ element.id }}"&amp;gt;
            {% block block_text_left_inner %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}

    {% block block_center_image %}
        {% set element = block.slots.getSlot('center-image') %}

        &amp;lt;div class="col-md-4" data-cms-element-id="{{ element.id }}"&amp;gt;
            {% block block_center_image_center_inner %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}

    {% block block_right_text %}
        {% set element = block.slots.getSlot('right-text') %}

        &amp;lt;div class="col-md-4" data-cms-element-id="{{ element.id }}"&amp;gt;
            {% block block_text_right_inner %}
                {% sw_include '@Storefront/storefront/element/cms-element-' ~ element.type ~ '.html.twig' ignore missing %}
            {% endblock %}
        &amp;lt;/div&amp;gt;
    {% endblock %}
{% endblock %}

element
cms-element-button.html.twig

{% block element_button %}
    &amp;lt;a class="btn btn-primary" href="{{ element.config.url.value }}"&amp;gt;{{ element.config.title.value }}&amp;lt;/a&amp;gt;
{% endblock %}

cms-element-custom-image.htm.twig

{% block cms_element_custom_image %}

{% set imageStruct = element.data.media %}
{% if imageStruct and imageStruct.media %}
    {% set media = imageStruct.media %}
    &amp;lt;div class="cms-element-custom-image"&amp;gt;
        &amp;lt;a href="{{ element.config.url.value }}"&amp;gt;
            &amp;lt;img src="{{ media.url }}"
             loading="lazy"
             class="img-fluid"

        /&amp;gt;
        &amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;
{% else %}
    &amp;lt;div class="cms-element-custom-image"&amp;gt;
        &amp;lt;p&amp;gt;No image selected.&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
{% endif %}
{% endblock %}

cms-element-product-description-reviews.html.twig

{% sw_extends '@Storefront/storefront/element/cms-element-product-description-reviews.html.twig' %}

{% block element_product_description_reviews_tabs_navigation_review %}
    {{ parent() }}
    {% block custom_block_short_description %}
        &amp;lt;li class="nav-item"&amp;gt;
            &amp;lt;a class="nav-link product-detail-tab-navigation-link short-desc-tab"
               id="short-description-tab-id"
               data-bs-toggle="tab"
               data-off-canvas-tabs="true"
               href="#short-description-content-id"
               role="tab"
               aria-controls="short-description-content-id"
               aria-selected="true"&amp;gt;
                Short description
                &amp;lt;span class="product-detail-tab-navigation-icon"&amp;gt;
        {% sw_icon 'arrow-medium-right' style {'pack':'solid'} %} &amp;lt;/span&amp;gt;
            &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
    {% endblock %}
{% endblock %}

{% block element_product_description_reviews_tabs_content_review %}
    {{ parent() }}
    {% block custom_block_short_description_content %}
        &amp;lt;div class="tab-pane fade show"
             id="short-description-content-id"
             role="tabpanel"
             aria-labelledby="short-description-tab-id"&amp;gt;
            {{ product.customFields.custom_product_short|raw }}
        &amp;lt;/div&amp;gt;
    {% endblock %}
{% endblock %}

layout
footer
footer.html.twig

{% sw_extends '@Storefront/storefront/layout/footer/footer.html.twig' %}

{% if not feature('cache_rework') and not footer is defined and page is defined %}
    {% set footer = page.footer %}
{% endif %}

header
header.html.twig

{% sw_extends '@Storefront/storefront/layout/header/header.html.twig' %}

{% block layout_header_actions_cart %}
    {{ parent() }}
    &amp;lt;a href="{{ theme_config('ctaUrl') }}"
       class="btn btn-primary"
       target="_blank"
       rel="noopener noreferrer"&amp;gt;
        {{ 'Open Link' }}
    &amp;lt;/a&amp;gt;
{% endblock %}

    theme.json

{
  "name": "Blog Theme",
  "author": "demo tech",
  "views": [
    "@Storefront",
    "@Plugins",
    "@Blog"
  ],
  "style": [
    "app/storefront/src/scss/overrides.scss",
    "@Storefront",
    "app/storefront/src/scss/base.scss"
  ],
  "script": [
    "@Storefront",
    "app/storefront/dist/storefront/js/blog/blog.js"
  ],
  "asset": [
    "@Storefront",
    "app/storefront/src/assets"
  ],
  "config": {
    "fields": {
      "ctaUrl": {
        "label": {
          "en-GB": "CTA Button URL",
          "de-DE": "CTA-Button-URL"
        },
        "type": "url",
        "scss": false
      }
    }
  }
  }



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>cms element</title>
      <dc:creator>vuejstest</dc:creator>
      <pubDate>Wed, 28 May 2025 15:32:34 +0000</pubDate>
      <link>https://dev.to/vuejstest_9d7a4f2fbe7631a/cms-element-90</link>
      <guid>https://dev.to/vuejstest_9d7a4f2fbe7631a/cms-element-90</guid>
      <description>&lt;p&gt;Liquid syntax error: Unknown tag 'block'&lt;/p&gt;
</description>
    </item>
    <item>
      <title>api crud vue js</title>
      <dc:creator>vuejstest</dc:creator>
      <pubDate>Sun, 30 Mar 2025 10:10:50 +0000</pubDate>
      <link>https://dev.to/vuejstest_9d7a4f2fbe7631a/api-crud-vue-js-3274</link>
      <guid>https://dev.to/vuejstest_9d7a4f2fbe7631a/api-crud-vue-js-3274</guid>
      <description>&lt;p&gt;app.vue&lt;/p&gt;

&lt;p&gt;import AddProduct from './components/AddProduct.vue';&lt;br&gt;
import ShowProduct from './components/ShowProduct.vue';&lt;br&gt;
import EditProduct from './components/EditProduct.vue';&lt;/p&gt;

&lt;p&gt;export default{&lt;br&gt;
  components:{&lt;br&gt;
    AddProduct,&lt;br&gt;
    ShowProduct,&lt;br&gt;&lt;br&gt;
    EditProduct&lt;br&gt;
  },&lt;br&gt;
  data(){&lt;br&gt;
    return {&lt;br&gt;
      products:[],&lt;br&gt;
      categories:[],&lt;br&gt;
      currentPage:1,&lt;br&gt;
      pageSize:5,&lt;br&gt;
      selectedCategory:'',&lt;br&gt;
      searchQuery:'',&lt;br&gt;
      searchDescription:'',&lt;br&gt;
      selectedProduct:null,&lt;br&gt;
      showAddModal: false,&lt;br&gt;
      showDetailsModal:false,&lt;br&gt;
      showEditModal:false,&lt;br&gt;
      sortBy:'',&lt;br&gt;
      orderBy:''&lt;br&gt;
    };&lt;br&gt;
  }&lt;br&gt;
,&lt;br&gt;
  async created(){&lt;br&gt;
    try {&lt;br&gt;
    const response = await fetch('&lt;a href="https://fakestoreapi.com/products'" rel="noopener noreferrer"&gt;https://fakestoreapi.com/products'&lt;/a&gt;);&lt;br&gt;
    this.products = await response.json();&lt;br&gt;
    this.categories=[ ...new Set(this.products.map(product =&amp;gt;product.category))]&lt;br&gt;
  } catch (error) {&lt;br&gt;
    console.error(error);&lt;br&gt;
  }&lt;br&gt;
  },&lt;br&gt;
  watch: {&lt;br&gt;
    sortBy(newValue) {&lt;br&gt;
    if (newValue) {&lt;br&gt;
    this.orderBy = '';&lt;br&gt;
}&lt;br&gt;
},&lt;br&gt;
orderBy(newValue) {&lt;br&gt;
    if (newValue) {&lt;br&gt;
    this.sortBy = '';&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
},&lt;br&gt;
  computed:{&lt;br&gt;
    totalPrice(){&lt;br&gt;
      return this.filteredProducts.reduce((sum,product) =&amp;gt; sum+product.price,0);&lt;br&gt;
    },&lt;br&gt;
    totalPerPage(){&lt;br&gt;
      return this.paginatedProducts.reduce((sum,product)=&amp;gt; sum + product.price, 0);&lt;br&gt;
    },&lt;br&gt;
    totalPages(){&lt;br&gt;
      return Math.ceil(this.filteredProducts.length/this.pageSize);&lt;br&gt;
    },&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;paginatedProducts(){
  const start=(this.currentPage-1)*this.pageSize;
  const end=start+this.pageSize;
  return this.filteredProducts.slice(start,end)
},
filteredProducts(){
  let filtered=this.products;
  if(this.selectedCategory){
    filtered=filtered.filter(product=&amp;amp;gt;product.category===this.selectedCategory)
  }
  if(this.searchQuery){
    filtered=filtered.filter(product=&amp;amp;gt;product.title.toLowerCase().includes(this.searchQuery.toLowerCase()))
  }
  if(this.searchDescription){
    filtered=filtered.filter(product=&amp;amp;gt;product.description.toLowerCase().includes(this.searchDescription.toLowerCase()))
  }
  if(this.sortBy){
    filtered=filtered.slice().sort((a,b)=&amp;amp;gt;{
      return this.sortBy==='asc'
      ?a.title.localeCompare(b.title)
      :b.title.localeCompare(a.title)
    })
  }

  if(this.orderBy){
  filtered=filtered.slice().sort((a,b)=&amp;amp;gt;{
  return this.sortBy==='asc'
  ?a.price-b.price
  :b.price-a.price;
  })
  }
  return filtered;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;},&lt;br&gt;
  methods:{&lt;br&gt;
addProduct(newProduct){&lt;br&gt;
  this.products.unshift({id:Date.now(), ...newProduct});&lt;br&gt;
  if(!this.categories.includes(newProduct.category)){&lt;br&gt;
    this.categories.push(newProduct.category);&lt;br&gt;
  }&lt;br&gt;
  this.showAddModal=false;&lt;br&gt;
  this.currentPage=1;&lt;br&gt;
},&lt;br&gt;
showProduct(product){&lt;br&gt;
  this.selectedProduct=product;&lt;br&gt;
  this.showDetailsModal=true;&lt;br&gt;
},&lt;br&gt;
editProduct(product){&lt;br&gt;
  this.selectedProduct={ ...product};&lt;br&gt;
  this.showEditModal=true;&lt;br&gt;
},&lt;br&gt;
updateProduct(updatedProduct){&lt;br&gt;
  const index=this.products.findIndex(p=&amp;gt;p.id === updatedProduct.id);&lt;br&gt;
  if(index !== -1){&lt;br&gt;
    this.products.splice(index,1,updatedProduct);&lt;br&gt;
  }&lt;br&gt;
  this.showEditModal=false;&lt;br&gt;
},&lt;br&gt;
deleteProduct(id){&lt;br&gt;
  if(confirm("do you want to delete")){&lt;br&gt;
    this.products=this.products.filter(product=&amp;gt;product.id!==id)&lt;br&gt;
  }&lt;br&gt;
},&lt;br&gt;
changePage(page){&lt;br&gt;
  if(page&amp;gt;=1 &amp;amp;&amp;amp; page&amp;lt;=this.totalPages){&lt;br&gt;
    this.currentPage=page;&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
}}&lt;/p&gt;


&lt;h5&gt;product listing&lt;/h5&gt;
&lt;br&gt;
    &lt;br&gt;
      &lt;br&gt;
        select category&lt;br&gt;
        {{ category }}&lt;br&gt;
      &lt;br&gt;
      &lt;br&gt;
          Sort By:&lt;br&gt;
          title&lt;br&gt;
          price&lt;br&gt;
        &lt;br&gt;
        &lt;br&gt;
          Order by:&lt;br&gt;
          ascending&lt;br&gt;
          descending&lt;br&gt;
        &lt;br&gt;
    &lt;br&gt;
    &lt;br&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div&amp;gt;
  &amp;lt;input type="text" v-model="searchQuery" placeholder="enter title to search" &amp;gt; 
&amp;lt;/div&amp;gt;
&amp;lt;div&amp;gt;
  &amp;lt;input style="margin-left: 848px;" type="text" v-model="searchDescription" placeholder="enter description to search"&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br&gt;&lt;br&gt;
    add new data&lt;br&gt;
    &lt;/p&gt;
&lt;br&gt;
        &lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;br&gt;
          &lt;thead&gt;
&lt;br&gt;
          &lt;tr&gt;
&lt;br&gt;
            &lt;th&gt;Sr no&lt;/th&gt;
&lt;br&gt;
            &lt;th&gt;Title&lt;/th&gt;
&lt;br&gt;
            &lt;th&gt;Price&lt;/th&gt;
&lt;br&gt;
            &lt;th&gt;Image&lt;/th&gt;
&lt;br&gt;
            &lt;th&gt;Category&lt;/th&gt;
&lt;br&gt;
            &lt;th&gt;Description&lt;/th&gt;
&lt;br&gt;
            &lt;th rowspan="3"&gt;Actions&lt;/th&gt;
&lt;br&gt;
          &lt;/tr&gt;
&lt;br&gt;
          &lt;/thead&gt;
&lt;br&gt;
          &lt;tbody&gt;
&lt;br&gt;
          &lt;tr&gt;
&lt;br&gt;
            &lt;td&gt;{{ product.id }}&lt;/td&gt;
&lt;br&gt;
            &lt;td&gt;{{ product.title }}&lt;/td&gt;
&lt;br&gt;
            &lt;td&gt;{{ product.price }}&lt;/td&gt;
&lt;br&gt;
            &lt;td&gt;
&lt;br&gt;
              &lt;img alt="product Image"&gt;&lt;br&gt;
            &lt;/td&gt;
&lt;br&gt;
            &lt;td&gt;{{ product.category }}&lt;/td&gt;
&lt;br&gt;
            &lt;td&gt;{{ product.description }}&lt;/td&gt;
&lt;br&gt;
            &lt;td&gt;
&lt;br&gt;
              &lt;img src="/view.png"&gt;&lt;br&gt;
              &lt;img src="/edit.png"&gt;&lt;br&gt;
              &lt;img src="/delete.png"&gt;&lt;br&gt;
            &lt;/td&gt;
&lt;br&gt;
          &lt;/tr&gt;
&lt;br&gt;
          &lt;/tbody&gt;
&lt;br&gt;
        &lt;/table&gt;&lt;/div&gt;
&lt;br&gt;
        &lt;br&gt;
          &lt;h6&gt;total products {{ products.length }}&lt;/h6&gt;
&lt;br&gt;
          &lt;h6&gt;total price {{ totalPrice.toFixed(3) }}&lt;/h6&gt;
&lt;br&gt;
          &lt;h6&gt;total price per page {{ totalPerPage.toFixed(3) }}&lt;/h6&gt;
&lt;br&gt;
        
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;nav aria-label="Page navigation example"&amp;gt;
&amp;lt;ul style="margin-left: 472px;" class="pagination"&amp;gt;
  &amp;lt;li&amp;gt;
  &amp;lt;button @click="changePage(currentPage-1)"&amp;gt;&amp;lt;&amp;lt;/button&amp;gt;
  &amp;lt;/li&amp;gt;
  &amp;lt;li
  v-for="page in totalPages"
  :class="{active:currentPage===page}"
  :key="page"
  &amp;gt;
  &amp;lt;button @click="changePage(page)"&amp;gt;{{ page }}&amp;lt;/button&amp;gt;
  &amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;
    &amp;lt;button @click="changePage(currentPage+1)"&amp;gt;&amp;gt;&amp;lt;/button&amp;gt;
  &amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;br&gt;
&lt;br&gt;
        
        :showModal="showAddModal"&lt;br&gt;
        @add-product="addProduct"&lt;br&gt;
        @close="showAddModal=false"&lt;br&gt;
        /&amp;gt;&lt;br&gt;
        
        :showDetailsModal="showDetailsModal"&lt;br&gt;
        :product="selectedProduct"&lt;br&gt;
        @close="showDetailsModal=false"&lt;br&gt;
        /&amp;gt;&lt;br&gt;
        
        :showEditModal="showEditModal"&lt;br&gt;
        :product="selectedProduct"&lt;br&gt;
        :categories="categories"&lt;br&gt;
        @update-product="updateProduct"&lt;br&gt;
        @close="showEditModal=false"&lt;br&gt;
        /&amp;gt;&lt;br&gt;
 

&lt;p&gt;add.vue&lt;/p&gt;

&lt;p&gt;export default{&lt;br&gt;
  props: {&lt;br&gt;
    showModal: Boolean,&lt;br&gt;
  },&lt;br&gt;
  data(){&lt;br&gt;
    return{&lt;br&gt;
        product:{&lt;br&gt;
            title:'',&lt;br&gt;
            price:'',&lt;br&gt;
            image:'',&lt;br&gt;
            category:'',&lt;br&gt;
        },&lt;br&gt;
    };&lt;br&gt;
  },&lt;br&gt;
  methods:{&lt;br&gt;
    handleFileUpload(event){&lt;br&gt;
 const file = event.target.files[0];&lt;br&gt;
    if(file){&lt;br&gt;
    const reader = new FileReader();&lt;br&gt;
    reader.onload = ()=&amp;gt;{&lt;br&gt;
    this.product.image = reader.result;&lt;br&gt;
         };&lt;br&gt;
    reader.readAsDataURL(file);&lt;br&gt;
    }&lt;br&gt;
  },&lt;br&gt;
submitProduct(){&lt;br&gt;
    this.$emit('add-product',{ ...this.product});&lt;br&gt;
    this.product={&lt;br&gt;
        title:'',&lt;br&gt;
        price:'',&lt;br&gt;
        image:'',&lt;br&gt;
        category:'',&lt;br&gt;
    };&lt;br&gt;
    this.$emit('close');&lt;br&gt;
}&lt;br&gt;
}&lt;br&gt;
};&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      &amp;lt;h5&amp;gt;add new product&amp;lt;/h5&amp;gt;
      X
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;form @submit.prevent="submitProduct"&amp;gt;
    &amp;lt;div class="mb-3"&amp;gt;
      &amp;lt;div class="row"&amp;gt;
        &amp;lt;div class="col"&amp;gt;
    &amp;lt;label class="form-label"&amp;gt;title&amp;lt;/label&amp;gt;
    &amp;lt;input type="text" v-model="product.title" placeholder="enter title" class="form-control" required /&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class="col"&amp;gt;
    &amp;lt;label class="form-label"&amp;gt;price&amp;lt;/label&amp;gt;
    &amp;lt;input type="number" v-model="product.price" placeholder="enter price" class="form-control" required /&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
    &amp;lt;div class="mb-3"&amp;gt;
      &amp;lt;div class="row"&amp;gt;
        &amp;lt;div class="col"&amp;gt;
        &amp;lt;label class="form-label"&amp;gt;category&amp;lt;/label&amp;gt;
        &amp;lt;input type="text" v-model="product.category" placeholder="enter category" class="form-control" required /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class="col"&amp;gt;

    &amp;lt;label class="form-label"&amp;gt;description&amp;lt;/label&amp;gt;
    &amp;lt;input type="text" v-model="product.description" placeholder="enter description" class="form-control" required /&amp;gt;
    &amp;lt;/div&amp;gt;
   &amp;lt;/div&amp;gt;
   &amp;lt;br&amp;gt;
    &amp;lt;div class="mb-3"&amp;gt;
    &amp;lt;label class="form-label"&amp;gt;image&amp;lt;/label&amp;gt;
    &amp;lt;input type="file" @change="handleFileUpload" required /&amp;gt;
    &amp;lt;img v-if="product.image" :src="product.image" alt="product image" style="height: 50px; width: 50px;" /&amp;gt;
   &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
    &amp;lt;button style="margin-left: 343px;"  type="submit"&amp;gt;add&amp;lt;/button&amp;gt;  
  &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;div v-if="showModal"&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;.form-control{&lt;br&gt;
  width: 307px;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;edit&lt;/p&gt;

&lt;p&gt;export default{&lt;br&gt;
    props:{&lt;br&gt;
        showEditModal:Boolean,&lt;br&gt;
        product: Object,&lt;br&gt;
        Categories:Array&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;data(){&lt;br&gt;
    return{&lt;br&gt;
        productData:{...this.product}&lt;br&gt;
    };&lt;br&gt;
},&lt;br&gt;
watch:{&lt;br&gt;
    product(newProduct){&lt;br&gt;
        this.productData={ ...newProduct};&lt;br&gt;
    }&lt;br&gt;
},&lt;br&gt;
methods:{&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    handleFileUpload(event) {
  const file = event.target.files[0];
  if (file) {
    const reader = new FileReader();
    reader.onload = () =&amp;amp;gt; {
      this.productData.image = reader.result;
    };
    reader.readAsDataURL(file);
  }
},
    updateProduct() {
  this.$emit('update-product', this.productData);
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}};&lt;/p&gt;


&lt;h5&gt;edit product&lt;/h5&gt;
&lt;br&gt;
  X&lt;br&gt;
        &lt;br&gt;
        &lt;br&gt;
        &lt;br&gt;
          &lt;br&gt;
            &lt;br&gt;
        title&lt;br&gt;
        &lt;br&gt;
      &lt;br&gt;
      &lt;br&gt;
      &lt;br&gt;
      &lt;br&gt;
        price&lt;br&gt;
        &lt;br&gt;
      &lt;br&gt;
      &lt;br&gt;
      &lt;br&gt;
        &lt;br&gt;
          &lt;br&gt;
            &lt;br&gt;
        category&lt;br&gt;
        &lt;br&gt;
      &lt;br&gt;
      &lt;br&gt;
        description&lt;br&gt;
        &lt;br&gt;
      &lt;br&gt;
    &lt;br&gt;
        image&lt;br&gt;
        &lt;br&gt;
        &lt;img alt="product image"&gt;&lt;br&gt;
       &lt;br&gt;
        &lt;br&gt;
        update  
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;div v-if="showEditModal"&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
 


&lt;p&gt;show&lt;/p&gt;

&lt;p&gt;export default{&lt;br&gt;
    props: {&lt;br&gt;
    showDetailsModal: Boolean,&lt;br&gt;
    product: Object,&lt;br&gt;
  },&lt;br&gt;
 };&lt;/p&gt;


&lt;h5&gt;product Details&lt;/h5&gt;
&lt;br&gt;
             X&lt;br&gt;
             &lt;br&gt;
             &lt;br&gt;
              &lt;br&gt;
                &lt;p&gt;title: {{ product.title }}&lt;/p&gt;
&lt;br&gt;
                &lt;p&gt;price: {{ product.price }}&lt;/p&gt;
&lt;br&gt;
                &lt;p&gt;category: {{ product.category }}&lt;/p&gt;
&lt;br&gt;
                &lt;p&gt;image: &lt;/p&gt;
&lt;br&gt;
                &lt;br&gt;
                    &lt;img alt="product image"&gt;&lt;br&gt;
                &lt;br&gt;
              &lt;br&gt;
             &lt;br&gt;
             
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        &amp;lt;/div&amp;gt;
       &amp;lt;/div&amp;gt;
     &amp;lt;/div&amp;gt;    
  &amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;




</description>
    </item>
    <item>
      <title>vue js crud using api</title>
      <dc:creator>vuejstest</dc:creator>
      <pubDate>Sun, 30 Mar 2025 07:45:33 +0000</pubDate>
      <link>https://dev.to/vuejstest_9d7a4f2fbe7631a/vue-js-crud-using-api-3kog</link>
      <guid>https://dev.to/vuejstest_9d7a4f2fbe7631a/vue-js-crud-using-api-3kog</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.vue
&amp;lt;script&amp;gt;
import AddProduct from './components/AddProduct.vue';
import ShowProduct from './components/ShowProduct.vue';
import EditProduct from './components/EditProduct.vue';

 export default{
 components:{
  AddProduct,
  ShowProduct,  
  EditProduct
  },
 data(){
  return {
  products:[],
  categories:[],
  currentPage:1,
  pageSize:5,
  selectedCategory:'',
  searchQuery:'',
  searchDescription:'',
  selectedProduct:null,
  showAddModal: false,
  showDetailsModal:false,
  showEditModal:false,
  sortBy:'',
  orderBy:''
 };
 }
 ,
  async created(){
  try {
  const response = await fetch('https://fakestoreapi.com/products');
  this.products = await response.json();
  this.categories=[ ...new Set(this.products.map(product =&amp;gt;product.category))]
  } catch (error) {
  console.error(error);
 }
 },
 watch: {
  sortBy(newValue) {
 if (newValue) {
 this.orderBy = '';
 }
 },
 orderBy(newValue) {
 if (newValue) {
 this.sortBy = '';
 }
 }
 },
 computed:{
 totalPrice(){
  return this.filteredProducts.reduce((sum,product) =&amp;gt; sum+product.price,0);
 },
 totalPerPage(){
  return this.paginatedProducts.reduce((sum,product)=&amp;gt; sum + product.price, 0);
 },
 totalPages(){
  return Math.ceil(this.filteredProducts.length/this.pageSize);
},

 paginatedProducts(){
  const start=(this.currentPage-1)*this.pageSize;
  const end=start+this.pageSize;
  return this.filteredProducts.slice(start,end)
 },
 filteredProducts(){
  let filtered=this.products;
  if(this.selectedCategory){
    filtered=filtered.filter(product=&amp;gt;product.category===this.selectedCategory)
  }
  if(this.searchQuery){
    filtered=filtered.filter(product=&amp;gt;product.title.toLowerCase().includes(this.searchQuery.toLowerCase()))
  }
  if(this.searchDescription){
    filtered=filtered.filter(product=&amp;gt;product.description.toLowerCase().includes(this.searchDescription.toLowerCase()))
  }
  if(this.sortBy){
    filtered=filtered.slice().sort((a,b)=&amp;gt;{
      return this.sortBy==='asc'
      ?a.title.localeCompare(b.title)
      :b.title.localeCompare(a.title)
    })
  }

  if(this.orderBy){
  filtered=filtered.slice().sort((a,b)=&amp;gt;{
  return this.sortBy==='asc'
  ?a.price-b.price
  :b.price-a.price;
  })
  }
  return filtered;
 }
 },
 methods:{
 addProduct(newProduct){
 this.products.unshift({id:Date.now(), ...newProduct});
 if(!this.categories.includes(newProduct.category)){
 this.categories.push(newProduct.category);
  }
  this.showAddModal=false;
 this.currentPage=1;
 },
 showProduct(product){
 this.selectedProduct=product;
 this.showDetailsModal=true;
 },
 editProduct(product){
 this.selectedProduct={ ...product};
 this.showEditModal=true;
 },
 updateProduct(updatedProduct){
 const index=this.products.findIndex(p=&amp;gt;p.id === updatedProduct.id);
 if(index !== -1){
 this.products.splice(index,1,updatedProduct);
 }
 this.showEditModal=false;
 },
 deleteProduct(id){
 if(confirm("do you want to delete")){
  this.products=this.products.filter(product=&amp;gt;product.id!==id)
 }
 },
 changePage(page){
 if(page&amp;gt;=1 &amp;amp;&amp;amp; page&amp;lt;=this.totalPages){
  this.currentPage=page;
 }
 }
 }}
 &amp;lt;/script&amp;gt; 

 &amp;lt;template&amp;gt;
 &amp;lt;div class="container"&amp;gt; 
 &amp;lt;h5 style="text-align: center;" &amp;gt;product listing&amp;lt;/h5&amp;gt;
 &amp;lt;div&amp;gt;
  &amp;lt;select v-model="selectedCategory"&amp;gt;
    &amp;lt;option value=""&amp;gt;select category&amp;lt;/option&amp;gt;
    &amp;lt;option :value="category" :key="category" v-for="category in categories"&amp;gt;{{ category }}&amp;lt;/option&amp;gt;
  &amp;lt;/select&amp;gt;
  &amp;lt;select style="margin-left: 350px;" v-model="sortBy"&amp;gt;
      &amp;lt;option value="" selected&amp;gt;Sort By:&amp;lt;/option&amp;gt;
      &amp;lt;option value="title"&amp;gt;title&amp;lt;/option&amp;gt;
      &amp;lt;option value="price"&amp;gt;price&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
    &amp;lt;select style="margin-left: 416px;" v-model="orderBy"&amp;gt;
      &amp;lt;option value="" selected&amp;gt;Order by:&amp;lt;/option&amp;gt;
      &amp;lt;option value="asc"&amp;gt;ascending&amp;lt;/option&amp;gt;
      &amp;lt;option value="desc"&amp;gt;descending&amp;lt;/option&amp;gt;
    &amp;lt;/select&amp;gt;
 &amp;lt;/div&amp;gt;
 &amp;lt;br&amp;gt;

 &amp;lt;div&amp;gt;
  &amp;lt;input type="text" v-model="searchQuery" placeholder="enter title to search" &amp;gt; 
 &amp;lt;/div&amp;gt;
 &amp;lt;div&amp;gt;
  &amp;lt;input style="margin-left: 848px;" type="text" v-model="searchDescription" placeholder="enter description to search"&amp;gt;
 &amp;lt;/div&amp;gt;
 &amp;lt;br&amp;gt;
  &amp;lt;button style="margin-left: 12px;" @click.prevent="showAddModal=true"&amp;gt;add new data&amp;lt;/button&amp;gt;
  &amp;lt;div class="container"&amp;gt;
    &amp;lt;table style="margin-left: auto; margin-right: auto;" class="table table-striped" border="1"&amp;gt;
      &amp;lt;thead&amp;gt;
      &amp;lt;tr&amp;gt;
        &amp;lt;th&amp;gt;Sr no&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Title&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Price&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Image&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Category&amp;lt;/th&amp;gt;
        &amp;lt;th&amp;gt;Description&amp;lt;/th&amp;gt;
        &amp;lt;th rowspan="3"&amp;gt;Actions&amp;lt;/th&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;/thead&amp;gt;
      &amp;lt;tbody&amp;gt;
      &amp;lt;tr v-for="(product) in paginatedProducts" :key="product.id"&amp;gt;
        &amp;lt;td&amp;gt;{{ product.id }}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;{{ product.title }}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;{{ product.price }}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;
          &amp;lt;img :src="product.image" alt="product Image" style="width: 40px; height: 40px;" /&amp;gt;
        &amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;{{ product.category }}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;{{ product.description }}&amp;lt;/td&amp;gt;
        &amp;lt;td&amp;gt;
          &amp;lt;button @click="showProduct(product)"&amp;gt;&amp;lt;img style="height: 25x; width: 25px;" src="/view.png"&amp;gt;&amp;lt;/button&amp;gt;
          &amp;lt;button @click="editProduct(product)"&amp;gt;&amp;lt;img style="height: 25x; width: 25px;" src="/edit.png"&amp;gt;&amp;lt;/button&amp;gt;
          &amp;lt;button @click="deleteProduct(product.id)"&amp;gt;&amp;lt;img style="height: 25x; width: 25px;" src="/delete.png"&amp;gt;&amp;lt;/button&amp;gt;
        &amp;lt;/td&amp;gt;
      &amp;lt;/tr&amp;gt;
      &amp;lt;/tbody&amp;gt;
     &amp;lt;/table&amp;gt;
     &amp;lt;div style="display: flex"&amp;gt;
      &amp;lt;h6&amp;gt;total products {{ products.length }}&amp;lt;/h6&amp;gt;
      &amp;lt;h6 style="margin-left: 346px"&amp;gt;total price {{ totalPrice.toFixed(3) }}&amp;lt;/h6&amp;gt;
      &amp;lt;h6 style="margin-left: auto"&amp;gt;total price per page {{ totalPerPage.toFixed(3) }}&amp;lt;/h6&amp;gt;
     &amp;lt;/div&amp;gt;

     &amp;lt;nav aria-label="Page navigation example"&amp;gt;
     &amp;lt;ul style="margin-left: 472px;" class="pagination"&amp;gt;
     &amp;lt;li&amp;gt;
     &amp;lt;button @click="changePage(currentPage-1)"&amp;gt;&amp;lt;&amp;lt;/button&amp;gt;
     &amp;lt;/li&amp;gt;
     &amp;lt;li
     v-for="page in totalPages"
     :class="{active:currentPage===page}"
     :key="page"
     &amp;gt;
     &amp;lt;button @click="changePage(page)"&amp;gt;{{ page }}&amp;lt;/button&amp;gt;
      &amp;lt;/li&amp;gt;
     &amp;lt;li&amp;gt;
     &amp;lt;button @click="changePage(currentPage+1)"&amp;gt;&amp;gt;&amp;lt;/button&amp;gt;
     &amp;lt;/li&amp;gt;
     &amp;lt;/ul&amp;gt;
     &amp;lt;/nav&amp;gt;
     &amp;lt;/div&amp;gt;
     &amp;lt;/div&amp;gt;
     &amp;lt;AddProduct 
     :showModal="showAddModal"
     @add-product="addProduct"
     @close="showAddModal=false"
     /&amp;gt;
     &amp;lt;ShowProduct 
     :showDetailsModal="showDetailsModal"
     :product="selectedProduct"
     @close="showDetailsModal=false"
     /&amp;gt;
     &amp;lt;EditProduct
     :showEditModal="showEditModal"
     :product="selectedProduct"
     :categories="categories"
     @update-product="updateProduct"
     @close="showEditModal=false"
     /&amp;gt;
    &amp;lt;/template&amp;gt; 

add
&amp;lt;script&amp;gt;
export default{
 props: {
 showModal: Boolean,
 },
 data(){
 return{
    product:{
        title:'',
        price:'',
        image:'',
        category:'',
    },
 };
 },
 methods:{
 handleFileUpload(event){
 const file = event.target.files[0];
 if(file){
 const reader = new FileReader();
 reader.onload = ()=&amp;gt;{
 this.product.image = reader.result;
     };
 reader.readAsDataURL(file);
 }
 },
 submitProduct(){
 this.$emit('add-product',{ ...this.product});
  this.product={
    title:'',
    price:'',
    image:'',
    category:'',
 };
 this.$emit('close');
 }
 }
 };
 &amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
 &amp;lt;div v-if="showModal" tabindex="-1" class="modal d-block"&amp;gt;
 &amp;lt;div class="modal-dialog modal-lg"&amp;gt;
  &amp;lt;div class="modal-content"&amp;gt;
    &amp;lt;div class="modal-header" &amp;gt;
      &amp;lt;h5&amp;gt;add new product&amp;lt;/h5&amp;gt;
      &amp;lt;button type="button" @click="$emit('close')"&amp;gt;X&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;form @submit.prevent="submitProduct"&amp;gt;
    &amp;lt;div class="mb-3"&amp;gt;
      &amp;lt;div class="row"&amp;gt;
        &amp;lt;div class="col"&amp;gt;
    &amp;lt;label class="form-label"&amp;gt;title&amp;lt;/label&amp;gt;
    &amp;lt;input type="text" v-model="product.title" placeholder="enter title" class="form-control" required /&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class="col"&amp;gt;
    &amp;lt;label class="form-label"&amp;gt;price&amp;lt;/label&amp;gt;
    &amp;lt;input type="number" v-model="product.price" placeholder="enter price" class="form-control" required /&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
    &amp;lt;div class="mb-3"&amp;gt;
      &amp;lt;div class="row"&amp;gt;
        &amp;lt;div class="col"&amp;gt;
        &amp;lt;label class="form-label"&amp;gt;category&amp;lt;/label&amp;gt;
        &amp;lt;input type="text" v-model="product.category" placeholder="enter category" class="form-control" required /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class="col"&amp;gt;

    &amp;lt;label class="form-label"&amp;gt;description&amp;lt;/label&amp;gt;
    &amp;lt;input type="text" v-model="product.description" placeholder="enter description" class="form-control" required /&amp;gt;
    &amp;lt;/div&amp;gt;
   &amp;lt;/div&amp;gt;
   &amp;lt;br&amp;gt;
    &amp;lt;div class="mb-3"&amp;gt;
    &amp;lt;label class="form-label"&amp;gt;image&amp;lt;/label&amp;gt;
    &amp;lt;input type="file" @change="handleFileUpload" required /&amp;gt;
    &amp;lt;img v-if="product.image" :src="product.image" alt="product image" style="height: 50px; width: 50px;" /&amp;gt;
   &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
    &amp;lt;button style="margin-left: 343px;"  type="submit"&amp;gt;add&amp;lt;/button&amp;gt;  
  &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/form&amp;gt;
 &amp;lt;div v-if="showModal"&amp;gt;&amp;lt;/div&amp;gt;
 &amp;lt;/div&amp;gt;
 &amp;lt;/div&amp;gt;
 &amp;lt;/div&amp;gt;
 &amp;lt;/template&amp;gt;
 &amp;lt;style scoped&amp;gt;
 .form-control{
 width: 307px;
 }
 &amp;lt;/style&amp;gt;

 edit
 &amp;lt;script&amp;gt;
 export default{
  props:{
    showEditModal:Boolean,
    product: Object,
    Categories:Array

  },
 data(){
 return{
    productData:{...this.product}
  };
 },
 watch:{
 product(newProduct){
    this.productData={ ...newProduct};
 }
 },
 methods:{

    handleFileUpload(event) {
  const file = event.target.files[0];
  if (file) {
    const reader = new FileReader();
    reader.onload = () =&amp;gt; {
      this.productData.image = reader.result;
    };
    reader.readAsDataURL(file);
  }
 },
    updateProduct() {
  this.$emit('update-product', this.productData);
 },
 }};
 &amp;lt;/script&amp;gt;

 &amp;lt;template&amp;gt;
 &amp;lt;div v-if="showEditModal" class="modal d-block"&amp;gt;
    &amp;lt;div class="modal-dialog"&amp;gt;
  &amp;lt;div class="modal-content" &amp;gt;
    &amp;lt;div class="modal-header"&amp;gt;
 &amp;lt;h5&amp;gt;edit product&amp;lt;/h5&amp;gt;
 &amp;lt;button style="margin-left: 300px;" type="button" @click="$emit('close')"&amp;gt;X&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;form @submit.prevent="updateProduct"&amp;gt;
    &amp;lt;div class="mb-3"&amp;gt;
      &amp;lt;div class="row"&amp;gt;
        &amp;lt;div class="col"&amp;gt;
    &amp;lt;label class="form-label"&amp;gt;title&amp;lt;/label&amp;gt;
    &amp;lt;input type="text" v-model="productData.title" placeholder="enter title" class="form-control"  /&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class="col"&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class="col"&amp;gt;
    &amp;lt;label class="form-label"&amp;gt;price&amp;lt;/label&amp;gt;
    &amp;lt;input type="number" v-model="productData.price" placeholder="enter price" class="form-control"  /&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
    &amp;lt;div class="mb-3"&amp;gt;
      &amp;lt;div class="row"&amp;gt;
        &amp;lt;div class="col"&amp;gt;
    &amp;lt;label class="form-label"&amp;gt;category&amp;lt;/label&amp;gt;
    &amp;lt;input type="text" v-model="productData.category" placeholder="enter category" class="form-control"  /&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class="col"&amp;gt;
    &amp;lt;label class="form-label"&amp;gt;description&amp;lt;/label&amp;gt;
    &amp;lt;input type="text" v-model="productData.description" placeholder="enter category" class="form-control"  /&amp;gt;
  &amp;lt;/div&amp;gt;
 &amp;lt;/div&amp;gt;
    &amp;lt;label class="form-label"&amp;gt;image&amp;lt;/label&amp;gt;
    &amp;lt;input type="file" @change="handleFileUpload"  /&amp;gt;
    &amp;lt;img v-if="product.image" :src="product.image" alt="product image" style="height: 50px; width: 50px;" /&amp;gt;
   &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
    &amp;lt;button type="submit"&amp;gt;update&amp;lt;/button&amp;gt;  

    &amp;lt;/div&amp;gt;
  &amp;lt;/form&amp;gt;
 &amp;lt;div v-if="showEditModal"&amp;gt;&amp;lt;/div&amp;gt;
 &amp;lt;/div&amp;gt;
 &amp;lt;/div&amp;gt;
 &amp;lt;/div&amp;gt;
 &amp;lt;/template&amp;gt;

 &amp;lt;style&amp;gt; 
 &amp;lt;/style&amp;gt;

 show
 &amp;lt;script&amp;gt;
 export default{
 props: {
 showDetailsModal: Boolean,
 product: Object,
  },
 };
 &amp;lt;/script&amp;gt;

 &amp;lt;template&amp;gt;
 &amp;lt;div v-if="showDetailsModal" class="modal d-block" tabindex="-1"&amp;gt;
    &amp;lt;div class="modal-dialog"&amp;gt;
       &amp;lt;div class="modal-content"&amp;gt;
      &amp;lt;div class="modal-header"&amp;gt;
         &amp;lt;h5 class="modal-title"&amp;gt;product Details&amp;lt;/h5&amp;gt;
         &amp;lt;button type="button" @click="$emit('close')"&amp;gt;X&amp;lt;/button&amp;gt;
         &amp;lt;/div&amp;gt;
         &amp;lt;div class="modal-body"&amp;gt;
          &amp;lt;div v-if="product"&amp;gt;
            &amp;lt;p&amp;gt;title: {{ product.title }}&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;price: {{ product.price }}&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;category: {{ product.category }}&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;image: &amp;lt;/p&amp;gt;
            &amp;lt;div&amp;gt;
                &amp;lt;img :src="product.image" alt="product image" style="height: 50px; width: 50px;"/&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
         &amp;lt;/div&amp;gt;
         &amp;lt;div class="modal-footer"&amp;gt;

        &amp;lt;/div&amp;gt;
       &amp;lt;/div&amp;gt;
     &amp;lt;/div&amp;gt;    
  &amp;lt;/div&amp;gt;

 &amp;lt;/template&amp;gt;
 &amp;lt;div v-if="showDetailsModal" &amp;gt;&amp;lt;/div&amp;gt;

 &amp;lt;style scoped&amp;gt;
 &amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
  </channel>
</rss>
