Use parent slot variables in Vue template slot - javascript

I have a simple FormComponent:
<template>
<form>
<fieldset>
<slot />
</fieldset>
<span v-if="!isEditing" #click="edit()">Edit</span>
</form>
</template>
<script>
export default {
data () {
return {
isEditing: false,
}
},
methods: {
edit (state) {
this.isEditing = !this.isEditing
}
}
}
</script>
And when I use the component:
<FormComponent>
<input value="Man" type="text" :disabled="!isEditing">
</FormComponent>
The input field are correctly slotted into the component but the :disabled="!isEditing" from the slot isn't reacting to the change of isEditing in the FormComponent.
The Vue documentation is pretty good, but it doesn't cover each edge case.

The component with the <slot></slot> tag has to bind the data onto an attribute on the <slot> tag, like a prop:
<slot :isEditing="isEditing"></slot>
Then when you render that slotted component, Vue creates and exposes an object containing all of the bound data, with each attribute having a property on that object.
Access the object by adding an expression to the v-slot directive in this format:
<FormComponent v-slot:default="slotProps">
(Or use the alias # as in #default="slotProps".) You can access individual properties like isEditing via that object, like slotProps.isEditing:
<FormComponent #default="slotProps">
<input value="Man" type="text" :disabled="!slotProps.isEditing">
</FormComponent>
Here's the more common (and versatile) <template> syntax:
<FormComponent>
<template #default="slotProps">
<input value="Man" type="text" :disabled="!slotProps.isEditing">
</template>
</FormComponent>
You can also destructure slotProps for more direct access to the properties:
<FormComponent>
<template #default="{ isEditing }">
<input value="Man" type="text" :disabled="!isEditing">
</template>
</FormComponent>

Related

Vue: Pass parent data to child when updated with button click

I am new to Vue and trying to pass data from a parent component to the child when the parent data is updated via button click. The parent data is being updated, but the updated data is not being passed onto the child.
My main App.vue file is fairly standard:
<template>
<div id="app">
<parent></parent>
</div>
</template>
<script>
import parent from './components/parent.vue';
export default {
components: {
'parent':parent
},
name: 'app',
data () {
return {
}
}
}
</script>
The parent component has a button click to update the data and the change is displayed in the component
Parent.vue:
<template>
<div>
<h2>Text Input</h2>
<input v-model="input1"> {{ input1 }}
<h2>Multi Checkbox</h2>
<input type="checkbox" id="jack" value="Jack" v-model="input2">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="input2">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="input2">
<label for="mike">Mike</label>
<p>Checked names: <pre>{{ input2 }}</pre></p>
<h2>Radio</h2>
<input type="radio" id="one" value="One" v-model="input3">
<label for="one">One</label>
<br>
<input type="radio" id="two" value="Two" v-model="input3">
<label for="two">Two</label>
<br>
<span>Picked: {{ input3 }}</span>
<h2>new stuff</h2>
<button #click="getData()">Push Me</button>
<p> The data is changing here: {{ allInputs }} </p>
<child :all-inputs="allInputs"></child >
</div>
</template>
<script>
import child from './child .vue';
export default{
components: {
'child':child
},
data(){
return{
input1: '',
input2: [],
input3: '',
allInputs: ''
}
},
methods: {
getData(){
this.allInputs = {
input1: this.input1,
input2: this.input2,
input3: this.input3
}
}
}
}
</script>
But when I pass the data from the parent to a child, the data is not displayed on update:
Child.vue:
<template>
<div class="all-inputs">
<p>{{ passedData }}</p>
</div>
</template>
<script>
export default {
props: ["allInputs"],
data (){
return{
passedData: this.allInputs
}
}
}
</script>
Your child is not updating because you are using the local data property passedData in the template, not the props allInputs. If you will console the allInputs somewhere in the child component you will see that it is updating whenever the parent updates but as you are not using the prop directly so you are not seeing its reactivity. So, use props in your template instead of local data property, like this-
<p>{{ allInputs }}</p>
Not this-
<p>{{ passedData }}</p>
The approach you are trying to do is used when the child component wants to update the props passed by the parent but mutating parent props is erroneous because a child should not mutate (modify) the parent's state. So we usually assign props to a local data property and then mutate that data property, not props (same as you are doing). You can read more about this concept here.
Instead of having passedData in your template like this:
<template>
<div class="all-inputs">
<p>{{ passedData }}</p>
</div>
</template>
Change it to:
<template>
<div class="all-inputs">
<p>{{ allInputs }}</p>
</div>
</template>

Grab template form input in Vue 3 class components (like done in Angular)

I started vue 3 today, and opted for the class-based approach in the cli. I'm from the Angular background, so forgive me for thinking like Angular. Every example I see (even in the docs) is still using the Vue({...}) thing, however, I wanna do something like this (still thinking Angular-ish)
In angular, I can do this
<form #formData="ngForm" (ngSubmit)="onSubmit(formData.value)">
<input (ngModel)="name" name="name" placeholder="name">
</form>
Then in component
...
export class AppComponent {
public name!: string;
onSubmit(formData: string) {
console.log(formData)
}
}
What would be the vue 3 class components approach like the above?
I currently have this in vue 3
export default class Welcome extends Vue {
name!: string;
onSubmit(formData: any) {
console.log(formData)
}
}
<template>
<div>
<form #submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
What changes do I need to do to the above to connect the form in template to the component?
Without a third party library there isn't the same type of functionality that angular provides. Angular is doing a bunch of additional things to enhance the form object for validation and value tracking, and Vue natively does not do that. However you could instead put your data properties in an object to group them together. That way when you need to access them in something like the submit event to perhaps send all the values to an API, you can simply refer to that object instead of having to handle/build each property separately:
Class:
export class AppComponent {
// create object with bound form properties
public values: { name: string; } = { name: '' };
onSubmit() {
console.log(this.values); // { name: '' }
// axios.post('/api', this.values).then(res => console.log(res.data));
}
}
Template:
<template>
<div>
<form v-on:submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="values.name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
That being said, if you do need advanced form features like validation, sanitization, and similar there are plenty of libraries that do it well.
Hopefully that helps!
The native FormData constructor takes a <form> element, which creates the values of the form for all named inputs (<input>s or <textarea>s with name attribute).
So you could update your onSubmit method to retrieve the form data values:
export default class Welcome extends Vue {
name!: string;
onSubmit(formData: any) {
const form = e.target
const formData = new FormData(form)
}
}
Example using the Options API:
<template>
<div>
<form #submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
<script>
export default {
methods: {
onSubmit(e) {
const form = e.target
const formData = new FormData(form)
console.log({ formData: Array.from(formData.entries()) })
}
}
}
</script>
demo

How to make changes in vue v-model syntax in vue

I have a little task in vue.js and I want to change the syntax of v-model to model, or something else with webpack and vue-loader. I tried directives but that's not what I need.
This my example:
<input v-model="anyVarible">
<input model="anyVarible">
v-model is just a syntax sugar on setting & getting the data - prepared for all the input types:
You can use the v-model directive to create two-way data bindings on
form input, textarea, and select elements. It automatically picks the
correct way to update the element based on the input type. Although a
bit magical, v-model is essentially syntax sugar for updating data on
user input events, plus special care for some edge cases.
Source: Vue 2 v-model
That means, you can change v-model to something else, but then you have to handle the cases:
new Vue({
el: "#app",
data() {
return {
vmodeltext: null,
modeltext: null,
vmodelcb: null,
modelcb: null,
}
},
methods: {
handleInputText({
target: {
value
}
}) {
this.modeltext = value
},
handleInputCb({
target: {
checked
}
}) {
this.modelcb = checked
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-model="vmodeltext" /><br /> With v-model: {{ vmodeltext }}<br />
<hr />
<input type="text" :value="modeltext" #input="handleInputText($event)" /><br /> With modeltext: {{ modeltext }}<br />
<hr />
<input type="checkbox" v-model="vmodelcb" /><br /> With v-model: {{ vmodelcb }}<br />
<hr />
<input type="checkbox" :checked="modelcb" #change="handleInputCb($event)" /><br /> With modelcb: {{ modelcb }}<br />
</div>
So, essentially you don't need to change v-model with Webpack or a loader - just write the code in the way you need.

Vue - passing v-model from child to parent in v-for loop

I'm building a form builder in Vue which allows users to add/remove various types of input. My method so far is to have a template component for each input type, then when a user selects that input type I push it into an array on the parent component to loop over and display.
However, I also need to pass the value of the input up to the parent (and store it in the input type="hidden" element), so I'm emitting an event in the child component and catching it in the parent. My problem is that the value of labelText gets updated identically for every input type="hidden" at once when I type, rather than individually. I can't work out what I'm missing?
Text input template
<template>
<div id="TextInput">
<input type="text" placeholder="Question" class="form__input_label" #input="sendLabelUp($event.target.value)" />
<input type="text" placeholder="Test placeholder" />
<slot name="removeField"></slot>
<slot name="hiddenInputs"></slot>
</div>
</template>
<script>
export default {
name: 'TextInput',
methods: {
sendLabelUp: function(val) {
this.$emit('input', val);
},
},
};
</script>
Parent template (just including the part I think is relevant)
<transition-group name="form__input_list" tag="div">
<component :is="input.type" v-for="(input, index) in inputs" v-bind="input" :key="input" v-model="labelText" class="form__input">
<div slot="removeField">
<a class="btn btn--tertiary sml-push-top-half" #click="removeField(index)">Remove</a>
</div>
<div slot="hiddenInputs">
<!-- Hidden inputs used to store question config -->
<input type="hidden" :name="`pages[0]questions[${index}]type[${input.type}]label[${labelText}]`" />
</div>
</component>
</transition-group>
<script>
export default {
name: 'InputGenerator',
components: {
TextInput,
TextArea,
NumberInput,
LikertScale,
},
data() {
return {
inputs: [],
dropdownActive: false,
labelText: '',
};
},
};
</script>

VueJs2 checkbox v-bind:true-value is not working

I have created a vuejs2 app using vue-cli. I'm trying to bind dynamic value for checkbox as vuejs documentation said: value binding. But its giving me undefined. If I don't bind vlaue its giving me true or false. This is my ValueBinding.vue component.
<template>
<div id="input">
<p> Selected value for smoking: {{ smoking }} </p>
<input v-model="smoking" v-bind:true-value="Y" v-bind:false-value="N" type="checkbox">
<label>No Smoking</label>
<br>
<button #click="submit">Submit</button>
</div>
</template>
<script>
export default {
name: 'value-binding',
data() {
return {
smoking: ''
}
},
methods: {
submit() {
console.log(this.smoking) //shows undefined
}
}
}
</script>
I'm new to vuejs. Thanks in advance.
When you use v-bind, it dynamically bind one or more attributes to an expression. In your case when you do
v-bind:true-value="Y"
It will try to find a data attribute: Y in vue instance, as you have not defined any such attribute, it will become undefined.
If you just want to true-value as "Y" and false-value as "N", do follwing:
<input v-model="smoking" true-value="Y" false-value="N" type="checkbox">

Categories