How To Check A Radio Button in Vue.JS 2 WITHOUT using v-model - vue.js

I am trying to set the checked value of a Radio Button list in Vue.JS 2. I've reviewed existing articles here and tried them and also several different manual approaches and I just cannot get this to work at all.
I am NOT using v-model here as I'm working on a custom radio button list control which is consumed by a forms builder. This is further complicated by the fact that I am building, on top, a nested radio button list to handle nullable booleans. Putting that complexity aside, my radio component looks like this....
<template>
<div class="form__radio-list">
<span class="form__radio-list__intro">{{ introText }}</span>
<ul class="form__radio-list__options">
<li v-for="item in options"
:key="item.key ? item.key : item"
class="form__radio-list__options__item">
<input type="radio"
:id="item.key ? item.key.toKebabCase() : item.toKebabCase()"
:name="def"
:value="item.value != undefined ? item.value : item"
:disabled="disabled"
:checked="isChecked(item)"
#input="onInput"
#change="$emit('change', $event.target.checked)">
<label :for="item.key ? item.key : item">{{ item.text ? item.text : item }}</label>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
def: {
type: String,
required: true
},
introText: {
type: String,
default: ''
},
options: {
type: Array,
required: true
},
initialValue: {
type: [String, Number, Boolean],
default: null
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
value: this.initialValue
}
},
methods: {
_parseValue() {
return this.value ? parseInt(this.value) : null
},
isChecked(item) {
const checked = item.value === this.value || item === this.value || item.value == this._parseValue()
this.$logger.logObject({ item, parentValue: this.value, checked }, 'Checking value for an item')
return checked
},
onInput($event) {
this.value = $event.target.checked
this.$emit('input', $event.target.checked)
}
}
}
</script>
From the logging I can see that the value of 'checked' SHOULD be set correctly (but it isn't).
I also tried splitting the input tag into a 'v-if' statement so I'd have one with a checked parameter set and one without (although this felt horrible) and that worked from an HTML point of view (checked="checked" appeared where I would expect it to) but, on the browser neither of the 2 options were checked.
I am consuming the component through a boolean component renederer that looks like this...
<template>
<hh-radio class="form__radio-list--yes-no"
:def="def"
:intro-text="introText"
:options="options"
:initial-value="initialValue"
:disabled="disabled"
#change="$emit('change', $event)"
#input="$emit('input', $event)" />
</template>
<script>
export default {
props: {
def: {
type: String,
required: true
},
introText: {
type: String,
default: ''
},
initialValue: {
type: [String, Boolean],
default: null
},
disabled: {
type: Boolean,
default: false
}
},
computed: {
options() {
return [{
key: 'yes',
text: 'Yes',
value: true
}, {
key: 'no',
text: 'No',
value: false
}]
}
}
}
</script>
This is then consumed ultimately on a form like this...
<hh-yes-no class="editable-segment-field__bool"
:def="pvc-enabled"
:initial-value="pvc.value"
#input="onInput" />
The value pass throughs seem to work fine - The key issue that I have is that it will NOT specify the currently selected item from any existing data.
I have tried suggestions here - Vue.JS radio input without v-model and here - Vue.JS checkbox without v-model without much success.
Using the example given me below I've tried to strip this back as far as I can, adding in pieces of the dynamic elements from my components as I go to identify the problem root.
I now have a simpler component which looks like this...
<template>
<div :class="`form__radio-list ${(this.def ? `form__radio-list--${this.def} js-${this.def}` : '' )}`">
<span class="form__radio-list__intro">{{ introText }}</span>
<ul class="form__radio-list__options">
<li class="form__radio-list__options__item">
<input id="awesome"
type="radio"
name="isawesome"
:checked="radio === 'Awesome'"
value="Awesome"
#change="radio = $event.target.value">
<label for="awesome">Awesome</label>
</li>
<li class="form__radio-list__options__item">
<input id="super"
type="radio"
name="isawesome"
:checked="radio === 'Super Awesome'"
value="Super Awesome"
#change="radio = $event.target.value">
<label for="super">Super</label>
</li>
</ul>
<span>Selected: {{ value }} | {{ radio }}</span>
</div>
</template>
<script>
export default {
props: {
def: {
type: String,
required: true
},
introText: {
type: String,
default: ''
},
initialValue: {
type: [String, Boolean],
default: null
},
disabled: {
type: Boolean,
default: false
}
},
// delete this
data() {
return {
value: this.initialValue,
radio: 'Awesome'
}
},
computed: {
options() {
return [{
key: 'yes',
text: 'Yes',
value: true
}, {
key: 'no',
text: 'No',
value: false
}]
}
}
}
</script>
I managed to get this to fail as soon as I added name="isawesome" to the radio button items. It seems that when you introduce 'name' something goes awry. Surely I need 'name' to prevent multiple radio button lists interacting with each other or is this something that Vue handles which I've been unaware of.

Here is an working example:
new Vue({
el: "#app",
data: {
radio: 'Awesome'
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<span>Vue is:</span> <br>
<label>Awesome
<input
type="radio"
:checked="radio === 'Awesome'"
value="Awesome"
#change="radio = $event.target.value"
>
</label>
<label>Super Awesome
<input
type="radio"
:checked="radio === 'Super Awesome'"
value="Super Awesome"
#change="radio = $event.target.value"
>
</label>
<hr>
<span>Selected: {{radio}}</span>
</div>

This appears to be a bug / oddity with Vue.js. Using roli roli's example I was able to take both his example and my requirement and keep tweaking until they met in the middle so I could find out the problem via process of elimination.
Here is a copy of the component with both elements together. As #birdspider commented above, I would not expect this to work in HTML. In Vue, however, it DOES....
<template>
<div :class="`form__radio-list ${(this.def ? `form__radio-list--${this.def} js-${this.def}` : '' )}`">
<span class="form__radio-list__intro">{{ introText }}</span>
<ul class="form__radio-list__options">
<li class="form__radio-list__options__item">
<input id="yes"
type="radio"
:checked="value === true"
:value="true"
#change="value = $event.target.value">
<label for="yes">Yes</label>
</li>
<li class="form__radio-list__options__item">
<input id="no"
type="radio"
:checked="value === false"
:value="false"
#change="value = $event.target.value">
<label for="no">No</label>
</li>
<li class="form__radio-list__options__item">
<input id="awesome"
type="radio"
:checked="radio === 'Awesome'"
value="Awesome"
#change="radio = $event.target.value">
<label for="awesome">Awesome</label>
</li>
<li class="form__radio-list__options__item">
<input id="super"
type="radio"
:checked="radio === 'Super Awesome'"
value="Super Awesome"
#change="radio = $event.target.value">
<label for="super">Super</label>
</li>
</ul>
<span>Selected: {{ value }} | {{ radio }}</span>
</div>
</template>
<script>
export default {
props: {
def: {
type: String,
required: true
},
introText: {
type: String,
default: ''
},
initialValue: {
type: [String, Boolean],
default: null
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
value: this.initialValue,
radio: 'Awesome'
}
}
}
</script>
This will work completely fine and renders as 2 separate radio button lists - one for yes / no and one for awesome / super awesome. If you try and add a 'name' tag to the radio inputs then the checked state is no longer set in the radio button group at all.
---- UPDATE ----
This seems like a bug where an attempt to add 'name' should simply be ignored by Vue, but it isn't. However, this isn't the case if you create an ES5 style component in codepen (I am unable to repro this in codepen for this reason.)
Using ES6 style files, this component will not set any checked values (credit to #birdspider for half of this simplified example)...
<template>
<div>
<ul class="form__radio-list__options">
<li class="form__radio-list__options__item">
<input id="awesome" type="radio" name="isawesome"
:checked="radio === 'Awesome'"
value="Awesome"
#change="onChange($event.target.value)">
<label for="awesome">Awesome</label>
</li>
<li class="form__radio-list__options__item">
<input id="super" type="radio" name="isawesome"
:checked="radio === 'Super Awesome'"
value="Super Awesome"
#change="onChange($event.target.value)">
<label for="super">Super</label>
</li>
</ul>
<ul class="form__radio-list__options">
<li class="form__radio-list__options__item">
<input id="awesome" type="radio" name="other"
:checked="radio2 === 'other'"
value="other"
#change="onChange2($event.target.value)">
<label for="awesome">other</label>
</li>
<li class="form__radio-list__options__item">
<input id="super" type="radio" name="other"
:checked="radio2 === 'another'"
value="another"
#change="onChange2($event.target.value)">
<label for="super">another</label>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
radio: 'Awesome',
radio2: 'another'
}
},
methods: {
onChange(e) {
this.radio = e
},
onChange2(e) {
this.radio2 = e
}
}
}
</script>
(if anyone can tell me how to get it running in Codepen or JS Fiddle like this then that would be great!)
If I remove the name attributes then it works.
If you put essentially the same thing in Codepen as a single Vue instance rather than a component file then that works too.

Related

Hide label for particular component in vue / nuxt js

I have created a reusable input component with label, but i want label to hide(if hidden it should not take a space something like display none in css) on some place and label should be visible on some places
here is my code of the input component
<template>
<div>
<label for="" :label="label" class="mb-1 select-label">{{label}}</label> //hide or visible depending on requirement
<div class="custom-select" :tabindex="tabindex" #blur="open = false">
<div class="selected" :class="{ open: open }" #click="open = !open">
{{ selected }}
</div>
<div class="items" :class="{ selectHide: !open }">
<div
v-for="(option, i) of options"
:key="i"
#click="
selected = option;
open = false;
$emit('input', option);
"
class="border-bottom px-3"
>
{{ option }}
</div>
</div>
</div>
</div>
</template>
here is the code of my script
<script>
export default {
props: {
label: {
type: String,
required: false,
default: ''
},
options: {
type: Array,
required: true,
},
default: {
type: String,
required: false,
default: null,
},
tabindex: {
type: Number,
required: false,
default: 0,
},
},
data() {
return {
selected: this.default
? this.default
: this.options.length > 0
? this.options[0]
: null,
open: false,
};
},
mounted() {
this.$emit("input", this.selected);
},
}
</script>
You can use v-if directive to conditionally render the label element based on the props value.
As v-if will actually destroy and recreate elements when the conditional is toggled. Hence, all the classes/attributes applied to the element will also destroy.
Demo :
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
showMessage: false
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p v-if="showMessage">{{ message }}</p>
</div>
If you will run above code snippet and open the developer console. You will see that <p> element will not be there as it has been removed from the DOM.
console screenshot :
You can just add a prop to control label visibility
script:
props: {
showLabel: {
type: Boolean,
default: false
}
}
template:
<label v-show="showLabel" />
parent component:
<MyCustomInput :show-label="false" />
<MyCustomInput :show-label="true" />

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')">

Including text when an image/icon is shown

At the moment I am trying to include different text when an image and/or icon shows on the page. Here is the code for the vue file:
<template>
<div class="profile">
<div
:class="{
'flags--relevant': hasFlagType('medication'),
'flags--active': flag == 'medication'
}"
class="flags"
#click="setFlag('medication')"
>
<medication-icon
:class="[
hasFlagType('medication')
? 'medication-icon--focus'
: 'medication-icon--blur',
]"
/>
</div>
<div
:class="{
'flags--relevant': hasFlagType('condition'),
'flags--active': flag == 'condition'
}"
class="flags"
#click="setFlag('condition')"
>
<treatment-icon
:class="[
hasFlagType('condition')
? 'treatment-icon--focus'
: 'treatment-icon--blur',
]"
/>
</div>
<div
:class="{
'flags--relevant': hasFlagType('translator'),
'flags--active': flag == 'translator',
}"
class="flags"
#click="setFlag('translator')"
>
<foreign-dialect-icon
:class="[
hasFlagType('translator')
? 'foreign-dialect-icon--focus'
: 'foreign-dialect-icon--blur',
]"
/>
</div>
</div>
</template>
<script>
export default {
components: {
ForeignDialectIcon,
MedicationIcon,
TreatmentIcon,
},
props: {
userFlags: {
type: Array,
default() {
return {};
},
},
},
data() {
return {
flags: this.userFlags,
flag: null,
title: "Requires daily medication",
title2: "Specialist health condition",
title3: "Requires a translator",
};
},
methods: {
hasFlagType(flagType) {
return this.flags[flagType] !== undefined;
},
setFlag(flagType) {
if (this.hasFlagType(flagType)) {
this.flag = flagType;
}
},
resetFlag() {
this.flag = null;
},
},
};
</script>
I have tried outputting the titles in the data section for each icon and they still show even if the icon doesn't show. I need it to output the title when the image is shown and the many attempts I've tried haven't worked so was wondering how I am able to solve this?
Assuming the title should only appear when the corresponding icon is focused, you could use the same condition (hasFlagType(...)) with v-if to render the title:
<div>
<medication-icon .../>
<span v-if="hasFlagType('medication')">{{ title }}</span>
</div>
<div>
<treatment-icon .../>
<span v-if="hasFlagType('condition')">{{ title2 }}</span>
</div>
<div>
<foreign-dialect-icon .../>
<span v-if="hasFlagType('translator')">{{ title3 }}</span>
</div>

