Vue js apply filter on v-model in an input field

54,775

Solution 1

I understand what you are trying to do, however, because of the two way binding when using v-model, it may be better to just format the date as you receive it from the server, and then, use it with the desired format in your front-end app ('DD/MM/YYYY').

When sending the data back to the back-end, you just format it back to the desired server format ('YYYY-MM-DD').

In your Vue app, the work flow would be something like this:

 new Vue({
    el: 'body',
    data: {
      date: null,
    },
    methods: {
        getDataFromServer: function() {
                // Ajax call to get data from server

                // Let's pretend the received date data was saved in a variable (serverDate)
                // We will hardcode it for this ex.
                var serverDate = '2015-06-26';

                // Format it and save to vue data property
                this.date = this.frontEndDateFormat(serverDate);
        },
        saveDataToServer: function() {
            // Format data first before sending it back to server
            var serverDate = this.backEndDateFormat(this.date);

            // Ajax call sending formatted data (serverDate)
        },
        frontEndDateFormat: function(date) {
            return moment(date, 'YYYY-MM-DD').format('DD/MM/YYYY');
        },
        backEndDateFormat: function(date) {
            return moment(date, 'DD/MM/YYYY').format('YYYY-MM-DD');
        }
    }
  });

This works well for me, hope it helps.

Here is a fiddle for it:

https://jsfiddle.net/crabbly/xoLwkog9/

Syntax UPDATE:

    ...
    methods: {
        getDataFromServer() {
                // Ajax call to get data from server

                // Let's pretend the received date data was saved in a variable (serverDate)
                // We will hardcode it for this ex.
                const serverDate = '2015-06-26'

                // Format it and save to vue data property
                this.date = this.frontEndDateFormat(serverDate)
        },
        saveDataToServer() {
            // Format data first before sending it back to server
            const serverDate = this.backEndDateFormat(this.date)

            // Ajax call sending formatted data (serverDate)
        },
        frontEndDateFormat(date) {
            return moment(date, 'YYYY-MM-DD').format('DD/MM/YYYY')
        },
        backEndDateFormat(date) {
            return moment(date, 'DD/MM/YYYY').format('YYYY-MM-DD')
        }
    }
  })

Solution 2

I had a similar problem when I wanted to uppercase an input value.

This is what I ended up doing:

// create a directive to transform the model value
Vue.directive('uppercase', {
  twoWay: true, // this transformation applies back to the vm
  bind: function () {
    this.handler = function () {
      this.set(this.el.value.toUpperCase());
    }.bind(this);
    this.el.addEventListener('input', this.handler);
  },
  unbind: function () {
    this.el.removeEventListener('input', this.handler);
  }
});

Then I can use this directive on the input field with a v-model.

<input type="text" v-model="someData" v-uppercase="someData">

Now, whenever I type into this field or change someData, the value is transformed to uppercase.

This essentially did the same thing as I hoped v-model="someData | uppercase" would do. But of course, you can't do that.

In summation: make a directive that transforms the data, not a filter.

Solution 3

Go to main.js and add the following code :

import moment from 'moment'
Vue.filter('myDate', function (value) {
    if (value) {
        return moment(String(value)).format('dd/mm/yyyy')
    }
});

In your HTML do the following :

<label>Date</label>
<v-text-field :value="date | myDate" @input="value=>date=value"></v-text-field>
<p>{{ date | myDate 'dd/mm/yyyy' }}</p>

So we used above v-bind to bind the value and @input event handler to have the v-model functionality.

Solution 4

This is how I implemented a vue filter for a v-model using the watch callback, this won't update the value on load.

Vue.filter('uppercase', function (value) {
    return value.toUpperCase();
});

The html:

<input type="text" v-model="someData">

And the watch callback:

watch:{
   someData(val) {
       this.someData = this.$options.filters.uppercase(val);
    },
}

Solution 5

