Inject property into Vuetify component inside a slot - vue.js

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>

Related

Can't pass value prop to custom input component

I have a custom input component that generally works well. But since yesterday I want to pass to it a value from the parent component in certain cases (namely if a cookie is found for that field, pre-fill the field with the cookie value).
Parent component (simplified):
<custom-input
v-model="userEmail"
value="John Doe"
/>
But for a reason I cannot comprehend, the value prop doesn't work. Why not?
My custom input component (simplified):
<template>
<input
v-bind="$attrs"
:value="value"
#blur="handleBlur"
>
</template>
<script>
export default {
inheritAttrs: false,
props: {
value: {
type: String,
default: ''
}
},
mounted () {
console.log(this.value) // displays nothing, whereas it should display "John Doe"
},
methods: {
handleBlur (e) {
this.$emit('input', e.target.value)
}
}
}
</script>
value prop is used with the emitted event input to do the v-model job, so you should give your prop another name like defaultValue to avid this conflict:
<custom-input
v-model="userEmail"
defaultValue="John Doe"
/>
and
<template>
<input
v-bind="$attrs"
:value="value"
#blur="emitValue($event.target.vaklue)"
>
</template>
<script>
export default {
inheritAttrs: false,
props: {
value: {
type: String,
default: ''
},
defaultvalue: {
type: String,
default: ''
},
},
mounted () {
this.emitValue(this.defaultValue)
},
methods: {
emitValue(val) {
this.$emit('input', val)
}
}
}
</script>

(vuetify in nuxt js) autocomplete isnt update relative to items prop

Every input in search i update the items prop but the v-autocomplete become empty
although the data in my component changed
i tried to add the no-filter prop it didnt help i guess something with the reactivity destroyed
i allso tried with computed property as an items but still same result
Every input in search i update the items prop but the v-autocomplete become empty
although the data in my component changed
i tried to add the no-filter prop it didnt help i guess something with the reactivity destroyed
i allso tried with computed property as an items but still same result
<script>
import ProductCartCard from "~/components/cart/ProductCartCard";
export default {
name: "search-app",
components: {
ProductCartCard
},
props: {
items: {
type: Array,
default: () => []
}
},
data() {
return {
loading: false,
filteredItems: [],
search: null,
select: null
};
},
watch: {
search(val) {
if (!val || val.length == 0) {
this.filteredItems.splice(0, this.filteredItems.length);
return;
} else {
val !== this.select && this.querySelections(val);
}
}
},
methods: {
querySelections(v) {
this.loading = true;
// Simulated ajax query
setTimeout(() => {
this.filteredItems.splice(
0,
this.filteredItems.length,
...this.items.filter(i => {
return (i.externalName || "").toLowerCase().includes((v || "").toLowerCase());
})
);
this.loading = false;
}, 500);
}
}
};
</script>
<template>
<div class="search-app-container">
<v-autocomplete
v-model="select"
:loading="loading"
:items="filteredItems"
:search-input.sync="search"
cache-items
flat
hide-no-data
hide-details
label="searchProduct"
prepend-icon="mdi-database-search"
solo-inverted
>
<template v-slot:item="data">
<ProductCartCard :regularProduct="data" />
</template>
</v-autocomplete>
</div>
</template>
One of the caveat of the v-autocomplete as described in the documentation:
When using objects for the items prop, you must associate item-text and item-value with existing properties on your objects. These values are defaulted to text and value and can be changed.
That may fix your issue

VueJS: how to trigger 'change' on <input> changed programmatically

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.

How to fix Warning: `getFieldDecorator` will override `value`,so please don't set `value and v-model` directly and use `setFieldsValue` to set it.?

