shopware custom admin config component doesn't save sales channel id - shopware6

For a shopware 6 plugin , in config.xml I have a custom component
<card>
<title>My Plugin</title>
<title lang="de-DE">My Plugin</title>
<component name="config-component">
<name>configComponent</name>
</component>
</card>
In the twig of this component i have a simple field
<sw-text-field :label="Test" size="medium" required />
I saw that for components, if i want to change the sales channel and save the data, the sales channel id is not saved in the system_config table for that text field.
What can i do so my inputs from components be dependent on sales channels? Thank you

The following component and template should work as intended.
import template from './config-component.html.twig';
const { Component } = Shopware;
Component.register('config-component', {
template,
props: {
value: {
required: true,
}
},
methods: {
onChange(value) {
this.$emit('change', value || '');
},
onInput(value) {
this.$emit('input', value);
}
}
});
<sw-text-field
v-model="value"
label="Test"
size="medium"
required
#input="onInput"
#change="onChange"
/>

Related

how to validate child form from parent component in Vue

I have a child component which includes form:
<el-form :model="abc" ref="ruleForm" :rules="rules">
<el-form-item prop="files">
<abc-card :title="getTranslation('abc.files')">
<file-selector v-model="abc.files" />
</abc-card>
</el-form-item>
</el-form>
And I want to add simple validations to this form:
rules: function () {
return {
files: [
{
type: 'object',
required: true,
trigger: 'change',
message: 'Field required',
},
],
};
},
But my click button is in the parent component:
<files v-model="editableAbc" ref="editableTab" />
<el-button type="primary" #click="submitForm()">Create</el-button>
methods: {
submitForm() {
this.$refs.form.validate((isValid) => {
if (!isValid) {
return;
}
////API CALLS////
});
},
}
So I am trying to achieve that when the button is clicked the navigation should be rendered. How can I do that?
As per your requirement, My suggestion would be to use a ref on child component to access its methods and then on submit click in parent component, trigger the child component method.
In parent component template :
<parent-component>
<child-component ref="childComponentRef" />
<button #click="submitFromParent">Submit</button>
</parent-component>
In parent component script :
methods: {
submitFromParent() {
this.$refs.childComponentRef.submitForm();
}
}
In child component script :
methods: {
submitForm() {
// Perform validations and do make API calls based on validation passed.
// If you want to pass success or failure in parent then you can do that by using $emit from here.
}
}
The "files" component is the form you're talking about?
If so, then ref should be placed exactly when calling the 'files' component, and not inside it. This will allow you to access the component in your parent element.
<files v-model="editableAbc" ref="ruleForm" />
There is a method with the props, which was mentioned in the comments above. I really don't like it, but I can tell you about it.
You need to set a value in the data of the parent component. Next you have to pass it as props to the child component. When you click the button, you must change the value of this key (for example +1). In the child component, you need to monitor the change in the props value via watch and call your validation function.
// Parent
<template>
<div class="test">
<ChildComponent />
</div>
</template>
<script>
export default {
data() {
return {
updateCount: 0,
};
},
methods: {
submitForm() {
// yout submit method
this.updateCount += 1;
},
},
};
</script>
// Child
<script>
export default {
props: {
updateCount: {
type: Number,
default: 0,
},
},
watch: {
updateCount: {
handler() {
this.validate();
},
},
},
methods: {
validate() {
// yout validation method
},
},
};
</script>
And one more solution. It is suitable if you cannot place the button in the child component, but you can pass it through the slot.
You need to pass the validate function in the child component through the prop inside the slot. In this case, in the parent component, you will be able to get this function through the v-slot and bind it to your button.
// Parent
<template>
<div class="test">
<ChildComponent>
<template #button="{ validate }">
<button #click="submitForm(validate)">My button</button>
</template>
</ChildComponent>
</div>
</template>
<script>
import ChildComponent from "./ChildComponent";
export default {
components: {
ChildComponent,
},
methods: {
submitForm(cb) {
const isValid = cb();
// your submit code
},
},
};
</script>
// Child
<template>
<div class="child-component">
<!-- your form -->
<slot name="button" :validate="validate" />
</div>
</template>
<script>
export default {
methods: {
validate() {
// yout validation method
console.log("validate");
},
},
};
</script>

