3

I have a form, on which I am trying to use Vue's "currency" filter on certain inputs that are then validated using Vue Validator (https://github.com/vuejs/vue-validator).

HTML

<validator name="foo">
  <input id="example" type="tel" v-model="baz | currency" v-validate:bar="['required']" />...
  <span> equals {{ baz }}</span>...
</validator>

JavaScript

Vue.config.warnExpressionErrors = false;

var vm = new Vue({
    el: '#demo',
    data: {
    baz: ''
    }
});

The filtered and validated fields update with every keystroke — such that they are cleared/reset each time. The effect is that attempting to key in a number, such as 1234, will result in the <input> showing "$3.004" or "$4.00" (though you may see: "$1.00" "$1.002" "$2.00" "$2.003" or "$3.00" as you type).

I'm thinking there's a conflict between the filter and the component for which gets the final say over the value(?)

There's a very good possibility that I'm not implementing this correctly. I distilled the issue down to the salient components int he following JSFiddle…

http://jsfiddle.net/itopizarro/b9a2oyL4/

1 Answer 1

2

I think the main problem has to do with the currency filter. Every time there's a key event in your input, the following things occur:

  1. the value of the model gets updated to the value of the input
  2. the updated model is read, filtered through the currency filter and displayed in the input (and your cursor gets put at the end)

When you type 1-2-3, it goes like this:

1 =>

  • baz = 1
  • input displays $1.00

2 =>

  • baz = "$1.002"
  • input displays "" (because the currency filter can't parse "$1.002")

3 =>

  • baz = 3
  • input displays "$3.00"

Part of the problem is that the built-in currency filter is a one-way filter - it formats the model for display but doesn't do anything when data is written back. You could try writing your own two-way currency filter. Here's a sample:

Vue.filter('currencyInput', {
  // model -> view
  read: function(value) {
    // use the built-in currency filter for display
    var currencyFilter = Vue.filter('currency');
    return currencyFilter(value);
  },
  // view -> model
  write: function(value, oldValue) {
    var number = +value.replace(/[^\d.]/g, '')
    return isNaN(number) ? 0 : parseFloat(number.toFixed(2))
  }
})

This ensures that the model is displayed as currency but written/stored as a number. This is still not perfect though because every time you keyup, the data gets formatted and the cursor moves to the end.

You could use the debounce or lazy attributes on the input so the update doesn't occur until the user has paused or moved on from the field.

<input id="example" type="tel" v-model="baz | currencyInput" debounce="500" v-validate:baz="['required']" />

But then you don't get the immediate formatting as the user types.

I guess it depends on your requirements. Hopefully this gives you some ideas.

Sign up to request clarification or add additional context in comments.

1 Comment

Fantastic, thank you! The "lazy" attribute does what I was looking for. jsfiddle.net/itopizarro/b9a2oyL4/7 The filter alone left the input in the same position (once it does the .toFixed(), the cursor is on the wrong side of the string). jsfiddle.net/itopizarro/b9a2oyL4/6

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.