How to use <sw-media-list-selection/> in Shopware 6? - plugins

I'm building a custom cms element following that guide
https://docs.shopware.com/en/shopware-platform-dev-en/how-to/custom-cms-element?category=shopware-platform-dev-en/how-to source code is at the end of the guide.
For my element (Custom Image Slider) I need multiple image selection, so inside config template(.html.twig) I use sw-media-list-selection (https://component-library.shopware.com/components/sw-media-list-selection).
But there is a problem when I try to configure new element settings (admin panel -> Content -> Shopping Experiences) I can only upload new images (both from PC and URl) but that new Images is not adding to the selected grid (two columns is always empty) Also "Open media" button is not working. My main thought is there is a problem with my config twig.html file or defaultConfig.
I'm new to shopware 6 and I will appreciate any help.
Here is my config template(.html.twig)
{% block sw_cms_element_image_config %}
<div class="sw-cms-el-config-slider">
<sw-media-list-selection class="sw-cms-el-config-slider__media-selection"
:entityMediaItems="element.config.entityMediaItems.value"
:entity="element.config.entity.value"
:defaultFolder="element.config.defaultFolder.value"
:uploadTag="element.config.uploadTag.value">
</sw-media-list-selection>
</div>
{% endblock %}
And there is my defaultConfig inside Shopware.Service('cmsService').registerCmsElement();
defaultConfig: {
entityMediaItems: {
value: []
},
entity: {
value: {
isLoading: false,
id: "myUploadTag"
}
},
uploadTag: {
source: 'static',
value: "myUploadTag"
},
defaultFolder: {
source: 'static',
value: "media"
},
}
Ps. sw-media-list-selection-v2 is not working at all
Ps 2. Thanks for your time.

Related

Is it possible in vue3 to access the root DOM element in a child component slot? I am trying to use a 3rd party library (sortablejs) in vue3

In vue2 I could use this.$el
export default {
render() {
return this.$slots.default[0]
},
mounted() {
Sortable.create(this.$el, {});
})
}
If, in vue3 I try to use this.$slots.default()[0] I can't see how to target the element.
If I use a template ref, I can get the div, but not the contained slot.
The closest question / answer I have found is here Vue 3 Composition API - How to get the component element ($el) on which component is mounted
but this also seems to give the div, but not the slot $el.
This was extremely powerful in vue2 because sortable could be passed a ul, or a div, or another constructed sortable vue component in a slot, and work without the element having to be defined in the child component and I can't work out how to replicate this in vue3.
I originally came across this in a screen cast by Adam Wathan: "Building a Sortable Component with Vue.js", but this was vue2.
I've come up with the following (perhaps there are better out there)
Use template ref:
<template>
<div ref="root">
<slot></slot>
</div>
</template>
Then in the script:
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
// console.log(root.value.children[0]) // this is your $el
let el = root.value.children[0]
Sortable.create(el, {})
})
return {
root
}
}
}

Search results link tabs

