vue: react-like controlled checkbox? - vue.js

in React, we can have a controlled checkbox like this
<input type="checkbox" checked={true} />
and this will keep the checkbox always checked, even the users click it
but in Vue, after binding a true value for checked
<input type="checkbox" :checked="true" />
it's initially checked, but the users can still click to change it.
how to prevent this behavior in Vue?
update:
what I'm really trying to implement is
a checkbox can click to uncheck
but can not click to check, to only way to make it checked is by changing the <select /> next to it
uncheck the checkbox will clear <select />
my current implementation: https://codesandbox.io/s/goofy-mcnulty-seh6w?file=/src/App.vue
using key and #click.prevent kind of does not fit my head.

I've come up with following solution for your problem.
Lets go step by step and explain how it works.
We set the input element's checked property to be a local boolean value (checkboxChecked). Also we want it to be automatically changed once the checkbox is clicked, which is why we also add this checkboxChecked boolean as v-model attribute.
We prevent the user from checking it again by setting the disabled attribute of the input element to be true if the checkbox is not checked (checkboxChecked = false).
On the select element we listen for the #change event (See this question on the change event) and run the onSelectChange method. This method then sets the checkboxChecked boolean back to true, enabling the user to uncheck the checkbox again.
Furthermore, we add a ref attribute to the first option (The one we want to select if the checkbox gets unchecked).
Lastly we add a watcher (Check out the docs on value watchers) to the checkboxChecked attribute. If the checkbox is now clicked the if statement in our watcher function selects the first option using the ref attribute if the checkboxChecked value is false.
Edit
What I forgot to mention is, that you would also have to check if the user actually selects an other option than empty (I've done this by comparing the event.target.value to an empty string). I've added this to the code now.
Also a client still could go ahead and manually set the disabled attribute with his webbrowsers html editor to false.
<template>
<div id="app">
<input
type="checkbox"
:checked="checkboxChecked"
:disabled="!checkboxChecked"
v-model="checkboxChecked"
/>
<select style="width: 5em" #change="onSelectChange($event)">
<option ref="emptySelectOption" value="">empty</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
checkboxChecked: true,
};
},
methods: {
onSelectChange(event) {
if (event.target.value !== "") this.checkboxChecked = true;
},
},
watch: {
checkboxChecked(value) {
if (!value) this.$refs.emptySelectOption.selected = true;
},
},
};
</script>

Related

How does one make arrow keys on number inputs lazy in Vue?

