Custom component for handling clin dollar input
- Macro for CLIN dollar input HTML. - Custom Vue component to react to "fundingValid" validaiton
This commit is contained in:
parent
cdbbc9f785
commit
6224026d72
180
js/components/clin_dollar_amount.js
Normal file
180
js/components/clin_dollar_amount.js
Normal file
@ -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
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
70
templates/components/clin_dollar_amount.html
Normal file
70
templates/components/clin_dollar_amount.html
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{% from 'components/icon.html' import Icon %}
|
||||||
|
|
||||||
|
{% macro CLINDollarAmount(type, field=None, funding_validation=False) -%}
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-col">
|
||||||
|
<clindollaramount
|
||||||
|
v-cloak
|
||||||
|
inline-template
|
||||||
|
{% if funding_validation %}
|
||||||
|
:funding-valid='fundingValid'
|
||||||
|
{% else %}
|
||||||
|
:funding-valid='true'
|
||||||
|
{% endif %}
|
||||||
|
{% if field %}
|
||||||
|
name='{{ field.name }}'
|
||||||
|
{% if field.data is not none %}initial-value='{{ field.data }}'{% endif %}
|
||||||
|
{% if field.errors %}v-bind:initial-errors='{{ field.errors | list }}'{% endif %}
|
||||||
|
key='{{ field.name }}'
|
||||||
|
{% else %}
|
||||||
|
:name="'clins-' + clinIndex + '-' + '{{ type }}' + '_amount'"
|
||||||
|
:key="'clins-' + clinIndex + '-' + '{{ type }}' + '_amount'"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
validation="clinDollars"
|
||||||
|
:watch='true'>
|
||||||
|
<div v-bind:class="['usa-input usa-input--validation--dollars', { 'usa-input--error': showError, 'usa-input--success': showValid}]">
|
||||||
|
{% if field %}
|
||||||
|
<label for='{{ field.name }}'>
|
||||||
|
{% else %}
|
||||||
|
<label :for='name'>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if type=="obligated" %}
|
||||||
|
<div class="usa-input__title">{{ 'task_orders.form.obligated_funds_label' | translate }}</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="usa-input__title">{{ 'task_orders.form.total_funds_label' | translate }}</div>
|
||||||
|
{% endif %}
|
||||||
|
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
|
||||||
|
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<masked-input
|
||||||
|
v-on:input='onInput'
|
||||||
|
v-on:blur='onBlur'
|
||||||
|
v-on:change='onChange'
|
||||||
|
v-bind:value='value'
|
||||||
|
v-bind:mask='mask'
|
||||||
|
v-bind:pipe='pipe'
|
||||||
|
v-bind:keep-char-positions='keepCharPositions'
|
||||||
|
v-bind:aria-invalid='showError'
|
||||||
|
type='text'
|
||||||
|
:id='name'
|
||||||
|
ref='input'>
|
||||||
|
</masked-input>
|
||||||
|
|
||||||
|
<input type='hidden' v-bind:value='rawValue' :name='name' />
|
||||||
|
<template v-if='!fundingValid'>
|
||||||
|
<span class='usa-input__message'>Obligated amount must be less than or equal to the total amount</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if='showError'>
|
||||||
|
<span class='usa-input__message' v-html='validationError'></span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class='usa-input__message'></span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</clindollaramount>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{%- endmacro %}
|
@ -5,6 +5,7 @@
|
|||||||
{% from 'components/icon.html' import Icon %}
|
{% from 'components/icon.html' import Icon %}
|
||||||
{% from 'components/options_input.html' import OptionsInput %}
|
{% from 'components/options_input.html' import OptionsInput %}
|
||||||
{% from 'components/text_input.html' import TextInput %}
|
{% from 'components/text_input.html' import TextInput %}
|
||||||
|
{% from "components/clin_dollar_amount.html" import CLINDollarAmount %}
|
||||||
{% from 'task_orders/form_header.html' import TOFormStepHeader %}
|
{% 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) %}
|
{% 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 }}
|
{{ 'task_orders.form.clin_funding' | translate }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if fields %}
|
{% if fields %}
|
||||||
<div class="form-row">
|
{{ CLINDollarAmount("total", field=fields.total_amount) }}
|
||||||
<div class="form-col">
|
{{ CLINDollarAmount("obligated", field=fields.obligated_amount, funding_validation=True) }}
|
||||||
{{ TextInput(fields.obligated_amount, validation='dollars', watch=True, optional=False) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="form-row">
|
{{ CLINDollarAmount("total") }}
|
||||||
<div class="form-col">
|
{{ CLINDollarAmount("obligated", funding_validation=True) }}
|
||||||
<textinput
|
|
||||||
v-cloak
|
|
||||||
inline-template
|
|
||||||
:name="'clins-' + clinIndex + '-obligated_amount'"
|
|
||||||
validation="dollars"
|
|
||||||
:watch='true'>
|
|
||||||
<div v-bind:class="['usa-input usa-input--validation--' + validation, { 'usa-input--error': showError, 'usa-input--success': showValid, 'usa-input--validation--paragraph': paragraph, 'no-max-width': noMaxWidth }]">
|
|
||||||
<label :for="name">
|
|
||||||
<div class="usa-input__title">{{ 'task_orders.form.obligated_funds_label' | translate }}</div>
|
|
||||||
<span v-show='showError'>{{ Icon('alert',classes="icon-validation") }}</span>
|
|
||||||
<span v-show='showValid'>{{ Icon('ok',classes="icon-validation") }}</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<masked-input
|
|
||||||
v-on:input='onInput'
|
|
||||||
v-on:blur='onBlur'
|
|
||||||
v-on:change='onChange'
|
|
||||||
v-bind:value='value'
|
|
||||||
v-bind:mask='mask'
|
|
||||||
v-bind:pipe='pipe'
|
|
||||||
v-bind:keep-char-positions='keepCharPositions'
|
|
||||||
v-bind:aria-invalid='showError'
|
|
||||||
v-bind:show-mask='false'
|
|
||||||
type='text'
|
|
||||||
:id='name'
|
|
||||||
ref='input'>
|
|
||||||
</masked-input>
|
|
||||||
|
|
||||||
<input type='hidden' v-bind:value='rawValue' :name='name' />
|
|
||||||
|
|
||||||
<template v-if='showError'>
|
|
||||||
<span class='usa-input__message' v-html='validationError'></span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span class='usa-input__message'></span>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</textinput>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="h4 clin-card__title">
|
<div class="h4 clin-card__title">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user