Nuxtjs : How to disappear the tooltip only when form input is valid - javascript

In the template below, the input disappears when fullName is true, but I wanna remove the tooltip only. Where should I fix it?
<a-tooltip placement="topRight" trigger="focus" :style="
fullName === true
? { display: 'none' }
: ''
">
<template slot="title">
<span>Please fill in your Fullname</span>
</template>
<a-input
#input="checkFullname"
:placeholder="$t('contact.placeholder.fullName')"
v-model="dropMessage.full_name"
:style="
fullName === false
? { backgroundColor: 'rgba(211,47,47,.025)' }
: ''
"
/>
</a-tooltip>

Using display: none to style your tooltip would cause the tooltip's default slot (the input) to also be hidden, but you don't have to resort to style bindings here.
<a-tooltip> has a v-model that can be set to false to hide the tooltip. For example, you could use a data prop (e.g., named tooltipVisible) as the v-model binding, and set it based on the user input in checkFullname():
<template>
<a-tooltip title="Enter John Doe" v-model="tooltipVisible">
<a-input #input="checkFullname" v-model="dropMessage.full_name" />
</a-tooltip>
</template>
<script>
export default {
data() {
return {
tooltipVisible: false,
}
},
methods: {
checkFullname() {
this.tooltipVisible = this.dropMessage.full_name !== 'John Doe'
},
},
}
</script>
demo

Related

Vue: Input Manual Autocomplete component

I have a vue-cli project, that has a component named 'AutoCompleteList.vue' that manually handled for searching experience and this component has some buttons that will be fill out the input.
It listens an array as its item list. so when this array has some items, it will be automatically shown; and when I empty this array, it will be automatically hidden.
I defined an oninput event method for my input, that fetches data from server, and fill the array. so the autocomplete list, will not be shown while the user doesn't try to enter something into the input.
I also like to hide the autocomplete list when the user blurs the input (onblur).
but there is a really big problem! when the user chooses one of items (buttons) on the autocomplete list, JS-engine first blurs the input (onblur runs) and then, tries to run onclick method in autocomplete list. but its too late, because the autocomplete list has hidden and there is nothing to do. so the input will not fill out...
here is my code:
src/views/LoginView.vue:
<template>
<InputGroup
label="Your School Name"
inputId="schoolName"
:onInput="schoolNameOnInput"
autoComplete="off"
:onFocus="onFocus"
:onBlur="onBlur"
:vModel="schoolName"
#update:vModel="newValue => schoolName = newValue"
/>
<AutoCompleteList
:items="autoCompleteItems"
:choose="autoCompleteOnChoose"
v-show="autoCompleteItems.length > 0"
:positionY="autoCompletePositionY"
:positionX="autoCompletePositionX"
/>
</template>
<script>
import InputGroup from '../components/InputGroup'
import AutoCompleteList from '../components/AutoCompleteList'
export default {
name: 'LoginView',
components: {
InputGroup,
AutoCompleteList
},
props: [],
data: () => ({
autoCompleteItems: [],
autoCompletePositionY: 0,
autoCompletePositionX: 0,
schoolName: ""
}),
methods: {
async schoolNameOnInput(e) {
const data = await (await fetch(`http://[::1]:8888/schools/${e.target.value}`)).json();
this.autoCompleteItems = data;
},
autoCompleteOnChoose(value, name) {
OO("#schoolName").val(name);
this.schoolName = name;
},
onFocus(e) {
const position = e.target.getBoundingClientRect();
this.autoCompletePositionX = innerWidth - position.right;
this.autoCompletePositionY = position.top + e.target.offsetHeight + 20;
},
onBlur(e) {
// this.autoCompleteItems = [];
// PROBLEM! =================================================================
}
}
}
</script>
src/components/AutoCompleteList.vue:
<template>
<div class="autocomplete-list" :style="'top: ' + this.positionY + 'px; right: ' + this.positionX + 'px;'">
<ul>
<li v-for="(item, index) in items" :key="index">
<button #click="choose(item.value, item.name)" type="button">{{ item.name }}</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'AutoCompleteList',
props: {
items: Array,
positionX: Number,
positionY: Number,
choose: Function
},
data: () => ({
})
}
</script>
src/components/InputGroup.vue:
<template>
<div class="input-group mb-3">
<label class="input-group-text" :for="inputId ?? ''">{{ label }}</label>
<input
:type="type ?? 'text'"
:class="['form-control', ltr && 'ltr']"
:id="inputId ?? ''"
#input="$event => { $emit('update:vModel', $event.target.value); onInput($event); }"
:autocomplete="autoComplete ?? 'off'"
#focus="onFocus"
#blur="onBlur"
:value="vModel"
/>
</div>
</template>
<script>
export default {
name: 'input-group',
props: {
label: String,
ltr: Boolean,
type: String,
inputId: String,
groupingId: String,
onInput: Function,
autoComplete: String,
onFocus: Function,
onBlur: Function,
vModel: String
},
emits: [
'update:vModel'
],
data: () => ({
}),
methods: {
}
}
</script>
Notes on LoginView.vue:
autoCompletePositionX and autoCompletePositionY are used to find the best position to show the autocomplete list; will be changed in onFocus method of the input (inputGroup)
OO("#schoolName").val(name) is used to change the value of the input, works like jQuery (but not exactly)
the [::1]:8888 is my server that used to fetch the search results
If there was any unclear code, ask me in the comment
I need to fix this. any idea?
Thank you, #yoduh
I got the answer.
I knew there should be some differences between when the user focus out the input normally, and when he tries to click on buttons.
the key, was the FocusEvent.relatedTarget property. It should be defined in onblur method. here is its full tutorial.
I defined a property named isFocus and I change it in onBlur method, only when I sure that the focus is not on the dropdown menu, by checking the relatedTarget

