diff --git a/js/components/text_input.js b/js/components/text_input.js new file mode 100644 index 00000000..e027a800 --- /dev/null +++ b/js/components/text_input.js @@ -0,0 +1,82 @@ +import MaskedInput, { conformToMask } from 'vue-text-mask' +import inputValidations from '../lib/input_validations' + +export default { + name: 'textinput', + + components: { + MaskedInput + }, + + props: { + name: String, + validation: { + type: String, + default: () => 'anything' + }, + value: { + type: String, + default: () => '' + } + }, + + data: function () { + return { + showError: false, + showValid: false, + mask: inputValidations[this.validation].mask, + renderedValue: this.value + } + }, + + mounted: function () { + const value = this.$refs.input.value + if (value) { + this._checkIfValid({ value, invalidate: true }) + this.renderedValue = conformToMask(value, this.mask).conformedValue + } + }, + + methods: { + // When user types a character + onInput: function (value) { + this._checkIfValid({ value }) + }, + + // When field is blurred (un-focused) + onChange: function (e) { + // Only invalidate the field when it blurs + this._checkIfValid({ value: e.target.value, invalidate: true }) + }, + + // + _checkIfValid: function ({ value, invalidate = false}) { + // Validate the value + const valid = this._validate(value) + + // Show error messages or not + if (valid) { + this.showError = false + } else if (invalidate) { + this.showError = true + } + this.showValid = valid + + // Emit a change event + this.$emit('fieldChange', { + value, + valid, + name: this.name + }) + }, + + _validate: function (value) { + // Strip out all the mask characters + let rawValue = inputValidations[this.validation].unmask.reduce((currentValue, character) => { + return currentValue.split(character).join('') + }, value) + + return inputValidations[this.validation].match.test(rawValue) + } + } +} diff --git a/js/index.js b/js/index.js index 72dbda3d..59883c65 100644 --- a/js/index.js +++ b/js/index.js @@ -1,8 +1,13 @@ import classes from '../styles/atat.scss' import Vue from 'vue/dist/vue' +import textinput from './components/text_input' + const app = new Vue({ el: '#app-root', + components: { + textinput + }, methods: { closeModal: function(name) { this.modals[name] = false diff --git a/js/lib/input_validations.js b/js/lib/input_validations.js new file mode 100644 index 00000000..6e7a066d --- /dev/null +++ b/js/lib/input_validations.js @@ -0,0 +1,20 @@ +import createNumberMask from 'text-mask-addons/dist/createNumberMask' +import emailMask from 'text-mask-addons/dist/emailMask' + +export default { + anything: { + mask: false, + match: /^(?!\s*$).+/, + unmask: [], + }, + dollars: { + mask: createNumberMask({ prefix: '$', allowDecimal: true }), + match: /^-?\d+\.?\d*$/, + unmask: ['$',','] + }, + email: { + mask: emailMask, + match: /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/, + unmask: [], + } +} diff --git a/package.json b/package.json index 61eddafa..b52327bf 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,10 @@ "dependencies": { "npm": "^6.0.1", "parcel": "^1.9.7", + "text-mask-addons": "^3.8.0", "uswds": "^1.6.3", - "vue": "^2.5.17" + "vue": "^2.5.17", + "vue-text-mask": "^6.1.2" }, "devDependencies": { "node-sass": "^4.9.2" diff --git a/templates/base.html b/templates/base.html index 185cd205..8416032f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -16,6 +16,7 @@