From 6476972508bd3f50df785cf73dfc84eea082b132 Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Mon, 4 Dec 2023 19:06:36 -0600 Subject: [PATCH] deeper typescript integration --- Unbinder/.eslintrc.js | 5 --- Unbinder/Views/Recipe/Search.cshtml | 31 ++------------- Unbinder/wwwroot/ts/createRecipe.ts | 2 +- Unbinder/wwwroot/ts/searchRecipes.ts | 52 +++++++++++++++++++++++++ Unbinder/wwwroot/ts/types/index.ts | 2 + Unbinder/wwwroot/ts/types/models.ts | 25 ++++++++++++ Unbinder/wwwroot/ts/types/validators.ts | 37 ++++++++++++++++++ 7 files changed, 121 insertions(+), 33 deletions(-) delete mode 100644 Unbinder/.eslintrc.js create mode 100644 Unbinder/wwwroot/ts/searchRecipes.ts create mode 100644 Unbinder/wwwroot/ts/types/index.ts create mode 100644 Unbinder/wwwroot/ts/types/models.ts create mode 100644 Unbinder/wwwroot/ts/types/validators.ts diff --git a/Unbinder/.eslintrc.js b/Unbinder/.eslintrc.js deleted file mode 100644 index 59f25f9..0000000 --- a/Unbinder/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - extends: [ - 'semistandard' - ] -}; diff --git a/Unbinder/Views/Recipe/Search.cshtml b/Unbinder/Views/Recipe/Search.cshtml index ebfe937..dd2850d 100644 --- a/Unbinder/Views/Recipe/Search.cshtml +++ b/Unbinder/Views/Recipe/Search.cshtml @@ -1,12 +1,13 @@ @model IEnumerable @{ - string InputValue = ViewBag.Query ?? String.Empty; + string InputValue = ViewBag.Query ?? ""; + string InitialMessage = ViewBag.Query == null ? "Search for a recipe" : $"Search results for '{ViewBag.Query}'"; }
-

+

@InitialMessage

@@ -18,28 +19,4 @@ }
- \ No newline at end of file + \ No newline at end of file diff --git a/Unbinder/wwwroot/ts/createRecipe.ts b/Unbinder/wwwroot/ts/createRecipe.ts index 8c850e9..e3cb921 100644 --- a/Unbinder/wwwroot/ts/createRecipe.ts +++ b/Unbinder/wwwroot/ts/createRecipe.ts @@ -1,4 +1,4 @@ -export async function getAllIngredients () { +async function getAllIngredients () { const ingredients = await fetch('/api/ingredients'); return await ingredients.json(); } diff --git a/Unbinder/wwwroot/ts/searchRecipes.ts b/Unbinder/wwwroot/ts/searchRecipes.ts new file mode 100644 index 0000000..4a093dd --- /dev/null +++ b/Unbinder/wwwroot/ts/searchRecipes.ts @@ -0,0 +1,52 @@ +import { isRecipeArray, type Recipe } from "./types"; + +function setSearchResultMessage(model: Recipe[]): void { + const message = document.getElementById("query-message"); + message.innerHTML = `Viewing ${model.length} results for: ${window.location.search.split("=")[1]}`; +} + +async function handleSearch(textField?: HTMLInputElement) { + if (!textField) { + console.log("NO TEXT FIELD!"); + return; + } + + const newResult = await fetch(`/api/recipe/search?q=${textField.value}`); + const newResultJson = await newResult.json(); + const searchResultsContainer = document.getElementById("search-results"); + + if (isRecipeArray(newResultJson)) { + let updatedHTML = ""; + for (const recipe of newResultJson) { + updatedHTML += ` +

${recipe.Name}

+

${recipe.ShortDescription}

+ `; + } + + console.log(updatedHTML); + + setSearchResultMessage(newResultJson); + searchResultsContainer.innerHTML = updatedHTML; + } +} + +document.addEventListener('DOMContentLoaded', () => { + console.log("Loaded DOM"); + + // dom elements + const searchButton = document.getElementById("execute-search"); + + const inputNodes = document.querySelectorAll("input"); + let textField: HTMLInputElement | null = null; + + inputNodes.forEach(node => { + if (node.id == "new-search-query") { + textField = node; + console.log("Found text field"); + } + }); + + // handle search updates on client side + searchButton.onclick = async () => handleSearch(textField); +}); \ No newline at end of file diff --git a/Unbinder/wwwroot/ts/types/index.ts b/Unbinder/wwwroot/ts/types/index.ts new file mode 100644 index 0000000..05df7f4 --- /dev/null +++ b/Unbinder/wwwroot/ts/types/index.ts @@ -0,0 +1,2 @@ +export * from "./models"; +export * from "./validators"; \ No newline at end of file diff --git a/Unbinder/wwwroot/ts/types/models.ts b/Unbinder/wwwroot/ts/types/models.ts new file mode 100644 index 0000000..73d47f2 --- /dev/null +++ b/Unbinder/wwwroot/ts/types/models.ts @@ -0,0 +1,25 @@ +export type Recipe = { + RecipeId: number, + Name: string, + ShortDescription: string, + S3Url?: string, + Author?: string, + RecipeText?: string, + MainImageUrl?: string, +} + +export type Ingredient = { + IngredientId: number, + Name: string, + Description?: string +} + +export type RecipeIngredient = { + RecipeIngredientId: number, + RecipeId: number, + IngredientId: number, + Amount?: number, + Unit?: string, +} + +export type IngredientWithDetails = Ingredient & Pick \ No newline at end of file diff --git a/Unbinder/wwwroot/ts/types/validators.ts b/Unbinder/wwwroot/ts/types/validators.ts new file mode 100644 index 0000000..6c854ec --- /dev/null +++ b/Unbinder/wwwroot/ts/types/validators.ts @@ -0,0 +1,37 @@ +import type { Ingredient, Recipe } from "./models"; + +export function isRecipe(input: unknown): input is Recipe { + return ( + typeof input == 'object' + && input != null + && 'RecipeId' in input + && 'Name' in input + && 'ShortDescription' in input + && (input as Recipe).RecipeId != undefined + && (input as Recipe).Name != undefined + && (input as Recipe).ShortDescription != undefined + ); +} + +export function isArrayOfType(input: unknown, isType: (input: unknown) => input is T): input is T[] { + return Array.isArray(input) && input.every(isType); +} + +export function isRecipeArray(input: unknown): input is Recipe[] { + return isArrayOfType(input, isRecipe); +} + +export function isIngredient(input: unknown): input is Ingredient { + return ( + typeof input == 'object' + && input != null + && 'IngredientId' in input + && 'Name' in input + && (input as Ingredient).IngredientId != undefined + && (input as Ingredient).Name != undefined + ); +} + +export function isIngredientArray(input: unknown): input is Ingredient[] { + return isArrayOfType(input, isIngredient); +} \ No newline at end of file