diff --git a/js/components/checkbox_input.js b/js/components/checkbox_input.js index bfea50ad..604dd355 100644 --- a/js/components/checkbox_input.js +++ b/js/components/checkbox_input.js @@ -1,3 +1,5 @@ +import { emitFieldChange } from '../lib/emitters' + export default { name: 'checkboxinput', @@ -7,10 +9,7 @@ export default { methods: { onInput: function(e) { - this.$root.$emit('field-change', { - value: e.target.checked, - name: this.name, - }) + emitFieldChange(this, { value: e.target.checked, name: this.name }) }, }, } diff --git a/js/components/date_selector.js b/js/components/date_selector.js index e5e48128..df1142b1 100644 --- a/js/components/date_selector.js +++ b/js/components/date_selector.js @@ -1,5 +1,6 @@ import Vue from 'vue' import { getDaysInMonth } from 'date-fns' +import { emitFieldChange } from '../lib/emitters' var paddedNumber = function(number) { if ((number + '').length === 1) { @@ -62,18 +63,23 @@ export default { isMonthValid: function() { var _month = parseInt(this.month) - - return _month >= 0 && _month <= 12 + var valid = _month >= 0 && _month <= 12 + this._emitChange('month', this.month, valid) + return valid }, isDayValid: function() { var _day = parseInt(this.day) - - return _day >= 0 && _day <= this.daysMaxCalculation + var valid = _day >= 0 && _day <= this.daysMaxCalculation + this._emitChange('day', this.day, valid) + return valid }, isYearValid: function() { - return parseInt(this.year) >= 1 + // Emit a change event + var valid = parseInt(this.year) >= 1 + this._emitChange('year', this.year, valid) + return valid }, isWithinDateRange: function() { @@ -128,6 +134,12 @@ export default { }, }, + methods: { + _emitChange: function(name, value, valid) { + emitFieldChange(this, { value, name }) + }, + }, + render: function(createElement) { return createElement('p', 'Please implement inline-template') }, diff --git a/js/components/forms/base_form.js b/js/components/forms/base_form.js new file mode 100644 index 00000000..749365ba --- /dev/null +++ b/js/components/forms/base_form.js @@ -0,0 +1,26 @@ +import ally from 'ally.js' + +import FormMixin from '../../mixins/form' +import textinput from '../text_input' +import optionsinput from '../options_input' +import DateSelector from '../date_selector' +import MultiStepModalForm from './multi_step_modal_form' +import multicheckboxinput from '../multi_checkbox_input' +import checkboxinput from '../checkbox_input' +import levelofwarrant from '../levelofwarrant' +import Modal from '../../mixins/modal' + +export default { + name: 'base-form', + components: { + textinput, + optionsinput, + DateSelector, + MultiStepModalForm, + multicheckboxinput, + checkboxinput, + levelofwarrant, + Modal, + }, + mixins: [FormMixin, Modal], +} diff --git a/js/components/forms/new_application.js b/js/components/forms/new_application.js index b9275c3b..df7bc4e9 100644 --- a/js/components/forms/new_application.js +++ b/js/components/forms/new_application.js @@ -118,7 +118,6 @@ export default { return newVal.showValid && previous }, true) - this.validate() isValid = this.errors.length == 0 && isValid diff --git a/js/components/multi_checkbox_input.js b/js/components/multi_checkbox_input.js index cf008326..44a8c184 100644 --- a/js/components/multi_checkbox_input.js +++ b/js/components/multi_checkbox_input.js @@ -1,5 +1,6 @@ import optionsinput from '../components/options_input' import textinput from '../components/text_input' +import { emitFieldChange } from '../lib/emitters' export default { name: 'multicheckboxinput', @@ -40,10 +41,7 @@ export default { methods: { onInput: function(e) { - this.$root.$emit('field-change', { - value: e.target.value, - name: this.name, - }) + emitFieldChange(this, { value: e.target.value, name: this.name }) this.showError = false this.showValid = true }, diff --git a/js/components/options_input.js b/js/components/options_input.js index 0a291b02..085212e5 100644 --- a/js/components/options_input.js +++ b/js/components/options_input.js @@ -1,3 +1,5 @@ +import { emitFieldChange } from '../lib/emitters' + export default { name: 'optionsinput', @@ -21,10 +23,7 @@ export default { methods: { onInput: function(e) { - this.$root.$emit('field-change', { - value: e.target.value, - name: this.name, - }) + emitFieldChange(this, { value: e.target.value, name: this.name }) this.showError = false this.showValid = true }, diff --git a/js/components/text_input.js b/js/components/text_input.js index cb838025..51972d82 100644 --- a/js/components/text_input.js +++ b/js/components/text_input.js @@ -1,6 +1,7 @@ import MaskedInput, { conformToMask } from 'vue-text-mask' import inputValidations from '../lib/input_validations' import { formatDollars } from '../lib/dollars' +import { emitFieldChange } from '../lib/emitters' export default { name: 'textinput', @@ -124,7 +125,7 @@ export default { this.showValid = this.value != '' && valid // Emit a change event - this.$root.$emit('field-change', { + emitFieldChange(this, { value: this._rawValue(value), valid, name: this.name, diff --git a/js/index.js b/js/index.js index a5e58f45..0a419224 100644 --- a/js/index.js +++ b/js/index.js @@ -33,6 +33,7 @@ import { isNotInVerticalViewport } from './lib/viewport' import DateSelector from './components/date_selector' import SidenavToggler from './components/sidenav_toggler' import KoReview from './components/forms/ko_review' +import BaseForm from './components/forms/base_form' Vue.config.productionTip = false @@ -68,12 +69,14 @@ const app = new Vue({ EditOfficerForm, SidenavToggler, KoReview, + BaseForm, }, mounted: function() { - this.$on('modalOpen', isOpen => { - if (isOpen) { + this.$on('modalOpen', data => { + if (data['isOpen']) { document.body.className += ' modal-open' + this.activeModal = data['name'] } else { document.body.className = document.body.className.replace( ' modal-open', diff --git a/js/lib/emitters.js b/js/lib/emitters.js new file mode 100644 index 00000000..8bed5096 --- /dev/null +++ b/js/lib/emitters.js @@ -0,0 +1,6 @@ +export const emitFieldChange = (el, data) => { + el.$root.$emit('field-change', { + ...data, + parent_uid: el.$parent && el.$parent._uid, + }) +} diff --git a/js/mixins/form.js b/js/mixins/form.js index e0fb546f..5436f821 100644 --- a/js/mixins/form.js +++ b/js/mixins/form.js @@ -8,7 +8,23 @@ export default { const { value, name } = event if (typeof this[name] !== undefined) { this[name] = value + if (event['parent_uid'] === this._uid) { + this.changed = true + } } }, }, + + data: function() { + return { + changed: this.hasChanges, + } + }, + + props: { + hasChanges: { + type: Boolean, + default: false, + }, + }, } diff --git a/js/mixins/modal.js b/js/mixins/modal.js index 13c5f18d..6a922db8 100644 --- a/js/mixins/modal.js +++ b/js/mixins/modal.js @@ -10,7 +10,7 @@ export default { openModal: function(name) { this.activeModal = name - this.$emit('modalOpen', true) + this.$root.$emit('modalOpen', { isOpen: true, name: name }) const idSelector = `#${this.modalId}` this.allyHandler = ally.maintain.disabled({ diff --git a/styles/components/_portfolio_layout.scss b/styles/components/_portfolio_layout.scss index 7a33345b..1f24f582 100644 --- a/styles/components/_portfolio_layout.scss +++ b/styles/components/_portfolio_layout.scss @@ -297,7 +297,11 @@ .members-table-footer { float: right; - padding: 3 * $gap; + padding: 3 * $gap 0; + + .action-group.save { + padding-right: 3 * $gap; + } } a.modal-link.icon-link { diff --git a/styles/sections/_task_order.scss b/styles/sections/_task_order.scss index 05cea515..cfd321b6 100644 --- a/styles/sections/_task_order.scss +++ b/styles/sections/_task_order.scss @@ -493,12 +493,10 @@ align-items: center; justify-content: flex-end; - .usa-button { + button.usa-button { margin-left: 4 * $gap; margin-top: 0; margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; } } } diff --git a/templates/components/save_button.html b/templates/components/save_button.html new file mode 100644 index 00000000..4adb6e37 --- /dev/null +++ b/templates/components/save_button.html @@ -0,0 +1,10 @@ +{% macro SaveButton(text, element="button", additional_classes="", form=None) -%} + {% set class = "usa-button usa-button-primary" + additional_classes %} + {% if element == "button" %} + + {% elif element == 'input' %} + + {% endif %} +{%- endmacro %} diff --git a/templates/fragments/admin/portfolio_members.html b/templates/fragments/admin/portfolio_members.html index f548f857..75b90078 100644 --- a/templates/fragments/admin/portfolio_members.html +++ b/templates/fragments/admin/portfolio_members.html @@ -1,5 +1,6 @@ {% from "components/icon.html" import Icon %} {% from "components/options_input.html" import OptionsInput %} +{% from 'components/save_button.html' import SaveButton %} {% from "components/modal.html" import Modal %} {% from "components/alert.html" import Alert %} @@ -9,51 +10,59 @@ {% if g.matchesPath("portfolio-members") %} {% include "fragments/flash.html" %} {% endif %} -