I'm coding a custom validation form component using ant-design-vue
I have changed my code nearly same as the example showed on the official website, but still got warning, the only difference is the example use template to define child component, but I use single vue file
//parent component
...some other code
<a-form-item
label="account"
>
<ReceiverAccount
v-decorator="[
'receiverAccount',
{
initialValue: step.receiverAccount,
rules: [
{
required: true,
message: 'need account',
}
]
}
]"
/>
</a-form-item>
...some other code
//child component
<template>
<a-input-group compact>
<a-select
:value="type"
#change="handleTypeChange"
>
<a-select-option value="alipay">alipay</a-select-option>
<a-select-option value="bank">bank</a-select-option>
</a-select>
<a-input
:value="number"
#change="handleNumberChange"
/>
</a-input-group>
</template>
<script>
export default {
props: {
value: {
type: Object,
default: () => {}
}
},
data() {
const { type, number } = this.value
return {
type: type || 'alipay',
number: number || ''
}
},
watch: {
value(val = {}) {
this.type = val.type || 'alipay'
this.number = val.number || ''
}
},
methods: {
handleTypeChange(val) {
this.triggerChange({ val })
},
handleNumberChange(e) {
const number = parseInt(e.target.value || 0, 10)
if (isNaN(number)) {
return
}
this.triggerChange({ number })
},
triggerChange(changedValue) {
this.$emit('change', Object.assign({}, this.$data, changedValue))
}
}
}
</script>
I expect everything is fine, but the actual is I got 'Warning: getFieldDecorator will override value, so please don't set value and v-model directly and use setFieldsValue to set it.'
How can I fix it? Thanks in advance
because I am new of ant-design-vue, after one day research, solution is change :value to v-model and remove value props in the child component
<template>
<a-input-group compact>
<a-select
v-model="type"
#change="handleTypeChange"
>
<a-select-option value="alipay">alipay</a-select-option>
<a-select-option value="bank">bank</a-select-option>
</a-select>
<a-input
v-model="number"
#change="handleNumberChange"
/>
</a-input-group>
</template>

VueJs: Textarea input binding

I'm trying to figure out how to detect change of the value on the textarea from within the component.
For input we can simply use
<input
:value="value"
#input="update($event.target.value)"
>
However on textarea this won't work.
What I'm working with is the CKEditor component, which should update wysiwyg's content when model value of the parent component (attached to this child component) is updated.
My Editor component currently looks like this:
<template>
<div class="editor" :class="groupCss">
<textarea :id="id" v-model="input"></textarea>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: ''
},
id: {
type: String,
required: false,
default: 'editor'
}
},
data() {
return {
input: this.$slots.default ? this.$slots.default[0].text : '',
config: {
...
}
}
},
watch: {
input(value) {
this.update(value);
}
},
methods: {
update(value) {
CKEDITOR.instances[this.id].setData(value);
},
fire(value) {
this.$emit('input', value);
}
},
mounted () {
CKEDITOR.replace(this.id, this.config);
CKEDITOR.instances[this.id].setData(this.input);
this.fire(this.input);
CKEDITOR.instances[this.id].on('change', () => {
this.fire(CKEDITOR.instances[this.id].getData());
});
},
destroyed () {
if (CKEDITOR.instances[this.id]) {
CKEDITOR.instances[this.id].destroy()
}
}
}
</script>
and I include it within the parent component
<html-editor
v-model="fields.body"
id="body"
></html-editor>
however, whenever parent component's model value changes - it does not trigger the watcher - effectively not updating the editor's window.
I only need update() method to be called when parent component's model fields.body is updated.
Any pointer as to how could I approach it?
That's a fair bit of code to decipher, but what I would do is break down the text area and the WYSIWYG HTML window into two distinct components and then have the parent sync the values, so:
TextArea Component:
<template id="editor">
<textarea :value="value" #input="$emit('input', $event.target.value)" rows="10" cols="50"></textarea>
</template>
/**
* Editor TextArea
*/
Vue.component('editor', {
template: '#editor',
props: {
value: {
default: '',
type: String
}
}
});
All I'm doing here is emitting the input back to the parent when it changes, I'm using input as the event name and value as the prop so I can use v-model on the editor. Now I just need a wysiwyg window to show the code:
WYSIWYG Window:
/**
* WYSIWYG window
*/
Vue.component('wysiwyg', {
template: `<div v-html="html"></div>`,
props: {
html: {
default: '',
type: String
}
}
});
Nothing much going on there, it simply renders the HTML that is passed as a prop.
Finally I just need to sync the values between the components:
<div id="app">
<wysiwyg :html="value"></wysiwyg>
<editor v-model="value"></editor>
</div>
new Vue({
el: '#app',
data: {
value: '<b>Hello World</b>'
}
})
Now, when the editor changes it emits the event back to the parent, which updates value and in turn fires that change in the wysiwyg window. Here's the entire thing in action: https://jsfiddle.net/Lnpmbpcr/