I have an input field which by default set to readonly and when you click a button it should be possible to edit the item.
Without the readonly field, it seems that the field updates correctly.
Without it, I edit the item and when the focus is lost it reverts to the text that was previously.
<template>
<input ref="input"
:readonly="!edit"
type="text"
v-model="inputValue"
#blur="edit = false">
<button #click="editItem"/>
</template>
data(){
return {
edit: false,
}
}
methods: {
...mapMutations(['setKey']),
editItem() {
this.edit = true;
this.$nextTick( () => this.$refs.input.focus() );
}
}
computed: {
...mapGetters(['getKey']),
inputValue: {
get() {
return this.getKey();
}
set(value) {
this.setKey({value});
}
}
}
Note: the store is updated correctly but the getter is not triggered.
Without the readonly he is triggered.
Could it be the incorrect way to use readonly?
Also tried this instead of true/false.
:readonly="edit ? null : 'readonly'"
In my opinion, there is a better way to handle your textfield value. Use mapGetters + mapActions. Use getter value with :value=inputValue. And commit this value on blur via your action. Btw, pls dont change your data inside html template (edit = false). :readonly works fine with true/false.
Or you could use watcher. Smth like this:
v-model="inputValue",
#blur="toggleEgit"
...
data () {
return {
inputValue: this.getKey
}
},
methods: {
toggleEgit () {
this.isEdit = !this.isEdit;
}
},
watch: {
inputValue: {
handler: function (value) {
this.$commit('setKey', value) // or whatever you want to update your store
}
}
}
Related
my application has a "view" and "edit" mode, and in the "view" mode, I need to set all of the controls readonly.
I've created an InputWrapper component with a single slot, and my goal is to inject the readonly prop to the v-autocomplete component inside it. In the mounted lifecycle event of the InputWrapper I can access this.$slots.default[0].componentInstance.propsData, but when I set readonly property to "" (which is the value that appears when I set the prop of v-autocomplete directly), nothing happens. I also tried setting that in componentOptions. Is there any way to achieve this?
This is what I currently have:
<template>
<v-col :cols="cols" :class="{ 'input-wrapper': true, readonly: isReadOnly }">
<slot></slot>
</v-col>
</template>
<script>
export default {
name: 'InputWrapper',
mounted() {
if (this.isReadOnly) {
this.$set(this.$slots.default[0].componentOptions.propsData, 'readonly', '');
}
},
computed: {
isReadOnly() {
return this.readonly || this.$route.params.action === 'view';
}
},
props: {
readonly: {
type: Boolean
},
cols: {
type: Number
}
}
};
</script>
In the end, I decided to extend VAutocomplete, and override isReadonly computed property. Since I have a special case where I need for a control to be enabled in the view mode as well, I set the default value for readonly property to null.
<script>
import { VAutocomplete } from 'vuetify/lib';
export default VAutocomplete.extend({
name: 'auto-complete',
computed: {
isReadonly() {
return
this.readonly ||
(this.$route.params.mode == 'view' && this.readonly !== false);
}
},
props: {
readonly: {
type: Boolean,
default: null
}
}
});
</script>
I'm trying to get rid of these leading zeros. I have a method that gets called on input, the emits it's own input:
handleInput: function (value) {
const newValue = String(parseInt(value));
this.$emit('input', newValue);
}
When I inspect the code, I do see that the value reflects this change through the props:
But looking at the actual input itself, the leading zeros still remain. I'm starting to suspect that its' because:
00000000000000123 === 123
I only think this because the only time it actually updates the input is when I change the numbers after the zero:
And the actual input element won't update. But I quite frankly don't even know what to Google for this one. Does anyone have any ideas?
<template>
<input :value="local_value" type="text" v-on:input="handleInput" v-bind="$attrs" />
</template>
<script>
export default {
props: {
value: {
type: [String, Number],
default: "",
}
},
data: function () {
return {
local_value: 0,
}
},
methods: {
handleInput: function (value) {
const newValue = String(parseInt(value.target.value));
this.$set(this, 'local_value', newValue);
this.$emit('input', newValue);
}
}
}
</script>
If you want to still emit that as a number then you can solve that by doing the below
<input type="tel" pattern="[0-9]*">
and then in the methods
methods: {
handleInput: function (val) {
// Removed the parseInt line
this.$set(this, 'local_value', val.target.value);
this.$emit('input', val.target.value);
}
}
I'm going to build a customized virtual keyboard, so that's the first problem I've encountered.
I have an input element, whose value is changed from outside, in my case by pressing a button. The problem is that there seems to be no way to trigger the normal 'change' event.
Neither clicking outside the input, nor pressing Enter gives any result. What might be the correct way of solving this problem?
<template>
<div class="app-input">
<input #change="onChange" type="text" v-model="value" ref="input">
<button #click="onClick">A</button>
</div>
</template>
<script>
export default {
name: "AppInput",
data() {
return {
inputDiv: null,
value: ""
};
},
props: {
msg: String
},
methods: {
onClick() {
this.value = this.value + "A";
this.inputDiv.focus();
},
onChange() {
alert("changed");
}
},
mounted() {
this.$nextTick(() => {
this.inputDiv = this.$refs.input;
});
}
};
</script>
The whole pen can be found here.
v-on:change would only trigger on a direct change on the input element from a user action.
What you are looking for is a wathcer for your data property, whenever your value changes, watcher will execute your desired function or task.
watch: {
value: function() {
this.onChange();
}
}
The watch syntax is elaborated on the provided official vuejs docs link. use your data property as the key and provide a function as a value.
Check the snippet.
export default {
name: "AppInput",
data() {
return {
inputDiv: null,
value: ""
};
},
props: {
msg: String
},
methods: {
onClick() {
this.value = this.value + "A";
this.inputDiv.focus();
},
onChange() {
alert("changed");
}
},
// this one:
watch: {
value: function() {
this.onChange();
}
},
// --- rest of your code;
mounted() {
this.$nextTick(() => {
this.inputDiv = this.$refs.input;
});
}
};
When I build any new vue application, I like to use these events for a search input or for other inputs where I don't want to fire any functions on #change
<div class="class">
<input v-model="searchText" #keyup.esc="clearAll()" #keyup.enter="getData()" autofocus type="text" placeholder="Start Typing ..."/>
<button #click="getData()"><i class="fas fa-search fa-lg"></i></button>
</div>
These will provide a better user experience in my opinion.
Let's say I have the following vuex store...
state: {
someObj: {
someList: [
{ key:'a', someSubList: [] },
{ key:'b', someSubList: [] },
{ key:'c', someSubList: [] },
]
}
}
How would I bind a separate v-model to each someSubList? As an example, after I check some checkboxes, I would expect to see some Ids be populated into the someSubList like this:
someList: [
{ key:'a', someSubList: [1, 13, 17, 19] },
{ key:'b', someSubList: [1, 2, 3, 4] },
{ key:'c', someSubList: [4, 16, 20] },
]
In other words, If I check a checkbox an associated id would be added to someSubList. If I uncheck the box, the id associated with that checkbox would be removed from the someSubList. Keep in mind that each someList has a different someSubList.
I'm thinking it would be similar to below, but I'm not sure what to use for the v-model param and how to pass the index to the set method
ex.
<span v-for="(someListRow.someSubList, index2) in someList" v-bind:key="index2">
<v-checkbox v-model="myModel" />
</span>
computed: {
someList: {
get() {
return this.$store.state.someObj.someList;
},
set(value) {
this.$store.commit('someCommit', value)
}
}
}
UPDATE:
For anyone interested I got it solved using the tips provided in the posts below and ended up doing this:
<v-checkbox #change="myChangeMethod($event, myObj)" label="MyLabel"
:input-value="isMyObjSelected(myObj)" />
myChangeMethod(event, myObj) {
if (event) {
this.$store.commit('AddToMyList', {myObj});
} else {
this.$store.commit('RemoveFromMyList', {myObj});
}
}
isMyObjSelected(myObj){
this.$store.getters.isMyObjSelected(myObj});
}
I believe you want to map your inputs to some value in your store?
For this to work you cannot use v-model. Instead work with a input="updateStore($event, 'pathToStoreField')" (or #change="...") listener and a :value="..." binding. In case of a checkbox you need to use :checked="..." instead of value.
For example:
<input type="checkbox" :checked="isChecked" #input="updateField($event.target.checked, 'form.field')">
...
computed:
...
isChecked() {
return this.$store.state.form.field;
},
...
methods: {
...
updateField(value, path) {
const options = { path, value };
this.$store.commit('setFieldByPath', options);
},
...
},
Then in your store you will need a mutation setFieldByPath that resolves the string-path to a property in the state object (state.form.field) and sets this property to value.
You can also place the updateField() method as setter of a computer property.
There is library that makes this a bit more convenient: https://github.com/maoberlehner/vuex-map-fields
Just look out for checkboxes: to set them checked the checked property needs to be true not the value property.
I think, unsure but Computed values usually become like a local variable available, you need to v-model what other variable is being posted through form, so if myModel is the variable passed through then set the value of the setter once received:
<span v-for="(myList, index2) in someList" v-bind:key="index2">
<v-checkbox v-model="myModel" />
</span>
Then in script
computed: {
myList() {
return this.$store.state.someObj.someList;
}
}
I guess u ask for this:
<span v-for="(someSubList, index2) in someList" v-bind:key="index2">
<v-checkbox v-model="myModel" />
</span>
data() {
return {
myModel: ''
}
},
computed: {
someList: {
get() {
return this.$store.state.someObj.someList;
},
set(value) {
this.$store.commit('someCommit', value)
}
}
}
properties inside data() can be used to bind your v-model's on your template.
For anyone interested I got it solved using the tips provided in the posts below and ended up doing this:
<v-checkbox #change="myChangeMethod($event, myObj)" label="MyLabel"
:input-value="isMyObjSelected(myObj)" />
myChangeMethod(event, myObj) {
if (event) {
this.$store.commit('AddToMyList', {myObj});
} else {
this.$store.commit('RemoveFromMyList', {myObj});
}
}
isMyObjSelected(myObj){
this.$store.getters.isMyObjSelected(myObj});
}
I'm trying to send form to certain action, based on select value.
I have such template:
<template>
<form method="post" :action="myRoute" ref="myForm">
<select #change="entitySelected" v-model="selected">
<!-- -->
</select>
</form>
</template>
I'm trying to set up form action dynamically when new select value is appeared:
<script>
export default {
data() {
return {
selected: '',
}
},
computed: {
myRoute: function () {
return 'example.com/'+this.selected
}
},
methods: {
entitySelected(event) {
console.log(this.$refs.myForm.action) //<- action is 'example.com' without selected value
console.log(this.selected) //<- this value is as expected
this.$refs.myForm.submit()
}
}
}
</script>
What's wrong?
P. S. Browser - Firefox
Probably not the best way, but it works:
userSelected(event) {
this.$nextTick(function () {
this.$refs.backdoor.submit()
})
}
You can use setAttribute() when updating the selected value :
this.$refs.myForm.setAttribute('action', this.myRoute);