Vue : Limit characters in text area input, truncate filter?

J W picture J W · Sep 19, 2017 · Viewed 34.6k times · Source

<textarea name="" id="" cols="30" rows="10" v-model="$store.state.user.giftMessage | truncate 150"></textarea> I tried creating a custom filter :

filters: {
    truncate(text, stop, clamp) {
        return text.slice(0, stop) + (stop < text.length ? clamp || '...' : '')
    }
}

but that didn't broke the build when I put it on the v-model for the input...

Any advice?

Answer

Bert picture Bert · Sep 19, 2017

This is one of those cases, where you really want to use a component.

Here is an example component that renders a textarea and limits the amount of text.

Please note: this is not a production ready, handle all the corner cases component. It is intended as an example.

Vue.component("limited-textarea", {
  props:{
    value:{ type: String, default: ""},
    max:{type: Number, default: 250}
  },
  template: `
    <textarea v-model="internalValue" @keydown="onKeyDown"></textarea>
  `,
  computed:{
    internalValue: {
      get() {return this.value},
      set(v){ this.$emit("input", v)}
    }
  },
  methods:{
    onKeyDown(evt){
      if (this.value.length >= this.max) {
        if (evt.keyCode >= 48 && evt.keyCode <= 90) {
          evt.preventDefault()
          return
        }
      }
    }
  }
})

This component implements v-model and only emits a change to the data if the length of the text is less than the specified max. It does this by listening to keydown and preventing the default action (typing a character) if the length of the text is equal to or more than the allowed max.

console.clear()

Vue.component("limited-textarea", {
  props:{
    value:{ type: String, default: ""},
    max:{type: Number, default: 250}
  },
  template: `
    <textarea v-model="internalValue" @keydown="onKeyDown"></textarea>
  `,
  computed:{
    internalValue: {
      get() {return this.value},
      set(v){ this.$emit("input", v)}
    }
  },
  methods:{
    onKeyDown(evt){
      if (this.value.length >= this.max) {
        if (evt.keyCode >= 48 && evt.keyCode <= 90) {
          evt.preventDefault()
          return
        }
      }
    }
  }
})

new Vue({
  el: "#app",
  data:{
    text: ""
  }
})
<script src="https://unpkg.com/[email protected]"></script>
<div id="app">
  <limited-textarea v-model="text" 
                    :max="10"
                    cols="30"
                    rows="10">
  </limited-textarea>
</div>

Another issue with the code in the question is Vuex will not allow you set a state value directly; you have to do it through a mutation. That said, there should be a Vuex mutation that accepts the new value and sets it, and the code should commit the mutation.

mutations: {
  setGiftMessage(state, message) {
    state.user.giftMessage = message
  }
}

And in your Vue:

computed:{
  giftMessage:{
    get(){return this.$store.state.user.giftMessage},
    set(v) {this.$store.commit("setGiftMessage", v)}
  }
}

Technically the code should be using a getter to get the user (and it's giftMessage), but this should work. In the template you would use:

<limited-textarea cols="30" rows="10" v-model="giftMessage"></limited-textarea>

Here is a complete example using Vuex.

console.clear()

const store = new Vuex.Store({
  state:{
    user:{
      giftMessage: "test"
    }
  },
  getters:{
    giftMessage(state){
      return state.user.giftMessage
    }
  },
  mutations:{
    setGiftMessage(state, message){
      state.user.giftMessage = message
    }
  }
})



Vue.component("limited-textarea", {
  props:{
    value:{ type: String, default: ""},
    max:{type: Number, default: 250}
  },
  template: `
    <textarea v-model="internalValue" @keydown="onKeyDown"></textarea>
  `,
  computed:{
    internalValue: {
      get() {return this.value},
      set(v){ this.$emit("input", v)}
    }
  },
  methods:{
    onKeyDown(evt){
      if (this.value.length >= this.max) {
        if (evt.keyCode >= 48 && evt.keyCode <= 90) {
          evt.preventDefault()
          return
        }
      }
    }
  }
})

new Vue({
  el: "#app",
  store,
  computed:{
    giftMessage:{
      get(){ return this.$store.getters.giftMessage},
      set(v){ this.$store.commit("setGiftMessage", v)}
    }
  }
})
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.4.0/vuex.js"></script>
<div id="app">
  <limited-textarea v-model="giftMessage" 
                    :max="10"
                    cols="30"
                    rows="10">
  </limited-textarea>
  Message: {{giftMessage}}
</div>