When you get the value initially, adjust it to fit the input. I got it working in the ready function, but you could do this after your DB call as well:

ready: function(){    
  var year = this.date.substr(0, 4);
  var monDay = this.date.substr(5,5);
  var result = monDay + "-" + year;
  this.date = result.replace(/-/g,"/");
}

You may have to do something similar on the way back up to your database as well.

Share:
54,775
Gustavo Bissolli
Author by

Gustavo Bissolli

Updated on July 13, 2022

Comments

  • Gustavo Bissolli
    Gustavo Bissolli almost 2 years

    Hope someone can help me! I have made a directive wrapping the Jasny Bootstrap Plugin more specifically the input-mask thing and everything goes well!

    Now I have made a custom filter supported by moment to format the date field!

    The date format that I receive from my backend application is YYY-MM-DD and I must show on the view as DD/MM/YYYY... I've tried v-model="date | myDate" but it didn't work properly!

    JS

    Vue.directive('input-mask', {
      params: ['mask'],
    
      bind: function() {
        $(this.el).inputmask({
          mask: this.params.mask
        });
    
      },
    });
    
    Vue.filter('my-date', function(value, formatString) {
    
      if (value != undefined)
        return '';
    
      if (formatString != undefined)
        return moment(value).format(formatString);
    
      return moment(value).format('DD/MM/YYYY');
    
    });
    
    var vm = new Vue({
      el: 'body',
      data: {
        date: '2015-06-26',
      }
    });
    

    HTML

    <label>Date</label>
    <input type="text" class="form-control" v-input-mask mask="99/99/9999" v-model="date">
    <p>{{ date | myDate 'dd/mm/yyyy' }}</p>
    

    There is the JSBin if somebody's interested!

    Thanks in advance!

    EDIT: Explaining better what I expect =)

    When the page first load the input receive the value of 2015-06-26 and I would like to show that value as DD/MM/YYYY so 26/06/2015! It works properly only after I start typing something!

  • RobG
    RobG about 8 years
    The OP wants "DD/MM/YYYY", not 'MM/DD/YYYY' (though since both are ambiguous, using the month name or abbreviation would be clearer).
  • crabbly
    crabbly about 8 years
    @RobG Thank you, just edited it. Typo here. Moment will accept his desired format as long it is explicit.
  • flyfrog
    flyfrog over 7 years
    directive works when page is loaded or user types in text. When UI is refreshed due to change in other controls, update method on directive won't be invoked. The value in textbox will be reverted to unformatted one.
  • james2doyle
    james2doyle over 7 years
    @flyfrog I think a better choice here would be a computed property
  • Heroselohim
    Heroselohim over 4 years
    Very useful! To avoid recursive call in the watch function, first get the filtered value, compare it to the current one and only set if different. if (this.someData != filteredVal) this.someData = filteredVal
  • Abid Khairy
    Abid Khairy over 4 years
    @flyfrog you can use inserted option to invoke directive onload. vuejs.org/v2/guide/custom-directive.html#Hook-Functions
  • Gabriel Tortomano
    Gabriel Tortomano over 4 years
    I dont know why this isn't the chosen answer. This perfectly solves OP problem the way he wanted. I wasn't sure if it was going to work but it turns out it was this simple. Thank you.
  • Jason Foglia
    Jason Foglia about 3 years
    Thank you this helps keep separation of concern. Played around with view models, but this is a much better solution then VM's.
  • Rosdi Kasim
    Rosdi Kasim almost 3 years
    Not sure the state of Vue in 2016. But we can use computed property to achieve this, the code will be much simpler.
  • crabbly
    crabbly almost 3 years
    @RosdiKasim Computed properties were available in Vue since v1. However, I wouldn't recommend using them in this scenario. There is no need to keep two reactive format versions for each date, at least for the purpose above. What I would recommend instead, is to move those date formatters into a utility es6 module, making them reusable across other components. It would also clean the above component as well.