Skip to content
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

[Enhancement] v-text-field with decimal amount #2138

Closed
Christilut opened this issue Oct 11, 2017 · 42 comments
Closed

[Enhancement] v-text-field with decimal amount #2138

Christilut opened this issue Oct 11, 2017 · 42 comments
Labels
C: VTextField VTextField T: enhancement Functionality that enhances existing features
Milestone

Comments

@Christilut
Copy link

Displaying currency is often in a format of 3.50 but as a number it is displayed as 3.5.

An option to choose the amount of decimals in a v-text-field that has type="number" would be nice here.

Property: decimals as number

If you choose 3, then it would always display contents of the text field as 3.500.

Perhaps it will display 3.5 while typing and 3.500 after blur. I imagine this would make the implementation alot simpler.

@KaelWD
Copy link
Member

KaelWD commented Oct 11, 2017

This would probably be an extension of the current masking system to allow arbitrary mask lengths,

@KaelWD KaelWD added the T: enhancement Functionality that enhances existing features label Oct 11, 2017
@valpet
Copy link

valpet commented Oct 12, 2017

I would like to see this implemented to support locales and different currency formats. Most (maybe all?) european countries use comma as decimal separator. A lot of european countries also use space as thousands separator, while others use dot.

@jacekkarczmarczyk
Copy link
Member

jacekkarczmarczyk commented Oct 12, 2017

It's not only about separators, it's also about grouping digits, some country use 3 digits in a group (1.234.567,00) and some use 2 (12.34.567,00)

@valpet
Copy link

valpet commented Oct 13, 2017

Not sure how useful the following link is, but I post it anyways:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString

Seems to me that recent additions to javascript and new browsers aim to make the multi-locale-currency-formatting-hell a thing of the past.

@azaars
Copy link
Contributor

azaars commented Oct 16, 2017

@valpet Although it is supported at a certain extent, it's purpose is mainly for display. Using it in an input field is still a programming-hell thing because as-you-type editing is not internally supported. Given a myriad of international character sets, it's overwhelmingly challenging to detect what's considered separators, what's considered numerically acceptable, and how to control the cursor position when you do character inserts and deletions.

@azaars
Copy link
Contributor

azaars commented Oct 16, 2017

Here's an example:

This input is set to display locales='ar-EG' and options={ style: 'currency', currency: 'AED' }.
The moment I entered '1' it showed the surrounding prefix and suffix, then when I entered '4' a separator got inserted but the cursor was behind the '4'.
oct-16-2017 18-38-45

@Webifi
Copy link
Contributor

Webifi commented Oct 28, 2017

Until a feature like this is implemented by Vuetify natively, if you're using Vuetify à la carte, you could extend VTextField in a custom FormattedInput.js component with something like:

import {VTextField} from 'vuetify'

export default {
  name: 'formatted-input',
  extends: VTextField,
  props: {
    blurredFormat: {
      type: Function,
      default: (v) => {
        return v
      }
    }
  },
  methods: {
    showValue () {
      if (!this.isFocused) {
        this.lazyValue = this.blurredFormat(this.inputValue)
      } else {
        this.lazyValue = this.inputValue
      }
    }
  },
  watch: {
    isFocused () {
      this.showValue()
    }
  },
  mounted () {
    this.showValue()
  }
}

Then import and use your new component, add a :blurred-format="myFormatMethod" prop to it, and create a "myFormatMethod" that accepts the unformatted value in its first attribute and returns the formatted value.

There's probably a better way to do this, but it's what I'm currently doing until something better comes along.

@AlexandreBonneau
Copy link
Contributor

If there is an interest, I'm willing to modify AutoNumeric as needed to make it work with v-text-field.
What would be the way to go about integrating it with that component?

@plasmid87
Copy link

@AlexandreBonneau that would be awesome as there currently isn't any working currency support for Vuetify. The v-money directive comes close, but it won't update when the model changes. There was a PR to introduce dynamic masks (and cover numeric / currency masking) but this has been closed. I think integration with AutoNumeric would be really useful

@AlexandreBonneau
Copy link
Contributor

AlexandreBonneau commented Feb 15, 2018

