5

I have a Vue instance with five computed property functions that do the same things for different values. I repeat myself quite a bit and am wondering about a cleaner way to do this without repeating myself so much.

In the HTML template I have five input fields, each input field is limited to 25 characters and I would like to display I display a character counter:

        <div class='field is-grouped'>
        <p class='control is-expanded'>
            <input 
                class="input" 
                placeholder="Trophy engraving line 1 (25 character limit)" 
                v-model='engraving.line1' 
                v-validate="'required'"
                :class="{'input': true, 'is-danger': errors.has('engraving.line1') }" 
                name='engraving.line1'>
        </p>
        <p class='control'>
            <span>{{ line1count }}</span>
        </p>
    </div>

I have five fields that look exactly like that except they say engraving.line2, engraving.line3, engraving.line4 and engraving.line5.

Then my component javascript I define the engraving object and have the same computed method for each field.

export default {
    data(){
        return {
            engraving: {
                line1: '',
                line2: '',
                line3: '',
                line4: '',
                line5: '',
            }
        };
    },
    computed: {
        line1count() {
            var chars = this.engraving.line1.length,
                limit = 25;

            return (limit - chars) + "/" + limit + " characters remaining";
        },
        line2count(){
            var chars = this.engraving.line2.length,
                limit = 25;

            return (limit - chars) + "/" + limit + " characters remaining";
        },
        line3count(){
            var chars = this.engraving.line3.length,
                limit = 25;

            return (limit - chars) + "/" + limit + " characters remaining";
        },
        line4count(){
            var chars = this.engraving.line4.length,
                limit = 25;

            return (limit - chars) + "/" + limit + " characters remaining";
        },
        line5count(){
            var chars = this.engraving.line5.length,
                limit = 25;

            return (limit - chars) + "/" + limit + " characters remaining";
        }
    },

How can I resuse this function to accept a data parameter and not repeat myself so much?

1
  • 2
    This is the kind of thing that makes a great small component. Commented Oct 11, 2017 at 17:31

2 Answers 2

4

You could use a method:

methods: {
    linecount(line, limit) {
        var chars = this.engraving.line[line].length,
        return (limit - chars) + "/" + limit + " characters remaining";
    },
}

And then in your html just reference it:

<p class='control'>
     <span>{{ linecount(1,25) }}</span>
</p>
Sign up to request clarification or add additional context in comments.

Comments

4

Typically for this kind of thing I would make a small component. The below example removes the validation (just to make the example easier) but works just like a regular input element.

The advantage of using components is things like the validation you want to do are scoped to the individual elements and they are easily re-usable.

console.clear()

Vue.component("engraving-line",{
  props:["value", "placeholder", "limit"],
  template:`
    <div class='field is-grouped'>
        <p class='control is-expanded'>
            <input 
                class="input" 
                :placeholder="placeholder" 
                v-model='internalValue'>
        </p>
        <p class='control'>
            <span>{{ lineCount }}</span>
        </p>
    </div>
  `,
  computed:{
    internalValue:{
      get() {return this.value},
      set(v) {this.$emit("input", v)}
    },
    lineCount(){
      return `${this.limit - this.value.length}/${this.limit} characters remaining`
    }
  }
})

new Vue({
  el: "#app",
  data:{
    line1: "",
    line2: "",
    line3: "",
    line4: "",
    lineLimit: 25
  }
})
<script src="https://unpkg.com/[email protected]"></script>
<div id="app">
  <engraving-line :limit="lineLimit" 
                  v-model="line1" 
                  placeholder="Trophy engraving line 1 (25 character limit)">
  </engraving-line>
    <engraving-line :limit="lineLimit" 
                  v-model="line2" 
                  placeholder="Trophy engraving line 2 (25 character limit)">
  </engraving-line>
    <engraving-line :limit="lineLimit" 
                  v-model="line3" 
                  placeholder="Trophy engraving line 3 (25 character limit)">
  </engraving-line>
    <engraving-line :limit="lineLimit" 
                  v-model="line4" 
                  placeholder="Trophy engraving line 4 (25 character limit)">
  </engraving-line>
</div>

2 Comments

is there any name for this pattern?, I really like this solution
@RicardoOrellana I'm not sure there is a specific pattern I could point to other than recognizing re-usable UI elements. In this case, basically we want an input element, but with a bit of extra UI that shows some additional information. Just like breaking down code into re-usable functions, working with Vue (or other component bases frameworks like React) you'll come to recognize opportunities to break a UI into re-usable components.

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.