AlexBS
8. September 2022 um 20:38
1
Hallo,
ich habe ein Cms Element und bisher nur ein Bild mit Upload benötigt. Habe mich am image Cms Element von SW orientiert und dort alle JS Funktionen kopiert, genauso im Twig Template ein mapping-field, ein media-upload-vs, einen upload-listener und ein media-modal-v2. Alles funktioniert wunderbar.
Nun benötige ich noch ein zweites Bild inkl. Upload Funktion. Brauche ich jetzt einen zweiten upload-listener und ein zweites media-modal-v2? Die Funktionen, die mit diesen components verknüpft sind, beziehen sich ja explizit nur auf ein bestimmtes media Feld in der config.
z.B. im Twig template
<sw-upload-listener
:upload-tag="uploadTag"
auto-upload
@media-upload-finish="onImageUpload"
/>
und dann in der JS Datei:
async onImageUpload({ targetId }) {
const mediaEntity = await this.mediaRepository.get(targetId);
this.element.config.media.value = mediaEntity.id;
this.updateElementData(mediaEntity);
this.$emit('element-update', this.element);
}
Ich würde dann ja auch alle anderen Funktionen, zB previewSource, onImageRemove etc. kopieren müssen. Ich habe noch kein Cms-Element gefunden, an dem ich mich diesbezüglich orientieren könnte.
Wie setzt ihr so etwas um?
Lg Alex
AlexBS
9. September 2022 um 16:58
2
Niemand bisher das gleiche Szenario gehabt? Kann doch nicht sein
AlexBS
17. September 2022 um 12:07
3
opened 03:50PM - 13 Apr 22 UTC
Feature Request
### Please describe the feature you would like to see implemented.
When creatin… g CMS elements it's currently very cumbersome to implement a media upload field which uses `sw-media-upload-v2`.
I know of `sw-media-field` but I don't really like it, `sw-media-upload-v2` is much better. I couldn't find a better component for media upload for now.
Until now I copied the logic from https://github.com/shopware/platform/blob/26f5f18d499536139555029a40f29883a011280d/src/Administration/Resources/app/administration/src/module/sw-cms/elements/image/config/index.js but this still needs a lot of code in my own component. Also this logic would all have to be duplicated for multiple upload fields.
Now I had the case where I had two images in my element config and wasn't happy with code duplication so I wrote a mixin for it which I can use like in the code below. This is still a lot of code and I would be happy to be able to just add a `<sw-media-upload-v3 v-model="media" @media-upload-update="emitElementUpdate">` (or similiar) which abstracts all that logic away.
<details>
<summary>index.js</summary>
```js
import template from './sw-cms-el-config-link-tile.html.twig';
import './sw-cms-el-config-link-tile.scss';
const { Component, Mixin } = Shopware;
Component.register('sw-cms-el-config-link-tile', {
template,
mixins: [
Mixin.getByName('cms-element'),
Mixin.getByName('cms-media-field'),
],
data() {
return {
mediaFields: {}
}
},
watch: {
cmsPageState: {
deep: true,
handler() {
this.$forceUpdate();
},
},
},
created() {
this.createdComponent();
},
methods: {
createdComponent() {
this.initElementConfig('link-tile');
this.initElementData('link-tile');
this.mediaFields = {
backgroundImage: this.createMediaField('backgroundImage'),
iconImage: this.createMediaField('iconImage'),
}
},
},
});
```
</details>
<details>
<summary>sw-cms-el-config-link-tile.html.twig</summary>
```twig
{% block sw_cms_element_link_tile_config %}
<div class="sw-cms-el-config-link-tile">
{% block sw_cms_element_link_tile_config_media_upload %}
<template v-for="mediaField in mediaFields">
<sw-cms-mapping-field
v-model="element.config[mediaField.name]"
:label="$tc('vc-scholl.sw-cms.elements.link-tile.config.label.' + mediaField.name)"
value-types="entity"
entity="media"
>
<sw-upload-listener
:key="mediaField.name + '-upload-listener'"
:upload-tag="mediaField.getUploadTag()"
auto-upload
@media-upload-finish="mediaField.onImageUpload"
/>
<sw-media-upload-v2
:key="mediaField.name + '-media-upload'"
variant="regular"
:upload-tag="mediaField.getUploadTag()"
:source="mediaField.getMedia()"
:allow-multi-select="false"
:default-folder="cmsPageState.pageEntityName"
:caption="$tc('sw-cms.elements.general.config.caption.mediaUpload')"
@media-upload-sidebar-open="mediaField.onOpenMediaModal"
@media-upload-remove-image="mediaField.onImageRemove"
/>
<div
slot="preview"
slot-scope="{ demoValue }"
class="sw-cms-el-config-link-tile__mapping-preview"
>
<img
v-if="demoValue.url"
:src="demoValue.url"
alt=""
>
<sw-alert
v-else
class="sw-cms-el-config-link-tile__preview-info"
variant="info"
>
{{ $tc('sw-cms.detail.label.mappingEmptyPreview') }}
</sw-alert>
</div>
</sw-cms-mapping-field>
</template>
{% endblock %}
{% block sw_cms_element_link_tile_config_media_modals %}
<template v-for="mediaField in mediaFields">
<sw-media-modal-v2
:key="mediaField.name + '-media-modal'"
v-if="mediaField.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="mediaField.onImageRemove"
@media-modal-selection-change="mediaField.onSelectionChanges"
@modal-close="mediaField.onCloseModal"
/>
</template>
{% endblock %}
</div>
{% endblock %}
```
</details>
<details>
<summary>media-field.mixin.js</summary>
```js
import MediaField from './media-field'
const { Mixin } = Shopware;
Mixin.register('cms-media-field', {
inject: ['repositoryFactory'],
computed: {
mediaRepository() {
return this.repositoryFactory.create('media');
},
},
methods: {
createMediaField(mediaFieldName) {
let media;
if (
this.element?.data &&
this.element.data[mediaFieldName] &&
this.element.data[mediaFieldName].id
) {
media = this.element.data[mediaFieldName];
} else {
media = this.element.config[mediaFieldName].value
}
return new MediaField({
name: mediaFieldName,
parent: this,
mediaRepository: this.mediaRepository,
media,
})
}
}
});
```
</details>
<details>
<summary>media-field.js</summary>
```js
export default class MediaField {
constructor({
name,
parent,
mediaRepository,
media = null,
mediaModalIsOpen = false,
initialFolderId = null,
}) {
if (!name) {
throw new Error('MediaField requires a name');
}
if (!parent) {
throw new Error('MediaField requires a parent');
}
if (!mediaRepository) {
throw new Error('MediaField requires a mediaRepository');
}
this.name = name;
this.parent = parent;
this.mediaRepository = mediaRepository;
this.media = media;
this.mediaModalIsOpen = mediaModalIsOpen;
this.initialFolderId = initialFolderId;
this.onImageUpload = this.onImageUploadProxy.bind(this)
this.onImageRemove = this.onImageRemoveProxy.bind(this)
this.onOpenMediaModal = this.onOpenMediaModalProxy.bind(this)
this.onCloseModal = this.onCloseModalProxy.bind(this)
this.onSelectionChanges = this.onSelectionChangesProxy.bind(this)
}
getName() {
return this.name;
}
isMediaModalOpen() {
return this.mediaModalIsOpen;
}
setMediaModalIsOpen(mediaModalIsOpen) {
this.mediaModalIsOpen = mediaModalIsOpen;
}
getMedia() {
return this.media;
}
setMedia(media = null) {
this.media = media;
}
getInitialFolderId() {
return this.initialFolderId;
}
setInitialFolderId(initialFolderId) {
this.initialFolderId = initialFolderId;
}
getUploadTag() {
return `cms-element-media-config-${this.parent.element.id}-${this.name}`;
}
async onImageUploadProxy({ targetId }) {
const mediaEntity = await this.mediaRepository.get(targetId);
this.updateParentElementConfigValue(mediaEntity.id);
this.setMedia(mediaEntity);
this.updateParentElementData(mediaEntity);
this.updateParent()
}
onImageRemoveProxy() {
this.updateParentElementConfigValue(null);
this.setMedia(null);
this.updateParentElementData();
this.updateParent();
}
onOpenMediaModalProxy() {
this.setMediaModalIsOpen(true);
}
onCloseModalProxy() {
this.setMediaModalIsOpen(false);
}
onSelectionChangesProxy(mediaEntity) {
const media = mediaEntity[0];
this.updateParentElementConfigValue(media.id);
this.setMedia(media);
this.updateParentElementData(media);
this.updateParent();
}
updateParentElementData(media = null) {
if (!this.parent.element.data) {
this.parent.$set(this.parent.element, 'data', {
[this.name]: media
});
} else {
this.parent.$set(this.parent.element.data, this.name, media);
}
}
updateParentElementConfigValue(mediaId) {
this.parent.element.config[this.name].value = mediaId;
}
updateParent() {
this.parent.$emit('element-update', this.parent.element);
}
}
```
</details>
Das war für mich die Lösung. Anfangs hat er das mixin nicht gefunden, wenn ich es in der main.js einbinde. Erst als ich es in der /config/index.js imported habe, hat er es gefunden.
1 „Gefällt mir“
Ich versuche mich hier gerade auf ähnliche Weise an einem Image-Upload, habe die wesentlichen Dinge übernommen vom image Cms Element von SW. Ich bekomme aber das data Attribut nicht befüllt. Das data Attribut bleibt bei mir null. Hat jemand einen Hint wie ich hier weiterkomme?
Das einzige was ich finde ist das hier:
this.initElementData('image');
Und mir ist noch nicht so ganz klar was hier passiert.
Wovon hängt ab ob data gefüllt ist und wie wird es gefüllt?
Ok, durch diesen Thread bin ich einen Schritt weiter: https://forum.shopware.com/t/bild-wird-in-cms-element-in-der-administration-nicht-richtig-angezeigt/71270
Aber brauche ich wirklich einen eigenen DataResolver. Für sowas einfaches wie einen Image-Upload müsste es doch was geben oder? Kann ich das irgendwie analog machen wie das image Cms Element von SW?
Aber wie und wo passiert das?