@plasmid87 well, if you only want a vue component that integrates AutoNumeric, you can directly use the official vue-autoNumeric component (and it support v-model updates as well as other external changes ;)).

However since my goal is to have an input that behaves like Vuetify's v-text-field, I've whipped up a quick and dirty hack by 'vuetifying' vue-autonumeric, by creating a monster component named v-autonumeric 😱.

It works for what I need, but a real integration where the v-text-field could use an external library like AutoNumeric is really the only way to go there, because, let me repeat it again; it's pretty ugly now :)

If you want to play with it, have fun with that temporary component.
Did I say it's a hack? 😁

@plasmid87
Copy link

@AlexandreBonneau hey this is a great hack, thank you for sharing it! 👍😀 I agree with you that directives on v-text-field that are compatible with AutoNumeric would be ideal and solve this issue. The trouble with using another component instead is that we can't easily use things like the built-in validation hooks, styling, hints, etc. without maintaining this sibling component just for localized numerics and currency.

It might be possible to extend v-text-field, override genInput and inject vue-autonumeric... there might be a more elegant way of doing this, but I'll take a look

@ecmel
Copy link

ecmel commented Feb 18, 2018

For an Excel like number input, I use the following pattern:

Template

<v-text-field
  v-model="amount"
  @blur="focus.amount = false"
  @focus="focus.amount = true"/>

Script

  data() {
    return {
      focus: {
        amount: false
      },
      amount_: '0'
    }
  },
  computed: {
    amount: {
      get() {
        return mask(this.amount_, 2, this.focus.amount)
      },
      set(value) {
        this.amount_ = unmask(value)
      }
    }
  }

Helper

export const isDecimal = function(value) {
  return /^-?(?:0|0\.\d*|[1-9]\d*\.?\d*)$/.test(value)
}

export const unmask = function(value, ds = ',') {
  return value.replace(ds, '.')
}

export const mask = function(value, dp = -1, editing = false, ds = ',', gs = '.') {
  if (editing || !isDecimal(value)) {
    return value.replace('.', ds)
  }

  const parts = value.split(/\./)

  let dec = parts[1] || ''

  if (dp >= 0) {
    dec = dec.length < dp ? dec.padEnd(dp, '0') : dec.substr(0, dp)
  }

  if (dec) {
    dec = ds + dec
  }

  return parts[0].replace(/(\d)(?=(?:\d{3})+$)/g, '$1' + gs) + dec
}

@ecmel
Copy link

ecmel commented Feb 25, 2018

It is worth looking at https://github.com/text-mask/text-mask for integration with vuetify. Masking is better than what vueitfy currently has and has number formatter as well.

@paulpv
Copy link

paulpv commented Apr 4, 2018

Apparently text-mask converted from directive to component on 2017/02/28:
text-mask/text-mask#360
text-mask/text-mask#511

I'm new to Vue/Vuetify: I take it this would complicate integrating text-mask in to Vuetify?

@Christilut
Copy link
Author

Well in the last 6 months I've come across my own issue 4 times... Too bad nothing has been done with it yet.
I get that there are a ton of possible options to add here, but I think the biggest problem for currency here is that there is no (easy) way to change the decimal and thousands separators.
For finances that is simply a must-have and complementing that with the prefix option that already exists, it is good enough in most scenarios.

Browsers already support Number.toLocaleString(), why not simply add support for that? Add a method or emit for the raw value and it's simple but effective.

@AlexandreBonneau
Copy link
Contributor

Well, on my end I tried integrating AutoNumeric with Vuetify, but with the ever-changing refactoring and mainly the fact that I don't know much about the Vuetify code organization, I unfortunately did not advance much.

If a Vuetify dev is willing to team up (and do a coding session with shared screens for instance), I'm up for the task :)

@paulpv
Copy link

paulpv commented May 22, 2018

Feel free to try out my implemention here:
https://github.com/paulpv/vuetify-number-field

I ran in to complications making it a standalone Node package, so for now you would just have to copy it's code directly in to the Vuetify's code's components folder.

@Christilut
Copy link
Author

Christilut commented Jun 6, 2018