How to do conditional rendering on dynamic props

I'm trying to add conditional rendering on the dynamically passed prop. I have a component called card-item.vue which is passing the prop cta to component profile.vue.
Now in profile.vue I want the prop cta to display on every card-item component except the first one.
Here is my card-item.vue:
<template>
<span v-if="cta">
{{ cta }}
</span>
</template>
<script>
export default {
name: 'card-item',
props: {
cta: {
type: String
}
}
}
</script>
profile.vue:
<template>
<CardItem
v-for="address in addresses.slice(1)"
:key="uniqueKey('address', address)"
:cta="cms.page.cta" // remove cta from the first address only
/>
</template>
<script>
import CardItem from "./card-item";
const data = require('./profile.json');
export default {
name: 'profile',
comonents: {
CardItem,
},
props: {
cms: {
type: Object,
default: () => {
return {
page: data
}
}
}
}
}
</script>
profile.json:
{
"cta": "Report"
}
In my <CardItem /> component I'm rendering addresses. So I want my :cta on every address except the first one.
I was trying something like:
v-if="addresses[0] ? {cta="null"} : "cms.page.cta""
I know this is incorrect syntax but somewhat I'm trying to achieve.
Please any help would be appreciated.
v-for also supports an optional second argument for the index of the
current item. -- vue docs
<CardItem
v-for="(address, index) in addresses.slice(1)"
:key="uniqueKey('address', address)"
:cta="index !== 0 ? cms.page.cta : null" // remove cta from the first address only
/>

Access page data on component

I'm writing my first app using NUXT. I'm stuck at this issue for 2 days, so I decided to ask even if I think this is a question with a simple answer (it has to be).
On my project's layouts I have a default.vue and a home.vue
default.vue:
<template>
<div>
<!-- call Header component, this has an nav menu -->
<Header />
<!-- call Hero component -->
<Hero />
<nuxt />
<Footer />
</div>
</template>
<script>
import Header from '~/components/Header.vue'
import Footer from '~/components/Footer.vue'
import Hero from '~/components/Hero.vue'
export default {
components: {
Header,
Footer,
Hero
},
}
</script>
I want to display data from each page (title, subtitle and imageUrl). This data sometimes come from an apollo query request other times are defined on page file.
I've read the docs and searched here for the answer but I wans't able to implement it. I think it has to be done thought Vuex store but I don't know how.
Thank you
You can use nuxtServerInit action in vuex as one way to populate page data.
If you are using nuxt >= 2.12, you can use the new-and-improved fetch hook inside your layouts to make your apollo queries.
I DID IT!
So, it took a time to figure out, but I've learnt a lot during this process.
I'll let here some references I've used to come with this solution.
Very nice article on passing data through props, custom events and Vuex Store
CodeSandBox from Nuxt Documentation.
This question has a method to await apollo data and then render data
Let's go to the way I did it. I don't know if it's the best, but worked like a charm here.
I've created a hero.js file on my store folder:
data: {
title: "",
subtitle: "",
imgUrl: ""
}
})
export const mutations = {
setData (state, obj) {
state.data = {...state.data, ...obj}
}
}
export const getters = {
getHero (state) {
return state.data
}
}
Then on my default.vue I did:
<div>
<!-- call Header component -->
<Header />
<!-- call Hero component with his slots-->
<Hero>
<template v-slot:title>
<h1 class="title">{{ hero.title }}</h1>
</template>
<template v-slot:subtitle>
<h2 class="subtitle">{{ hero.subtitle }}</h2>
</template>
<template v-slot:heroImg>
<img :src="hero.imgUrl" />
</template>
</Hero>
<!-- This is where all yours pages will be -->
<nuxt />
<Footer />
</div>
</template>
<script>
// Import Header component
import Header from '~/components/Header.vue'
import Footer from '~/components/Footer.vue'
import Hero from '~/components/Hero.vue'
import { mapGetters } from 'vuex'
export default {
data(){
return {
//declaring hero Obj to contain hero data
hero: {
title: "",
subtitle: "",
imgUrl: ""
}
}
},
components: {
Header,
Footer,
Hero
},
//Getting getHero getter from hero.js and saving it to newHero
computed: mapGetters({
newHero: 'hero/getHero'
}),
//watching newHero to change and then updating this.hero Obj. This action will update the displayed data
watch: {
newHero: function (obj) {
this.hero = {...this.hero, ...obj}
}
}
}
</script>
Here I declare the variables and store than into Vuex Store:
<template>
...
</template>
<script>
export default {
data() {
return {
hero: {
title: "Awesome Static title",
subtitle: "Awesome static subtitle"
}
}
},
//Saving the declared Hero to Vuex Store, then my default.vue will be able to get it through this.$store.getters
mounted() {
this.$store.commit("hero/setData", this.hero);
},
}
</script>
At some pages the title are fetched from the database (GraphQL using Apollo). Then I did:
<template>
...
</template>
<script>
import getLojaInfo from '~/apollo/queries/loja/loja.gql'
export default {
//declaring data
data() {
return {
lojas: Array,
loading: 0,
hero: {
title: "",
subtitle: "",
imgUrl: ""
}
}
},
//making the query
apollo: {
lojas: {
$loadingKey: 'loading',
prefetch: true,
query: getLojaInfo,
variables () {
return { slug: this.$route.params.singleLoja }
},
//it will wait for query result that and then populate this hero, it will update the hero title, subtitle and image
result(ApolloQueryResult, key) {
this.hero.title = ApolloQueryResult.data.lojas[0].name
this.hero.subtitle = ApolloQueryResult.data.lojas[0].description
this.hero.imgUrl = ApolloQueryResult.data.lojas[0].logo.url
//then commit it to Vuex Store
this.$store.commit("hero/setData", this.hero);
}
},
},
}
</script>
Thank you all, I would appreciate contributions to my code.

