Checkbox list without using v-model - vue.js

Vue has a good example of using multiple checkboxes with the same name attribute bound to the same array using v-model
However, I can't use v-model for some reason so I must use #input to emit checked while keeping the value unchanged.
Its not working for me though, all the checkboxes are checked/unchecked at the same time or I have to change the value which I don't want.
Is there a workaround?
Code: https://codesandbox.io/s/71pm2wllp1?fontsize=14

Vue generates special code when compiling a template containing checkboxes bound in this way. Since you're not using v-model, you'll have to handle this functionality yourself.
Try something like this:
new Vue({
el: '#app',
data: {
checkboxes: [
{ name: 'jack', value: 'Jack' },
{ name: 'bob', value: 'Bob' },
{ name: 'alice', value: 'Alice' },
],
model: [],
},
computed: {
modelJson() {
return JSON.stringify(this.model);
},
},
methods: {
handleChange(value, checked) {
if (checked) {
this.model = this.model.concat(value);
} else {
this.model = this.model.filter(x => x !== value);
}
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="checkbox of checkboxes" :key="checkbox.name">
<input
type="checkbox"
:id="checkbox.name"
:name="checkbox.name"
:value="checkbox.value"
:checked="model.includes(checkbox.value)"
#change="handleChange(checkbox.value, $event.target.checked)"
/>
<label :for="checkbox.name">{{ checkbox.value }}</label>
</div>
<pre>{{ modelJson }}</pre>
</div>

Related

VueJs Watch per key of a model

I have an object (model), as an input field,for a search method, and I want that the watcher to detect changes per key, and not for all.
Right now ,if an input is changed, the watcher is called 10 times (the number of inputs that I have).
<b-form-input
v-model="search[field.column]"
type="search"
id="filterInput"
placeholder="search.."
></b-form-input>
watch:{
search: {
handler(){
// do somenthing
},
deep: true
}
}
You can watch a computed value that return the specific field that you want to watch.
Vue.config.devtools = false;
Vue.config.productionTip = false;
var app = new Vue({
el: '#app',
data: {
form: {
name : '',
lastname: ''
}
},
computed: {
formName() {
return this.form.name;
}
},
watch: {
formName() {
console.log("Name has changed")
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
Name :
<input v-model="form.name" />
Lastname :
<input v-model="form.lastname" />
</div>
You can explicitly watch the required key only.
new Vue({
el: '#app',
data: {
search: {
name: { placeholder: 'search name', value: '' },
age: { placeholder: 'search age', value: '' },
country: { placeholder: 'search country', value: '' }
}
},
watch:{
'search.name.value': { // Explicitly watch the required key.
handler(){
console.log('name changed...')
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form>
<input
v-for="(value, key, i) in search"
:key="i"
v-model="search[key].value"
type="search"
id="filterInput"
:placeholder="value.placeholder"
/>
</form>
</div>
So, here watch's handler is called only when the name is searched & does not get called when age or country is searched.

Change value without trigger watch again

I'm trying to find a workaround to the problem of changing 2-way binding inside a watch of the same property avoiding calling the watch again. for example:
<select v-model="language">
<option ... />
</select>
watch:{
language(newVal // 'fr' , oldVal // 'en'){
if(condition){
// do something
} else {
// roll back to the old language
this.language = "en" // will call watch again.
// Looking for something like this:
// Vue.set(this, 'language', 'en', { watch: false })
}
}
}
I thought about using #change but it won't help cause I have to set the value again with an object and not a plain value.
I know I can use other 2-way property and use it as a flag, but I look for something more elegant.
Why roll back the user's selection in the first place? You can use a computed property to provide a filtered list of valid options to provide a better user experience.
The example below will only let you select en if the condition checkbox is true.
Vue.config.productionTip = false;
new Vue({
el: '#app',
template: `
<div>
<p>Condition: <input type="checkbox" v-model="condition" /></p>
<p>Selection: {{selection}}</p>
<select v-model="selection">
<option v-for="opt in filteredOptions" :key="opt.value" :value="opt.value">{{opt.label}}</option>
</select>
</div>
`,
data: () => ({
selection: undefined,
condition: true,
options: [
{ label: 'English', value: 'en' },
{ label: 'French', value: 'fr' },
{ label: 'Spanish', value: 'es' },
],
}),
computed: {
filteredOptions() {
return this.condition ? this.options.filter(x => x.value === 'en') : this.options;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

How can I add operator ternary in input type text on vue.js 2?

My vue component like this :
<template>
<div>
...
<li v-for="category in categories">
...
<input type="radio" class="category-radio" :value="category.id" (category.id == categoryId) ? 'checked data-waschecked=true' : ''>
...
</li>
...
</div>
</template>
<script>
export default {
props: ['categories', 'categoryId'],
}
</script>
I want to add condition in input type text. I use operator ternary like above code
If the code executed, it does not work
There is no error. So i'm confused to solve it
Maybe my code is still not correct
How can I solve it?
The issue is you're trying to use JavaScript expression inside plain HTML. This won't work.
You can either bind each attribute manually like this:
:checked="(expression) ? true : false"
or bind to a computed property which depends on your expression and returns your calculated property. Alternatively, you can bind an object with one to many properties, and bind the whole object at once (this is possible also):
new Vue({
el: '#app',
data: {
categories: [
{ id: 1, name: 'one' },
{ id: 2, name: 'two' },
{ id: 3, name: 'three' }
],
selectedId: 2 // for simplicity
},
computed: {
attrs: function() {
return function(id) { // computed can also return a function, so you can use args
return (id === this.selectedId) ? { checked: true, 'data-waschecked': true } : {}
}
}
},
mounted() { // log your element
console.log(document.querySelector('input[data-waschecked=true]'))
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<ul>
<li v-for="category in categories">
<input type="checkbox" v-bind="attrs(category.id)">
</li>
</ul>
</div>

Vuejs 2 input radio's v-model not working

Live example.
HTML
<script src="https://unpkg.com/vue"></script>
<div id="app">
<radio v-model="eventTypeInput" id="et-internal" name="event_type"
:selectedValue="0">internal</radio>
<radio v-model="eventTypeInput" id="et-external" name="event_type"
:selectedValue="1">external</radio>
{{eventTypeInput}}
</div>
JavaScript
new Vue({
el: '#app',
components: {
radio: {
template: `<div class="control-item">
<input type="radio" autocomplete="off" ref="input"
v-model="model"
:value="selectedValue"
:id="id"
:name="name"
:disabled="disabled">
<label :for="id"><slot></slot></label>
</div>`,
name: 'input-radio',
props: {
disabled: {type: Boolean, default: false},
name: {type: String, default: null},
selectedValue: {default: true}, // this radio value
value: {default: false}, // v-model variable
id: {required: true}
},
computed: {
model: {
get () {
return this.value
},
set (val) {
this.$emit('input', val)
}
}
}
}
},
data () {
return {
eventTypeInput: 'initial value'
}
}
});
I made a radio input component that works with v-model.
When I click the radio element, the expression bound via v-model is not changing.
What am I doing wrong? Is this a Vuejs bug?
Use selected-value when you pass the selectedValue property.
<radio v-model="eventTypeInput" id="et-internal" name="event_type" :selected-value="0">internal</radio>
When properties are defined in camelCase, they need to be passed in kebab-case. This is described here in the documentation.
HTML attributes are case-insensitive, so when using non-string templates, camelCased prop names need to use their kebab-case (hyphen-delimited) equivalents
Updated fiddle.
The problem is that HTML properties are case insensitive, if you change selectedValue with selected_value (for example) in every instance, it works.

Passing data into a Vue template

I am fairly new to vue and can't figure out how to add data values within a template. I am trying to build a very basic form builder. If I click on a button it should add another array of data into a components variable. This is working. The I am doing a v-for to add input fields where some of the attributes are apart of the array for that component. I get it so it will add the input but no values are being passed into the input.
I have created a jsfiddle with where I am stuck at. https://jsfiddle.net/a9koj9gv/2/
<div id="app">
<button #click="add_text_input">New Text Input Field</button>
<my-component v-for="comp in components"></my-component>
<pre>{{ $data | json }}</pre>
</div>
new Vue({
el: "#app",
data: function() {
return {
components: [{
name: "first_name",
showname: "First Name",
type: "text",
required: "false",
fee: "0"
}]
}
},
components: {
'my-component': {
template: '<div>{{ showname }}: <input v-bind:name="name" v-bind:type="type"></div>',
props: ['showname', 'type', 'name']
}
},
methods: {
add_text_input: function() {
var array = {
name: "last_name",
showname: "Last Name",
type: "text",
required: "false",
fee: "0"
};
this.components.push(array);
}
}
})
I appreciate any help as I know I am just missing something obvious.
Thanks
Use props to pass data into the component.
Currently you have <my-component v-for="comp in components"></my-component>, which doesn't bind any props to the component.
Instead, do:
<my-component :showname="comp.showname"
:type="comp.type"
:name="comp.name"
v-for="comp in components"
></my-component>
Here is a fork of your fiddle with the change.
while asemahle got it right, here is a boiled down version on how to expose data to the child component. SFC:
async created() {
await this.setTemplate();
},
methods: {
async setTemplate() {
// const templateString = await axios.get..
this.t = {
template: templateString,
props: ['foo'],
}
},
},
data() {
return {
foo: 'bar',
t: null
}
}
};
</script>
<template>
<component :is="t" :foo="foo"></component>
It pulls a template string that is compiled/transpiled into a js-render-function. In this case with Vite with esm to have a client-side compiler available:
resolve: {
alias: {
// https://github.com/vuejs/core/tree/main/packages/vue#with-a-bundler
vue: "vue/dist/vue.esm-bundler.js",
the index.js bundle size increases by few kb, in my case 30kb (which is minimal)
You could now add some what-if-fail and show-while-loading with defineasynccomponent