I just wrote a simple component that does what I need. Feel free to use it.
In my case it is specifically for Dutch formatting but that can be easily changed.

Basically it displays 10000,10 and 10000,1 as 10.000,10 and reverts back to the former on focus. Should handle the comma, minus and NaN. Hopefully this helps someone :)

See gist here

@paulpv
Copy link

paulpv commented Jun 6, 2018

Beautiful @Christilut!

@Christilut
Copy link
Author

Just updated it for some minor fixes. And if you notice a bug, feel free to let me know (or fix 🤓 )

@paulpv
Copy link

paulpv commented Jun 6, 2018

Gist it!

@nogueiraever
Copy link

nogueiraever commented Jun 7, 2018

@Christilut I trying to adjust this to pt-BR (BRL), but don't have success.
It's nice if anyone implements this directly to Vuetify 😄

@Christilut
Copy link
Author

Christilut commented Jun 7, 2018 via email

@Christilut
Copy link
Author

Christilut commented Jun 7, 2018

You can find the gist here

If you find any bugs, report them there and I'll try to fix them and update the gist. I've tested € and $ which seem to work. If it feels robust enough I'll turn it into a package :)

@ehvetsi try the gist version. If it doesn't work, let me know what values you're using and what you expect to see and I'll check it out.

@nogueiraever
Copy link

nogueiraever commented Jun 9, 2018

Thanks, it's working to me! 👍

@avirankatz
Copy link

Any updates? I also need a currency mask, and would love to have it without workarounds.

@AlexandreBonneau
Copy link
Contributor

I haven't had time to follow the latest Vuetify improvements, so I'm not sure if implementing AutoNumeric into a VTextField is easier now, but I'm still willing to integrate it with Vuetify, given a dev guidance!

@rvboris
Copy link

rvboris commented Nov 13, 2018

I haven't had time to follow the latest Vuetify improvements, so I'm not sure if implementing AutoNumeric into a VTextField is easier now, but I'm still willing to integrate it with Vuetify, given a dev guidance!

Hi, I've created simple integration for autonumeric lib, may it helps :)

Edit Vuetify and AutoNumeric

@KyleMit
Copy link

KyleMit commented Nov 27, 2018

Can't you just change the step attribute?

<v-text-field
    v-model="height"
    min="0"
    step=".1"
    type="number"
></v-text-field>

@AlexandreBonneau
Copy link
Contributor

@rvboris this is great!

What is this this.$refs.field.$refs.input trickery? :)

@rvboris
Copy link

rvboris commented Dec 2, 2018

@rvboris this is great!

What is this this.$refs.field.$refs.input trickery? :)

No tricks, just access to child ref of component

https://vuejs.org/v2/guide/components-edge-cases.html#Accessing-Child-Component-Instances-amp-Child-Elements

@MajesticPotatoe MajesticPotatoe changed the title Request: v-text-field with decimal amount [Enhancement] v-text-field with decimal amount Dec 7, 2018
@waspinator
Copy link

consider using https://sarahdayan.github.io/dinero.js in a similar way moment.js is used to format time?

@AlexandreBonneau
Copy link
Contributor

@waspinator it looks like dinero.js is a library to format a given amount/currency/locale, but does not manage any live user input like a v-text-field component do (and like AutoNumeric does).
I'm not sure this would add the needed user interaction management to Vuetify.

@blalan05 blalan05 added this to the v2.x.x milestone Apr 2, 2019
@jacekkarczmarczyk
Copy link
Member

masks have been removed in 2.0

@paulodiogo
Copy link

masks have been removed in 2.0

I'm terrified with this. I had many issues doing the decimal mask, the mask that came with vuetify was just perfect for the other cases. I think this is a great lost for the project. Vuetify is awesome, I love you guys but we need to talk when the subject is removal of features, maybe deprecate and remove in a next version?

@ozum
Copy link

ozum commented Oct 30, 2019

BEWARE: DOES NOT WORK

Unfortunately, below solution is not working as expected. After you reset value by clicking Reset Button, if you hover text field, you see the field is updated to old value. Sorry.

Any solution would be appreciated.