Get input values from child components in Vue

I would like to retrieve all input values from my child components (client and advice, seen below), but not sure how to proceed.
client.vue
<template>
<div id="client">
<input type="text" v-model="client.name" />
<input type="text" v-model="client.code" />
</div>
</template>
<script>
export default {
data() {
return {
client: {
name: '',
code: '',
}
}
}
}
</script>
advice.vue
<template>
<div id="advice">
<input type="text" v-model="advice.foo" />
<input type="text" v-model="advice.bar" />
<div v-for="index in 2" :key="index">
<input type="text" v-model="advice.amount[index]" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
advice: {
foo: '',
bar: '',
amount:[]
}
}
}
}
</script>
Each component has more fields than the above example.
My home page (parent) looks as simple as:
<template>
<form id="app" #submit="printForm">
<clientInfo />
<advice />
<input type="submit" value="Print" class="btn" />
</form>
</template>
<script>
import clientInfo from "#/components/clientInfo.vue";
import advice from "#/components/advice.vue";
export default {
components: {
clientInfo,
advice
},
methods: {
printForm() {}
}
}
</script>
My first idea was to $emit, but not sure how to do that efficiently with more than 20 fields without attaching a #emitMethod="parentEmitMethod" to every single field.
My second idea was to have a Vuex store (as seen below), but I don't know how to save all the states at once and not sure if I should.
new Vuex.Store({
state: {
client: {
name:'',
code:''
},
advice: {
foo:'',
bar:'',
amount:[]
}
}
})
You could use FormData to get the values of the form's <input>s or <textarea>s that have a name attribute (anonymous ones are ignored). This works even if the form has nested Vue components that contain <input>s.
export default {
methods: {
printForm(e) {
const form = e.target
const formData = new FormData(form) // get all named inputs in form
for (const [inputName, value] of formData) {
console.log({ inputName, value })
}
}
}
}
demo
You could use v-model with your custom components. Let's say you want to use them like this:
<advice v-model="adviceData"/>
For this, you would need to watch for value changes on your input elements inside your advice component and then emit an input event with the values. This will update the adviceData binded property. One generic way to do this could be including a watcher inside your advice component, like this:
export default {
data() {
return {
advice: {
foo: '',
bar: '',
amount:[]
}
}
},
watch: {
advice: {
handler: function(newValue, oldValue) {
this.$emit('input', newValue);
},
deep: true,
}
},
}
This way you will not have to add a handler for each input field. The deep option must be included if we need to detect changes on nested data in the advice object.
I think you can achieve what you want is when the user writes something using#change this will trigger a method when the input value is changed, you could use a button instead or anything you want, like this:
The child component
<input type="text" v-model="advice.amount[index]" #change="emitCurrStateToParent ()"/>
You gotta add #change="emitCurrStateToParent ()" in every input you have.
emitCurrStateToParent () {
this.$emit("emitCurrStateToParent", this.advice)
}
Then in you parent component
<child-component v-on:emitCurrStateToParent="reciveDataFromChild ($event)"></child-component>
reciveDataFromChild (recivedData) {
// Do something with the data
}
I would use a button instead of #change, like a "Save" button idk, the same goes for vuex, you can use the #change event
saveDataAdviceInStore () {
this.$store.commit("saveAdvice", this.advice)
}
Then in the store
mutations: {
saveAdvice (state, advice) {
state.advice = advice
}
}

