feat: search for books

This commit is contained in:
Brian Pooe 2023-10-11 16:46:46 +02:00
parent 6aae6b14dd
commit 4f75e1a2ea
22 changed files with 426 additions and 6 deletions

27
nx.json
View file

@ -4,17 +4,31 @@
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build", "lint", "test", "e2e"]
"cacheableOperations": [
"build",
"lint",
"test",
"e2e"
]
}
}
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
"dependsOn": [
"^build"
],
"inputs": [
"production",
"^production"
]
},
"test": {
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"]
"inputs": [
"default",
"^production",
"{workspaceRoot}/jest.preset.js"
]
},
"lint": {
"inputs": [
@ -26,7 +40,10 @@
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"default": [
"{projectRoot}/**/*",
"sharedGlobals"
],
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",

View file

@ -3,7 +3,8 @@
"version": "0.0.0",
"license": "MIT",
"scripts": {
"part1": "nx run part-1-html-css:serve"
"part1": "nx run part-1-html-css:serve",
"part2": "nx run part-2-javascript:serve"
},
"private": true,
"devDependencies": {

View file

@ -0,0 +1,33 @@
{
"extends": [
"../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

8
part-2-javascript/.swcrc Normal file
View file

@ -0,0 +1,8 @@
{
"jsc": {
"parser": {
"syntax": "typescript"
},
"target": "es2016"
}
}

View file

@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: 'part-2-javascript',
preset: '../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
transform: {
'^.+\\.[tj]s$': '@swc/jest'
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../coverage/part-2-javascript'
};

View file

@ -0,0 +1,86 @@
{
"name": "part-2-javascript",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "part-2-javascript/src",
"tags": [],
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": [
"{options.outputPath}"
],
"defaultConfiguration": "production",
"options": {
"outputPath": "dist/part-2-javascript",
"compiler": "swc",
"main": "part-2-javascript/src/main.ts",
"tsConfig": "part-2-javascript/tsconfig.app.json",
"webpackConfig": "part-2-javascript/webpack.config.js",
"assets": [
"part-2-javascript/src/assets"
],
"index": "part-2-javascript/src/index.html",
"baseHref": "/",
"styles": [
"part-2-javascript/src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"fileReplacements": [
{
"replace": "part-2-javascript/src/environments/environment.ts",
"with": "part-2-javascript/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"options": {
"buildTarget": "part-2-javascript:build"
},
"configurations": {
"production": {
"buildTarget": "part-2-javascript:build:production"
}
}
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": [
"part-2-javascript/**/*.ts"
]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": [
"{workspaceRoot}/coverage/{projectRoot}"
],
"options": {
"jestConfig": "part-2-javascript/jest.config.ts",
"passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}
}
}

View file

@ -0,0 +1,47 @@
/*
* Remove template code below
*/
html {
-webkit-text-size-adjust: 100%;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
line-height: 1.5;
tab-size: 4;
scroll-behavior: smooth;
}
body {
font-family: inherit;
line-height: inherit;
margin: 0;
}
h1,
h2,
p,
pre {
margin: 0;
}
*,
::before,
::after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: currentColor;
}
h1,
h2 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
text-decoration: inherit;
}

View file

@ -0,0 +1,21 @@
import { AppElement } from './app.element';
describe('AppElement', () => {
let app: AppElement;
beforeEach(() => {
app = new AppElement();
});
it('should create successfully', () => {
expect(app).toBeTruthy();
});
it('should have a greeting', () => {
app.connectedCallback();
expect(app.querySelector('h1').innerHTML).toContain(
'Welcome part-2-javascript'
);
});
});

View file

@ -0,0 +1,88 @@
import "./app.element.scss";
import { Book } from "./book.interface";
export class AppElement extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div class="container mx-auto sm:px-6 lg:px-8">
<!-- Search -->
<label for="search" class="block text-sm font-medium leading-6 text-gray-900">Quick search</label>
<div class="relative mt-2 flex items-center">
<input type="text" name="search" id="search" class="block w-full rounded-md border-0 py-1.5 pr-14 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" data-search>
</div>
<div>
<h2 class="text-sm font-medium text-gray-500 py-6">Books</h2>
<ul role="list" class="mt-3 grid grid-cols-1 gap-5 sm:grid-cols-2 sm:gap-6 lg:grid-cols-4" data-book-cards-container></ul>
<template data-book-card-template>
<li class="col-span-1 flex rounded-md shadow-sm">
<div class="flex w-16 flex-shrink-0 items-center justify-center bg-[#9BD83A] rounded-l-md text-sm font-medium text-gray-900 hover:text-gray-600" data-initials></div>
<div class="flex flex-1 items-center justify-between truncate rounded-r-md border-b border-r border-t border-gray-200 bg-white hover:text-gray-600">
<div class="flex-1 truncate px-4 py-2 text-sm">
<a href="#" class="font-medium text-gray-900 hover:text-gray-600" data-title></a><br>
<a href="#" class="font-medium text-gray-900 hover:text-gray-600" data-author></a>
<p class="text-gray-500" data-year></p>
</div>
</div>
</li>
</template>
</div>
</div>
`;
const searchInput = this.querySelector("[data-search]");
const bookCardTemplate: any = this.querySelector("[data-book-card-template]");
const bookCardContainer: HTMLDivElement = this.querySelector("[data-book-cards-container]");
let books = [];
fetch("assets/books.json")
.then(res => res.json())
.then(data => {
books = data.map((book: Book) => {
const card = bookCardTemplate.content.cloneNode(true).children[0];
const initials = card.querySelector("[data-initials]");
const title = card.querySelector("[data-title]");
const author = card.querySelector("[data-author]");
const year = card.querySelector("[data-year]");
initials.textContent = book.initials;
title.textContent = book.title;
author.textContent = book.author;
year.textContent = book.year;
bookCardContainer.append(card);
return { initials: book.initials, title: book.title, author: book.author, year: book.year, element: card };
});
});
// Create a function to filter the books array based on the search input value.
const filterBooks = (value: string) => {
return books.filter((book) => {
return book.author.toLowerCase().includes(value) ||
book.title.toLowerCase().includes(value);
});
};
// Add a change event listener to the search input field.
searchInput.addEventListener("input", (e: any) => {
// Get the search input value.
const value = e.target.value.toLowerCase();
// Filter the books array based on the search input value.
const filteredBooks = filterBooks(value);
// Update the visibility of the book elements.
books.forEach((book) => {
book.element.classList.toggle("hidden", !filteredBooks.includes(book));
});
// Update UI when no books are found
});
}
}
customElements.define("app-root", AppElement);

View file

@ -0,0 +1,6 @@
export interface Book {
author: string;
initials: string;
title: string;
year: number;
}

View file

View file

@ -0,0 +1,26 @@
[
{
"author": "F. Scott Fitzgerald",
"initials": "FS",
"title": "The Great Gatsby",
"year": 1925
},
{
"author": "Harper Lee",
"initials": "HL",
"title": "To Kill a Mockingbird",
"year": 1960
},
{
"author": "Stephenie Meyer",
"initials": "SM",
"title": "Twilight",
"year": 2005
},
{
"author": "Stephen King",
"initials": "S",
"title": "If It Bleeds",
"year": 2020
}
]

View file

@ -0,0 +1,3 @@
export const environment = {
production: true
};

View file

@ -0,0 +1,6 @@
// This file can be replaced during build by using the `fileReplacements` array.
// When building for production, this file is replaced with `environment.prod.ts`.
export const environment = {
production: false
};

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en" class="h-full bg-gray-100">
<head>
<meta charset="utf-8" />
<title>Part2Javascript</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="https://www.payspace.com/wp-content/uploads/2018/03/PS-FAVICON-NEW-2017.png" sizes="32x32" />
<link rel="icon" href="https://www.payspace.com/wp-content/uploads/2018/03/PS-FAVICON-NEW-2017.png" sizes="192x192" />
<link rel="apple-touch-icon" href="https://www.payspace.com/wp-content/uploads/2018/03/PS-FAVICON-NEW-2017.png" />
<meta name="msapplication-TileImage"
content="https://www.payspace.com/wp-content/uploads/2018/03/PS-FAVICON-NEW-2017.png" />
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="h-full">
<app-root></app-root>
</body>
</html>

View file

@ -0,0 +1 @@
import './app/app.element';

View file

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View file

View file

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"types": ["node"]
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View file

@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View file

@ -0,0 +1,9 @@
const { composePlugins, withNx } = require('@nx/webpack');
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
// Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())`
return config;
});