diff --git a/.gitignore b/.gitignore index 27d9177e..e4d5814c 100644 --- a/.gitignore +++ b/.gitignore @@ -101,3 +101,6 @@ venv.bak/ # mypy .mypy_cache/ + +# node_modules +node_modules \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 4c464fdf..d7f63bbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,6 +34,9 @@ WORKDIR /app COPY ./requirements.txt /app/requirements.txt RUN pip install --no-cache-dir -r requirements.txt +# Install npm +RUN apt-get update && apt-get install -y npm + # Install Node requirements COPY ./package.json /app/package.json RUN npm install diff --git a/README.md b/README.md index a6b6a2a3..66517f54 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,14 @@ this assessment. To get started, fork this repo and follow the instructions below. +## Completed By + +Hi, my name is [Dan Grossberg](https://github.com/dwgrossberg), and I'm grateful that you're taking the time to review +my application code challenge. Changes have been committed throughout the dev process +and there are numerous comments explaining my thought process where applicable. +Please feel free to reach out with any questions or comments and I will be happy to +answer! + ## Installation Development requires a local installation of [Docker](https://docs.docker.com/install/) diff --git a/package-lock.json b/package-lock.json index 04492877..b5cd3533 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1043,4 +1043,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 16980833..8cba86cc 100644 --- a/package.json +++ b/package.json @@ -4,5 +4,9 @@ "description": "JavaScript development setup for the DataMade Code Challenge", "devDependencies": { "eslint": "^7.23.0" + }, + "dependencies": { + "docker": "^1.0.0", + "docker-compose": "^0.24.8" } } diff --git a/parserator_web/static/css/custom.css b/parserator_web/static/css/custom.css index 79084665..d870a871 100644 --- a/parserator_web/static/css/custom.css +++ b/parserator_web/static/css/custom.css @@ -1 +1,21 @@ /* Add custom style overrides here. */ +#error-container { + background-color: #e34d3f; + width: 100%; + padding: 7px 13px; + margin-top: 10px; + border-radius: 5px; + gap: 5px; +} + +#close-button, #error-message { + font-family: inherit; + font-size: inherit; + color: white; + background-color: #e34d3f; + border: none; +} + +#error-message { + margin-top: 13px; +} \ No newline at end of file diff --git a/parserator_web/static/js/index.js b/parserator_web/static/js/index.js index 492674cc..ca0a70c7 100644 --- a/parserator_web/static/js/index.js +++ b/parserator_web/static/js/index.js @@ -1,2 +1,111 @@ -/* TODO: Flesh this out to connect the form to the API and render results - in the #address-results div. */ +// This function triggers an eslint Parsing error: The keyword 'const' is reserved, +// which I was unable to resolve in a satisfactory manner +const parseAddress = async (address) => { + /** + * Sends a user-inputted address to the api/parse API endpoint and calls a function to display + * the results or the error, if applicable. + * @param {string} address The address to parse, input by the user + */ + const url = "api/parse/?address=" + address + try { + const response = await fetch(url, { + method: "GET" + }); + // Check that the response is valid + if (!response.ok) { + throw new Error(`Response status: ${response.status}`); + } else { + const data = await response.json(); + // Check for input string API errors + if (data.Error) { + displayError(data.Error); + } else if (data.ParseError) { + displayError(data.ParseError); + } else if (data.RepeatedLabelError) { + displayError(data.RepeatedLabelError); + // Otherwise display the address parse results + } else { + displayAddress(data); + } + displayAddress(data); + } + // Catch any error messages -- 500 statusq + } catch (error) { + displayError(error.message); + } +}; + +const displayAddress = (addressData) => { + /** + * Displays the address response to the DOM if the parse was successful. + * @param {Object} addressData A dictionary object containing the input_string, + * address_components, and address_type. + */ + // Clear input value + document.getElementById("address").value = ""; + // Remove error if applicable + document.getElementById("error-container").style.display = "None"; + const addressResults = document.getElementById("address-results"); + // Show results table + addressResults.style.display = "block"; + // Display address type + document.getElementById("parse-type").innerText = addressData.address_type; + // Loop through address_components and add new rows to results table + const tableBody = document.getElementsByTagName('tbody')[0]; + // Clear previous search results + while (tableBody.firstChild) { + tableBody.removeChild(tableBody.lastChild); + } + for (let [tag, part] of Object.entries(addressData.address_components)) { + let resultsRow = document.createElement("tr"); + let partData = document.createElement("td"); + let tagData = document.createElement("td"); + partData.innerText = part; + tagData.innerText = tag; + resultsRow.appendChild(partData); + resultsRow.appendChild(tagData); + tableBody.appendChild(resultsRow); + } +} + +const displayError = (error) => { + /** + * Displays any errors encountered to the user via the DOM + * @param {Error} error The error data returned from the api/parse API endpoint + */ + // Clear address results if applicable + document.getElementById("address-results").style.display = "None"; + const errorContainer = document.getElementById("error-container"); + const errorText = document.getElementById("error-message"); + errorContainer.style.display = "flex"; + if (error === "Response status: 500") { + errorText.textContent = "Unable to parse this value due to repeated labels. Our team has been notified of the error."; + } else if (error === "no_value") { + errorText.textContent = "Please provide a value to parse." + } else { + errorText.textContent = error; + } +} + +// Handle form submission +const addressForm = document.getElementsByTagName("form")[0]; +console.log(addressForm); +addressForm.addEventListener("submit", (e) => { + e.preventDefault(); + const addressData = new FormData(e.target).get("address"); + // Check for blank string or string of whitespace + if (addressData) { + parseAddress(addressData); + } else { + // Otherwise display error message + displayError("no_value") + } +}) + +// Handle close error message event +const errorContainer = document.getElementById("error-container"); +const errorCloseButton = document.getElementById("close-button"); +errorCloseButton.addEventListener("mousedown", () => { + document.getElementById("address").value = ""; + errorContainer.style.display = "None"; +}) \ No newline at end of file diff --git a/parserator_web/templates/parserator_web/index.html b/parserator_web/templates/parserator_web/index.html index a72d9c80..073c155a 100644 --- a/parserator_web/templates/parserator_web/index.html +++ b/parserator_web/templates/parserator_web/index.html @@ -14,6 +14,11 @@