Emitting with component :is

I am building a dashboard app where users will be able to choose widgets that appear in certain positions. I am doing this using
<component :is="name-of-component">
Its all working well but I want the user to be able to edit the component and emit the changes to the parent. By editing lets say for example 1 component displays a welcome message which the user can change.
Right now I have the following
Dashboard.vue
<template>
<component :is="name-of-component"></component>
</template>
<script>
data () {
return {
name-of-component: 'selected-component-name'
}
}
</script>
In the script is the computed, mounted etc. which I don't think have relevance to the question.
Since Im using component :is Im finding it tricky to pass props and emit changes. In my store I have 2 props for the component (title & subtitle) in an array. I can hardcode :props="welcomeMessage" but I don't want to hard code since Im using :is and position of widgets can change.
Emit is also causing an issue for me. I can, of course, emit by hard coding the call to the component but since Im using :is its not going to work for me.
Heres what is working but I need to make it dynamic as any component :is wan contain any widget. Any ideas?
<component
:is="welcomeMessage"
:props="dashboard.welcomeMessage"
#update-welcomeMessage="welcomeMessage(e)">
</component>
OR
<component
:is="busStops"
:props="dashboard.myBusStop"
#update-busStop="busStop(e)">
</component>
Id like to have the components so that I can pull in the different concerns and have each one be more like this where "name-of-component" could be used to populate the :is, :props and #update:
<component
:is="name-of-component"
:props="dashboard.name-of-component"
#update-name-of-component="name-of-component(e)">
</component>
You can use the v-bind and v-on capabilities, and use computed properties just like you are already doing it. I'll explain myself:
<some-component #some-event="doThis" foo="bar"></some-component>
is the same as writing:
<some-component v-bind="{foo: 'bar'}" v-on="{'some-event': doThis}"></some-component>
That means that you can write computed properties to compute which listeners and attributes you want to use for your dynamic component.
I wrote a complete example on jsFiddle if you want: https://jsfiddle.net/tsc2pmrx/
Template:
<div id="app">
<component :is="componentToUse" v-on="listeners" v-bind="attributes"></component>
</div>
JS:
Vue.component('greeting', {
props: ['name'],
template: '<h1>Welcome {{ name }} !</h1>',
mounted () {
setTimeout(() => {
this.$emit('some-event')
}, 2000)
}
});
Vue.component('other-component', {
template: '<h1>Welcome to Other Component</h1>'
})
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app',
data: {
componentToUse: 'greeting'
},
methods: {
handleOtherComponentEvent () {
console.log('Hello World from other component listener')
},
handleGreetingComponentEvent () {
console.log('Hello World from greeting component event listener')
}
},
computed: {
listeners () {
if (this.componentToUse === 'greeting') {
return {
'some-event': this.handleOtherComponentEvent
}
} else if (this.componentToUse === 'other-component') {
return {
'some-greeting-event': this.handleGreetingComponentEvent
}
}
return {}
},
attributes () {
if (this.componentToUse === 'greeting') {
return {
'name': 'Hammerbot'
}
}
return {}
}
}
});