Remaining character count in Vue.js
Often you'll want to limit the number of characters a user can put into an input field such as a textarea. When you do this it's a good idea to show the number of characters the user has left available in real time to avoid frustration. This is a common practice and something that's really easy to achieve in Vue.js.
Vue component scaffolding
Let's start by scoping out a simple vue component. All that's happening here is we're rendering a HTML textarea where the content is bound to the text
data property and where the id, name and label are populated by props. There's a placeholder for the characters remaining message underneath too. We also need a data property to set the max number of characters a user can enter.
<!-- characters-remaining.vue -->
<template>
<div>
<label :for="id">Comment</label>
<textarea v-model="text" :id="id" :name="id"></textarea>
<p>You have xxx characters remaining.</p>
</div>
</template>
<script>
module.exports = {
props: {
id: {
type: String,
required: true
},
label: {
type: String,
required: true
}
},
data: function() {
return {
text: '',
maxCharacters: 100,
}
},
}
</script>
<!-- Displaying our component -->
<characters-remaining id="comment" label="Comment"></characters-remaining>
Dynamically showing the number of characters remaining
We can work out the number of characters remaining by subtracting the length of the text property (which is bound to the textarea) from the max characters.
This is a good use case for a computed property.
<!-- characters-remaining.vue -->
<template>
<div>
<label :for="id">Comment</label>
<textarea v-model="text" :id="id" :name="id"></textarea>
<p>You have {{charactersRemaining}} characters remaining.</p>
</div>
</template>
<script>
module.exports = {
props: {
id: {
type: String,
required: true
},
label: {
type: String,
required: true
}
},
data: function() {
return {
text: '',
maxCharacters: 100,
}
},
computed: {
charactersRemaining: function () {
return this.maxCharacters - this.text.length;
}
}
}
</script>
<!-- Displaying our component -->
<characters-remaining id="comment" label="Comment"></characters-remaining>
Notice the computed property for charactersRemaining
which returns the result of the calculation. The property gets reevaluated every time either the max character count or, more likely, the text in the textarea changes. We can display the result by referencing it like a normal data property in the template.
Telling the user when they've gone over
We now have a working countdown of the number of characters remaining as the user types, but what happens when the user goes over? We see a minus number. This is a bit rubbish, so let's add another message which tells them when they've gone over, and by how much.
<!-- characters-remaining.vue -->
<template>
<div>
<label :for="id">Comment</label>
<textarea v-model="text" :id="id" :name="id"></textarea>
<p v-if="! isOver()">You have {{charactersRemaining}} characters remaining.</p>
<p v-else class="over">You are {{ charactersOver }} characters over the limit.</p>
</div>
</template>
<script>
module.exports = {
props: {
id: {
type: String,
required: true
},
label: {
type: String,
required: true
}
},
data: function() {
return {
text: '',
maxCharacters: 100,
}
},
computed: {
charactersRemaining: function () {
return this.maxCharacters - this.text.length;
},
charactersOver: function () {
return this.isOver() ? this.text.length - this.maxCharacters : 0;
}
},
methods: {
isOver: function () {
return this.charactersRemaining < 0;
}
}
}
</script>
<!-- Displaying our component -->
<characters-remaining id="comment" label="Comment"></characters-remaining>
Here we've added a new computed property which calculates how many characters over the limit the text is. There's also a isOver
method which returns a boolean depending on if the user has gone over or not. This is used to toggle the display of the two messages.
Make it configurable
We won't always want to have the same limit for all of our textareas, and we want our component to be reusable, so let's make it configurable.
<!-- characters-remaining.vue -->
<template>
<div>
<label :for="id">Comment</label>
<textarea v-model="text" :id="id" :name="id"></textarea>
<p v-if="! isOver()">You have {{charactersRemaining}} characters remaining.</p>
<p v-else class="over">You are {{ charactersOver }} characters over the limit.</p>
</div>
</template>
<script>
module.exports = {
props: {
id: {
type: String,
required: true
},
label: {
type: String,
required: true
},
limit: {
type: Number,
required: true
},
},
data: function() {
return {
text: '',
maxCharacters: 100,
}
},
computed: {
charactersRemaining: function () {
return this.maxCharacters - this.text.length;
},
charactersOver: function () {
return this.isOver() ? this.text.length - this.maxCharacters : 0;
}
},
methods: {
isOver: function () {
return this.charactersRemaining < 0;
}
},
mounted: function () {
this.maxCharacters = this.limit;
}
}
</script>
<!-- Displaying our component -->
<characters-remaining id="comment" label="Comment" :limit="150"></characters-remaining>
We've added a new prop for limit
and a hook for when the component is mounted. We need to set the maxCharacter
data property to the value of the limit
prop when the component is initialised. The mounted hook is fired when the component is inserted into the DOM, which is just what we need. It's worth becoming familiar with the Vue instance lifecycle.
Demo time
Well that's it. There's a whole bunch of stuff we could add to make this even better, but that's our basic character counter.