Validate input element text when using unit test in VueJs

Problem
I was trying to solve a VueJs challenge. This challenge had a set of unit tests to validate the correctness of my code. Some of these tests were around an input text field. I was following the usual way to create a custom input component using v-model but the tests that check the input validation logic failed.
When the component is mounted the v-model has an initial value of "" (empty string). The test then sets a value using setValue and next checks if the input has the correct value using customInput.element.value
const app = mount(App);
const customInput = app.find(".custom-input-field-a");
customInput.setValue("invalid input");
expect(customInput.element.value).toEqual("");
After the setValue statement, the input element triggers an input event, handled by the custom component. This handler validates the input text and then emits an input event to update the v-model.
When this happens nothing gets updated because the previous and the next value are equal to "" (no "reactivity" here), thus the statement customInput.element.value returns "invalid input"
Code
This is the custom component:
<template>
<div class="custom-input">
<label :class="labelClasses" :for="name">{{ label }}</label>
<input
:class="`custom-input-${name}`"
:name="name"
:maxlength="3"
type="text"
:value="value"
#input="onInput"
#keypress="onlyLettersFilter"
/>
</div>
</template>
<script>
export default {
props: {
name: { type: String, required: true },
placeholder: { type: String, default: "some placeholder" },
label: String,
value: String,
},
computed: {
hasValue() {
return this.value?.length > 0;
},
labelClasses() {
return "custom-input--label " + (this.hasValue ? "active" : "");
},
},
methods: {
filterValue(input) {
let value = input.slice(0, 3);
value = value.toUpperCase().replace(/([^A-Z])+/g, "");
return value;
},
onInput(event) {
this.$emit("input", this.filterValue(event.target.value));
},
onlyLettersFilter(event) {
if (event.keyCode === 13) return true;
var key = String.fromCharCode(
!event.charCode ? event.which : event.charCode
);
// Accept only letters
var regex = new RegExp(/[a-zA-Z]+/);
if (regex.test(key)) {
return true;
}
event.preventDefault();
return false;
},
},
};
</script>
<style>
</style>
Here you can see that in any input change, the text is filtered, removing unwanted characters. If all characters are invalid, the result is an empty string.
Workaround
As a workaround, I set a reference to the input element and updated the content directly without using the VueJs capabilities. All tests passed.
<template>
...
<input
...
ref="inputRef"
...
/>
...
</template>
<script>
export default {
...
methods: {
...
onInput(event) {
const value = this.filterValue(event.target.value);
// this prevents any invalid character in the input element when the model value doesn't change, when seting the value in unit tests
this.$refs.inputRef.value = value;
if (value === this.value) event.preventDefault();
else {
this.$emit("input", value);
}
},
}
...
}
</script>
I know this is an awful solution, but I don't know any better.
Is there a better and cleaner way to do this without using ref and modifying the HTML component directly?

