Cleave.js is a super cool JavaScript library that lets you force the formatting of an input. It works for credit card numbers, dates, and several other kinds of formatted text. If you haven't heard of it before, check out the demos on the Cleave.js homepage.
I had a little bit of trouble getting Cleave to work in my project because I'm using Vue and Typescript in a Rails app. I've documented most of the steps i took to get these all workign together mostly as a reference for myself, but i hope other people find it useful too.
Install Cleave.js
The first thing that needs to be done is installing cleave.js and the type definition files for it. (Correct me if I'm wrong; I think they're called type definiton files.) Since I'm using yarn with webpacker, this is pretty straightforward:
yarn add cleave.js
yarn add @types/cleave.js --dev
Create a Reusable Cleave.js Component
Next, I created a basic reusable component to which I can pass in my Cleave.js options. I'll explain some of the more important lines of this component below.
// text-box.vue
<template>
<div class="form-group">
<label
:for="id"
class="form-control-placeholder"
>
{{ label }}
</label>
<input
:id="id"
:value="value"
type="text"
class="form-control"
@input="input"
>
</div>
</template>
<script lang="ts">
import Cleave = require('cleave.js');
export default {
mounted () {
if (this.cleaveOptions) {
new Cleave(this.$el.querySelector("input"), this.cleaveOptions);
this.$el.oninput = (event: Event) => {
const value = (event.target as HTMLInputElement).value
this.$emit('input', value)
}
}
},
props: {
cleaveOptions: {
type: Object,
},
id: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
value: {
type: String,
},
},
methods: {
input(event: Event) {
const value = (event.target as HTMLInputElement).value;
if (!this.cleaveOptions) {
this.$emit('input', value);
}
},
}
}
</script>
<style scoped>
</style>
Line-by-Line Explanation of Cleave.js Component
Setting the script type to "ts" is necessary to get Typescript to work:
<script lang="ts">
And I used the import method listed in the Cleave.js repository:
import Cleave = require('cleave.js');
My input is not the root element in this component, so I select it with .querySelector("input")
. I then add a new event listener that will pass the formated value of the input up to the paret component in an event. This is done in the mounted
section of the component because we want to hook that event listener onto the input element as early as possible.
However, this is only done if cleaveOptions
are passed into this component. If no options are passed in, then we don't attach a Cleave.js instance or emit any events from here.
mounted () {
if (this.cleaveOptions) {
new Cleave(this.$el.querySelector("input"), this.cleaveOptions);
this.$el.oninput = (event: Event) => {
const value = (event.target as HTMLInputElement).value
this.$emit('input', value)
}
}
},
Finally, I have a way to emit input events on the form field as a fallback in case no options are passed in for Cleave.js. This method is basic Vue + Typescript: it emits the data value in an input
event when the input value chagnes, and everything is typed for Typescript.
event: Event
indicates that the function parameter is of type Event
, and event.target as HTMLInputElement
indicates that the target of the event should be cast to an HTMLInputElement
before we call its value
method.
input(event: Event) {
const value = (event.target as HTMLInputElement).value;
if (!this.cleaveOptions) {
this.$emit('input', value);
}
},
When I tried to get the above code to work, I ran into some Typescript compiler errors, so I add to set the compiler module setting to commonjs
to get everything to work:
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
...
},
...
}
Have you been working with Vue and Typescript? Let me know if this was helpful for you in the comments!
Photo by Tom Barrett