Vue Scoped Slots two way data binding between component and slot [duplicate] - javascript

This question already has an answer here:
update data in slot vuejs
(1 answer)
Closed 2 years ago.
I have a scoped slot. I need the content that am passing to the slot to be able to affect the parent template.
This is what i have so far:
Parent.vue
<template>
<div>
<slot :text="text" :msg="msg"/>
<p>{{text}}</p>
<p>{{msg}}</p>
</div>
</template>
<script>
export default {
name: "Parent",
data() {
return {
text: "",
msg: ""
};
}
};
</script>
App.vue
<template>
<parent>
<template #default="{ text, msg }">
<input type="text" v-model="text"/>
<input type="text" v-model="msg"/>
</template>
</parent>
</template>
<script>
import Parent from "./components/Parent";
export default {
name: "App",
components: {
Toolbar
},
}
This does'nt work. How can i do something of the sort?

This is not possible. You can't change props (in this case slot props) given to your component(App.vue), but you can do something like this, with a "handler" method.
Parent.vue
<template>
<div>
<slot :text="text" :msg="msg" :setValue="setValue" />
<p>{{ text }}</p>
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
name: 'Parent',
data() {
return {
text: '',
msg: ''
};
},
methods: {
// set the current value with a function
setValue(e) {
this[e.target.name] = e.target.value;
}
}
};
</script>
App.vue
<template>
<parent>
<template #default="{ text, msg, setValue }">
Text: {{ text }}<br />
Msg: {{ msg }}<br /><br />
<!-- I have named the input fields after the variables in your data object and have the "setValue" method triggered by #input. -->
<input type="text" name="text" #input="setValue" />
<input type="text" name="msg" #input="setValue" />
</template>
</parent>
</template>
<script>
import Parent from './components/Parent';
export default {
name: 'App',
components: {
Parent
}
};
</script>

Related

Keep track of child component values in parent without using emit?

I have a custom component called HobbyForm which is a simple form with two controls, a checkbox and an input, this component is being called from a parent component called Content, along with other similar 'form' components.
<template>
<form>
<div class="row align-items-center">
<div class="col-1">
<Checkbox id="isHobbyActive" :binary="true" v-model="isActive"/>
</div>
<div class="col-5">
<InputText id="hobby" placeholder="Hobby" type="text" autocomplete="off" v-model="hobby"/>
</div>
</div>
</form>
</template>
<script>
export default {
name: 'HobbyForm',
data() {
return {
hobby: {
isActive: false,
hobby: null
}
}
},
}
</script>
My Content component is something like:
<template>
<language-form></language-form>
<hobby-form v-for="(hobbie, index) in hobbies" :key="index" v-bind="hobbies[index]"></hobby-form>
<Button label="Add Hobby" #click="addHobby"></Button>
</template>
<script>
export default {
name: "Content",
components: {
LanguageForm,
HobbyForm
},
data() {
return {
language: '',
hobbies: [
{
isActive: false,
hobby: null
}
]
};
},
methods: {
addHobby() {
this.hobbies.push({
isActive: false,
hobby: null
});
}
},
};
</script>
The idea is to be able to add more instances of the HobbyForm component to add another hobby record to my hobby data property; but I don't know how to keep track of these values from my parent without using an emit in my child components, since I don't want to manually trigger the emit, I just want to have the data updated in my parent component.
How should I access my child component's data from my parent and add it to my array?
In the current form passing parent data into a child component via v-bind="hobbies[index]" makes no sense as the child component (HobbyForm) has no props so it does not receive any data from the parent...
To make it work:
Remove data() from the child HobbyForm
Instead declare a prop of type Object
Bind form items to the properties of that Object
Pass the object into each HobbyForm
<template>
<form>
<div class="row align-items-center">
<div class="col-1">
<Checkbox id="isHobbyActive" :binary="true" v-model="hobby.isActive"/>
</div>
<div class="col-5">
<InputText id="hobby" placeholder="Hobby" type="text" autocomplete="off" v-model="hobby.hobby"/>
</div>
</div>
</form>
</template>
<script>
export default {
name: 'HobbyForm',
props: {
hobby: {
type: Object,
required: true
}
}
}
</script>
Even tho props are designed to be one way only so child should not mutate prop value, this is something else as you do not mutate prop value, you are changing (via a v-model) the properties of the object passed via a prop (see the note at the bottom of One-Way Data Flow paragraph)
Also change the parent to:
<hobby-form v-for="(hobby, index) in hobbies" :key="index" v-bind:hobby="hobby"></hobby-form>
Demo:
const app = Vue.createApp({
data() {
return {
hobbies: [{
isActive: false,
hobby: null
}]
};
},
methods: {
addHobby() {
this.hobbies.push({
isActive: false,
hobby: null
});
}
},
})
app.component('hobby-form', {
props: {
hobby: {
type: Object,
required: true
}
},
template: `
<form>
<div class="row align-items-center">
<div class="col-1">
<input type="checkbox" id="isHobbyActive" v-model="hobby.isActive"/>
</div>
<div class="col-5">
<input type="text" id="hobby" placeholder="Hobby" autocomplete="off" v-model="hobby.hobby"/>
</div>
</div>
</form>
`
})
app.mount('#app')
<script src="https://unpkg.com/vue#3.1.5/dist/vue.global.js"></script>
<div id='app'>
<hobby-form v-for="(hobby, index) in hobbies" :key="index" v-bind:hobby="hobby"></hobby-form>
<button #click="addHobby">Add Hobby</button>
<hr/>
<pre> {{ hobbies }} </pre>
</div>

