Search code examples
vue.jsvuejs2truncatev-model

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


<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?


Solution

  • 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/vue@2.4.2"></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/vue@2.4.2"></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>