diff --git a/src/digital-components/textbox-question/textbox-question.vue b/src/digital-components/textbox-question/textbox-question.vue index c514a0f81..8eb1bb1d1 100644 --- a/src/digital-components/textbox-question/textbox-question.vue +++ b/src/digital-components/textbox-question/textbox-question.vue @@ -222,13 +222,6 @@ export default { }, mounted() { this.$emit("textboxQuestionEvent.inputIdAssigned", this.inputId); - // const element = document.querySelector(`#${this.inputId}`, null); - - // element.addEventListener("change", () => { - // this.$nextTick(() => { - // console.log(window.getComputedStyle(element, ":-internal-autofill")); - // }); - // }); }, watch: { async value(newValue) { @@ -354,33 +347,5 @@ export default { p { display: none; } - - :-webkit-autofill { - animation-name: onAutoFillStart; - } - :-webkit-autofill:focus { - animation-name: onAutoFillSelected; - } - :not(:-webkit-autofill) { - animation-name: onAutoFillCancel; - } - @keyframes onAutoFillStart { - from { - } - to { - } - } - @keyframes onAutoFillSelected { - from { - } - to { - } - } - @keyframes onAutoFillCancel { - from { - } - to { - } - } } diff --git a/src/fmg-components/address-questions/address-questions.spec.js b/src/fmg-components/address-questions/address-questions.spec.js index f0d508943..829442783 100644 --- a/src/fmg-components/address-questions/address-questions.spec.js +++ b/src/fmg-components/address-questions/address-questions.spec.js @@ -15,6 +15,9 @@ describe("address-questions.vue", () => { autocompleteElement = document.createElement("input"); autocompleteElement.getPlace = jest.fn(); document.getElementById = jest.fn().mockReturnValue(autocompleteElement); + document.getElementsByClassName = jest + .fn() + .mockReturnValue([document.createElement("div")]); }); describe("initial state", () => { @@ -199,26 +202,26 @@ describe("address-questions.vue", () => { expect(wrapper.vm.$loadScript).not.toHaveBeenCalled(); }); - test("address field is focused => disable autofill", async () => { - // Arrange - let focusEventCallbackFunction; - autocompleteElement.addEventListener = jest - .fn() - .mockImplementation((eventName, callbackFunction) => { - if (eventName == "focus") { - focusEventCallbackFunction = callbackFunction; - } - }); - const { wrapper } = setupMocks({}); - await wrapper.vm.$nextTick(); + // test("address field is focused => disable autofill", async () => { + // // Arrange + // let focusEventCallbackFunction; + // autocompleteElement.addEventListener = jest + // .fn() + // .mockImplementation((eventName, callbackFunction) => { + // if (eventName == "focus") { + // focusEventCallbackFunction = callbackFunction; + // } + // }); + // const { wrapper } = setupMocks({}); + // await wrapper.vm.$nextTick(); - // Act - focusEventCallbackFunction(); - await wrapper.vm.$nextTick(); + // // Act + // focusEventCallbackFunction(); + // await wrapper.vm.$nextTick(); - // Assert - expect(autocompleteElement.getAttribute("autocomplete")).toEqual("do-not-autofill"); - }); + // // Assert + // expect(autocompleteElement.getAttribute("autocomplete")).toEqual("do-not-autofill"); + // }); test("street address is entered, user chooses good result from autocomplete results => other fields are filled in", async () => { // Arrange @@ -289,90 +292,90 @@ describe("address-questions.vue", () => { }); }); - test("street address is entered, but user clicks away => first result is selected and other fields are filled in", async () => { - // Arrange - let changeEventCallbackFunction; - autocompleteElement.addEventListener = jest - .fn() - .mockImplementation((eventName, callbackFunction) => { - if (eventName == "change") { - changeEventCallbackFunction = callbackFunction; - } - }); + // test("street address is entered, but user clicks away => first result is selected and other fields are filled in", async () => { + // // Arrange + // let changeEventCallbackFunction; + // autocompleteElement.addEventListener = jest + // .fn() + // .mockImplementation((eventName, callbackFunction) => { + // if (eventName == "change") { + // changeEventCallbackFunction = callbackFunction; + // } + // }); - const { wrapper } = setupMocks({ - querySelectorFunction: function (query) { - if (query == ".pac-container .pac-item") { - let element = document.createElement("div"); - element.textContent = "123 Test Street"; - return element; - } - }, - geocoderResult: { - address_components: [ - { - long_name: "1234", - short_name: "1234", - types: ["street_number"], - }, - { - long_name: "Test Road", - short_name: "Test Road", - types: ["route"], - }, - { - long_name: "East Columbus", - short_name: "Columbus", - types: ["neighborhood", "political"], - }, - { - long_name: "Columbus", - short_name: "Columbus", - types: ["locality", "political"], - }, - { - long_name: "Franklin County", - short_name: "Franklin County", - types: ["administrative_area_level_2", "political"], - }, - { - long_name: "Ohio", - short_name: "OH", - types: ["administrative_area_level_1", "political"], - }, - { - long_name: "United States", - short_name: "US", - types: ["country", "political"], - }, - { - long_name: "43215", - short_name: "43215", - types: ["postal_code"], - }, - ], - }, - }); + // const { wrapper } = setupMocks({ + // querySelectorFunction: function (query) { + // if (query == ".pac-container .pac-item") { + // let element = document.createElement("div"); + // element.textContent = "123 Test Street"; + // return element; + // } + // }, + // geocoderResult: { + // address_components: [ + // { + // long_name: "1234", + // short_name: "1234", + // types: ["street_number"], + // }, + // { + // long_name: "Test Road", + // short_name: "Test Road", + // types: ["route"], + // }, + // { + // long_name: "East Columbus", + // short_name: "Columbus", + // types: ["neighborhood", "political"], + // }, + // { + // long_name: "Columbus", + // short_name: "Columbus", + // types: ["locality", "political"], + // }, + // { + // long_name: "Franklin County", + // short_name: "Franklin County", + // types: ["administrative_area_level_2", "political"], + // }, + // { + // long_name: "Ohio", + // short_name: "OH", + // types: ["administrative_area_level_1", "political"], + // }, + // { + // long_name: "United States", + // short_name: "US", + // types: ["country", "political"], + // }, + // { + // long_name: "43215", + // short_name: "43215", + // types: ["postal_code"], + // }, + // ], + // }, + // }); - let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); - let verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" }); - expect(noMatchAlert.exists()).toBeFalsy(); - expect(verificationAlert.exists()).toBeFalsy(); + // let noMatchAlert = wrapper.findComponent({ ref: "alertNoMatchWarning" }); + // let verificationAlert = wrapper.findComponent({ ref: "alertVerificationWarning" }); + // expect(noMatchAlert.exists()).toBeFalsy(); + // expect(verificationAlert.exists()).toBeFalsy(); - await wrapper.vm.$nextTick(); + // await wrapper.vm.$nextTick(); - // Act - changeEventCallbackFunction(); + // // Act + // changeEventCallbackFunction(); - await wrapper.vm.$nextTick(); + // await wrapper.vm.$nextTick(); - // Assert - const addressModel = wrapper.vm.addressModel; - expect(addressModel.streetAddress).toEqual("1234 Test Road"); - expect(addressModel.city).toEqual("Columbus"); - expect(addressModel.state).toEqual("OH"); - expect(addressModel.zipCode).toEqual("43215"); - }); + // // Assert + // const addressModel = wrapper.vm.addressModel; + // expect(addressModel.streetAddress).toEqual("1234 Test Road"); + // expect(addressModel.city).toEqual("Columbus"); + // expect(addressModel.state).toEqual("OH"); + // expect(addressModel.zipCode).toEqual("43215"); + // }); }); describe("alerts", () => { diff --git a/src/fmg-components/address-questions/address-questions.vue b/src/fmg-components/address-questions/address-questions.vue index 839981b27..edeedf718 100644 --- a/src/fmg-components/address-questions/address-questions.vue +++ b/src/fmg-components/address-questions/address-questions.vue @@ -220,6 +220,7 @@ export default { ).then(() => { // When loaded, trigger the setup this.initializeAutocomplete(); + this.addressField1.focus(); }); }, initializeAutocomplete() { @@ -227,7 +228,7 @@ export default { this.autocomplete = new window.google.maps.places.Autocomplete(this.addressField1, { componentRestrictions: { country: ["us"] }, fields: ["address_components"], - types: ["geocode"], + types: ["address"], }); // Set up the Autocomplete place_changed event to call our method to fill in the address @@ -245,63 +246,50 @@ export default { 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"); - }); - - // When the user starts entering text into Street Address textbox disable autofill - this.addressField1.addEventListener("input", (e) => { - this.addressField1.setAttribute("autocomplete", "do-not-autofill"); }); this.addressField1.addEventListener("keydown", (e) => { + // When the user presses a key in the Street Address field, immediately disable autocomplete for that field. + // For some reason we have to explicitly tell FireFox to set it to "off" which doesn't actually disable autofill + // then set it to "new-password" which does disable it on both WebKit and FireFox. ¯\_(ツ)_/¯ + this.addressField1.setAttribute("autocomplete", "off"); + this.addressField1.setAttribute("autocomplete", "new-password"); + // If a match has been previously attempted then do nothing if (this.matchFound !== null) { return; } - 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" - ); + const firstNameField = document.getElementById("firstName"); - 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. + // Grab the selected item + const selectedItem = document.querySelector(".pac-item-selected"); + + if (selectedItem == null) { + // If there is no selected item, fill-in the address using first item from the list. this.fillInAddressUsingFirstItem(); + firstNameField.focus(); } } else { return; } }); - - this.addressField1.addEventListener("change", () => { - // If a match has been previously attempted then do nothing - if (this.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. - this.fillInAddressUsingFirstItem(); - } - }); + }, + findAddressComponentByType(place, componentName, componentLength) { + const component = place.address_components.find((component) => + component.types.find((type) => type == componentName) + ); + if (component) { + return component[componentLength] ?? ""; + } else { + return ""; + } }, fillInAddress(place) { if (!place) { @@ -310,51 +298,101 @@ export default { 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]; + const streetNumber = this.findAddressComponentByType( + place, + "street_number", + "long_name" + ); + const route = this.findAddressComponentByType(place, "route", "short_name"); + const city = this.findAddressComponentByType(place, "locality", "long_name"); - 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; - } - } + let state = ""; + let zipCode = ""; + + // If city has an exact match, fill in the state from the autocomplete. + if (city != "") { + state = this.findAddressComponentByType( + place, + "administrative_area_level_1", + "short_name" + ); } + // If state has an exact match, fill in the zipCode from the autocomplete. + if (state != "") { + zipCode = this.findAddressComponentByType( + place, + "postal_code", + "long_name" + ); + } + + const isAddressComplete = + (streetNumber ?? "") !== "" && + (route ?? "") !== "" && + (city ?? "") !== "" && + (state ?? "") !== "" && + (zipCode ?? "") !== ""; + + self.addressModel.streetAddress = `${streetNumber} ${route}`; + self.addressModel.city = city; + self.addressModel.state = state; + self.addressModel.zipCode = zipCode; + + if (!this.displayVerificationWarning) { + this.displayVerificationWarning = !isAddressComplete || place.partial_match; + } + + // 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 filling in the address fields, disable the address autocomplete + this.unloadAutocomplete(); // Restore focus to the first address field - this.addressField1.focus(); + //this.addressField1.focus(); }); } }, fillInAddressUsingFirstItem() { // Fill-in the address using first item in the list. - const item = document.querySelector(".pac-container .pac-item"); + const item = document.querySelector(".pac-container .pac-item > .pac-item-query"); + const item2 = document.querySelector(".pac-container .pac-item > span:nth-child(3)"); + //#streetAddressField > div.pac-container.pac-logo > div:nth-child(1) > span:nth-child(3) + if (item != null) { - const firstResult = item.textContent; + let firstResult = `${item.textContent}, ${item2.textContent}`; + const geocoder = new window.google.maps.Geocoder(); const self = this; geocoder.geocode( @@ -363,6 +401,8 @@ export default { }, function (results, status) { if (status === window.google.maps.GeocoderStatus.OK) { + console.log(results[0]); + self.fillInAddress(results[0]); self.displayVerificationWarning = true; } @@ -406,13 +446,26 @@ export default { const element = document.getElementsByClassName("address-questions")[0]; element.addEventListener("change", (e) => { - const inputs = element.querySelectorAll("input:-internal-autofill-selected"); - this.showAddressFields = inputs.length > 0; - // if (this.showAddressFields) { - // this.unloadAutocomplete(); - // } + let autoFilledInputs = []; + autoFilledInputs = element.querySelectorAll("input:-webkit-autofill"); + if (this.showAddressFields) { + return; + } + + this.showAddressFields = autoFilledInputs.length > 0; + if (this.showAddressFields) { + this.unloadAutocomplete(); + } + + this.addressField1.classList.remove("has-icon"); }); }, + beforeUpdate() { + // It is necessary to set focus on the street address on this lifecycle hook when this component is used in a modal. + if (!this.addressField1.matches(":focus")) { + this.addressField1.focus(); + } + }, unmounted() { this.unloadAutocomplete(); },