How to pass the chebox value from a parent to parent to child and after set it into the vuex store VueJS/Vuex

Hi everyone i have a big trouble i need to modify the value of checkbox from parent to another parent to child , is not this difficulty i know but i'm at the start and after spent 2 days for this i can't try anything else , now i'll add all the code the child is named "UICheckbox" and i pass it to "ToDoListItem" and this last is passed to "ToDoList" , and every Todos is an in an array into "store.js"
UICheckbox
<template>
<div class="checkbox-container">
<input
type="checkbox"
:id="id"
:v-model="localTodo"
>
<label :for="id">
<p class="font-bold">{{ text }}</p>
</label>
</div>
</template>
<script>
export default {
props: [
"id",
"value",
"text"
],
data() {
return {
localTodo: this.value
}
},
watch: {
localTodo:{
handler(newVal, oldVal) {
this.$emit("change", newVal)
},
deep: true,
}
}
}
</script>
<style>
</style>
ToDoListItem
<template>
<div class="flex flex-row w-full my-2">
<UICheckbox
:v-model="localTodo.checked"
:id="todo.id"
:text="todo.text"
#change="localTodo.cheked = $event"
/>
<delete-button :todo="todo" />
</div>
</template>
ToDoList
<to-do-list-item
:todo="todo"
#click="changeChecked(todo)"
#change="todo.checked = $event"
/>

Vue.js slots - how to retrieve slot content in computed properties

I have a problem with vue.js slots. On one hand I need to display the slot code. On the other hand I need to use it in a textarea to send it to external source.
main.vue
<template>
<div class="main">
<my-code>
<template v-slot:css-code>
#custom-css {
width: 300px
height: 200px;
}
</template>
<template v-slot:html-code>
<ul id="custom-css">
<li> aaa </li>
<li> bbb </li>
<li> ccc </li>
</ul>
</template>
</my-code>
</div>
</template>
my-code.vue
<template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"><slot name="css-code"></slot></component>
<slot name="html-code"></slot>
<!-- send the code -->
<form method="post" action="https://my-external-service.com/">
<textarea name="html">{{theHTML}}</textarea>
<textarea name="css">{{theCSS}}</textarea>
<input type="submit">
</form>
</div>
</template>
<script>
export default {
name: 'myCode',
props: {
codeId: String,
},
computed: {
theHTML() {
return this.$slots['html-code']; /* The problem is here, it returns vNodes. */
},
theCSS() {
return this.$slots['css-code'][0].text;
},
}
}
</script>
The issues is that vue doesn't turn the slot content. It's an array of <VNode> elements. Is there a way to use slots inside the textarea. Or a way to retrieve slot content in the theHTML() computed property.
NOTE: I use this component in vuePress.
You need to create a custom component or a custom function to render VNode to html directly. I think that will be the simplest solution.
vnode to html.vue
<script>
export default {
props: ["vnode"],
render(createElement) {
return createElement("template", [this.vnode]);
},
mounted() {
this.$emit(
"html",
[...this.$el.childNodes].map((n) => n.outerHTML).join("\n")
);
},
};
</script>
Then you can use it to your component
template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"
><slot name="css-code"></slot
></component>
<slot name="html-code"></slot>
<!-- send the code -->
<Vnode :vnode="theHTML" #html="html = $event" />
<form method="post" action="https://my-external-service.com/">
<textarea name="html" v-model="html"></textarea>
<textarea name="css" v-model="theCSS"></textarea>
<input type="submit" />
</form>
</div>
</template>
<script>
import Vnode from "./vnode-to-html";
export default {
name: "myCode",
components: {
Vnode,
},
props: {
codeId: String,
},
data() {
return {
html: "", // add this property to get the plain HTML
};
},
computed: {
theHTML() {
return this.$slots[
"html-code"
]
},
theCSS() {
return this.$slots["css-code"][0].text;
},
},
};
</script>
this thread might help How to pass html template as props to Vue component

How to show newly data added?

