Vuetify and Cleave.js

Supphachoke Suntiwichaya
3 min readAug 30, 2020

--

หลังจากพยายามเขียนตัว Format input เองสำหรับ Vuejs แล้วพบว่าไม่ค่อยเวิร์คเท่าไหร่ เช่นตอนกรอกเครื่องหมายจุดแล้วตรวจสอบไม่ละเอียดลบได้บ้างไม่ได้บ้างเป็นต้น เลยหา lib คนอื่นมาใช้ดีกว่าเจอตัวนี้น่าสนใจดีอย่างแรกเลยก็ตอบโจทย์ที่กำลังหาพอดีเลยเอามาใช้แล้วพบว่ามีปัญหากับ vuetify เล็กน้อยเลยแงะจนมันใช้งานได้บันทึกไว้

DEMO

https://mrchoke.github.io/vuetify-cleave/

Source

Create VueJs & Veutify Project

vue create vuetify-cleavecd vuetify-cleavevue add vuetify

Add Cleave.js

yarn add cleave.js

Add Global Directive

main.js

import Cleave from 'cleave.js';

Vue.directive('cleave', {
inserted: (el, binding) => {
el.cleave = new Cleave(el, binding.value || {})
},
update: (el) => {
const event = new Event('input', {bubbles: true});
setTimeout(function () {
el.value = el.cleave.properties.result
el.dispatchEvent(event)
}, 100);
}
})

link: https://github.com/nosir/cleave.js/blob/master/doc/vue.md

ตอนนี้ Vue จะเห็น directive cleave แล้วลองสร้าง text field ดูครับ

<v-text-field v-model="comma" label="Number with Comma" v-cleave="{ numeral: true, numeralThousandsGroupStyle: 'thousand' }"> </v-text-field>
vuetify text field error

ถ้าลอง input จะมี error ขึ้นมา แต่ถ้าใช้ input ปกติของ HTML จะไม่มีปัญหา

<input type="text" v-model="comma2" v-cleave="{ numeral: true, numeralThousandsGroupStyle: 'thousand' }" />
input ของ Vuetify vs Native HTML

ลองค้นหาข้อมูลพบว่า input ของ vuetify นั้นเป็นแบบ component สิ่งที่เห็นนั้นประกอบไปด้วย element ต่างๆ มากมายมันไม่ใช่ input ที่แท้จริงผมเลยไปแกะ directive ที่เค้าทำมาแล้วใช้ได้กับ Vuetify ซึ่งเค้าจำหา element ที่แท้จริงส่งให้นั่นเองเอาแบบง่ายๆ แก้ที่ main.js โดยสร้าง function เพิ่มมาแล้วเรียกใช้

function getInput(el) {
if (el.tagName.toLocaleUpperCase() !== 'INPUT') {
const els = el.getElementsByTagName('input')
if (els.length !== 1) {
throw new Error(`v-cleave requires 1 input, found ${els.length}`)
} else {
el = els[0]
}
}
return el
}
Vue.directive('cleave', {
inserted: (el, binding) => {
el = getInput(el)
el.cleave = new Cleave(el, binding.value || {})
},
update: el => {
el = getInput(el)
const event = new Event('input', { bubbles: true })
setTimeout(function() {
el.value = el.cleave.properties.result
el.dispatchEvent(event)
}, 100)
}
})
ทำงานได้เหมือนกันละ

TypeScript

สำหรับ TypeScript จะมีปัญหาเรื่อง properties ที่ Cleave.js แปะเข้าไปใน HTMLElement ทำให้เกิดการเตือนหรืออาจจะใช้งานไม่ได้

ขั้นแรกเพิ่ม @type/cleave.js ก่อน

yarn add -D @types/cleave.js

หลังจากนั้นให้สร้าง interface โดย extents HTMLElement เช่น

import Cleave from 'cleave.js'
import { CleaveOptions } from 'cleave.js/options'
class Cl extends Cleave {
properties?: Record<string, string>
constructor(selector: string | HTMLElement, options: CleaveOptions) {
super(selector, options) }
}
export interface HTMLElementA extends HTMLElement {
cleave?: Cl
value?: string
}

แล้วแก้ในส่วนของการประกาศ directive ใน main.ts

function getInput(el: HTMLElementA) {
if (el.tagName.toLocaleUpperCase() !== 'INPUT') {
const els = el.getElementsByTagName('input')
if (els.length !== 1) {
throw new Error(`v-cleave requires 1 input, found ${els.length}`)
} else {
el = els[0]
}
}
return el
}
Vue.directive('cleave', {
inserted: (el: HTMLElementA, binding) => {
el = getInput(el)
el.cleave = new Cleave(el, binding.value || {})
},
update: (el: HTMLElementA) => {
el = getInput(el)
const event = new Event('input', { bubbles: true })
setTimeout(function() {
el.value = el.cleave?.properties?.result
el.dispatchEvent(event)
}, 100)
}
})

ก็จะประมาณนี้ครับ

ตัวอย่าง

ปล. เท่าที่ทดสอบบน Safari iOS ค่อนข้างช้าบางทีนิ่งไปจนทำงานไม่ได้ T_T

--

--

No responses yet