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 %} -
- {{ member_perms_form.csrf_token }} + + + {{ member_perms_form.csrf_token }} -
-
-
{{ "portfolios.admin.portfolio_members_title" | translate }}
-
- {{ "portfolios.admin.portfolio_members_subheading" | translate }} +
+
+
{{ "portfolios.admin.portfolio_members_title" | translate }}
+
+ {{ "portfolios.admin.portfolio_members_subheading" | translate }} +
+
+ + {{ Icon('info') }} + {{ "portfolios.admin.settings_info" | translate }} +
-
- - {{ Icon('info') }} - {{ "portfolios.admin.settings_info" | translate }} - -
- {% if not portfolio.members %} -

{{ "portfolios.admin.no_members" | translate }}

- {% else %} - + {% if not portfolio.members %} +

{{ "portfolios.admin.no_members" | translate }}

+ {% else %} +
- - - - - - - - - - + + + + + + + + + + - - {% if user_can(permissions.EDIT_PORTFOLIO_USERS) %} - {% include "fragments/admin/members_edit.html" %} - {% elif user_can(permissions.VIEW_PORTFOLIO_USERS) %} - {% include "fragments/admin/members_view.html" %} - {% endif %} - + + {% if user_can(permissions.EDIT_PORTFOLIO_USERS) %} + {% include "fragments/admin/members_edit.html" %} + {% elif user_can(permissions.VIEW_PORTFOLIO_USERS) %} + {% include "fragments/admin/members_view.html" %} + {% endif %} + -
{{ "portfolios.members.permissions.name" | translate }}{{ "portfolios.members.permissions.app_mgmt" | translate }}{{ "portfolios.members.permissions.funding" | translate }}{{ "portfolios.members.permissions.reporting" | translate }}{{ "portfolios.members.permissions.portfolio_mgmt" | translate }}
{{ "portfolios.members.permissions.name" | translate }}{{ "portfolios.members.permissions.app_mgmt" | translate }}{{ "portfolios.members.permissions.funding" | translate }}{{ "portfolios.members.permissions.reporting" | translate }}{{ "portfolios.members.permissions.portfolio_mgmt" | translate }}
+ + + {% endif %} - {% endif %} - - + + {% if user_can(permissions.EDIT_PORTFOLIO_USERS) %} {% for member in portfolio.members %} @@ -84,19 +93,11 @@ -
diff --git a/templates/fragments/edit_user_form.html b/templates/fragments/edit_user_form.html index fe821b65..b04b1077 100644 --- a/templates/fragments/edit_user_form.html +++ b/templates/fragments/edit_user_form.html @@ -3,38 +3,39 @@ {% from "components/date_input.html" import DateInput %} {% from "components/phone_input.html" import PhoneInput %} {% from "components/date_picker.html" import DatePicker %} +{% from 'components/save_button.html' import SaveButton %} -
- {{ form.csrf_token }} -
-
-
-
- {{ TextInput(form.first_name, validation='requiredField') }} + + + {{ form.csrf_token }} +
+
+
+
+ {{ TextInput(form.first_name, validation='requiredField') }} +
+ +
+ {{ TextInput(form.last_name, validation='requiredField') }} +
-
- {{ TextInput(form.last_name, validation='requiredField') }} + {{ TextInput(form.email, validation='email') }} + {{ PhoneInput(form.phone_number, form.phone_ext) }} + + {{ OptionsInput(form.service_branch) }} + {{ OptionsInput(form.citizenship) }} + {{ OptionsInput(form.designation) }} + + +
+ {{ DatePicker(form.date_latest_training, mindate=mindate, maxdate=maxdate) }}
- - {{ TextInput(form.email, validation='email') }} - {{ PhoneInput(form.phone_number, form.phone_ext) }} - - {{ OptionsInput(form.service_branch) }} - {{ OptionsInput(form.citizenship) }} - {{ OptionsInput(form.designation) }} - - -
- {{ DatePicker(form.date_latest_training, mindate=mindate, maxdate=maxdate) }} -
-
-
- -
- +
+ {{ SaveButton(text=("fragments.edit_user_form.save_details_button" | translate), additional_classes="usa-button-big" )}} +
+ +
diff --git a/templates/portfolios/admin.html b/templates/portfolios/admin.html index ad7a0872..18d2ec86 100644 --- a/templates/portfolios/admin.html +++ b/templates/portfolios/admin.html @@ -4,6 +4,7 @@ {% from "components/icon.html" import Icon %} {% from "components/text_input.html" import TextInput %} {% from "components/multi_step_modal_form.html" import MultiStepModalForm %} +{% from 'components/save_button.html' import SaveButton %} {% set secondary_breadcrumb = "navigation.portfolio_navigation.portfolio_admin" | translate %} @@ -15,28 +16,30 @@
{% if user_can(permissions.VIEW_PORTFOLIO_NAME) %} -
- {{ portfolio_form.csrf_token }} -
-
- {{ TextInput(portfolio_form.name, validation="portfolioName") }} -
- -
- + + + {{ portfolio_form.csrf_token }} +
+
+ {{ TextInput(portfolio_form.name, validation="portfolioName") }}
-
-
-
-
{{ "forms.task_order.defense_component_label" | translate }}
- {% if portfolio.defense_component %} -
{{ portfolio.defense_component }}
- {% else %} -
{{ "fragments.portfolio_admin.none" | translate }}
- {% endif %} + +
+ {{ SaveButton(text='Save', additional_classes='usa-button-big') }}
- +
+
+
{{ "forms.task_order.defense_component_label" | translate }}
+ {% if portfolio.defense_component %} +
{{ portfolio.defense_component }}
+ {% else %} +
{{ "fragments.portfolio_admin.none" | translate }}
+ {% endif %} +
+
+ + {% endif %}
diff --git a/templates/portfolios/applications/new.html b/templates/portfolios/applications/new.html index 343bb10a..bc5b9a7a 100644 --- a/templates/portfolios/applications/new.html +++ b/templates/portfolios/applications/new.html @@ -4,6 +4,7 @@ {% from "components/icon.html" import Icon %} {% from "components/modal.html" import Modal %} {% from "components/text_input.html" import TextInput %} +{% from 'components/save_button.html' import SaveButton %} {% set secondary_breadcrumb = 'portfolios.applications.new_application_title' | translate %} @@ -76,7 +77,7 @@
- + {{ SaveButton(text=('portfolios.applications.create_button_text' | translate)) }}
diff --git a/templates/portfolios/task_orders/invitations.html b/templates/portfolios/task_orders/invitations.html index 92955268..e7b0b75a 100644 --- a/templates/portfolios/task_orders/invitations.html +++ b/templates/portfolios/task_orders/invitations.html @@ -6,6 +6,7 @@ {% from "components/icon.html" import Icon %} {% from "components/text_input.html" import TextInput %} {% from "components/confirmation_button.html" import ConfirmationButton %} +{% from 'components/save_button.html' import SaveButton %} {% macro Link(text, icon_name, onClick=None, url='#', classes='') %} @@ -59,7 +60,7 @@ {{ Icon("x") }} Cancel - + {{ SaveButton(text='Save Changes', element="input") }}
diff --git a/templates/portfolios/task_orders/review.html b/templates/portfolios/task_orders/review.html index 913b9b4a..3256baf2 100644 --- a/templates/portfolios/task_orders/review.html +++ b/templates/portfolios/task_orders/review.html @@ -10,7 +10,7 @@ {% from "components/alert.html" import Alert %} {% from "components/review_field.html" import ReviewField %} {% from "components/upload_input.html" import UploadInput %} - +{% from 'components/save_button.html' import SaveButton %} {% block content %} @@ -103,7 +103,7 @@ {% endblock %}
- + {{ SaveButton(text="Continue", element="input") }}
diff --git a/templates/portfolios/task_orders/so_review.html b/templates/portfolios/task_orders/so_review.html index 357cabce..7659656f 100644 --- a/templates/portfolios/task_orders/so_review.html +++ b/templates/portfolios/task_orders/so_review.html @@ -2,42 +2,41 @@ {% from "components/text_input.html" import TextInput %} {% from "components/multi_checkbox_input.html" import MultiCheckboxInput %} +{% from 'components/save_button.html' import SaveButton %} {% block content %} {% include "fragments/flash.html" %} + +
+
-
-
- -
-