I was searching a solution, tried a simple workaround with autonumeric by installing autonumeric and adding it directly to DOM element. I wanted to share here. It seems OK currently, but I didn't test it throughly. Please post if you disregard:

I added a rule, counter and model access to test quickly as below:

$ npm install autonumeric

<template>
  <div>
    <v-text-field ref="priceField" v-model="price" label="Price" counter="10" :rules="rules"></v-text-field>
    <button @click="price = 0">Reset Button</button>
  </div>
</template>
<script>
  import AutoNumeric from "autonumeric";

  export default {
    data: function() {
      return {
        price: 0,
        rules: [v => v.length <= 7 || "Max 7 characters"];
     };
    },
    mounted: function() {
      const vuetifyField = this.$refs.priceField; // Please note `ref="priceField"` in `v-text-field`.
      const inputField = vuetifyField.$refs.input; // Raw DOM Element.
      const e = new AutoNumeric(inputField); // Add autonumeric to DOM field.
    }
  };
</script>

@MarlBurroW
Copy link

MarlBurroW commented Dec 15, 2019

I just made a very hacky but working VNumericField based on AutoNumeric and VTextField. v-model is working even if set multiple times later after mounting. The v-model is the raw value of AutoNumeric (getNumber())

// VNumericField.vue extending VTextField
<script>
import { VTextField } from "vuetify/lib";
import AutoNumeric from "autonumeric";

export default {
  extends: VTextField,
  props: {
    anOptions: { // autonumeric options (see doc)
      type: Object,
      required: false,
      default() {
        return {};
      }
    }
  },
  data() {
    return {
      anElement: null, // autonumeric instance
      changedByInput: false // Flag to know if the value is changed by user input 
    };
  },
  mounted() {
    // Create the AutoNumeric instance on the VTextField input element
    this.anElement = new AutoNumeric(this.$refs.input, this.anOptions);
    // Set the AutoNumeric  default value
    this.anElement.set(this.value);
  },
  methods: {
    onInput() {
      // User has changed the input
      this.changedByInput = true; // set the flag to true
      this.updateVModel(); // emit v-model
    },
    updateVModel() { // emit raw value
      if (this.anElement !== null) {
        this.$emit("input", this.anElement.getNumber());
      }
    },
    genInput() {
      const listeners = Object.assign({}, this.listeners$);
      delete listeners["change"];
      let element = this.$createElement("input", {
        style: {},
        attrs: {
          ...this.attrs$,
          autofocus: this.autofocus,
          disabled: this.disabled,
          id: this.computedId,
          placeholder: this.placeholder,
          readonly: this.readonly,
          type: this.type
        },
        on: {
          blur: this.onBlur,
          input: this.onInput,
          focus: this.onFocus,
          keydown: this.onKeyDown,
          "autoNumeric:formatted": () => {
            this.changedByInput = false; // Remove the flag when autonumeric finish formatting
          }
        },
        ref: "input"
      });
      return element;
    }
  },
  watch: {
    value(newVal) {
      // Check if the last v-model update is fired by the input
      if (!this.changedByInput) {
        this.anElement.set(this.value); // Set the AutoNumeric raw value
      }
    }
  }
};
</script>

@chemoral87
Copy link

BEWARE: DOES NOT WORK

Unfortunately, below solution is not working as expected. After you reset value by clicking Reset Button, if you hover text field, you see the field is updated to old value. Sorry.

Any solution would be appreciated.

I was searching a solution, tried a simple workaround with autonumeric by installing autonumeric and adding it directly to DOM element. I wanted to share here. It seems OK currently, but I didn't test it throughly. Please post if you disregard:

I added a rule, counter and model access to test quickly as below:

$ npm install autonumeric

<template>
  <div>
    <v-text-field ref="priceField" v-model="price" label="Price" counter="10" :rules="rules"></v-text-field>
    <button @click="price = 0">Reset Button</button>
  </div>
