I am trying to build an only numeric field with VueJS. It kind of works but something is wrong:
PASS: Enter 12 in the text field, the value in VueJS is 12, the visual is 12.
FAILS: Enter 12a in the text field, the value in VueJS is 12, the visual is 12a. (expected behaviour is 12 in the text field)
PASS: Enter 12a4 in the text field, the value in VueJS is 12a4, the visual is 124
You can try with this JSFiddle I made: https://jsfiddle.net/El_Matella/rr2qex8k/
Here is my text field component:
const TextField = {
template: '<div><input type="text" v-model="copy"></div>',
props: ['value'],
data () {
return {
copy: null
}
},
created () {
this.copy = this.value.toString()
},
watch: {
value () {
this.copy = this.value.toString()
},
copy () {
this.$emit('input', this.copy)
}
}
}
Here is the code that should allow me to use that text field component as an only numeric text:
new Vue({
el: '#app',
data: {
number: 1
},
methods: {
update (value) {
this.number = parseInt(value.replace(/\D/g, ''))
}
},
components: {
TextField
}
})
The problem is that the input field does not update when the last entered char is a non numeric value. You have to enter an other numeric value in order to update the input and remove the non numeric chars.
try this on your input tag
<input type="text" onkeypress='return event.charCode >= 48 && event.charCode <= 57'/>
or
<input type="number" />
you can check for compatibility here:
https://caniuse.com/#search=type%3D%22number%22
You can use a getter and setter on a computed value to cleanse your input value on the way in:
const TextField = {
template: '<div><input type="text" v-model="inputValue"></div>',
props: ['value'],
data(){
return {
number: 0
}
},
computed: {
inputValue: {
get: function(){
return this.number;
},
set: function(value){
this.number = parseInt(value.replace(/\D/g, ''))
}
}
}
}
Related
I'm adding custom fields to the Sign Up form in Supertokens. I see there is a builtin validate function and you can basically have a true or false return on some conditional. I was wondering if there was a way to apply a "mask" to a phone number field which forces a format ie: (123)456-7890 something like an "onBlur()" or "onKeydown()" type of event. Here's my frontendConfig() code. (Snipped for brevity):
function inputMask(value) {
console.log("value: ", value);
const _num = value.replace(/\D/g, "").match(/(\d{3})(\d{3})(\d{4})/);
const phoneNumber = `(${_num[1]})${_num[2]}-${_num[3]}`;
return phoneNumber;
}
EmailPasswordReact.init({
signInAndUpFeature: {
signUpForm: {
formFields: [
{
id: "firstname",
label: "First Name",
placeholder: "Jane",
},
{
id: "lastname",
label: "Last Name",
placeholder: "Doe",
},
{
id: "phone",
label: "Phone",
placeholder: "(123)456-7890",
// as an example
validate: (value) => {
return inputMask(value);
},
},
],
},
},```
The validate function is only meant for checking if the input is correct or not. It's not meant for normalisation.
The SuperTokens react library render react components for the login / sign up UI, so you can use plain JS to change the form behaviour:
You want to add an event listener on the custom input field which fires on the blur event. In order to add this, you need to find that input field using JS and call addEventListener on it - this should be done in the useEffect block of the page that shows the sign up form.
When the event is fired, you want to call the inputMask function and change the value of the form field to be equal to that function's output.
You should also implement logic in the validate function to check that the user's input (normalised or not) is correct.
Finally, in case the input field blur event doesn't happen, then you want to normalise the user's input when the sign up button is clicked as well. This can be achieved via the override feature.
First, in order to add the event listener, you want override the react component in the sign up form and use a useEffect hook in your custom component. The component to override is called EmailPasswordSignUpForm_Override (supertokens-auth-react SDK version >= 0.20.0)
EmailPasswordReact.init({
override: {
components: {
EmailPasswordSignUpForm_Override: ({ DefaultComponent, ...props }) => {
React.useEffect(() => {
// This will get fired each time the sign up form is shown to the user
// TODO: adding the event listener logic come in here...
}, [])
return <DefaultComponent {...props} />;
}
}
},
signInAndUpFeature: {...}
})
Then you want to find the input field representing the input for the phone number. You can find it like:
React.useEffect(() => {
// Note that the phone number input is the 5th element in the sign up form. That's why we do nth-child(5)
let input = document.querySelector("#supertokens-root").shadowRoot.querySelector("form > div:nth-child(5) > div:nth-child(2) > div > input");
function onBlur() {
let phoneNumber = input.value;
let normalisedPhoneNumber = inputMask(phoneNumber);
input.value = normalisedPhoneNumber;
}
input.addEventListener('blur', onBlur);
return () => {
input.removeEventListener('blur', onBlur);
}
}, [])
The above will noramlise the phone number when the phone number input is blurred. Whilst the above works from a UI point of view, when the sign up button is clicked, or when the validate function is called, the user's input may still be unnormalised (cause the react state in the library has not been updated). So you should also call the inputMask function during the validate function call and when signUp is clicked:
Normalise when signUp is clicked
EmailPasswordReact.init({
override: {
functions: (oI) => {
return {
...oI,
signUp: async function (input) {
// we normalise the phone input
input.formFields = input.formFields.map(f => {
if (f.id === "phone") {
return {
...f,
value: inputMask(f.value)
}
}
return f;
})
// we call the original implementation
return oI.signUp(input);
}
}
}
}
})
Normalise when validate function is called:
validate: (value) => {
value = inputMask(value);
// TODO: logic for checking input syntax. If there is an error, return a string else undefined
}
So the overall code looks like:
import React from 'react'
import EmailPasswordReact from 'supertokens-auth-react/recipe/emailpassword'
import SessionReact from 'supertokens-auth-react/recipe/session'
import { appInfo } from './appInfo'
export let frontendConfig = () => {
return {
appInfo,
recipeList: [
EmailPasswordReact.init({
override: {
functions: (oI) => {
return {
...oI,
signUp: async function (input) {
// we normalise the phone input
input.formFields = input.formFields.map(f => {
if (f.id === "phone") {
return {
...f,
value: inputMask(f.value)
}
}
return f;
})
// we call the original implementation
return oI.signUp(input);
}
}
},
components: {
EmailPasswordSignUpForm_Override: ({ DefaultComponent, ...props }) => {
React.useEffect(() => {
let input = document.querySelector("#supertokens-root").shadowRoot.querySelector("form > div:nth-child(5) > div:nth-child(2) > div > input");
function onBlur() {
let phoneNumber = input.value;
let normalisedPhoneNumber = inputMask(phoneNumber);
input.value = normalisedPhoneNumber;
}
input.addEventListener('blur', onBlur);
return () => {
input.removeEventListener('blur', onBlur);
}
}, [])
return <DefaultComponent {...props} />;
}
}
},
signInAndUpFeature: {
signUpForm: {
formFields: [
{
id: "firstname",
label: "First Name",
placeholder: "Jane",
},
{
id: "lastname",
label: "Last Name",
placeholder: "Doe",
},
{
id: "phone",
label: "Phone",
placeholder: "(123)456-7890",
validate: (value) => {
value = inputMask(value);
// TODO: logic for checking input syntax
return undefined;
}
},
],
},
}
}),
SessionReact.init(),
],
}
}
function inputMask(value) {
const _num = value.replace(/\D/g, "").match(/(\d{3})(\d{3})(\d{4})/);
const phoneNumber = `(${_num[1]})${_num[2]}-${_num[3]}`;
return phoneNumber;
}
Hope this helps!
I'm using the Vue Currency Input to an input on the app that I'm working on, I'm not sure why I have this weird issue when the old prop comes back even thought no change was fired. The behaviour is: I edit the price and save, but when I click to be editable again the old price pops up.
This is my code:
<template>
<div>
<input ref="input" :value="val" v-currency="options" allow-negative="false" class="editableValue" #change="change" #keydown.enter.prevent>
</div>
</template>
<script>
import { parse } from "vue-currency-input";
import { UTILS } from "#/helpers/utils.js";
export default {
name: "EditableValue",
data(){
return {
val: this.value
}
},
props: {
currency: {
type: Object
},
value: {
type: Number
}
},
computed: {
options() {
return {
autoDecimalMode: true,
currency: {prefix: this.currency.currency + " "},
distractionFree: false
};
},
},
methods: {
change(){
console.log('chamou')
let newValue = parse(this.$refs.input.value, this.options);
this.val = newValue;
this.$emit('finishEditPrice', newValue)
},
},
watch: {
value(current) {
let inputValue = this.$refs.input.value;
let formattedValue = UTILS.getFormattedPrice(current, this.currency);
if (inputValue != formattedValue) {
this.val = formattedValue;
this.$refs.input.value = formattedValue;
}
},
},
updated(){
// if (inputValue != formattedValue) {
// this.val = formattedValue;
// this.$refs.input.value = formattedValue;
// }
}
};
</script>
The updated() and watch was a trial to change this behaviour but no luck. I'm using this version "vue-currency-input": "^1.22.6". Does anyone have any idea how to solve it? Thanks!
I have a text field containing the date value in (MM/DD/YYYY) format. Whenever I type 01242022, it should be autoformatted and shown as 01/24/2022. I tried the following way where the value is shown as expected, but I want it in real-time. This is working when we change the focus of the input element. Any help is appreciated. Thanks
Vue.filter("formatDate", function (value) {
let dateValue = value;
if (dateValue.length === 2 || dateValue.length === 5) {
dateValue = value + "/";
}
return dateValue;
});
const app = new Vue({
el: "#app",
data() {
return {
date: new Date(),
};
},
computed: {
dateVal: {
get() {
// eslint-disable-next-line no-extra-boolean-cast
if (!!this.date) return moment(this.date, "MMDDYYYY").format("L");
return this.date;
},
set(value) {
this.date = moment(value, "MMDDYYYY").format("L");;
},
},
},
methods: {
onInput(e) {
this.dateVal = this.$options.filters.formatDate(e.target.value);
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://momentjs.com/downloads/moment.min.js"></script>
<div id="app">
<input
type="text"
v-model.lazy="dateVal"
maxlength="10"
/>
</div>
you can use v-mask
<input
v-mask="'##/##/####'"
type="text"
v-model.lazy="dateVal"
maxlength="10"
/>
I'd like to solve the following issue:
I got an <input> field that is designed for users to enter decimals like 12.5 in their locale-specific format.
In my case: German locale.
In Germany, we are using a comma as decimal separator.
So the input 12,5 should be computed to a model's value of 12.5.
This is my approach:
<template>
<input
:id="'form-'+name"
v-model="displayValue">
</template>
<script>
export default {
name: 'DecimalField',
props: {
value: [String, Number],
},
data: () => ({
innerValue: '',
}),
watch: {
innerValue (newVal) {
this.$emit ('input', newVal);
},
value (newVal) {
this.innerValue = newVal;
}
},
computed: {
displayValue: {
get: function () {
return Intl.NumberFormat("de-DE",
{ style: "decimal" }).format(this.innerValue);
},
set: function (modifiedValue) {
this.innerValue = Intl.NumberFormat("en-GB",
{ style: "decimal" }).format(modifiedValue);
}
}
},
}
</script>
(You may want to tinker around with this on codepen: https://codepen.io/spqrinc/pen/QWEaYQo ).
** The problem is:**
If you enter 12.5 the value shown in the input is being shown as 12,5 (which is correct in Germany). But if you try to type 12,5, you get NaN.
My question: How can I solve this?
So here is the jsFiddle example:
html:
<div id="demo">
does not show $ sign in input as in second input - why? <br>
<!-- the diff is only that filter is defined diferently -->
<input type="text" v-model="a | currencyDisplay">
<span >model value: {{a }}</span>
</div>
Js:
Vue.filter('currencyDisplay', {
currencyDisplay: {
// model -> view
// formats the value when updating the input element.
read: function(val) {
console.log('filter red');
return '$'+val.toFixed(2)
},
// view -> model
// formats the value when updating the data.
write: function(val, oldVal) {
console.log('filter write');
var number = +val.replace(/[^\d.]/g, '')
return isNaN(number) ? 0 : number
}
}
})
var demo = new Vue({
el: '#demo',
data: {
a: 5
}
})
When I look in the source of the documentation example, they put code like this which works:
new Vue({
el: '#two-way-filter-demo',
data: {
money: 123.45
},
filters: {
currencyDisplay: {
read: function(val) {
return '$'+val.toFixed(2)
},
write: function(val, oldVal) {
var number = +val.replace(/[^\d.]/g, '')
return isNaN(number) ? 0 : number
}
}
}
})
So the difference is the way how filter is defined. But it has to work both ways according to documentation. What is wrong here?
http://jsfiddle.net/yMv7y/975/
You should remove the currencyDisplay key from the global filter:
Vue.filter('currencyDisplay', {
read: function (val) {
return '$' + val.toFixed(2)
},
write: function (val, oldVal) {
var number = +val.replace(/[^\d.]/g, '')
return isNaN(number) ? 0 : number
}
})