From 6224026d7280a856739c2faa240445e388105490 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Wed, 11 Sep 2019 15:44:43 -0400 Subject: [PATCH] Custom component for handling clin dollar input - Macro for CLIN dollar input HTML. - Custom Vue component to react to "fundingValid" validaiton --- js/components/clin_dollar_amount.js | 180 +++++++++++++++++++ templates/components/clin_dollar_amount.html | 70 ++++++++ templates/task_orders/step_3.html | 54 +----- 3 files changed, 257 insertions(+), 47 deletions(-) create mode 100644 js/components/clin_dollar_amount.js create mode 100644 templates/components/clin_dollar_amount.html diff --git a/js/components/clin_dollar_amount.js b/js/components/clin_dollar_amount.js new file mode 100644 index 00000000..5179a82a --- /dev/null +++ b/js/components/clin_dollar_amount.js @@ -0,0 +1,180 @@ +import MaskedInput, { conformToMask } from 'vue-text-mask' +import inputValidations from '../lib/input_validations' +import { formatDollars } from '../lib/dollars' +import { emitEvent } from '../lib/emitters' + +export default { + name: 'clindollaramount', + + components: { + MaskedInput, + }, + + props: { + name: String, + validation: { + type: String, + default: () => 'clinDollars', + }, + initialValue: { + type: String, + default: () => '', + }, + initialErrors: { + type: Array, + default: () => [], + }, + + optional: Boolean, + fundingValid: Boolean, + watch: { + type: Boolean, + default: false, + }, + }, + + data: function() { + return { + showErrorState: + (this.initialErrors && this.initialErrors.length) || false, + showValidationState: false, + mask: inputValidations[this.validation].mask, + pipe: inputValidations[this.validation].pipe || undefined, + keepCharPositions: + inputValidations[this.validation].keepCharPositions || false, + validationError: + this.initialErrors.join(' ') || + inputValidations[this.validation].validationError, + value: this.initialValue, + modified: false, + } + }, + + computed: { + rawValue: function() { + return this._rawValue(this.value) + }, + showError: function() { + return this.showErrorState || !this.fundingValid + }, + showValid: function() { + return this.showValidationState && this.fundingValid + }, + }, + + mounted: function() { + if (this.value) { + this._checkIfValid({ + value: this.value, + invalidate: true, + showValidationStateationIcon: false, + }) + + if (this.mask && this.validation !== 'email') { + const mask = + typeof this.mask.mask !== 'function' + ? this.mask + : mask.mask(this.value).filter(val => val !== '[]') + + this.value = conformToMask(this.value, mask).conformedValue + } + } + }, + + created: function() { + emitEvent('field-mount', this, { + optional: this.optional, + name: this.name, + valid: this._isValid(this.value), + }) + }, + + methods: { + // When user types a character + onInput: function(e) { + // When we use the native textarea element, we receive an event object + // When we use the masked-input component, we receive the value directly + const value = typeof e === 'object' ? e.target.value : e + this.value = value + this.modified = true + 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 }) + }, + + onBlur: function(e) { + if (!(this.optional && e.target.value === '')) { + this._checkIfValid({ value: e.target.value.trim(), invalidate: true }) + } else if (this.modified && !this.optional) { + this._checkIfValid({ value: e.target.value.trim(), invalidate: true }) + } + this.value = e.target.value.trim() + let value = Number.isNaN(e.target.value) ? '0' : e.target.value + this.value = formatDollars(this._rawValue(value)) + }, + + _checkIfValid: function({ + value, + invalidate = false, + showValidationStateationIcon = true, + }) { + const valid = this._isValid(value) + if (this.modified) { + this.validationError = inputValidations[this.validation].validationError + } + + // Show error messages or not + if (valid) { + this.showErrorState = false + } else if (invalidate) { + this.showErrorState = true + } + + if (showValidationStateationIcon) { + this.showValidationState = this.value != '' && valid + } + + // Emit a change event + emitEvent('field-change', this, { + value: this._rawValue(value), + valid: this._isValid(value), + name: this.name, + watch: this.watch, + }) + }, + + _rawValue: function(value) { + return inputValidations[this.validation].unmask.reduce( + (currentValue, character) => { + return currentValue.split(character).join('') + }, + value + ) + }, + + _validate: function(value) { + const rawValue = this._rawValue(value) + if (rawValue < 0 || rawValue > 1000000000 || !this.fundingValid) { + return false + } + return inputValidations[this.validation].match.test(rawValue) + }, + + _isValid: function(value) { + let valid = this._validate(value) + if (!this.modified && this.initialErrors && this.initialErrors.length) { + valid = false + } else if (this.optional && value === '') { + valid = true + } else if (!this.optional && value === '') { + valid = false + } + + return valid + }, + }, +} diff --git a/templates/components/clin_dollar_amount.html b/templates/components/clin_dollar_amount.html new file mode 100644 index 00000000..2263cbb7 --- /dev/null +++ b/templates/components/clin_dollar_amount.html @@ -0,0 +1,70 @@ +{% from 'components/icon.html' import Icon %} + +{% macro CLINDollarAmount(type, field=None, funding_validation=False) -%} +
+
+ +
+ {% if field %} +
+
+
+
+ {%- endmacro %} \ No newline at end of file diff --git a/templates/task_orders/step_3.html b/templates/task_orders/step_3.html index 1515e6b0..fabf706e 100644 --- a/templates/task_orders/step_3.html +++ b/templates/task_orders/step_3.html @@ -5,6 +5,7 @@ {% from 'components/icon.html' import Icon %} {% from 'components/options_input.html' import OptionsInput %} {% from 'components/text_input.html' import TextInput %} +{% from "components/clin_dollar_amount.html" import CLINDollarAmount %} {% from 'task_orders/form_header.html' import TOFormStepHeader %} {% set action = url_for("task_orders.submit_form_step_three_add_clins", task_order_id=task_order_id) %} @@ -117,56 +118,15 @@ {{ 'task_orders.form.clin_funding' | translate }} + {% if fields %} -
-
- {{ TextInput(fields.obligated_amount, validation='dollars', watch=True, optional=False) }} -
-
+ {{ CLINDollarAmount("total", field=fields.total_amount) }} + {{ CLINDollarAmount("obligated", field=fields.obligated_amount, funding_validation=True) }} {% else %} -
-
- -
- - - - - - - - - -
-
-
-
+ {{ CLINDollarAmount("total") }} + {{ CLINDollarAmount("obligated", funding_validation=True) }} {% endif %} +