</template>
<script>
  import AutoNumeric from "autonumeric";

  export default {
    data: function() {
      return {
        price: 0,
        rules: [v => v.length <= 7 || "Max 7 characters"];
     };
    },
    mounted: function() {
      const vuetifyField = this.$refs.priceField; // Please note `ref="priceField"` in `v-text-field`.
      const inputField = vuetifyField.$refs.input; // Raw DOM Element.
      const e = new AutoNumeric(inputField); // Add autonumeric to DOM field.
    }
  };
</script>

there is a problem when the object is inside a dialog or v-if="false", how do you solve that??

@chemoral87
Copy link

I just made a very hacky but working VNumericField based on AutoNumeric and VTextField. v-model is working even if set multiple times later after mounting. The v-model is the raw value of AutoNumeric (getNumber())

// VNumericField.vue extending VTextField
<script>
import { VTextField } from "vuetify/lib";
import AutoNumeric from "autonumeric";

export default {
  extends: VTextField,
  props: {
    anOptions: { // autonumeric options (see doc)
      type: Object,
      required: false,
      default() {
        return {};
      }
    }
  },
  data() {
    return {
      anElement: null, // autonumeric instance
      changedByInput: false // Flag to know if the value is changed by user input 
    };
  },
  mounted() {
    // Create the AutoNumeric instance on the VTextField input element
    this.anElement = new AutoNumeric(this.$refs.input, this.anOptions);
    // Set the AutoNumeric  default value
    this.anElement.set(this.value);
  },
  methods: {
    onInput() {
      // User has changed the input
      this.changedByInput = true; // set the flag to true
      this.updateVModel(); // emit v-model
    },
    updateVModel() { // emit raw value
      if (this.anElement !== null) {
        this.$emit("input", this.anElement.getNumber());
      }
    },
    genInput() {
      const listeners = Object.assign({}, this.listeners$);
      delete listeners["change"];
      let element = this.$createElement("input", {
        style: {},
        attrs: {
          ...this.attrs$,
          autofocus: this.autofocus,
          disabled: this.disabled,
          id: this.computedId,
          placeholder: this.placeholder,
          readonly: this.readonly,
          type: this.type
        },
        on: {
          blur: this.onBlur,
          input: this.onInput,
          focus: this.onFocus,
          keydown: this.onKeyDown,
          "autoNumeric:formatted": () => {
            this.changedByInput = false; // Remove the flag when autonumeric finish formatting
          }
        },
        ref: "input"
      });
      return element;
    }
  },
  watch: {
    value(newVal) {
      // Check if the last v-model update is fired by the input
      if (!this.changedByInput) {
        this.anElement.set(this.value); // Set the AutoNumeric raw value
      }
    }
  }
};
</script>

try to set v-model to zero, and it doesnt work

@chemoral87
Copy link

finally this solution works for me https://phiny1.github.io/v-currency-field/config.html#component-props

@ekulabuhov
Copy link

Modified @Webifi's code above. Working example here: https://codesandbox.io/s/vuetify-money-input-ixd66

// money-text-field.js
import { VTextField } from "vuetify/lib";

var formatter = new Intl.NumberFormat("en-IE", {
  style: "currency",
  currency: "EUR"
});

export default {
  name: "money-text-field",
  extends: VTextField,
  data: () => ({
    inputValue: null
  }),
  props: {
    blurredFormat: {
      type: Function,
      default: (v) => {
        // Format and remove the currency symbol - use prefix="€" attribute for cleaner look
        if (v) return formatter.format(parseFloat(v)).slice(1);
      }
    }
  },
  methods: {
    showValue() {
      if (!this.isFocused) {
        // Store the value before change
        this.inputValue = this.lazyValue;
        this.lazyValue = this.blurredFormat(this.lazyValue);
      } else {
        // Show unformatted value on focus
        this.lazyValue = this.inputValue;
      }
    }
  },
  watch: {
    isFocused() {
      this.showValue();
    },
    value() {
        // Handle v-model updates
        if (!this.isFocused) {
            this.showValue();
        }
    }
  },
  mounted() {
    this.showValue();
  }
};

@kallbuloso
Copy link

Try this;
v-currency-field
or
Vuetify-mask

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C: VTextField VTextField T: enhancement Functionality that enhances existing features
Projects
None yet
Development

No branches or pull requests