Decimal/Float input field with locale format - javascript

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?

Related

Vue 2 classValidator ZipCode display proper format

Is it possible to DISPLAY IN MESSAGE such a format (as it should be) e.g. to a zip code in classValidator?
export function valIsPostalCode(locale:any, property: string, validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
propertyName,
name: 'valIsPostalCode',
target: object.constructor,
options: validationOptions,
constraints: [property],
validator: {
validate(value:any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints;
const relatedValue = (args.object as any)[relatedPropertyName];
const realLocale = typeof locale === 'function' ? locale(relatedValue) : locale;
return isPostalCode(value, realLocale);
},
defaultMessage() {
return `${validationDict.classValidator.isZipCodeCorrect} like: ${PROPER_FORMAT}`;
},
},
});
};
}
Example: Enter the zip code in the correct format: XXX-XX

Supertokens: Adding custom form fields with modifier function capabilities

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!

Old prop keeps coming back when there's a change on Vue Currency Input

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!

Vue computed setter with object prop

The idea is to pass different objects to an input component, have them displayed there as CSV and be able to edit/validate the text and change the style depending on the validation results. Here is the code I currently have:
<div id="vue">
<base-input :cslice="c.workspace"></base-input>
</div>
javascript:
(function () {
Vue.component('base-input', {
props: ['cslice'],
data: function () {
return {
colors: ['red', 'white', 'yellow', 'green', 'orange', 'purple'],
ind: 1
}
},
computed: {
str: {
get: function () {
return Object.values(this.cslice).join(", ");
},
set: function (val) {
if(val.indexOf('0'))
this.ind = Math.floor(this.colors.length * Math.random());
},
},
styleObj: {
get: function () {
return { color: this.colors[this.ind] };
},
set: function () {
},
}
},
template: '<div><input v-model="str" :style="styleObj" type="text"/></div>'
});
let vue = new Vue({
el: '#vue',
data: {
c: {},
},
created: function () {
this.c = Object.assign({}, this.c, {
workspace: { width: 820, height: 440 },
});
},
});
})();
Here is the fiddle:
https://jsfiddle.net/tfoller/sz946qe2/3/
This code allows me to delete the last character only if the new style is the same as the current, otherwise the text is practically uneditable, how do I fix this problem so I'm able to normally edit the input field?
Since the computed in the child uses a prop in its getter, you need to $emit back up a value with the same shape in its setter:
set: function (val) {
const arrValues = val.split(',').map(v => v.trim());
console.log(arrValues);
this.$emit('input', {
width: arrValues[0],
height: arrValues[1]
})
},
That means reversing some of the string formatting stuff you were doing in order to get the right shape.
Listen for that input event in the parent. You can change your prop name to value so that you can use v-model as the listener:
<base-input v-model="c.workspace"></base-input>
Move all of the color changing functionality into a separate method in the child that's triggered by changing the input as well. Here's a demo:
Demo

Create an only numeric text field

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, ''))
}
}
}
}

Categories

Resources