VueJS default/starting a value ina list of searched terms

Trying to make a search plugin using Vue and I'm having a problem with adding a starting/default value to the list of options. If I comment out the pair or template lines involving the start prop the rest of it works fine, but nothing renders if I leave them in.
Component Code:
Vue.component('search', {
props: {
type: String,
hidein: String,
start: {
type: Object,
default: null
}
},
//props: ['type', 'hidein', 'start'],
data: function () {
return {
search: "",
select: "",
results: [],
};
},
template: '<div #load="loaded"><input :id="hidein" type="text" v-model="search" #keyup="updateList">'+
'<input type="hidden" :name="hidein" v-model="select" class="form-control">'+
'<div v-if="start">Current: <span #click="select=start.id" :class="{\'label label-success\':(select==start.id)}>'+
'+ {{start.name}}</span></div>'+
'<div v-if="results.length">Do you mean:<ul>'+
'<li v-for="result in results" :key="result.id"><span #click="select=result.id" :class="{\'label label-success\':(select==result.id)}">'+
'+ {{result.name}}</span></li>'+
'</ul></div></div>',
methods: {
updateList: function(e) {
var response = [];
console.log("search: "+this.search);
$.ajax({
method: "GET",
url: "/api/search/"+this.type,
data: { key: this.search }
}).done(function( msg ) {
this.results = msg;
console.log(this.results);
}.bind(this));
},
loaded: function () {
this.select=!!this.start ? this.start.id : null;
}
},
});
Component Call:
<search type="ships" hidein="ship_id" ></search>
Can anyone tell me what I'm doing wrong? (Besides the hacked together template, that's hopefully a completely separate issue with the pipeline I'm having)
There is a missing " here
:class="{\'label label-success\':(select==start.id)}
But also, please, use a template literal to make your life easier.
`<div #load="loaded"><input :id="hidein" type="text" v-model="search" #keyup="updateList">
<input type="hidden" :name="hidein" v-model="select" class="form-control">
<div v-if="start">
Current:
<span #click="select=start.id" :class="{'label label-success':(select==start.id)}">
{{start.name}}
</span>
</div>
<div v-if="results.length">
Do you mean:
<ul>
<li v-for="result in results" :key="result.id">
<span #click="select=result.id" :class="{'label label-success':(select==result.id)}">
{{result.name}}
</span>
</li>
</ul>
</div>
</div>`