I'm building a search application and am trying to get some results links that appear in tab format to work. Much like you see on google.com/search=?user_query and bing.com/search=?user_query... where after you submit the initial query, you can click on "Images", "Shopping" and "News" for example and a different results view is rendered based on the the result link/tab that you clicked.
I'm using ReactiveSearch to build my search UI. So far I have this:
ResultNavigationTabs.tsx to build the link tabs
class ResultNavigationTabs extends Component {
render() {
const { classes, items, location: { pathname } } = this.props;
return (
<ul className={classes.nav}>
{items.map(item => (
<Link to={item.link} key={item.text}>
<li className={item.link.startsWith(pathname) ? "active" : ""}>
{item.text}
</li>
</Link>
))}
</ul>
);
}
}
export default withStyles(styles)(withRouter(ResultNavigationTabs));
I then render this component in my ResultsViewPage.tsx like so:
render() {
const { selected } = this.state;
...(omitted for brevity)
<ResultNavigationTabs2
className={classes.myNavigationTabs}
items={[
{ text: "Web", link: `/search?q="${selected}"` },
{ text: "News", link: `/news?q="${selected}"` },
{ text: "Shopping", link: `/shopping?q="${selected}"` },
]}
/>
The links do render but they do not work. If I hover over the links, the search query string is empty (http://localhost:3000/search?q=%22%22). If you've worked with ReactiveSearch, it should be: http://localhost:3000/search?q=%22user%20query%22.
I have had it working but only when I render ResultNavigationTabs in the same file as the search box (DataSearch in ReactiveSearch language). However, if I do that it appears right below the search box in the Header instead of the results area.
I need to figure out a way to render ResultNavigationTabs in the ResultsViewPage.tsx file with working links.
After doing some more thoughtful searching, I found the answer right here on SO! Comes complete with 2 CodeSandBox demos as well - check them out.

Dynamically changing the language of columns labels in VueJS

I implemented a multilanguage support for the website. Using VueJS and VueI18n. There are 3 pages - home, registers and messages. The problem is in messages, where there is a dynamically rendered table - vue-good-table. While being on this page(with the table) if I click on the buttons for changing languages, everywhere the languages is being changed dynamically, but not the labels and placeholders of the table. If I go to one of the other pages and comeback to the table, the labels and placeholders are updated correctly. Can you help me make it change while I am on the same page?
I was wondering if beforeMount() would help in this situation?
main.js
import VueI18n from 'vue-i18n'
import {messages} from './locales/bg_en_messages'
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: 'bg', // set locale
fallbackLocale: 'bg',
messages // set locale messages
});
Vue.prototype.$locale = {
change (lang) {
i18n.locale = lang
},
current () {
return i18n.locale
}
};
Messages.vue:
<vue-good-table
:columns="columns"
:rows="items"
:paginate="true"
:lineNumbers="true">
</vue-good-table>
<script type="text/javascript">
export default {
data(){
return {
items:[],
columns: [
{
label: this.$t("columns.date"),
field: 'changeddate',
type: 'String',
filterable: true,
placeholder: this.$t("columns.date")
},
{
label: this.$t("columns.userChange"),
field: 'userchange',
type: 'String',
filterable: true,
placeholder: this.$t("columns.userChange")
}
]
}
}
}
App.vue
<div style="padding: 10px; width: 99%;">
<ui-button #click="changeLang('bg')">
<img src="../src/assets/images/skin/Flag1.png" v-bind:alt="home" height="15" width="25"/>
</ui-button>
<ui-button #click="changeLang('en')">
<img src="../src/assets/images/skin/Flag2.png" v-bind:alt="home" height="15" width="25"/>
</ui-button>
</div>
<script>
export default {
name: 'Localization',
methods: {
changeLang: function(newLang){
this.$locale.change(newLang)
}
}
}
</script>
The reason is that the data that's changing is nested inside an object itself and your template only listens to changes to that parent object and not for its children (your language data). So even if that changes, your view wont notice it. If you use data from your translation directly in the template using the template syntax, the data will re-render automatically because that is not nested(that's why it probably works everywhere else).
Now of course you can't do that in your table's case, because your table component accepts nested data only, so the workaround would be to use a computed property for your columns, instead of putting them into your component's data. This way all changes will be mirrored to your component.
Simply change your component like that and you should be good to go:
export default {
data(){
return {
items:[]
}
},
computed: {
columns () {
return [
{
label: this.$t("columns.date"),
field: 'changeddate',
type: 'String',
filterable: true,
placeholder: this.$t("columns.date")
},
{
label: this.$t("columns.userChange"),
field: 'userchange',
type: 'String',
filterable: true,
placeholder: this.$t("columns.userChange")
}
]
}
}
}

EmberJS: Observer Not Being Triggered on Computed Property