Vue supports lazy binding to models with the lazy modifier, e.g.
<input v-model.lazy="value" />
Now the model isn't updated until the input loses focus. However, if I change the type to Number and use the arrow keys to set the value, the model updates while the input has focus:
<input type="number" v-model.lazy="value" />
Is there an (easy) way to delay binding until after focus is lost?
v-model is synonymous for :value + #change. Assuming the arrows on input trigger a focus event, you can try replacing v-model with :value and #blur pair. Might not work if .lazy modifier already does this.
<input type="number" :value="value" #blur="value = $event.target.value" />
Another alternative is to "debounce" the change event with a set time so the value doesn't update while the user is changing the value.
Edit: debounce example using npm package
After installing and importing the debounce package, you need to create/assign the "debounced" version of the method (should define it in methods) to a method name under created (can be a different method name but should match what you put in #blur listener.
<input type="number" :value="value" #blur="updateValueOnBlur" />
created() {
this.updateValueOnBlur = debounce(this.updateValueOnBlur, 500);
},
methods: {
updateValueOnBlur(e) {
this.value = e.target.value;
},
}

vue.js show/hide input field based on radio button selection

I'm trying to customize an out-of-the-box form in Vue.js where inputs are shown/hidden depending on the selection of 2 radio buttons:
<b-form-group label-class="ssrv-form-control">
<div class="ssrv-5">
<b-form-radio v-model="isOperator" name="operatorRad" value="false">Consultant</b-form-radio>
</div>
<div class="ssrv-0">
OR
</div>
<div class="ssrv-1 rad">
<b-form-radio v-model="isOperator" name="operatorRad" value="true">{{ userDetails.operator.description }}</b-form-radio>
</div>
</b-form-group>
I have defined isOperator in the data (am I defining data correctly? I'm trying to modify the out-of-the-box code, not sure what this means):
export default {
name: 'User-Details',
components: {...},
props: {...},
data () {
let data = {
...
isOperator: true,
...
};
and I'm trying to make this show/hide a button and input fields. I'm starting with the button as it seems simpler:
<b-button v-show="isOperator === true" #click="save" :block="true" size="lg" variant="primary" active-class="ssrv-form-button" class="ssrv-form-button">
{{$t("common.form.signUp")}}
</b-button>
My current problem, is the button isn't showing/hiding based on the two radio buttons. If I make isOperator: true in the data, the page loads with the 2nd radio button selected and the button showing. When I click the second radio button, it disappears. But then when I click the original radio button again, the button doesn't show back up. I get the same result when I try to show/hide an input field, I can get it to show initially by setting isOperator to true, but then when I select the other radio button to make it disappear I can't make it appear again. If isOperator is set to false, it just never shows.
I put a isOperator is {{ isOperator }} p element and I can see the value is change true/false as expected, but the buttons/inputs aren't showing back up.
From my very limited understanding of Vue.js, I set the v-model to a variable I want an element to modify, and the value what that variable will be set to when the radio button is selected. Then on a separate element I want to show/hide, I can use v-if/v-show with "myvalue === true/false" to show/hide. Is this an oversimplification and I'm missing steps?
That's because of a mismatch in the type of isOperator property. When you first mount the component the value of isOperator is a boolean (true), and then later on when you click on the radio buttons it becomes a string. You need to adjust the value property in your template as below:
<b-form-group label-class="ssrv-form-control">
<div class="ssrv-5">
<b-form-radio v-model="isOperator" name="operatorRad" :value="false">Consultant</b-form-radio>
</div>
<div class="ssrv-0">
OR
</div>
<div class="ssrv-1 rad">
<b-form-radio v-model="isOperator" name="operatorRad" :value="true">{{ userDetails.operator.description }}</b-form-radio>
</div>
</b-form-group>

vue computed placeholder changes multiple select-boxes

Vue version 2.6.10
I'll try writing in the code that gives the relevant information to prevent this from being huge
this is part of my component that has to do with select boxes
<div class="input-field">
<input
:id="name"
v-model="searchFilter"
type="text"
tabindex="-1"
:class="{ searchbar: true, 'validation-error': validateError }"
autocomplete="off"
spellcheck="false"
:disabled="disabled || loading"
:readonly="single"
:placeholder="placeholder"
#click="openList"
/>
<input-icon :loading="loading"></input-icon>
</div>
this is the computed part which does the placeholder part
computed: {
placeholder() {
if (this.single) {
const selected = this.singleList.filter(item => item.selected === true).shift();
return selected === undefined ? `Select ${_.startCase(this.name)}` : selected.name;
}
},
},
The issue is, that lets say I've got 3 instances of this component running?
once I choose one of them? the rest change their UI (aka placeholder value)
This is strictly a ui problem since I can tell that the value stays the same but I can't seem to find a way to access that value in order to show it.
I hope this is enough information to go on
Will provide additional code if needed.
Thanks in advance.

Saving an entire form with vuex

I am just starting with Vue and Vuex and am wondering how to go about saving an entire form to an API. I have found this and it only seems like a good solution for a single field. Does this imply I would need to do a custom computed attribute with a getter and setter for each field in the form? I understand how data binding works well for local storage (which seems to be what most examples use) but updating a backend service with every keystroke seems like overkill.
What I would like to do is perform a single commit on a form when the user performs an action (like click a save button) and I feel like making a computed property or method for every field is not the right way to go.
Template:
<div v-show="isEditing" class="edit-view">
<form>
<div class="form-group">
<label>Title</label>
<input :value="item.title" type="text" class="form-control" #input="update" />
</div>
<div class="form-group">
<label>Description</label>
<input :value="item.description" type="text" class="form-control" #input="update" />
</div>
</form>
</div>
JS:
export default {
name: 'todo',
props: ['item'],
data() {
return {
isEditing: false
}
},
methods: {
showEdit() {
this.isEditing = true;
},
update() {
// Commit a change to vuex store
}
}
Keep the form data local to your form component. So define all form properties in data(). Apply v-model to all the input elements and your corresponding data properties. When user clicks submit, make a single commit with the form data.
This way, your form component will contain the edited values, and the vuex store will contain the submitted values.

Vuex nested loop, how to handle v-model on select/option

In my application I need to use a nested v-for to display a list of elements with a select-option.. This is the scenario
<div class="stuck" v-for="box in items">
<p>Pick an option for this box:</p>
<select v-model="box">
<option v-for="package in packages"
:value="package.id">{{ package.name }} </option>
</select>
</div>
The variable items come from Vuex store. In this way, i'm getting the error:
You are binding v-model directly to a v-for iteration alias. This will
not be able to modify the v-for source array because writing to the
alias is like modifying a function local variable. Consider using an
array of objects and use v-model on an object property instead.
With this in mind, i'm going to change the code like so:
<div class="stuck" v-for="box in items">
<p>Pick an option for this box:</p>
<select v-model="box.id">
<option v-for="package in packages"
:value="package.id">{{ package.name }} </option>
</select>
</div>
I've just changed the select v-model from the alias box, to the right id: box.id
In this way, all works... or... half works. Because, if i'm going to pick an option from the select, i got another error:
[vuex] Do not mutate vuex store state outside mutation handlers.
This is correct, because the v-model is bind to box.id (that is not an alias but a real value). But, when i pick an option the v-model "try" to change box.id that come from Vuex store.
Now, in a simple scenario i will create a computed property for set/get to avoid vuex store mutation.
But... here i have a nested loop, so i cant create a computed on 'box.id'.
Do you have a solution for this ?
Thanks a lot!
you could try a different data flow pattern.
Your select listens to the store (but does not directly update it)
<div class="stuck" v-for="box in items">
<p>Pick an option for this box:</p>
<select :value="box.id" #change="updateBox">
<option v-for="package in packages" :value="package.id">
{{ package.name }}
</option>
</select>
</div>
Then you create a method that fires whenever the selected option changes
updateBox(e) {
const id = e.target.value;
this.$store.commit('changeYourBox', id);
},
This function should commit a vuex mutation that alteres the box id. So you'd need that mutation too.
Once the store value updates, your components box object updates and the select that listens to that store will update it's selected value accordingly.
That way, you can alter the store value from anywhere and the selected value will change as well.
with usage of mine library vuex-dot in this situation you can do so:
let's go with such state
{
state: {
boxes: []
},
mutations: {
editBox(state, {target, key, value}) {
Vue.set(target, key, value);
}
}
};
So let's create additional component BoxEdit:
<template>
<div class="stuck">
<p>Pick an option for this box:</p>
<select v-model="id">
<option v-for="package in packages"
:value="package.id">{{ package.name }} </option>
</select>
</div>
</template>
<script>
import { take } from 'vuex-dot'
export default {
props: ['box', 'packages'],
computed: {
...take('box')
.expose(['id'])
.commit('editBox', true)
.map()
}
}
</script>
and now you can make simply write
<box-edit v-for="box in boxes" :box="box" :packages="packages"></box-edit>
in your subject component template.
link to library site: https://github.com/yarsky-tgz/vuex-dot