:disabled Form button not working in Vue 3

I am trying to use the state to make the form button toggle disabled and enabled.
If the I tick the check box, the :disable will becomes true and the button can be use. But it seem like in Vue 3 is not working?
Here's the code:
<template lang="pug">
.container
.row
.col-lg-12.desktop-sign-up
.row.no-gutters
.col-lg-6
.image-container
img.logo-container(src=".././assets/images/logo.png")
.col-lg-6
form.sign-in-form
img.sign-in-image(src=".././assets/images/logo.png")
.sign-in-header
h1 Hello
.sign-in-content
h4 Please create <br/>an account to continue.
.form-group
input.form-control(placeholder="Username" type="text" v-model="user.username")
.form-group
input.form-control(placeholder="Email Address" type="email" v-model.lazy="user.email")
.form-group(style="margin-bottom: 0;")
input.form-control(placeholder="Password" type="password" v-model="user.password")
.form-check
input.form-check-input(id="customeCheck1" type="checkbox" v-model="user.isTncChecked")
label.form-check-label(for="customCheck1") I agree with our
router-link(:to="'/'" target="_blank") Terms
| and
router-link(:to="'/'" target="_blank") Conditions
| .
.error-msg
p(v-for="item in errorMsg" :class="{ success : isSuccess }" ) {{ item.msg }}
.cta-conainer
a.cta.cta-submit(href="" #click.prevent="handleSubmit" :disabled="isDisabled") Sign up
.sign-in-msg
p Already have an account.
a(href="#") Sign in here
</template>
<script>
import axios from 'axios'
export default {
name: 'Register',
components: {
},
directives: {
},
data() {
return {
user: {
username: '',
email: '',
first_name:'',
last_name:'',
password: '',
tracks: [],
dateCreated: '',
profile_pic: '',
isTncChecked: false
}
}
},
beforeCreate() {
},
mounted () {
},
computed: {
isDisabled () {
return !this.user.isTncChecked
}
},
methods: {}
}
</script>
It works on Vue 2.
Couldn't find any info about how the vue 3 breaking change affect this. Hope anyone can help me understand this.
Thank you
There is no disabled attribute for <a> tags, use a <button> instead.
button.cta.cta-submit(#click.prevent="handleSubmit" :disabled="isDisabled") Sign up
Or use CSS to change the <a> style:
a.disabled {
pointer-events: none;
cursor: default;
}
Or you could check isDisabled inside handleSubmit:
handleSubmit() {
if (!this.isDisabled) {
}
}

How to modify my custom input component to work with vee-validate?

I have created an input component in my Vue project and I'm using this component inside my forms. I wanna use vee-validate to validate my inputs.
First, I tried to validate my component just like a normal input, but after failing at displaying error messages and checking Vue Devtools, I figured out that errors are scoped to the input component. (although there are two computed properties named "errors" and "fields" that all my components have access to, from root to input component itself.)
Then I checked vee-validate docs and tried to use Validation Provider and Validation Observer. These two components are too confusing to me and I couldn't figure out how to use them even after checking this medium article. I don't know how these components are making use of scoped slots.
I wanna be able to validate input components in the parent and also display their error messages in the parent above the form. (with or without Validation Observer and/or Validation Provider). Any approach is appreciated.
This is my input component:
<template>
<div class="info-input-control">
<div class="info-input-icon">
<slot name="icon"><span uk-icon="icon: pencil; ratio: 1.4"></span></slot>
</div>
<input :id="id" :type="type" :value="value" :name="name"
v-validate="validations || ''"
#keyup.enter="$emit('enter')"
#focus="isActive = true"
#blur="isActive = value.length > 0"
#input="$emit('input', $event.target.value)" :key="name">
<label :for="id" :class="{'info-input-label': true, 'is-active': isActive}">{{label}}</label>
</div>
</template>
<script>
export default {
name: 'InfoTextInput',
props: {
id: String,
label: String,
ltr: Boolean,
name: {
type: String,
required: true
},
type: {
type: String,
required: true,
validator: function (value) {
return ['text', 'password', 'email'].indexOf(value) !== -1
}
},
validations: Object,
value: String
},
data() {
return {
isActive: false
}
},
mounted() {
this.isActive = this.value.length > 0
}
}
</script>
and this is minimal version of my form with just one input component:
<form action="#" #submit.prevent="userLogin">
<div class="uk-text-danger">
<span>Errors: </span>
<span>{{errors.first('mobile')}}</span>
</div>
<div class="uk-flex uk-flex-center">
<div class="uk-width-medium">
<info-text-input type="text" id="user-login-phone" label="Mobile" name="mobile" ltr v-model="login.mobile" :validations="mobileValidations" key="login-mobile">
<template v-slot:icon>
<span uk-icon="icon: phone; ratio: 1.4"></span>
</template>
</info-text-input>
</div>
</div>
</form>
P.S: I have registered Validation Observer and Validation Provider globally.
Try following this example:
<template>
<div class="info-input-control">
<div class="info-input-icon">
<slot name="icon"><span uk-icon="icon: pencil; ratio: 1.4"></span></slot>
</div>
<input :id="id" :type="type" :name="name"
v-model="localValue"
v-validate="validations || ''"
#keyup.enter="$emit('enter')"
#focus="isActive = true"
#blur="isActive = value.length > 0"
#input="$emit('input', localValue)" :key="name">
<label :for="id" :class="{'info-input-label': true, 'is-active': isActive}">{{label}}</label>
</div>
</template>
<script>
export default {
$_veeValidate: {
value() {
return this.localValue;
},
name() {
return this.name;
}
},
name: 'InfoTextInput',
props: {
id: String,
label: String,
ltr: Boolean,
name: {
type: String,
required: true
},
type: {
type: String,
required: true,
validator: function (value) {
return ['text', 'password', 'email'].indexOf(value) !== -1
}
},
validations: Object,
value: String
},
data() {
return {
localValue: this.value,
isActive: false
}
},
mounted() {
this.isActive = this.localValue.length > 0
}
}
</script>
I added only a couple things - the $_veeValidate section in the script. I also changed your component to use the value prop but store the changes in a local reactive item called localValue. Generally you won't want to change the value of a prop, although it might work in this simple of a case. Re-read One-Way Data Flow for details.

How to add multiple attribute - vue.js

This works:
<input v-model="project.name" :readonly="isReadOnly" :disabled="isReadOnly">
Is there a way to make below code work?
<input v-model="project.name" {{ readOnlyAttr }} >
<input v-model="project.name" {{ isReadOnly : 'readonly disabled' ? ''}}>
Script as follows:
<script>
export default {
props: {
isReadOnly: {
default: ture,
type: Boolean
}
},
data () {
return {
readOnlyAttr: 'readonly disabled'
}
}
}
</script>
v-bind is your friend here.
Because you want the attributes to be computed, I created a computed method to build the object everytime there is a change to isReadOnly value.
When you want to bind statically group of attributes, you can use the data method.
<template>
<div>
<input v-model="project.name" v-bind="readOnlyAttributes">
</div>
</template>
<script>
export default {
name: 'example',
computed: {
readOnlyAttributes() {
return {
readonly: this.isReadOnly,
disabled: this.isReadOnly ? 'readonly' : ''
}
},
isReadOnly() {
// returning always true for the example
return true;
}
}
}

Categories

Resources