I am trying to create Quasar custom select component with autocomplete. Everything works fine except the validation error, the validation error is showing only when I click the input box and leave without adding any value. But, the form is submitting even there are any errors.
Component code
<q-select
ref="members"
v-model="sModel"
use-input
:options="filteredOptions"
:multiple="multiple"
:use-chips="useChips"
:label="label"
:option-label="optionLabel"
:option-value="optionValue"
#filter="filterFn"
#input="handleInput"
emit-value
map-options
hint
dense
outlined
lazy-rules
:rules="rules"
>
<template v-slot:prepend>
<q-icon :name="icon" />
</template>
</q-select>
</template>
<script>
export default {
props: {
value: Array,
rules: Array,
icon: String,
label: String,
optionValue: String,
optionLabel: String,
options: Array,
multiple: Boolean,
useChips: Boolean
},
data () {
return {
filteredOptions: this.options,
sModel: this.value,
validationErrors:{
}
}
},
methods: {
filterFn (val, update) {
if (val === '') {
update(() => {
this.filteredOptions = this.options
// with Quasar v1.7.4+
// here you have access to "ref" which
// is the Vue reference of the QSelect
})
return
}
update(() => {
const needle = val.toLowerCase()
const optionLabel = this.optionLabel
this.filteredOptions = this.options.filter(function(v){
// optionLabel
return v[optionLabel].toLowerCase().indexOf(needle) > -1
})
})
},
handleInput (e) {
this.$emit('input', this.sModel)
}
},
}
</script>
In the parent component, this is how I am implementing it,
<AdvancedSelect
ref="members"
v-model="members"
:options="extAuditEmployees"
icon="people_outline"
multiple
use-chips
label="Team Members *"
option-label="formatted_name"
option-value="id"
:rules="[ val => val && val.length && !validationErrors.members > 0 || validationErrors.members ? validationErrors.members : 'Please enter Team members' ]">
</AdvancedSelect>
Try adding this method on select component methods:
validate(...args) {
return this.$refs.members.validate(...args);
}
It worked for me, apparently it sends the validation of the input to the parent
Source consulted: https://github.com/quasarframework/quasar/issues/7305
add ref to the form and try to validate the form.
you can give give props "greedy" to the form.
Related
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
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.
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>
I use v-autocomplete component and updating items list after http request. But browser was freeze when i set items. What's wrong?
{
mixins: [search_field_block],
data: function () {
return {
item_component: {
props: ['item'],
template: '<div v-html="item.full_name"></div>'
}
};
},
methods: {
search: function (text) {
this.search_text = text.trim();
if (this.search_text) {
this.doGET('/api-method/search_place/', {'query': this.search_text}, this.update_items);
}
},
update_items: function (data) {
this.items = data;
}
}
}
I use a mixin for other components. It contained universal template with v-autocomplete:
<field-block :label="label" :error="field_error" :description="item_description">
<v-autocomplete slot="input"
class="page-form__field required"
:class="{ focused: focused, 'not-empty': not_empty, error: field_error != null, 'list-open': is_list_open }"
v-model="value"
ref="autocomplete"
:required="required"
:inputAttrs="{ref: 'input', autocomplete: 'off'}"
:items="items"
:component-item="item_component"
:get-label="getLabel"
:min-len="2"
#update-items="search"
#item-selected="onSelect"
#input="onInput"
#blur="onBlur"
#focus="onFocus">
</v-autocomplete>
</field-block>
I was find v-autocomplete on the github. It contain a v-for block for rendering search results
I found the problem. I have "computed" property and set value to parent app:
computed: {
is_list_open: function () {
this.$parent.list_opened = this.focused && this.items.length > 0 || (this.$refs.autocomplete ? this.$refs.autocomplete.show : false);
return this.$parent.list_opened;
}
},
This is incorrect behavior.
I have the following component:
Vue.component('email-input', {
template: '#tmpl-email-input',
name: 'email-input',
delimiters: ['((', '))'],
props: ['name', 'required', 'value'],
data: () => ({
suggestedEmail: '',
email: '',
}),
methods: {
onInput() {
this.checkEmail();
this.$emit('input', this.email);
},
checkEmail() {
Mailcheck.run({
email: this.email,
suggested: suggestion => {
this.suggestedEmail = suggestion.full;
},
empty: () => {
this.suggestedEmail = '';
},
});
},
confirmSuggestion(confirm) {
if (confirm) this.email = this.suggestedEmail;
this.suggestedEmail = '';
},
},
mounted() {
this.checkEmail = _.debounce(this.checkEmail.bind(this), 1000);
},
});
using this template
<template id="tmpl-email-input">
<div>
<input
type="email"
class="form-control"
:name="name || 'email'"
:required="required"
v-on:input="onInput"
v-model="email"
/>
<small class="email-correction-suggestion" v-if="suggestedEmail">
Did you mean ((suggestedEmail))?
Yes
No
</small>
</div>
</template>
<!-- Lodash from GitHub, using rawgit.com -->
<script src="https://cdn.rawgit.com/lodash/lodash/4.17.4/dist/lodash.min.js"></script>
<!-- Mailcheck: https://github.com/mailcheck/mailcheck -->
<script src="/js/lib/mailcheck.js"></script>
<script src="/js/views/partials/email_input.js"></script>
And I'm calling it using
<email-input name="email" required></email-input>
I'd like to set an initial value for this email input, something like
<email-input name="email" required value="test#test.com"></email-input>
and have that show in the input.
I assumed I could do this by simply setting email to this.value in data but that doesn't help. How can I do this?
There's a value prop but you are not using it at all! So it doesn't really matter which value you pass down as value prop: it won't be used.
I think what you are trying to achieve is expose an API similar to the one exposed by input component. That can be done and it's detailed in the docs.
What Vue does to handle the v-model bindings is assuming the component will emit an input event passing the new value as $event. It will also pass down to the component a value to the value prop. So this 2-way binding is automatically handled by Vue as long as you define a value prop and emit an input event.
The problem is that your component acts as a middleware for the underlying input component but it is passing down a different binding instead of forwarding it.
Translating this into your component, you should not use v-model to pass down email to the input component but a combination of :value and #input bindings: you pass down the value prop of email-input component to the value prop of the input component and as handler of input event of the input component you should just emit another input event with the same $event payload.
Template:
<template id="tmpl-email-input">
<div>
<input
type="email"
class="form-control"
:name="name || 'email'"
:required="required"
:value="value"
#input="onInput($event.target.value)"
/>
<small class="email-correction-suggestion" v-if="suggestedEmail">
Did you mean ((suggestedEmail))?
Yes
No
</small>
</div>
</template>
<!-- Lodash from GitHub, using rawgit.com -->
<script src="https://cdn.rawgit.com/lodash/lodash/4.17.4/dist/lodash.min.js"></script>
<!-- Mailcheck: https://github.com/mailcheck/mailcheck -->
<script src="/js/lib/mailcheck.js"></script>
<script src="/js/views/partials/email_input.js"></script>
Note the change from #input="onInput" to #input="onInput($event.target.value)" so we have access to the new value in onInput method.
Component:
Vue.component('email-input', {
template: '#tmpl-email-input',
name: 'email-input',
delimiters: ['((', '))'],
props: ['name', 'required', 'value'],
data: () => ({
suggestedEmail: ''
}),
methods: {
onInput(newValue) {
this.$emit('input', newValue);
this.checkEmail();
},
checkEmail() {
Mailcheck.run({
email: this.value,
suggested: suggestion => {
this.suggestedEmail = suggestion.full;
},
empty: () => {
this.suggestedEmail = '';
},
});
},
confirmSuggestion(confirm) {
if (confirm) this.$emit('input', this.suggestedEmail);
this.suggestedEmail = '';
},
},
mounted() {
this.checkEmail = _.debounce(this.checkEmail.bind(this), 1000);
},
});
Note the change in onInput method: now it takes a parameter with the new value and emits an input event with that value before checking the email address. It's emitted in that order to ensure we have synced the value of the value binding before checking the address.
Also note the change in confirmSuggestion method: instead of updating email data attribute it just emits an input event.
That's the key to solve this issue: the old implementation forced us to have 2 different variables: one where parent component could pass down a value and another one email-input could modify to store the chosen suggestion.
If we just emit the chosen suggestion as a regular change then we can get rid of the email variable and work with just one binding.
Suggestion totally not related with the issue: you can use debounce directly in methods instead of replacing the method on mounted hook:
Vue.component('email-input', {
template: '#tmpl-email-input',
name: 'email-input',
delimiters: ['((', '))'],
props: ['name', 'required', 'value'],
data: () => ({
suggestedEmail: ''
}),
methods: {
onInput(newValue) {
this.$emit('input', newValue);
this.checkEmail();
},
checkEmail: _.debounce(function () {
Mailcheck.run({
email: this.value,
suggested: suggestion => {
this.suggestedEmail = suggestion.full;
},
empty: () => {
this.suggestedEmail = '';
},
});
}, 1000),
confirmSuggestion(confirm) {
if (confirm) this.$emit('input', this.suggestedEmail);
this.suggestedEmail = '';
},
}
});
Lodash will take care of binding this of the underlying function to the same this that called the debounced function.