How fix issue with chrome auto complete overlap with labels in vuetify? - javascript

When trying to create a login form with outlined text fields in Vutify, the chrome autocomplete overlap with labels,
<v-text-field
v-model="email"
label="e-mail"
name="email"
outlined
prepend-icon="mdi-account"
type="text"
required
>
</v-text-field>
you can regeneare here please fill and submit, then go back.

This is how I have fixed this issue.
It seems our main problems are the following:
The autofill from Chrome, at loading page, not made interface react, letting the design like in your image.
So at the time of injection, we should do a fix ourself but no event, from Chrome, can inform us when login/password are auto-filled.
It's interesting to see any click from the browser window FROM USER automatically inform reactivity and all work fine again but it's not work FROM trigger/dispatch internal way.
So first, we need to find a way to react after login/password autofill.
And second, we need to fix ourself the design because only a FROM USER action made the design work fine again.
1. React after autofilling at loading page
Like Harkness mention it, we can try to check :-webkit-autofill at regular interval while X second after code was mounted to see if an autofilling was injected (work fine for Chrome/Firefox/Edge from my test)
Another solution is to use the animationstart event (see here: https://github.com/material-components/material-components-web/issues/4447#issuecomment-580401216)
I use the first solution:
export default {
//...
data() {
return {
//...
autofillFix: false,
}
},
//...
mounted() {
this.autoLoginCheckingInterface()
},
//...
autoLoginCheckingInterface() {
// each 100ms we check if the issue was produced
let intervalDetectAutofill = setInterval(() => {
if (
// we target at least one of the stuff that will be affected by autofill
// to do our checking
document.querySelectorAll('input[type="password"]:-webkit-autofill')
.length > 0
) {
// and we inform the system about the issue if it is produced
this.autofillFix = true
// we stop to check if issue was produced
clearInterval(intervalDetectAutofill)
}
}, 100)
// if after 3s nothing appear, means no autofill was made
setTimeout(() => {
if (intervalDetectAutofill) {
clearInterval(intervalDetectAutofill)
intervalDetectAutofill = null
}
}, 3000)
},
//...
}
<!--
we will inject `.autofill-fix` class to be able fix design ourself at time of this bug occur
-->
<v-text-field
...
:class="{ 'autofill-fix': autofillFix }"
...
label="Email address or username"
...
dense
outlined
#focus="autofillFix = false"
/>
<!--
we use #focus to let the normal behavior take again the lead
because we know this USER ACTION will made Chrome work well again
-->
<v-text-field
...
:class="{ 'autofill-fix': autofillFix }"
...
label="Password"
type="password"
...
dense
outlined
#focus="autofillFix = false"
/>
2. Fix ourself the design
We can see what are the change when v-text-field is filled. Without content, we can see this:
And after autofilling, we can see this:
So from the red part, we can see the following code need to be injected at time
of .autofill-fix presence to fix the design in the proper way
.autofill-fix.v-text-field--outlined.v-input--dense .v-label {
left: -28px!important;
transform: translateY(-16px) scale(.75);
}
Note: You need change the CSS selector if you not use outlined or dense. Be careful about the specificity of selector https://specificity.keegan.st/. In fact, you need adapt the fixed change to your design

Another way is to defined like #elazard suggest here an autofill variable like this
data () {
return {
login: null,
password: null,
autofill: false,
intervalDetectAutofill: null
}
},
<v-text-field
v-model="password"
type="password"
label="Password"
:placeholder="autofill ? ` ` : null"
/>
With the solution given by #adam-reis, in the mounted() of the login page
mounted () {
// search for autofill every 100ms
this.intervalDetectAutofill = setInterval(() => {
if (document.querySelectorAll("input[type=\"password\"]:-webkit-autofill").length > 0) {
this.autofill = true
}
}, 100)
// clean interval if needed after 3s
setTimeout(() => {
if (this.intervalDetectAutofill) {
clearInterval(this.intervalDetectAutofill)
this.intervalDetectAutofill = null
}
}, 3000)
},
And of course setting autofill to false if user input
watch: {
password () {
this.autofill = false
},
autofill () {
// clean interval if autofill detected or user input
if (this.intervalDetectAutofill) {
clearInterval(this.intervalDetectAutofill)
this.intervalDetectAutofill = null
}
}
},

I believe I've achieved a good result with very generic few lines of coding.
mounted() {
setTimeout(() => {
const els = document.querySelectorAll("input:-webkit-autofill")
els.forEach((el) => {
const label = el.parentElement.querySelector("label")
label.classList.add("v-label--active")
})
}, 500)
},
If the browser autofill the v-text-field, this code will add the "active" class to the Label. The v-text-field behaves have no change.

ok so what i did is something like this :
on the input
:placeholder="!autofilled ? ' ' : ''"
in the script
data() {
return {
form: {
email: '',
password: '',
},
error: null,
autofilled: false,
};
},
watch: {
'form.email'() {
this.autofilled = true;
},
},
What it does : basically setting placeholder to one blank space always "raises" the label. the unfortunate side is that setting it statically will make the label unable to go back down even if you empty the input after it is filled. so what i did is make the placeholder dynamic and only set it as a blank space before any change is made to the input after that placeholder goes back to nothing.
it isnt perfect because on initial load before the user has a password saved the labels will be raised but i havent found anything much better than that.

The autofill feature on browsers usually works by straight away setting the value of the fields in question. In this case, the label of the fields, moves out of the way only when the input field is focused, and stays away when it blurs with a value in the field. In case of autofill, the focus event isn't triggered, so the label stays where it is.
To fix this behaviour, you would have to (or get someone to) make changes in Vuetify.

You could give your input an id and read the input's value when the component is mounted and if it's anything else than empty then you could set your data value to that value that the input is holding, that way the label will go up as you would expect. Based on your more recent comments it seems like you would also need to wait for the DOM to be updated so the best thing we can do is to do our check with the help of nextTick:
mounted() {
this.$nextTick(() => {
const emailValue = document.getElementById('email').value;
this.email = emailValue || '';
});
}

Related

Why does e.target return undefined when trying to change an inputs value?

I know this question has been asked before but the solutions posted there didn't correct my issue.
So I'm working with React and have a component. Everything else works fine, but now I'm trying to use a few useStates to get dynamic information as I intend to connect to an API/server further down the line.
I'm also using styled-components, which is why you'll see odd tag names. These work fine because I worked on them first and have them visible when I run npm start
The issue is that when I try to type something other than 0 in to this input, it refuses to update and gives me e.target is undefined
Here's where my error is occuring (on the e.target.value):
<InnerWrap flexColumn>
<AttributeFrame>
<EngravingInput
type= "text"
placeholder="20"
inputWidth="50px"
name="charSTR"
value={charAttributes.charSTR}
onChange={(e) => handle_attr_Change(e.target.value)}/>
<Spacer />
<>{((charAttributes.charSTR)-10)/2}</>
<>STR</>
</AttributeFrame>
</InnerWrap>
Here's the useState (Recently learned I could put multiple fields in a single useState):
const [charAttributes, set_CharAttributes] = useState(
{
charSTR: 0,
charSTRmod: 0
});
Then, here's the function that's supposed to fire on the onChange part of my element input:
//This is in my component definition, before the return block
const handle_attr_Change = (e) => {
const value = e.target.value;
set_CharAttributes(
{
...charAttributes,
[e.target.name]: value
}
);
The whole idea is that the charSTRmod value is supposed to update whenever the user types in a number. It's meant to calculate the attribute bonus for a strength attribute from DnD 5e.
Screengrab of the part:
Please could someone tell me what I did wrong?
You can pass the event itself e into handle_attr_Change instead of unpacking it:
<EngravingInput
...
onChange={handle_attr_Change}
/>
...
const handle_attr_Change = (e) => {
set_CharAttributes(
{
...charAttributes,
[e.target.name]: e.target.value
}
);
...

VueJS - disable space in input text

I have a component called TextInput.vue, and inside I created a div.
<div ts-text-input :ts-text-input-filled="setFilledAttribute && !!value"
:ts-text-input-not-valid="!valueValid">
<input :value="value" #input="setValue" #keyup.enter="enterClicked" :placeholder="placeholder" :title="title">
what I wanted to do now is that to disable some spaces inside the input box so that the user is unable to type in with spaces/spacebar (like, e.g., username input box)
Here is what I have done; I try to use the function trim(), but it seems I can't still fix it.
in the computed function
computed: {
value: function() {
const {valueGetter, valueGetterOptions} = this,
getter = this.$store.getters[valueGetter];
value.trim();
return valueGetterOptions ? getter(valueGetterOptions) : getter;
},
Any hints would be helpful. thanks. (sorry for my bad English)
You can directly prevent that the user adds a white space to your input field. preventDefault() tells the user agent that the default action should not be taken as it normally would be.
<input #keydown.space="(event) => event.preventDefault()">
Or to make it even shorter (as Sovalina pointed out):
<input #keydown.space.prevent>
To prevent spaces on all input events - keypress, paste or drag and drop text:
const removeEventSpaces = e => {
e.preventDefault();
const left = e.target.value.substring(0, e.target.selectionStart);
const right = e.target.value.substring(e.target.selectionEnd, e.target.value.length);
const pasted = (e.dataTransfer || e.clipboardData).getData('text').replace(/ /g, '');
e.target.value = left + pasted + right;
}
<input #keydown.space.prevent #paste="removeEventSpaces" #drop="removeEventSpaces"/>
#keydown.native.space didn't work for me. #keydown.native.space.prevent did.
In this case, you can use Regular Expressions
value.replace(/\s/, '')
or to be sure the data is stored without any capital letters
value.replace(/\s/, '').toLowerCase()
u could use get and set
var inputData = ''
export default {
name: 'yourFromComponent',
computed: {
inputValue: {
get () {
return inputData
},
set (value) {
inputData = value.replace(/\s/g,'')
}
}
}
}
if you use vuex just change the inputData to your store referenz

VueJS: #keydown, #keyup, and this.$refs works with input, but not with md-textarea

I have successfully completed a basic speed typing game using vue.js. For the "typing box", I used <input>, and everything worked fine. However, when I added more levels to my game, with longer sentences for the user to type, I have identified the need to change my <input> element into <md-textarea> - a vue.js component.
THE PROBLEM:
The following attributes that were working fine with <input> won't work as expected with <md-textarea>:
#keyup="checkAnswer()"
#keydown="keymonitor"
#keydown.ctrl.86="noPaste" (prevents paste via Ctrl+V)
#keydown.shift.45="noPaste" (prevents paste via Shift+Insert)
ref="typeBox" (allows focusing on the element via this.$refs.typeBox[0].focus())
Please refer to the codes below.
Am I missing anything? Please help me debug this. Thank you.
NOTE: I know it throws an error here in SO snippet feature, but that error does not exist in my development environment.
export default {
name: 'game',
data () {
return {
disabledKeys: ['ArrowLeft', 'Home']
}
},
methods: {
/**
* prevents pasting via 'Ctrl + V' (#keydown.ctrl.86) and 'Shift + Insert' (#keydown.shift.45)
*/
noPaste: function (event) {
event.preventDefault()
},
/**
* Monitors every single key input in the answer text box.
*
* Prevents using of disabled keys in the text input.
*/
keymonitor: function (event) {
if (this.disabledKeys.indexOf(event.key) >= 0) {
event.preventDefault()
}
}, /*END keymonitor*/
startTimer () {
this.timer.id = setInterval(this.updateTimer, 1000)
this.$nextTick(() => {
this.$refs.typeBox[0].focus()
})
this.game.status = 'in progress'
}, /*END startTimer*/
} /* END methods */
} /* END export default */
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.4/vue.min.js"></script>
<template>
<md-input-container md-flex="100">
<!-- this one is working -->
<input id="typeBox" autocomplete="off" placeholder="Type here..." ref="typeBox" v-model="answer" #keydown="keymonitor" #keydown.ctrl.86="noPaste" #keydown.shift.45="noPaste"/>
<!-- this one is not working -->
<md-textarea id="typeBox" autocomplete="off" placeholder="Type here..." ref="typeBox" v-model="answer" #keydown="keymonitor" #keydown.ctrl.86="noPaste" #keydown.shift.45="noPaste"></md-textarea>
</md-input-container>
</template>
You can bind functions to the native events of the root element of a component by using the .native modifier. See the documentation here.
In your case, you can add native event handlers to the <md-textarea> component like so:
<md-textarea
id="typeBox"
autocomplete="off"
placeholder="Type here..."
ref="typeBox"
v-model="answer"
#keydown.native="keymonitor"
#keydown.native.ctrl.86="noPaste"
#keydown.native.shift.45="noPaste"
></md-textarea>

react-selectize createFromSearch showing additional overlay

I am using react-selectize component for customizable dropdown which allows users to add new options.
<Dropdown
options={myOptions}
value={selectedValue}
onValueChange={value => {
this.valueUpdated(emptyStringToNull(value));
}}
createFromSearch={this.createFromSearch}
/>
My createFromSearch and onValueChange functions are as below;
createFromSearch: function(options, search){
if (search.length === 0 || (options.map(function(option){
return option.label;
})).indexOf(search) > -1)
return null;
else {
return {'label': search, 'value': search};
}
},
onValueChange: function(text) {
// update the value in state
},
Everything works fine other than this small UI issue. It shows duplicate options soon after I click .
When I click anywhere in the screen it removes this duplicate layover and showing properly. Can anyone please suggest is it styling issue or any other thing I need to do?
I able to fix this issue by trying several things. I was overriding onValueChange method of the component and passed only the value to the actual onValueChange method as below;
const onValueChangeInDropdown = props => value => {
if (value) {
props.onValueChange(value.value);
} else {
props.onValueChange(null);
}
};
This cause the above styling issue since component couldn't find out item.newOption attribute. So solution is when adding newly created item for the option list add it as item.newOption = 'true' and pass the whole item object to onValueChange method.

React - change input defaultValue by passing props

Consider this example:
var Field = React.createClass({
render: function () {
// never renders new value...
return (
<div>
<input type="text" defaultValue={this.props.value || ''} />
</div>
);
}
});
var App = React.createClass({
getInitialState: function () {
return {value: 'Hello!'};
},
changeTo: function (str) {
this.setState({value: str});
},
render: function () {
return (
<div>
<Field value={this.state.value} />
<button onClick={this.changeTo.bind(null, 'Whyyyy?')}>Change to "Whyyyy?"</button>
<button onClick={this.changeTo.bind(null, void 0)}>Change to undefined</button>
</div>
);
}
});
React.render(
<App />,
document.getElementById('app')
);
I want to pass value into defaultValue as prop of dumb input component. However it never re-renders it.
As a previous answer mentioned, defaultValue only gets set on initial load for a form. After that, it won't get "naturally" updated because the intent was only to set an initial default value.
You can get around this if you need to by passing a key to the wrapper component, like on your Field or App component, though in more practical circumstances, it would probably be a form component. A good key would be a unique value for the resource being passed to the form - like the id stored in the database, for example.
In your simplified case, you could do this in your Field render:
<div key={this.props.value}>
<input type="text" defaultValue={this.props.value || ''} />
</div>
In a more complex form case, something like this might get what you want if for example, your onSubmit action submitted to an API but stayed on the same page:
const Form = ({item, onSubmit}) => {
return (
<form onSubmit={onSubmit} key={item.id}>
<label>
First Name
<input type="text" name="firstName" defaultValue={item.firstName} />
</label>
<label>
Last Name
<input type="text" name="lastName" defaultValue={item.lastName} />
</label>
<button>Submit!</button>
</form>
)
}
Form.defaultProps = {
item: {}
}
Form.propTypes = {
item: PropTypes.object,
onSubmit: PropTypes.func.isRequired
}
When using uncontrolled form inputs, we generally don't care about the values until after they are submitted, so that's why it's more ideal to only force a re-render when you really want to update the defaultValues (after submit, not on every change of the individual input).
If you're also editing the same form and fear the API response could come back with different values, you could provide a combined key of something like id plus timestamp.
defaultValue only works for the initial load. After that, it won't get updated. You need to maintain the state for you Field component:
var Field = React.createClass({
//transfer props to state on load
getInitialState: function () {
return {value: this.props.value};
},
//if the parent component updates the prop, force re-render
componentWillReceiveProps: function(nextProps) {
this.setState({value: nextProps.value});
},
//re-render when input changes
_handleChange: function (e){
this.setState({value: e.target.value});
},
render: function () {
// render based on state
return (
<div>
<input type="text" onChange={this._handleChange}
value={this.state.value || ''} />
</div>
);
}
});
I'm fairly certain this has to do with Controlled vs. Uncontrolled inputs.
If I understand correctly, since your <input> is Uncontrolled (doesn't define a value attribute), then the value will always resolve to the value that it is initialized with. In this case Hello!.
In order to overcome this issue, you can add a value attribute and set it during the onChange:
var Field = React.createClass({
render: function () {
// never renders new value...
return (
<div>
<input type="text" defaultValue={this.props.default || ''} value={this.props.value} />
</div>
);
}
});
Here is a plunker showing the change.
You can make the input conditionally and then every time you want to force an update of the defaultValue you just need to unmount the input and then immediately render it again.
The issue is here:
onClick={this.changeTo.bind(null, 'Whyyyy?')}
I'm curious why you bind to null.
You want to bind to 'this', so that changeTo will setState in THIS object.
Try this
<button onClick={this.changeTo.bind(this, 'Whyyyy?')}>Change to "Whyyyy?"</button>
<button onClick={this.changeTo.bind(this, void 0)}>Change to undefined</button>
In Javascript, when a function is called, its called in the scope where it was called from, not where it was written (I know, seems counter intuitive). To ensure it is called in the context you write it, you need to '.bind(this)'.
To learn more about binding and function scope, there are lots of online tutes, (some much better than others) - you might like this one: http://ryanmorr.com/understanding-scope-and-context-in-javascript/
I also recommend using the React Dev tools if you are using firefox or chrome, this way you would have been able to see that state.message was not changing:
https://facebook.github.io/react/blog/2015/09/02/new-react-developer-tools.html
Use conditional rendering, then the component will load correct initial value. Something like in this module:
class MenuHeaderInput extends React.Component{
constructor(props){
super(props);
this.handleBlur = this.handleBlur.bind (this);
}
handleBlur (e) {
this.props.menuHeaderUpdate(e.target.value);
}
render(){
if (this.props.menuHeader) {
return (
<div className="w3-row w3-margin" onClick = {() => this.props.handleTitleClick (10)}>
<div className="w3-third" ><pre></pre></div>
<input
className = {"w3-third w3-input w3-jumbo " + EDIT_COLOR}
type = "text"
defaultValue = {this.props.menuHeader}
onBlur = {this.handleBlur}
/>
<div className="w3-third" ><pre></pre></div>
</div>
)
}
else {
return null;
}
}
}
Related to Sia's excellent answer above: https://stackoverflow.com/a/41962233/4142459.
For my case I had a few ways in which a form could be updated:
users could input values into form fields
An API request allowed users to restore from previous versions
Users could navigate to a filled out form (using queryParams of the URL)
clearing the form fields.
Etc more ways of allowing all the fields or just a single change to happen from user action or websockets.
I found that the easiest way to make sure the state of the form is reflected in its inputs is indeed:
To provide a manually-controlled key prop on the top level of the form or parent element to the form (as long as it is above the inputs in the DOM tree.
When users are typing a key update does not need to happen.
I made the key be a simple formHistoricalVersion and as certain updates external to a user typing/selecting/etc interacting with the form field's values happened I incremented the formHistoricalVersion.
This made sure that the state of the form whether by user action or by API request was in-sync--I had complete control over it.
Other solutions I tried:
While making the API request make the whole form disappear (when loading change to a loading spinner instead of the form). Disadvantage to performance and for clearForm it was a bit crazy to do, but possible with setImmediate to convert the form to a loading spinner when they first clear it, then setting isLoading back to false in the setImmediate.
Adding a key on each input: this worked amazingly, but it had a weird blip whenever users would type so I had to get rid of it.
Putting a static key for the form (field.id) (as suggested by above answer) didn't cover all the use cases I had.
In conclusion, it worked pretty easily to set the key of the form with react/redux, I just would add the equivalent of:
return {
...state,
formFieldState: payload.formFields,
historicalFormVersion: state.historicalFormVersion + 1
}
This was necessary because I was using some 3rd party libraries and my own Numeric Input that took in value as a prop but used value as a defaultValue:
const NumberDisplay: FunctionComponent = ({ value, setValue }) => (
<input
defaultValue={convertToSpecialNumberDisplay(value)}
onBlur={(e) => convertToSpecialNumberDisplay(e.target.value)}
onFocus={(e) => convertToNumberFromDisplay(e.target.value)}
onChange={(e) => setValue(e.target.value)}
/>
)
Approximate Redux of overall Form:
const FullForm: FunctionComponent = () => {
const dispatch = useDispatch();
const formState = useState((state) => state.formState);
const formHistoricalVersion = useState((state) => state.formHistoricalVersion);
return (
<form key={formHistoricalVersion}>
{renderFormFields(formState, dispatch)}
</form>
)
}
I also face this problem, what I did was to manually update the input value when the props has change. Add this to your Field react class:
componentWillReceiveProps(nextProps){
if(nextProps.value != this.props.value) {
document.getElementById(<element_id>).value = nextProps.value
}
}
You just need to add an id attribute to your element so that it can be located.

Categories

Resources