I want to ask why the newly added data does not display in the template, but it does show the newly added data in using console.log (see the ViewPost.vue).
This is the result after I add new post: result.png. Someone knows how to achieve this?
Here is my parent component:
<template>
<section>
//more codes here
<ViewPost v-for="data in postData" :key="data.post_id" :data="data" />
</section>
</template>
<script>
import { mapState } from 'vuex';
export default {
components: {ViewPost},
computed: {
...mapState({
postData: state => state.post.datas,
}),
}
//more codes here
};
</script>
And below is the ViewPost.vue
<template>
<span>
<div class="card mb-10">
<h1>body</h1>
{{ data.post_body }}
</div>
</span>
</template>
<script>
export default {
props: {
data: {},
},
created() {
console.log(this.data);
},
};
index as a key may work fine.
<template>
<section>
//more codes here
<ViewPost v-for="(data,index) in postData" :key="index" :data="data" />
</section>
</template>
<script>
import { mapState } from 'vuex';
export default {
components: {ViewPost},
computed: {
...mapState({
postData: state => state.post.datas,
}),
}
//more codes here
};
</script>
ViewPost.vue
<template>
<span>
<div class="card mb-10">
<h1>body</h1>
{{ data.post_body || data.body }}
</div>
</span>
</template>
<script>
export default {
props: ['data']
created() {
console.log(this.data);
},
};

What's the correct ways of two-way binding props in Vue?

Here I have two components:
msg.vue
<template>
<div>
<input type="text" v-model="msg" />
</div>
</template>
<script>
export default {
name: "msg",
props: ["msg"]
};
</script>
samples.vue
<template>
<div>
<h2>{{ msg }}</h2>
</div>
</template>
<script>
export default {
name: "samples",
props: ["msg"]
};
</script>
and finally,
App.vue
<template>
<div id="app">
<samples v-bind:msg="msg"></samples>
<msg :msg="msg"></msg>
</div>
</template>
<script>
import samples from "./components/samples.vue";
import msg from "./components/msg.vue";
export default {
name: "app",
components: {
samples,
msg
},
data: () => {
return {
msg: "Hello World"
};
}
};
</script>
<style>
#app {
font-family: "Poppins", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
margin-top: 60px;
}
</style>
The thing I want to do is with the msg component I want to change the msg component in the data of App.vue. But when I change the value, Vue warns this :
So I can't understand what to do next. Please help me in this.
1. What's v-model?
<input v-model="msg" />
basically will transpile to
<input :value="msg" #input="msg = $event.target.value" />
2. data vs props
2.1 data is supposed to be component self-contained, whereas props is passed from parents.
2.2 data should be mutable within the component, props shouldn't. Why? Because of 2.1.
3. mutating a prop locally is considered an anti-pattern in Vue 2
Result: Here's the solution that I changed only 2 lines of code from yours:
App.vue
<template>
<div id="app">
<samples v-bind:msg="msg"></samples>
<msg :msg="msg" #update:msg="msg = $event"></msg> // changed this line
</div>
</template>
msg.vue
<template>
<div>
<input type="text" :value="msg" #input="$emit('uddate:msg', $event.target.value)" /> // changed this line
</div>
</template>
You're simply not allowed to mutate props, you're only allowed to change data variables.
To fix your problem here's what you could do. Implement a v-model on the component, so the msg always stays in sync, also in the App
App.vue
<template>
<div id="app">
<samples v-bind:msg="msg"></samples>
<msg v-model="msg"></msg>
</div>
</template>
<script>
import samples from "./components/samples.vue";
import msg from "./components/msg.vue";
export default {
name: "app",
components: {
samples,
msg
},
data: () => {
return {
msg: "Hello World"
};
}
};
</script>
msg.vue
<template>
<div>
<input type="text" v-model="selected" />
</div>
</template>
<script>
export default {
name: "msg",
props: ["value"],
computed: {
selected: {
get() {
return this.value;
},
set(value) {
return this.$emit('input', value)
}
}
}
};
</script>
Do not use v-model instead use $emit
Msg Component
Vue.component('msg',{
props:["msg"],
template: `<div>
<input #keyup="changeMsg" :value="msg">
</div>`,
methods:{
changeMsg:function(e){
this.$emit("changed",e.target.value);
}
}
});
<msg #changed="msg = $event" :msg="msg"></msg>
Check Solution in CodePen
One simple solution is to use v-bind.sync. Follow the link to see the document and usage. By using this, you need to change 2 lines of code from yours to make it work:
In App.vue:
change
<msg :msg="msg"></msg>
to
<msg :msg.sync="msg"></msg>
In msg.vue:
change
<input type="text" v-model="msg" />
to
<input type="text" #input="$emit('update:msg',$event.target.value)" />
However, this one is not recommended by Vue

Categories