flyfrog
flyfrog

Reputation: 762

What's the proper way to implement formatting on v-model in Vue.js 2.0

For a simple example: textbox to input currency data. The requirement is to display user input in "$1,234,567" format and remove decimal point.

I have tried vue directive. directive's update method is not called when UI is refreshed due to other controls. so value in textbox reverts to the one without any formatting.

I also tried v-on:change event handler. But I don't know how to call a global function in event handler. It is not a good practice to create a currency convert method in every Vue object.

So what is the standard way of formatting in Vue 2.0 now?

Regards

Upvotes: 34

Views: 55285

Answers (4)

Mani Jagadeesan
Mani Jagadeesan

Reputation: 24275

Please check this working jsFiddle example: https://jsfiddle.net/mani04/bgzhw68m/

In this example, the formatted currency input is a component in itself, that uses v-model just like any other form element in Vue.js. You can initialize this component as follows:

<my-currency-input v-model="price"></my-currency-input>

my-currency-input is a self-contained component that formats the currency value when the input box is inactive. When user puts cursor inside, the formatting is removed so that user can modify the value comfortably.

Here is how it works:

The my-currency-input component has a computed value - displayValue, which has get and set methods defined. In the get method, if input box is not active, it returns formatted currency value.

When user types into the input box, the set method of displayValue computed property emits the value using $emit, thus notifying parent component about this change.

Reference for using v-model on custom components: https://v2.vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events

Upvotes: 50

Mahmoud Mohamed Zakria
Mahmoud Mohamed Zakria

Reputation: 143

Using Vue custom directives + .toLocaleString() is also a very good option.

Vue.directive("currency", {
  bind(el, binding, vnode) {
    el.value = binding.value && Number(binding.value).toLocaleString('en-US', {style: 'currency', currency: !binding.arg ? 'USD' : binding.arg });
    el.onblur = function(e) {
      e.target.value = Number(e.target.value).toLocaleString('en-US', {style: 'currency', currency: !binding.arg ? 'USD' : binding.arg});
    };
    el.onfocus = function(e) {
      e.target.value =
        e.target.value && Number(e.target.value.replace(/[^\d.]/g, ""));
    };
    el.oninput = function(e) {
      vnode.context.$data[binding.expression] = e.target.value;
    };
  }
});

Here is the example link: https://codepen.io/Mahmoud-Zakaria/pen/YzPvNmO

Upvotes: 1

flyfrog
flyfrog

Reputation: 762

I implemented a component. According to Mani's answer, it should use $emit.

Vue.component('currency', {
template: '<input type="text"' +
            ' class="form-control"' +
            ' :placeholder="placeholder""' +
            ' :title="title"' +
            ' v-model="formatted" />',
props: ['placeholder', 'title', 'value'],
computed: {
    formatted: {
        get: function () {
            var value = this.value;
            var formatted = currencyFilter(value, "", 0);
            return formatted;
        },
        set: function (newValue) {
            var cleanValue = newValue.replace(",", "");
            var intValue = parseInt(cleanValue, 10);
            this.value = 0;
            this.value = intValue;
        }
    }
}
}

);

Upvotes: 1

Mani Jagadeesan
Mani Jagadeesan

Reputation: 24275

Here is a working example: https://jsfiddle.net/mani04/w6oo9b6j/

It works by modifying the input string (your currency value) during the focus-out and focus-in events, as follows:

<input type="text" v-model="formattedCurrencyValue" @blur="focusOut" @focus="focusIn"/>
  1. When you put the cursor inside the input box, it takes this.currencyValue and converts it to plain format, so that user can modify it.

  2. After the user types the value and clicks elsewhere (focus out), this.currencyValue is recalculated after ignoring non-numeric characters, and the display text is formatted as required.

The currency formatter (reg exp) is a copy-paste from here: How can I format numbers as money in JavaScript?

If you do not want the decimal point as you mentioned in question, you can do this.currencyValue.toFixed(0) in the focusOut method.

Upvotes: 8

Related Questions