-
{{ "task_orders.so_review.title" | translate }}
-

-
+
+

+
{{ "task_orders.so_review.title" | translate }}
+

+
-
-
- {{ form.csrf_token }} -

{{ "task_orders.so_review.certification" | translate }}

- {{ TextInput(form.certifying_official) }} - {{ TextInput(form.certifying_official_title) }} - {{ TextInput(form.certifying_official_phone, placeholder='(123) 456-7890', validation='usPhone') }} - {{ TextInput(form.certifying_official_address, paragraph=True) }} +
+ + {{ form.csrf_token }} +

{{ "task_orders.so_review.certification" | translate }}

+ {{ TextInput(form.certifying_official) }} + {{ TextInput(form.certifying_official_title) }} + {{ TextInput(form.certifying_official_phone, placeholder='(123) 456-7890', validation='usPhone') }} + {{ TextInput(form.certifying_official_address, paragraph=True) }} -
+
- {{ MultiCheckboxInput(form.required_distribution) }} + {{ MultiCheckboxInput(form.required_distribution) }} -
- -
- +
+ {{ SaveButton(text='Continue', additional_classes="usa-button-big") }} +
+ +
-
- + {% endblock %} diff --git a/templates/task_orders/signing/signature_requested.html b/templates/task_orders/signing/signature_requested.html index 1d3b5f35..fa22b52b 100644 --- a/templates/task_orders/signing/signature_requested.html +++ b/templates/task_orders/signing/signature_requested.html @@ -3,54 +3,53 @@ {% from "components/text_input.html" import TextInput %} {% from "components/checkbox_input.html" import CheckboxInput %} {% from "components/icon.html" import Icon %} +{% from 'components/save_button.html' import SaveButton %} {% block content %} -
- {{ form.csrf_token }} -
-
-
-
-