How to define values for checked and unchecked checkbox with VUE.js?

Is there a way how define checked and unchecked value for .? Now VUE sets model to true/false which makes sense but in real apps data format is somethink like '1' => true and ''=>false. How to achive this in VUE?
You can use true-value and false-value:
https://v2.vuejs.org/v2/guide/forms.html#Checkbox-1
<input
type="checkbox"
v-model="toggle"
true-value="yes"
false-value="no"
>
// when checked:
vm.toggle === 'yes'
// when unchecked:
vm.toggle === 'no'
If you need to wrap a field in a component, then the code will have to be slightly modified.
<template>
<div class="flex items-center h-5">
<input
#input="$emit('input', reversedValue)"
:id="id"
:value="value"
type="checkbox"
:checked="checked"
/>
</div>
</template>
<script>
export default {
props: {
value: [Boolean, String, Number],
trueValue: {
type: [Boolean, String, Number],
default: true
},
falseValue: {
type: [Boolean, String, Number],
default: false
},
},
computed: {
reversedValue() {
return this.value === this.trueValue ? this.falseValue : this.trueValue;
},
checked() {
return this.value === this.trueValue;
}
}
};
</script>
Not sure what it is exactly you need, but, as you say, if you do:
{{ boxtest }}
<input type="checkbox" v-model="boxtest"/>
Boxtest will display as 'true' or 'false' as you check or uncheck the box.
If you do need to convert it you could just do the likes of:
{{ boxtest ? 1 : '' }}