deeper typescript integration
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: [
|
|
||||||
'semistandard'
|
|
||||||
]
|
|
||||||
};
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
@model IEnumerable<Recipe>
|
@model IEnumerable<Recipe>
|
||||||
|
|
||||||
@{
|
@{
|
||||||
string InputValue = ViewBag.Query ?? String.Empty;
|
string InputValue = ViewBag.Query ?? "";
|
||||||
|
string InitialMessage = ViewBag.Query == null ? "Search for a recipe" : $"Search results for '{ViewBag.Query}'";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div id="search-page-header" class="mb-8 border-white border-b">
|
<div id="search-page-header" class="mb-8 border-white border-b">
|
||||||
<h3 id="query-message"></h3>
|
<h3 id="query-message">@InitialMessage</h3>
|
||||||
<input value="@InputValue" class="text-black" id="new-search-query" type="text" />
|
<input value="@InputValue" class="text-black" id="new-search-query" type="text" />
|
||||||
<button id="execute-search">Search</button>
|
<button id="execute-search">Search</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,28 +19,4 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script href="~/js/searchRecipes.js"></script>
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
// dom elements
|
|
||||||
const query = window.location.search?.split("=")[1];
|
|
||||||
const message = document.getElementById("query-message");
|
|
||||||
const textField = document.getElementById("new-search-query");
|
|
||||||
const searchButton = document.getElementById("execute-search");
|
|
||||||
const searchResultsContainer = document.getElementById("search-results");
|
|
||||||
|
|
||||||
console.log(query);
|
|
||||||
|
|
||||||
// set initial message value
|
|
||||||
message.innerHTML = query ? `Viewing ${@Model.ToArray().Length} results for: ${query}` : "Enter a new search term below:";
|
|
||||||
|
|
||||||
// handle search updates on client side
|
|
||||||
searchButton.onclick = () => {
|
|
||||||
// const newResult = await fetch(`/api/recipe/search?q=${textField.value}`);
|
|
||||||
|
|
||||||
// console.log(newResult);
|
|
||||||
|
|
||||||
// const newResultJson = await newResult.json();
|
|
||||||
window.location.search = `?q=${textField.value}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export async function getAllIngredients () {
|
async function getAllIngredients () {
|
||||||
const ingredients = await fetch('/api/ingredients');
|
const ingredients = await fetch('/api/ingredients');
|
||||||
return await ingredients.json();
|
return await ingredients.json();
|
||||||
}
|
}
|
||||||
|
|||||||
52
Unbinder/wwwroot/ts/searchRecipes.ts
Normal file
52
Unbinder/wwwroot/ts/searchRecipes.ts
Normal file
@@ -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 += `
|
||||||
|
<p>${recipe.Name}</p>
|
||||||
|
<p>${recipe.ShortDescription}</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
2
Unbinder/wwwroot/ts/types/index.ts
Normal file
2
Unbinder/wwwroot/ts/types/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./models";
|
||||||
|
export * from "./validators";
|
||||||
25
Unbinder/wwwroot/ts/types/models.ts
Normal file
25
Unbinder/wwwroot/ts/types/models.ts
Normal file
@@ -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<RecipeIngredient, 'Amount' | 'Unit'>
|
||||||
37
Unbinder/wwwroot/ts/types/validators.ts
Normal file
37
Unbinder/wwwroot/ts/types/validators.ts
Normal file
@@ -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<T>(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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user