-
{{ "task_orders.sign.task_order_builder_title" | translate }}
- {{ "task_orders.sign.title" | translate }} -

-
- -
-
-
- - {{ TextInput(form.level_of_warrant, validation='dollars', placeholder='$0.00', disabled=True) }} - - - - {{ TextInput(form.level_of_warrant, validation='dollars', placeholder='$0.00') }} - - - - {{ CheckboxInput(form.unlimited_level_of_warrant) }} -
+ + + {{ form.csrf_token }} +
+
+
+
+

+
{{ "task_orders.sign.task_order_builder_title" | translate }}
+ {{ "task_orders.sign.title" | translate }} +

- {{ CheckboxInput(form.signature) }} +
+
+
+ + {{ TextInput(form.level_of_warrant, validation='dollars', placeholder='$0.00', disabled=True) }} + + + + {{ TextInput(form.level_of_warrant, validation='dollars', placeholder='$0.00') }} + + + + {{ CheckboxInput(form.unlimited_level_of_warrant) }} +
+
+ + {{ CheckboxInput(form.signature) }} +
+
+
+ {{ SaveButton(text=('common.sign' | translate), additional_classes="usa-button-big") }} + + {{ Icon('caret_left') }} + + {{ "common.back" | translate }} +
-
- - - - {{ Icon('caret_left') }} - - {{ "common.back" | translate }} - -
-
- + + {% endblock %} -