-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug fixes on v-text-field #2099
Conversation
azaars
commented
Oct 9, 2017
•
edited
edited
- Resolves Masked inputs emit invalid characters #2057
- Resolves The <v-text-field return-masked-input> is not the same as the masked input field. #2096
- Resolves Illegal value inserted externally into <v-text-field> is not processed correctly. #2097
- Resolves <v-text-field> doesn't display the number 0. #2136
- Resolves <v-text-field> mask doesn't work when combined with .number modifier #2137
- Resolves VTextField selectionEnd error #2158
1. Resolves vuetifyjs#2057 2. Resolves vuetifyjs#2096 3. Resolves vuetifyjs#2097
Ups... It breaks some of the tests. I think those tests are no longer applicable and should be replaced with something else. |
It breaks one of the tests. Have you checked that clearable still works after these changes? |
Ok, will do so. |
// maskText() masks and removes dirty alphanum | ||
// Outer unmaskText() provides a clean lazyValue | ||
this.lazyValue = this.unmaskText(this.maskText(this.unmaskText(val))) | ||
this.setSelectionRange() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about non-masked inputs? This will probably break text fields with type="number"
as they don't support setSelectionRange()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did look at non-mask input. I even did some tests on first with a mask then later removed it on the fly.
Yeah you're probably right about type="number"
. Well, it's not really related to the issues this PR is trying to resolve. There are several other issues that exist even before this PR, which are overlooked yet to surface sooner or later. IMHO lets handle these separately as different issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean type="number"
in general. Currently setSelectionRange
is only called if a mask is set:
this.mask && this.setSelectionRange()
What happens with this patch if you just have a number input without a mask?
<v-text-field v-model="number" type="number">
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I see what you mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can probably just put the this.mask &&
back in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Placing back merely the this.mask &&
caused the clearable
test to fail once again. This time it's expecting an emit()
.
Admittedly, I did not give much attention to the totally valid null
value for input field before. So now I've fixed some more lines here and there. For the same reason, the extra check needs to be there once again @jacekkarczmarczyk :
if (!masked.length || !text || !text.length) return text
Yeay the test passed :-) |
src/util/mask.js
Outdated
@@ -90,7 +90,7 @@ const maskValidates = (mask, char) => { | |||
* @return {String} | |||
*/ | |||
export const maskText = (text, masked, dontFillMaskBlanks) => { | |||
if (!masked.length || !text.length) return text | |||
if (!masked.length || !text || !text.length) return text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!masked.length || !text) return text
should be enough
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really? I'm already too deep into strongly typed language hahaa.
Thanks @jacekkarczmarczyk
Tests need to be written for each issue. |
I'm sorry. I'm not familiar with the test tools at all. Will surely explore but for now it would take me ages to do that. |
Sorry, response was mainly for myself to remember to do it :) |
src/mixins/maskable.js
Outdated
if (oldText) { | ||
for (const char of oldText.substr(0, selection)) { | ||
isMaskDelimiter(char) || position++ | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oldText
will always be a string, this is redundant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, that's because I expect null
to be a valid value, and that this.lazyValue
has to be able to accept null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oldText
is directly from the html element, which is always a string, even if you set it to null first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still redundant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed it is. input.value
never becomes null. Even if it's explicitly set to null, it turns to an empty string. At least in the latest Firefox. I'm changing it right away.
- Dev should be allowed to set input to null and expect v-model to retain the null value. Requesting a reconsideration against returning a string as in commit 95b7f83 - Handle v-model.number modifier correctly. Whether the input is string, number or null, the resulting emit() should return the same type or null. Requesting this as an alternative to resolve vuetifyjs#2137. - Handle input of type number. Likewise, the resulting emit() should return the same type. Requesting this as an alternative to resolve vuetifyjs#2136.
src/util/mask.js
Outdated
if (text == null) return '' | ||
text = String(text) | ||
if (!masked.length || !text.length) return text | ||
if (!masked.length || !text) return text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just found out that !'' === true
, so !text.length
not only is redundant but will fail when text === null
. Still learning 😊
this.lazyValue = typeof val === 'number' ? +value : value | ||
this.mask ? this.setSelectionRange() | ||
: this.$emit('input', this.returnMaskedValue | ||
? this.$refs.input.value : this.lazyValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we emitting different values based on returnMaskedValue
when there is no mask?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this whole function should probably be split by an if statement so none of this casting is happening on non-masked inputs. The same applies for the value watcher too.
@@ -90,14 +90,15 @@ const maskValidates = (mask, char) => { | |||
* @return {String} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This docblock needs updating if you're returning arbitrary values. Does jsdoc have generics?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You know better than I do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please let me know if I miss anything else. |
src/util/mask.js
Outdated
@@ -90,14 +90,15 @@ const maskValidates = (mask, char) => { | |||
* @return {String} | |||
*/ | |||
export const maskText = (text, masked, dontFillMaskBlanks) => { | |||
if (text == null) return '' | |||
text = String(text) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These lines were here for a reason. The value should still be cast to a string before calculating the cursor position, because that's what's going to be set on the input in the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing if (text == null) return
was intentional because I asked to reconsider having lazyValue to accept null. I described my point here 95b7f83 . Does this mean you not buying the idea?
As for line text = String(text)
, I didn't remove it. You can find at in line 100, which I think looks more tidy without affecting the logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think accepting null is fine, but completely removing this means that the cursor position is wrong if the mask changes when the value is falsy (0
with v-model.number
for example). You should be casting newText
to string (with a proper null check) in updateRange()
and watch.mask()
before the loop. This will also allow the removal of the ifs, because for..of
on an empty string just gets skipped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see your point now. All codes that requires the return value of maskText()
are expecting a string. So, converting the falsy to '0'
is indeed correct. I'm reverting to your last commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (text == null) return text
text = String(text)
Might work to keep null values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, now I prefer your last commit. Like I said, all readers of maskText()
including newValue
and domProps.value
are expecting string.
this.lazyValue = val | ||
this.$emit('input', this.lazyValue) | ||
}, | ||
getLazyValue (val) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name is bad, it doesn't accurately describe what the function is doing. Is this method even needed? You only use it in one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's used in 2 places. The other one is at line 92 of VTextField.js
. Do you prefer not to have this this function? I'll just put the content where it was before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is it actually doing anyway? It looks like it just casts val
to a number when it already is one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it casts to the type that the dev enters in props.value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was here, but now it does nothing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OMG what was I thinking when I did that.
You're damn sharp man. I salute
}, | ||
updateOnValueChange (val) { | ||
this.lazyValue = val | ||
this.$emit('input', this.lazyValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is causing input
to be emitted twice, as you're also doing it in both inputValue.set()
and updateRange()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, actually I didn't quite get it when you said:
Also, this whole function should probably be split by an if statement so none of this casting is happening on non-masked inputs. The same applies for the value watcher too.
I understood it as having if statement to call different functions. Maybe because I didn't understand what you meant about casting issue anywhere in that function. Frankly I also don't like the changes I made and in fact I don't like the new function names I gave. Perhaps you would want to elaborate your suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On multiple emit problem, I realize that the watch.value
has never been completed for what it's supposed to function as. From the comment, it should handle external change but there is nothing in the function to detect whether the change is external or internal. So, there have always been multiple emits from internally. I'm going to address this in a later PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I said that because this.unmaskText(this.maskText(this.unmaskText(val)))
was being run on every change, even on non-masked inputs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.lazyValue = this.getLazyValue(value) | ||
this.$nextTick(() => { | ||
this.$refs.input.value = masked | ||
this.$emit('input', this.lazyValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one too.
What exactly does codeclimate means in the last commit? Where in the functions does it consider it as too complex? |
Don't worry about codeclimate for now |
- Revert to last commit to undo unnecessary changes - Fix computed.inputValue to account for type of emitted value - Enhance watch.value to check for external/internal change - Prevent resetSelections() from causing runtime error when type="number' - Fix multiple emits bug - Minor change to local variable names to be consistent between functions
@KaelWD I've commited the post-review changes. |
this.lazyValue = typeof val === 'number' ? +value : value | ||
|
||
// Emit when the externally set value was modified internally | ||
val !== this.lazyValue && this.$nextTick(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the nextTick here?
} | ||
|
||
this.setCaretPosition(selection) | ||
this.$nextTick(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and here too I guess