I am building a handelbars helper that renders a checkbox group. My goal is to display a checkbox group with something like this and get two-way binding on selectedOptions:
{{form-checkboxGroup options=allOptions selectedOptions=selectedOptions}}
I've used this pattern successfully with other form components and it's a big win. I'm able to render my allOptions and selectedOptions values as a checkbox group, but it's the two-way binding that's tripping me up. Any idea what I'm missing?
By the way, I'm using ember-cli, but that doesn't affect anything relating to this issue.
Here's my setup:
Handlebars Helper: helpers/form-checkbox-group.js
The sole purpose of this file is to link the Handelbars expression {{form-checkboxGroup}} to the view and template below.
import FormCheckboxGroupView from 'my-app/views/util/form/form-checkbox-group';
export default Ember.Handlebars.makeBoundHelper(function( options ) {
return Ember.Handlebars.helpers.view.call(this, FormCheckboxGroupView, options);
});
CheckboxGroup Handlebars Template: templates/util/form/form-checkbox-group.hbs
...
{{#each user in view.combinedOptions}}
{{input type="checkbox" name="view.fieldName" checked=user.checked }} {{user.name}}
{{/each}}
...
CheckboxGroup View: views/util/form/form-checkbox-group.js
...
export default FormCheckboxGroupView = Ember.View.extend( FormFieldMixin, {
templateName: 'util/form/form-checkbox-group',
selectedOptions: function() {
console.log("When triggered this could update view.selectedOptions");
}.observes('view.combinedOptions.#each.checked'),
// combines the "options" and "selected options" into a single array of "combinedOptions" explicitly indicating what's checked
combinedOptions: function() {
...
// sample result of combinedOptions:
// { name: "Johnny Five", id: "12", checked: true }
return combinedOptions;
}.property('view.options', 'view.selectedOptions')
});
And finally, to actually use my Handlebars helper, here's the consuming page's template and corresponding controller:
Consuming Page: templates/my-page.hbs
{{form-checkboxGroup options=allUsersArray selectedOptions=selectedUsersArray fieldName="selectedProvidersArray" }}
Backing Controller for Consuming Page: controllers/my-page.js
export default MyPageController = Ember.Controller.extend( FormMixin, {
allUsersArray: [
{ name: 'Bill Huxtable', id: 'billy' },
{ name: 'Samantha Jones', id: 'jones' },
{ name: 'Tony Pepperoni', id: 'tonyp' },
{ name: 'Ridonk Youliss', id: 'silly' }
],
selectedUsersArray: [
{ name: 'Tony Pepperoni', id: 'tonyp' },
{ name: 'Ridonk Youliss', id: 'silly' }
],
...
});
So, all of this successfully renders the checkbox group nicely, but my efforts to capture the fact that a checkbox has been newly selected by using observes("view.combinedOptions.#each.checked') is not working.
Any idea on how I can this up for two-way binding? Thanks in advance for assistance!
No jsbin so I'm flying blind, but try this:
selectedOptions: function() {
console.log("When triggered this could update view.selectedOptions");
}.observes('combinedOptions.#each.checked')
view.property is how you access view from template. You don't need that from the view itself (unless you have view property on your view).

Using KoGrid in HotTowel template

I am trying to use KoGrid in a HTML view within the HotTowel SPA template. I created a simple view:
<section>
<h2 class="page-title" data-bind="text: title"></h2>
<div class="gridStyle" data-bind="koGrid: gridOptions"></div>
</section>
and added the model data in the JS:
define(['services/logger'], function (logger) {
var vm = {
activate: activate,
title: 'My Grid'
};
return vm;
//#region Internal Methods
function activate() {
var self = this;
this.myData = ko.observableArray([{ name: "Moroni", age: 50 },
{ name: "Tiancum", age: 43 },
{ name: "Jacob", age: 27 },
{ name: "Nephi", age: 29 },
{ name: "Enos", age: 34 }]);
this.gridOptions = { data: self.myData };
return true;
}
//#endregion
});
The grid is on the page, but the styling seems to be rendering widths and positions completely wrong so that columns are on top of each other and most data is not visibly correct. The KoGrid.css file is being referenced correctly.
Thanks for any help.
The core of the problem is that "when KOGrid does its binding in Durandal/HotTowel, the KOGrid element is not yet part of the DOM". You need to ensure that KOGrid does not do its binding until after the view is attached. This can be achieved as follows:
1) Add a new observable to the viewmodel to hold a boolean value for whether the view has been attached or not by durandal:
isAttachedToView = ko.observable(false)
and expose it
isAttachedToView: isAttachedToView
2) Up date it to be true when the viewAttached function callback is invoked:
function viewAttached() {
isAttachedToView(true);
return true;
}
3) Surround your HTML with a ko if statement to ensure that bit of HTML is not evaluated (i.e. kogrid does not do its binding) until after the view is attached:
<!-- ko if: isAttachedToView() -->
<div data-bind="koGrid: { data: ...
<!-- /ko -->
4) Reset isAttachedToView to be false on deactivating view
function deactivate() {
isAttachedToView(false);
}
And expose this:
deactivate: deactivate
You have probably already figured this one out but was also faced with the same problem today. A quick look at the chrome inspector told me that koGrid dimensional properties have not registered correctly and this tells me its a timing issue. I found an answered question relating to the same problem here.
I did try this solution but there is still some work to do to make it play ball nicely. I have decided to give koGrid a miss since I don't really want all it's functionality anywayz :)