How to define Vue 2 component that contains a checkbox? - vuejs2

I've tried this about four times, and consistently find myself stuck.
Binding directly to a checkbox seems well documented. https://vuejs.org/guide/essentials/forms.html#checkbox shows
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>
But wrapping that up in a custom component, and then connecting to the component seems like something I can't get right.
I have another reference (which I can't find right now, but will update this question when I do) that has guided me toward the following solution:
<template>
<div>
<input type="checkbox" :name="prefix" :id="id" v-model="zheck"
#change="$emit('checked', $event.target.checked)">
<label :for="id"><span v-i18n>{{title}}</span></label>
</div>
</template>
...
export default Vue.extend({
name: 'LabelInput',
props: {
value: {
type: Boolean,
},
title: {
type: String,
required: false,
default: '',
},
prefix: {
type: String,
required: true,
},
},
data() {
return {
zheck: this.value,
};
},
And its use: <label-input title="..." prefix="..." v-model="xyz"></label-input>
While label-input emits eventsxyz never seems to be updated.
What am I doing wrong?

Related

Vue v-model binding form Checkbox to Boolean variable is passing an Event object instead of true/false

I´m using a Vue base component which wraps a simple check box. In my template Im doing a 2-way bind using a v-model to a Boolean variable in my data. Nothing too fancy but there´s a problem with my implementation where instead of the target variable receiving a true/false value when the control state is turned on/off (check/unchecked), it receives an event object. I don't know what I'm doing wrong, any ideas?
I see this exception in the console when I click the control:
[Vue warn]: Invalid prop: type check failed for prop "value". Expected
Boolean, got Event
found in
<BaseSwitch> at src/components/BaseSwitch.vue
<Card> at src/components/Card.vue
<SlideYUpTransition>
<Modal> at src/components/Modal.vue
<RFQSales> at src/views/rfq/RFQSales.vue
<FadeTransition>
<App> at src/App.vue
<Root>
base-switch component:
<template>
<label class="custom-toggle">
<input type="checkbox"
v-model="model"
v-bind="$attrs"
v-on="$listeners">
<span class="custom-toggle-slider rounded-circle"></span>
</label>
</template>
<script>
export default {
name: "base-switch",
inheritAttrs: false,
props: {
value: {
type: Boolean,
default: false,
description: "Switch value"
}
},
computed: {
model: {
get() {
return this.value;
},
set(value) {
this.$emit("base-switch", value);
}
}
}
};
</script>
<style>
</style>
Template:
<base-switch class="pull-right" v-model="modals.modalNewRFQ.data.borrowButton"
You haven't stated the version of Vue you use. Is it Vue2 or Vue3?
Your tag is also not fully copy/pasted. I have changed it to
<base-switch class="pull-right" v-model="borrowButton" #base-switch="process($event)"></base-switch>
Now your code works perfectly with Vue3.
Here is the link to the working playground:
https://stackblitz.com/edit/vue-f3nqz1?file=src/App.vue
For Vue3 you must define the emitted events, to make it work well.
emits: ['base-switch'],
Also, pay attention to passing value:
this.$emit("base-switch", value);
and
#base-switch="process($event)">
If your function gets an Even Object, then you can try JSON.stringify() it to check the properties. Possibly, it is related to your $listeners solution. I couldn't guess it to reproduce the problem.
Here is the code:
App.vue
<template>
<div id="app">
<base-switch class="pull-right" v-model="borrowButton" #base-switch="process($event)"></base-switch> <br />
Value: {{value}}
</div>
</template>
<script>
import BaseSwitch from './components/BaseSwitch.vue'
export default {
name: 'App',
components: {
BaseSwitch
},
data() {
return {
borrowButton: false,
value: null
}
},
methods: {
process(value) {
this.value = value;
}
}
}
</script>
BaseSwitch.vue
<template>
<label class="custom-toggle">
<input type="checkbox"
v-model="model"
v-bind="$attrs"
>
<span class="custom-toggle-slider rounded-circle"></span>
</label>
</template>
<script>
export default {
name: "base-switch",
inheritAttrs: false,
emits: ['base-switch'],
props: {
value: {
type: Boolean,
default: false,
description: "Switch value"
}
},
computed: {
model: {
get() {
return this.value;
},
set(value) {
this.$emit("base-switch", value);
}
}
}
};
</script>
<style>
</style>

How to get input data from multiple inputs in dynamic form as a single object?

How can I get all input values from dynamic form as a single object with key-values pairs? Where key is input name and value is user input.
I have a custom input component. In app component I dynamically render inputs from array:
<template>
<div>
<form #submit.prevent="submit">
<ul>
<li v-for="input in inputFields" :key="input.label">
<base-input
:placeholder="input.placeholder"
:type="input.type"
:name="input.name"
v-model="userInput[input.name]"
></base-input>
</li>
</ul>
<button type="submit">Console.log input</button>
</form>
</div>
</template>
<script>
import BaseInput from "./components/BaseInput.vue";
export default {
components: { BaseInput },
data() {
return {
inputFields: INPUT_FIELDS,
userInput: {
name: "",
phone: "",
email: "",
location: "",
address: "",
comment: "",
},
};
},
methods: {
submit() {
console.log("userInput:", this.userInput);
},
},
};
</script>
Then in base-input component:
<template>
<input
:type="type"
:name="name"
:placeholder="placeholder"
#input="$emit('update:modelValue', $event.target.value)"
:value="modelValue[name]"
/>
</template>
<script>
export default {
emits: ["update:modelValue"],
props: {
label: {
type: String,
required: false,
},
placeholder: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
name: {
type: String,
required: false,
},
modelValue: {},
},
};
</script>
Expected behavior: click on submit --> get object like in console:
{
name: "user name",
phone: "1111",
email: "test#test.com",
location: "World",
address: "",
comment: "no",
},
Actual behavior: I can only type 1 letter in a field and no more.
Here's a codesandbox: https://codesandbox.io/s/optimistic-alex-dzw0mp?file=/src/App.vue
You were close. You need to remove [name] from the end of :value="modelValue[name]" in the base-input component. Since you are already scoping to name in the parent component it is wrong to also look for a [name] prop on the value of modelValue within your child component.
See it working here: https://codesandbox.io/s/unruffled-cohen-bivefc?file=/src/components/BaseInput.vue
Your component template should be:
<input
:type="type"
:name="name"
:placeholder="placeholder"
#input="$emit('update:modelValue', $event.target.value)"
:value="modelValue"
/>

Components and updating parent's model

I found some code on the internet to develop an input component. The component works great. However, since there is no two-way binding between the parent and the child. I'm wondering what this.$emit("change", value); does. How does this update the parent? I'm not putting 1 and 2 together here. Thank you in advance.
<template>
<div>
<div class="form-control-label" v-if="label">{{ label }}</div>
<div v-for="(option, index) in options" :key="option.text">
<div class="custom-control custom-radio">
<input
:id="id + index"
:name="id"
type="radio"
:value="option.value"
:checked="option.value === value"
:class="inputClass"
class="custom-control-input"
:disabled="disabled"
:required="required"
#change="updateValue(option.value)"
/>
<label :for="id + index" class="custom-control-label">{{
option.text
}}</label>
<slot v-if="option.value === value" :name="option.value" />
</div>
</div>
<validation-provider v-slot="{ errors }" :name="label" rules="required">
<input type="hidden" v-model="selected" />
<slot name="error">
<div v-if="errors[0]" class="invalid-feedback" style="display: block;">
{{ errors[0] }}
</div>
</slot>
</validation-provider>
</div>
</template>
<script>
export default {
model: {
event: "change",
selected: null,
},
props: {
rules: {
type: String,
required: false,
},
id: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
value: {
type: [String, Number, Boolean, Object],
default: null,
},
options: {
type: [Array],
required: true,
},
required: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
inputClass: {
type: [String, Object],
default: "",
},
},
methods: {
updateValue(value) {
this.selected = value;
this.$emit("change", value);
},
},
};
</script>
Normally, v-model works by the component receiving a value prop and emitting an input event that contains the updated value.
Your component implements v-model by taking a value prop, and emitting a change event (instead of input event), as specified by the model option. Whenever the component emits the change event in updateValue(), the component's consumer sets the v-model variable to the event's value.
In the following example, selectedOption is set to "my value" when the radio option in MyOptions is selected:
// App.vue
<MyOptions v-model="selectedOption" />
// MyOptions.vue
<input type="radio" #change="$emit('change', 'my value')">

Vue.js custom component with HTML <select> and v-model (W3C compliant)

I'm new to Vue.js (using Nuxt.js) and what I'm trying to achieve is to have a Select component that I can reuse everywhere and is W3C compliant.
With the help of #Jasmonate answers, I managed to create this component, it's working. But the value attribute is still visible in the source code and so isn't W3C compliant. Maybe the problem is coming from somewhere else in the project ?!
Parent component
<custom-select
:options="options"
v-model="selectedOption"
></custom-select>
<span>Selected : {{ selectedOption }}</span>
<script>
data() {
return {
selectedOption: "A",
options: [
{ label: "One", value: "A" },
{ label: "Two", value: "B" },
{ label: "Three", value: "C" }
],
};
}
</script>
custom-select.vue
<template>
<select :value="value" #input="clicked">
<option
v-for="option in options"
:key="option.label"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</template>
<script>
export default {
props: {
value: {
required: true
},
options: {
type: Array,
required: true
}
},
methods: {
clicked($event) {
this.$emit("input", $event.target.value);
}
}
};
</script>
I read those documentation pages:
Form Input Bindings
Components Basics
And also looked around the web to find example of v-model in a custom component, but it's always about the input tag. The only example I found about a custom select with v-model isn't actually a select tag, like the Vue Select plugin or this thread on StackOverflow.
v-model is syntax sugar. By default, the value is a prop that has the name value, and it changes (two-way-binding) whenever the event input is emitted.
Also, v-model is bound on the select element, not option.
Your code can be modified as such:
<template>
<select :value="value" #input="clicked">
<option
v-for="option in options"
:key="option.label"
:value="option.value"
>
{{ option.label }}
</option>
</select>
</template>
<script>
export default {
props: {
value: {
required: true
},
options: {
type: Array,
required: true
}
},
methods: {
clicked($event) {
this.$emit('input', $event.target.value);
}
}
};
</script>
Documentation here: https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components
You can also change the prop name and event name that v-model uses, see: https://v2.vuejs.org/v2/guide/components-custom-events.html#Customizing-Component-v-model

vuejs semantic ui - drop down not displaying on arrow click

I'm faced with an issue where my semantic drop down in my vue project won't activate when clicking on the arrow icon but works when I click on the rest of the element. The drop down also works when I set the dropdown to activate on hover, but just not on click. Solutions I've tried:
tested if the dynamic id are at fault
tested if the back ticks are confusing things
placed the values directly into the semantic drop down
Aside from the dropdown not activating, the code below works as intended and brings back the selected value to the parent component and can be displayed.
Dropdown.vue:
<template>
<div class="ui selection dropdown" :id="`drop_${dropDownId}`">
<input type="hidden" name="gender" v-model="selected">
<i class="dropdown icon"></i>
<div class="default text">Gender</div>
<div class="menu">
<div class="item" v-for="option in options" v-bind:data-value="option.value">
{{ option.text }}
</div>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
selected: {}
}
},
watch: {
selected: function (){
this.$emit("dropDownChanged", this.selected)
}
},
props: {
options: Array, //[{text, value}]
dropDownId: String
},
mounted () {
let vm = this;
$(`#drop_${vm.dropDownId}`).dropdown({
onChange: function (value, text, $selectedItem) {
vm.selected = value;
},
forceSelection: false,
selectOnKeydown: false,
showOnFocus: false,
on: "click"
});
}
}
</script>
The component usage:
<vue-drop-down :options="dropDownOptions" dropDownId="drop1" #dropDownChanged="dropDownSelectedValue = $event"></vue-drop-down>
The data in the parent:
dropDownOptions: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
],
dropDownSelectedValue: ""
Here is a fiddle of the above but simplified to use a flatter project. However the problem doesn't reproduce :(
https://jsfiddle.net/eywraw8t/210520/
I'm not sure what is causing your issue (as the examples on the Semantic Ui website look similar), but there is a workaround. For you arrow icon:
<i #click="toggleDropDownVisibility" class="dropdown icon"></i>
And then in the methods section of your Vue component:
methods: {
toggleDropDownVisibility () {
$(`#drop_${this.dropDownId}`)
.dropdown('toggle');
}
},