diff --git a/src/digital-components/modal/modal.vue b/src/digital-components/modal/modal.vue
index e1f3fb462..0004be47e 100644
--- a/src/digital-components/modal/modal.vue
+++ b/src/digital-components/modal/modal.vue
@@ -90,10 +90,12 @@ export default {
openModal() {
const modal = Modal.getOrCreateInstance(document.getElementById(this.modalId));
modal.show();
+ this.$emit("isModalOpened", true);
},
closeModal() {
const modal = Modal.getInstance(document.getElementById(this.modalId));
modal.hide();
+ this.$emit("isModalOpened", false);
},
},
computed: {
diff --git a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.spec.js b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.spec.js
index 5cade9ddb..26a81341a 100644
--- a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.spec.js
+++ b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.spec.js
@@ -7,7 +7,6 @@ import { mount, shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { storeMutations } from "@/constants/store-mutations";
import store from "@/store";
-import { createImportSpecifier } from "typescript";
let autocompleteElement;
describe("address-questions.vue", () => {
@@ -134,22 +133,19 @@ describe("address-questions.vue", () => {
});
test("Should set this.showAddressFields to true when the model is prepopulated", async () => {
- // Arrange
- // Act
- const newAddressModel = {
- streetAddress: "foo",
- city: "foo",
- state: "foo",
- zipCode: "55555",
- };
- const wrapper = shallowMount(addressQuestions, {
- propsData: {
- modelValue: newAddressModel,
+ // Arrange / Act
+ const { wrapper } = setupMocks({
+ props: {
+ modelValue: {
+ streetAddress: "foo",
+ city: "foo",
+ state: "foo",
+ zipCode: "55555",
+ },
},
});
- // Act
- wrapper.vm.setupAddressLookup();
+ await wrapper.vm.$nextTick();
// Assert
expect(wrapper.vm.showAddressFields).toBe(true);
diff --git a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue
index 9842d42c3..20434da4d 100644
--- a/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue
+++ b/src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue
@@ -122,13 +122,15 @@ export default {
},
data() {
return {
- showAddressFields: false,
displayVerificationWarning: false,
displayNoMatchWarning: false,
alertHeadlineVerificationWarning: "",
alertCopyVerificationWarning: "",
alertHeadlineNoMatchWarning: "",
alertCopyNoMatchWarning: "",
+ autocomplete: null,
+ autocompleteListener: null,
+ showAddressFields: false,
matchFound: null, // null = no attempted match, true = match was found, false = match was not found
};
},
@@ -203,182 +205,194 @@ export default {
return this.captureApartmentNumberOrBusinessName;
},
},
+ addressField1: {
+ get: function () {
+ return document.getElementById("autocomplete");
+ },
+ },
},
methods: {
- setupAddressLookup() {
- this.showAddressFields = false;
- if (
- this.addressModel.streetAddress &&
- this.addressModel.city &&
- this.addressModel.state &&
- this.addressModel.zipCode
- ) {
- this.showAddressFields = true;
- return;
- }
-
- const addressField1 = document.getElementById("autocomplete");
- const self = this;
-
+ loadGooglePlacesAutocompleteScript() {
+ // Load the Google Places Autocomplete script
const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY;
-
this.$loadScript(
`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places&callback=Function.prototype`
- )
- .then(() => {
- // Script is loaded, initialize the autocomplete textbox
- const autocomplete = new window.google.maps.places.Autocomplete(addressField1, {
- componentRestrictions: { country: ["us"] },
- fields: ["address_components"],
- types: ["geocode"],
- });
+ ).then(() => {
+ // When loaded trigger the setup
+ this.initializeAutocomplete();
+ });
+ },
+ initializeAutocomplete() {
+ // Initialize the Google Places Autocomplete
+ this.autocomplete = new window.google.maps.places.Autocomplete(this.addressField1, {
+ componentRestrictions: { country: ["us"] },
+ fields: ["address_components"],
+ types: ["geocode"],
+ });
- // Standard place_changed event handling
- const autocompleteListener = window.google.maps.event.addListener(
- autocomplete,
- "place_changed",
- fillInAddress
+ // Set up the Autocomplete place_changed event to call our method to fill in the address
+ this.autocompleteListener = window.google.maps.event.addListener(
+ this.autocomplete,
+ "place_changed",
+ this.fillInAddress
+ );
+
+ // When the Street Address textbox receives focus,
+ // append the search results list container to the bottom of the textbox
+ // and disable browser autofill
+ this.addressField1.addEventListener("focus", (e) => {
+ // Make place results box stick to the input on scroll
+ const streetAddressField = document.getElementById("streetAddressField");
+ const autocompleteResultsContainer =
+ document.getElementsByClassName("pac-container")[0];
+ if (autocompleteResultsContainer) {
+ streetAddressField.appendChild(autocompleteResultsContainer);
+ }
+
+ // Unfortunately this is the only place we can set the autocomplete attribute without the
+ // Google Places object resetting it to "off" which does nothing to prevent browser autofill
+ this.addressField1.setAttribute("autocomplete", "do-not-autofill");
+ });
+
+ this.addressField1.addEventListener("keydown", (e) => {
+ const event = new Event("place_changed");
+
+ // When either of the two enter keys or the tab key are pressed
+ if (e.code === "Enter" || e.code === "NumpadEnter" || e.code === "Tab") {
+ // Grab the selected item
+ const selectedItem = document.querySelector(
+ ".pac-container .pac-item-selected"
);
- addressField1.addEventListener("focus", () => {
- // Wrapping the addressField1 element in the Google Address Autocomplete object
- // will cause "autocomplete='off'" which Chrome completely ignores. This event
- // handler will set the value to something arbitrary so autofill doesn't work.
- // https://stackoverflow.com/a/30976223
- addressField1.setAttribute("autocomplete", "do-not-autofill");
+ if (selectedItem !== null) {
+ // If an item was selected then fill in the address with the selected item
+ // by triggering the "place_changed" event of the Autocomplete object
+ this.autocomplete.dispatchEvent(event);
+ } else {
+ // Otherwise fill-in the address using first item from the list.
+ this.fillInAddressUsingFirstItem();
+ }
+ } else {
+ return;
+ }
+ });
- // Make place results box stick to the input on scroll
- const streetAddressField = document.getElementById("streetAddressField");
- const autocompleteResultsContainer =
- document.getElementsByClassName("pac-container")[0];
- if (autocompleteResultsContainer) {
- streetAddressField.appendChild(autocompleteResultsContainer);
- }
- });
+ this.addressField1.addEventListener("change", () => {
+ // If a match has been previously attempted then do nothing
+ if (this.matchFound !== null) {
+ return;
+ }
- addressField1.addEventListener("keydown", (e) => {
- const autocomplete = document.getElementById("autocomplete");
- const event = new Event("place_changed");
+ // Get the address that the user clicked on (if any)
+ const clickedAddress = document.querySelector(".pac-container .pac-item:hover");
- if (e.code === "Enter" || e.code === "NumpadEnter" || e.code === "Tab") {
- const selectedItem = document.querySelector(
- ".pac-container .pac-item-selected"
- );
- if (selectedItem !== null) {
- // Fill-in the address using selected item in the list.
- autocomplete.dispatchEvent(event);
- //fillInAddress(selectedItem.textContent);
- } else {
- // Fill-in the address using first item in the list.
- fillInAddressUsingFirstItem();
+ // If the Street Address field changed without clicking (i.e. by pressing Tab, or clicking outside the field)
+ if (clickedAddress === null) {
+ // Fill-in the address using first item in the list.
+ this.fillInAddressUsingFirstItem();
+ }
+ });
+ },
+ fillInAddress(place) {
+ if (!place) {
+ place = this.autocomplete.getPlace();
+ }
+
+ if (place && place.address_components) {
+ this.matchFound = true;
+
+ const self = this;
+ this.$nextTick(function () {
+ self.showAddressFields = true;
+
+ for (const component of place.address_components) {
+ const componentType = component.types[0];
+
+ switch (componentType) {
+ case "street_number": {
+ self.addressModel.streetAddress = component.long_name;
+ break;
+ }
+ case "route": {
+ self.addressModel.streetAddress += " " + component.short_name;
+ break;
+ }
+ case "locality": {
+ self.addressModel.city = component.long_name;
+ break;
+ }
+ case "administrative_area_level_1": {
+ self.addressModel.state = component.short_name;
+ break;
+ }
+ case "postal_code": {
+ self.addressModel.zipCode = component.long_name;
+ break;
}
- } else {
- return;
- }
- });
-
- addressField1.addEventListener("change", () => {
- // If a match has been previously attempted then do nothing
- if (self.matchFound !== null) {
- return;
- }
-
- // Get the address that the user clicked on (if any)
- const clickedAddress = document.querySelector(
- ".pac-container .pac-item:hover"
- );
-
- // If the Street Address field changed without clicking (i.e. by pressing Tab, or clicking outside the field)
- if (clickedAddress === null) {
- // Fill-in the address using first item in the list.
- fillInAddressUsingFirstItem();
- }
- });
-
- function fillInAddressUsingFirstItem() {
- // Fill-in the address using first item in the list.
- const item = document.querySelector(".pac-container .pac-item");
- if (item != null) {
- const firstResult = item.textContent;
- const geocoder = new window.google.maps.Geocoder();
- geocoder.geocode(
- {
- address: firstResult,
- },
- function (results, status) {
- if (status === window.google.maps.GeocoderStatus.OK) {
- fillInAddress(results[0]);
- self.displayVerificationWarning = true;
- }
- }
- );
- } else {
- self.matchFound = false;
}
}
- function fillInAddress(place) {
- if (!place) {
- place = autocomplete.getPlace();
- }
-
- if (place && place.address_components) {
- self.matchFound = true;
-
- self.$nextTick(function () {
- self.showAddressFields = true;
-
- for (const component of place.address_components) {
- const componentType = component.types[0];
-
- switch (componentType) {
- case "street_number": {
- self.addressModel.streetAddress = component.long_name;
- break;
- }
- case "route": {
- self.addressModel.streetAddress +=
- " " + component.short_name;
- break;
- }
- case "locality": {
- self.addressModel.city = component.long_name;
- break;
- }
- case "administrative_area_level_1": {
- self.addressModel.state = component.short_name;
- break;
- }
- case "postal_code": {
- self.addressModel.zipCode = component.long_name;
- break;
- }
- }
- }
-
- // after showing the address fields, disable the address autocomplete
- window.google.maps.event.removeListener(autocompleteListener);
- window.google.maps.event.clearInstanceListeners(autocomplete);
- addressField1.onchange = null;
- const pacContainer = document.querySelector(".pac-container");
- if (pacContainer) {
- pacContainer.remove();
- }
- });
- }
- }
- })
- .catch(() => {
- // Failed to fetch script
- console.log("Unable to load Google Places API script");
+ // After filling in the address fields, disable the address autocomplete
+ this.unloadAutocomplete();
});
+ }
+ },
+ fillInAddressUsingFirstItem() {
+ // Fill-in the address using first item in the list.
+ const item = document.querySelector(".pac-container .pac-item");
+ if (item != null) {
+ const firstResult = item.textContent;
+ const geocoder = new window.google.maps.Geocoder();
+ const self = this;
+ geocoder.geocode(
+ {
+ address: firstResult,
+ },
+ function (results, status) {
+ if (status === window.google.maps.GeocoderStatus.OK) {
+ self.fillInAddress(results[0]);
+ self.displayVerificationWarning = true;
+ }
+ }
+ );
+ } else {
+ this.matchFound = false;
+ }
},
resetAlerts() {
this.displayVerificationWarning = false;
},
+ unloadAutocomplete() {
+ if (this.autocompleteListener && this.autocomplete) {
+ window.google.maps.event.removeListener(this.autocompleteListener);
+ this.autocompleteListener = null;
+
+ window.google.maps.event.clearInstanceListeners(this.autocomplete);
+ this.autocomplete = null;
+
+ this.addressField1.onchange = null;
+
+ const pacContainer = document.querySelector(".pac-container");
+ if (pacContainer) {
+ pacContainer.remove();
+ }
+ }
+ },
},
mounted() {
- this.setupAddressLookup();
+ // If we already have a full address, show it
+ this.showAddressFields =
+ (this.addressModel.streetAddress ?? "") !== "" &&
+ (this.addressModel.city ?? "") !== "" &&
+ (this.addressModel.state ?? "") !== "" &&
+ (this.addressModel.zipCode ?? "") !== "";
+
+ if (!this.showAddressFields) {
+ this.loadGooglePlacesAutocompleteScript();
+ }
+ },
+ unmounted() {
+ this.unloadAutocomplete();
},
watch: {
matchFound: {
@@ -398,7 +412,7 @@ export default {
this.addressModel.state = "";
this.addressModel.zipCode = "";
}
- this.showAddressFields = true;
+
this.displayVerificationWarning = false;
// Only deep watch the Address Model after a failed match
@@ -414,11 +428,6 @@ export default {
}
},
},
- modelValue: {
- handler() {
- this.setupAddressLookup();
- },
- },
},
components: {
textboxQuestion,
diff --git a/src/layouts/license-plate-lookup/license-plate-lookup.vue b/src/layouts/license-plate-lookup/license-plate-lookup.vue
index 03b56a4cb..a3579318d 100644
--- a/src/layouts/license-plate-lookup/license-plate-lookup.vue
+++ b/src/layouts/license-plate-lookup/license-plate-lookup.vue
@@ -301,12 +301,18 @@ export default {
);
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.email, false);
+
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_LOCATION,
{
- zipCode: this.serviceZipCode,
+ address: "",
+ address2: "",
+ city: "",
state: resultMap.serviceZipValidationResponse.state,
+ zipCode: this.serviceZipCode,
zipCodeCtu: resultMap.serviceZipValidationResponse.zipCodeCtu,
+ appointmentType: "",
+ isVehicleProtected: null,
},
false
);
diff --git a/src/layouts/service-location/appointment-type-question/appointment-type-question.vue b/src/layouts/service-location/appointment-type-question/appointment-type-question.vue
index 82dd2980a..cce1a4d5b 100644
--- a/src/layouts/service-location/appointment-type-question/appointment-type-question.vue
+++ b/src/layouts/service-location/appointment-type-question/appointment-type-question.vue
@@ -95,8 +95,10 @@ export default {
diff --git a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
index 39f64d890..eebaaab07 100644
--- a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
+++ b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue
@@ -32,24 +32,27 @@
:footerButtonText="modalFooterText"
:onModalOpenedCallback="onModalOpened"
:onModalClosedCallback="onModalClosed"
+ @isModalOpened="setModalStatus"
@footer-button-event="setMobileLocation">
-
-
-
-
+
+
+
+
+
+
@@ -81,6 +84,8 @@ export default {
return {
internalModel: deepClone(this.modelValue),
displayInvalidZipAlert: false,
+ addressQuestionsKey: 0,
+ isModalOpened: false,
};
},
setup(props) {
@@ -184,6 +189,9 @@ export default {
modalFooterText() {
return this.getCmsContent(this.modalWidgetName, "FooterText");
},
+ modal() {
+ return this.$refs[this.modalName];
+ },
addressModel: {
get: function () {
return this.modelValue.addressQuestions;
@@ -192,48 +200,23 @@ export default {
},
methods: {
openModal() {
- this.$refs[this.modalName].openModal();
- },
- closeModal() {
- this.$refs[this.modalName].closeModal();
+ this.modal.openModal();
},
onModalOpened() {
this.internalModel = deepClone(this.modelValue);
},
+ setModalStatus(isOpened) {
+ this.isModalOpened = isOpened;
+ },
+ closeModal() {
+ this.modal.closeModal();
+ },
onModalClosed() {
this.displayInvalidZipAlert = false;
this.internalModel = deepClone(this.modelValue);
- this.resetValidation();
- },
- resetComponent(updatedServiceZipCodeInfo) {
- // Reset the validation form, setting the initial values
- // for the state and zipCode to those that were entered
- // on the service-zip-modal-question component
- this.$refs[this.modalName].resetForm({
- values: {
- autocomplete: updatedServiceZipCodeInfo.streetAddress,
- city: updatedServiceZipCodeInfo.city,
- state: updatedServiceZipCodeInfo.state,
- zipCode: updatedServiceZipCodeInfo.zipCode,
- isVehicleProtected: updatedServiceZipCodeInfo.isVehicleProtected,
- },
- });
},
resetModalButtonStyle() {
- this.$refs[this.modalName].resetButtonStyle();
- },
- resetValidation() {
- this.$refs.addressQuestions.resetAlerts();
-
- this.$refs[this.modalName].resetForm({
- values: {
- autocomplete: this.internalModel.addressQuestions.streetAddress,
- city: this.internalModel.addressQuestions.city,
- state: this.internalModel.addressQuestions.state,
- zipCode: this.internalModel.addressQuestions.zipCode,
- isVehicleProtected: this.internalModel.isVehicleProtected,
- },
- });
+ this.modal.resetButtonStyle();
},
async setMobileLocation() {
if (
@@ -283,14 +266,6 @@ export default {
this.internalModel = deepClone(newValue);
this.handleChange(newValue);
-
- this.resetComponent({
- streetAddress: newValue.addressQuestions.streetAddress,
- city: newValue.addressQuestions.city,
- state: newValue.addressQuestions.state,
- zipCode: newValue.addressQuestions.zipCode,
- isVehicleProtected: newValue.isVehicleProtected,
- });
},
deep: true,
},
diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue
index 8f63e5df7..b44f1c64c 100644
--- a/src/layouts/service-location/service-location.vue
+++ b/src/layouts/service-location/service-location.vue
@@ -122,10 +122,10 @@ import { errorMessages } from "@/constants/error-messages";
// DEFINE VALIDATION RULES
defineRule("mobile-location-required", (value) => {
if (
- value.addressQuestions.streetAddress == "" ||
- value.addressQuestions.city == "" ||
- value.addressQuestions.state == "" ||
- value.addressQuestions.zipCode == "" ||
+ value.addressQuestions.streetAddress == null ||
+ value.addressQuestions.city == null ||
+ value.addressQuestions.state == null ||
+ value.addressQuestions.zipCode == null ||
value.isVehicleProtected == null
) {
return errorMessages.MOBILE_LOCATION_REQUIRED;
diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue
index 3a2b79a8c..c1f2dd7ec 100644
--- a/src/layouts/vin-lookup/vin-lookup.vue
+++ b/src/layouts/vin-lookup/vin-lookup.vue
@@ -325,9 +325,14 @@ export default {
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
- zipCode: this.serviceZipCode,
+ address: "",
+ address2: "",
+ city: "",
state: resultMap.zipCodeData.state,
+ zipCode: this.serviceZipCode,
zipCodeCtu: resultMap.zipCodeData.zipCodeCtu,
+ appointmentType: "",
+ isVehicleProtected: null,
},
false
);
@@ -352,8 +357,8 @@ export default {
{
address: vehicleRegistrationInfo.address,
city: vehicleRegistrationInfo.city,
- zipCode: vehicleRegistrationInfo.zipCode,
state: vehicleRegistrationInfo.state,
+ zipCode: vehicleRegistrationInfo.zipCode,
zipCodeCtu: zipCodeData.zipCodeCtu,
},
false
@@ -362,9 +367,14 @@ export default {
await this.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
- zipCode: this.serviceZipCode,
+ address: null,
+ address2: null,
+ city: null,
state: zipCodeData.state,
+ zipCode: this.serviceZipCode,
zipCodeCtu: zipCodeData.zipCodeCtu,
+ appointmentType: null,
+ isVehicleProtected: null,
},
false
);
diff --git a/src/store/index.js b/src/store/index.js
index 6c95aa81b..83220bc3b 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -253,9 +253,15 @@ export const mutations = {
state.order.serviceLocation.appointmentType = serviceLocationInfo.appointmentType;
state.order.serviceLocation.isVehicleProtected = serviceLocationInfo.isVehicleProtected;
- if (serviceLocationInfo.provider) {
- state.order.serviceLocation.provider = serviceLocationInfo.provider;
- }
+ state.order.serviceLocation.provider = {
+ providerNumber: serviceLocationInfo.provider?.providerNumber,
+ address: {
+ streetAddress: serviceLocationInfo.provider?.address?.streetAddress,
+ city: serviceLocationInfo.provider?.address?.city,
+ state: serviceLocationInfo.provider?.address?.state,
+ zip: serviceLocationInfo.provider?.address?.zip,
+ },
+ };
},
updateSchedule(state, scheduleInfo) {
if (scheduleInfo) {
@@ -362,7 +368,15 @@ export const mutations = {
state.order.serviceLocation.appointmentType = null;
},
resetServiceLocationProvider(state) {
- state.order.serviceLocation.provider = null;
+ state.order.serviceLocation.provider = {
+ providerNumber: null,
+ address: {
+ streetAddress: null,
+ city: null,
+ state: null,
+ zip: null,
+ },
+ };
},
resetServiceLocationMobileAddress(state) {
state.order.serviceLocation.address = null;