├── .gitignore
├── images
└── logo.png
├── js
├── script.js
├── fake-results.json
├── shop.js
├── fake-products.json
└── ask.js
├── .prettierrc.json
├── README.md
├── css
├── style.css
├── footer.css
├── main.css
├── results.css
├── button.css
├── products.css
├── global.css
├── search.css
├── ask.css
└── header.css
├── .vscode
└── settings.json
├── index.html
├── about
└── index.html
├── shop
└── index.html
└── ask
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/orangespaceman/tfs-project-ai-frontend/HEAD/images/logo.png
--------------------------------------------------------------------------------
/js/script.js:
--------------------------------------------------------------------------------
1 | import ask from "./ask.js";
2 | import shop from "./shop.js";
3 |
4 | ask.init();
5 | shop.init();
6 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": false
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AI Project Frontend
2 |
3 | This is an example frontend for the AI project.
4 |
5 | You will want to fork this repository so that you can integrate the API and make modifications to the design and functionality.
6 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | @import url("global.css");
2 | @import url("header.css");
3 | @import url("main.css");
4 | @import url("button.css");
5 | @import url("ask.css");
6 | @import url("results.css");
7 | @import url("search.css");
8 | @import url("products.css");
9 | @import url("footer.css");
10 |
--------------------------------------------------------------------------------
/css/footer.css:
--------------------------------------------------------------------------------
1 | /* Footer */
2 | .footer {
3 | display: flex;
4 | justify-content: space-between;
5 | align-items: center;
6 | margin-left: auto;
7 | margin-right: auto;
8 | max-width: var(--width-max);
9 | padding: var(--spacing-l);
10 | }
11 | .footer__link {
12 | text-decoration: none;
13 | }
14 |
--------------------------------------------------------------------------------
/css/main.css:
--------------------------------------------------------------------------------
1 | .main {
2 | padding: var(--spacing-l);
3 | max-width: var(--width-max);
4 | margin-left: auto;
5 | margin-right: auto;
6 | margin-top: calc(-1 * var(--spacing-xl));
7 | background: var(--colour-background-white);
8 | border-radius: var(--border-radius-l);
9 | box-shadow: var(--box-shadow);
10 | }
11 |
--------------------------------------------------------------------------------
/js/fake-results.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "title": "Jupiter",
4 | "description": "A big planet"
5 | },
6 | {
7 | "title": "Saturn",
8 | "description": "A planet with rings"
9 | },
10 | {
11 | "title": "The Moon",
12 | "description": "Not a planet"
13 | },
14 | {
15 | "title": "Aliens",
16 | "description": "The truth is out there..."
17 | }
18 | ]
19 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "[javascript]": {
4 | "editor.defaultFormatter": "esbenp.prettier-vscode"
5 | },
6 | "[json]": {
7 | "editor.defaultFormatter": "esbenp.prettier-vscode"
8 | },
9 | "[html]": {
10 | "editor.defaultFormatter": "esbenp.prettier-vscode"
11 | },
12 | "[css]": {
13 | "editor.defaultFormatter": "esbenp.prettier-vscode"
14 | },
15 | "[jsonc]": {
16 | "editor.defaultFormatter": "esbenp.prettier-vscode"
17 | },
18 | "[javascriptreact]": {
19 | "editor.defaultFormatter": "esbenp.prettier-vscode"
20 | }
21 | }
--------------------------------------------------------------------------------
/css/results.css:
--------------------------------------------------------------------------------
1 | /* Results */
2 | .results {
3 | display: none;
4 | text-align: left;
5 | padding: var(--spacing-l);
6 | border-top: 2px solid var(--colour-background-light);
7 | }
8 |
9 | .results.is-shown {
10 | display: block;
11 | }
12 |
13 | .results__list {
14 | display: flex;
15 | flex-direction: column;
16 | align-items: stretch;
17 | gap: var(--spacing-l);
18 | }
19 |
20 | .results__item {
21 | background: var(--colour-background-white);
22 | color: var(--colour-text-dark);
23 | padding: var(--spacing-l);
24 | border: 1px solid var(--colour-background-light);
25 | border-radius: var(--border-radius-l);
26 | }
27 |
28 | .results__show-more {
29 | margin-top: var(--spacing-l);
30 | padding: var(--spacing-l);
31 | text-align: center;
32 | }
33 |
--------------------------------------------------------------------------------
/css/button.css:
--------------------------------------------------------------------------------
1 | .buttons {
2 | display: flex;
3 | gap: var(--spacing-l);
4 | align-items: center;
5 | }
6 |
7 | .button {
8 | padding: var(--spacing-s) var(--spacing-m);
9 | border-radius: var(--border-radius-s);
10 | cursor: pointer;
11 | transition: all 0.25s;
12 | }
13 |
14 | .button:disabled {
15 | opacity: 0.3;
16 | pointer-events: none;
17 | }
18 |
19 | .button--primary {
20 | border: 1px solid var(--colour-primary);
21 | background: var(--colour-primary);
22 | color: var(--colour-text-light);
23 | }
24 |
25 | .button--primary:hover {
26 | background: var(--colour-background-white);
27 | color: var(--colour-primary);
28 | }
29 |
30 | .button--secondary {
31 | background: transparent;
32 | border: 1px solid var(--colour-background-light);
33 | color: var(--colour-primary);
34 | }
35 |
36 | .button--secondary:hover {
37 | border-color: var(--colour-primary);
38 | }
39 |
--------------------------------------------------------------------------------
/css/products.css:
--------------------------------------------------------------------------------
1 | .products {
2 | display: none;
3 | text-align: left;
4 | padding: var(--spacing-l);
5 | border-top: 2px solid var(--colour-background-light);
6 | }
7 |
8 | .products.is-shown {
9 | display: block;
10 | }
11 |
12 | .products__list {
13 | display: flex;
14 | flex-wrap: wrap;
15 | flex-direction: row;
16 | align-items: stretch;
17 | gap: var(--spacing-l);
18 | }
19 |
20 | .products__item {
21 | background: var(--colour-background-white);
22 | color: var(--colour-text-dark);
23 | padding: var(--spacing-l);
24 | border: 1px solid var(--colour-background-light);
25 | border-radius: var(--border-radius-l);
26 | }
27 |
28 | @media (min-width: 800px) {
29 | .products__item {
30 | width: 31%;
31 | }
32 | }
33 |
34 | .products__item-image {
35 | width: 100%;
36 | }
37 |
38 | .products__show-more {
39 | margin-top: var(--spacing-l);
40 | padding: var(--spacing-l);
41 | text-align: center;
42 | }
43 |
--------------------------------------------------------------------------------
/css/global.css:
--------------------------------------------------------------------------------
1 | /* Variables */
2 | :root {
3 | --colour-primary: #6200ea;
4 | --colour-secondary: #ff0080;
5 | --colour-background-white: white;
6 | --colour-background-light: #f0f0f0;
7 | --colour-background-dark: black;
8 | --colour-text-light: white;
9 | --colour-text-dark: black;
10 |
11 | --width-max: 800px;
12 |
13 | --spacing-xs: 8px;
14 | --spacing-s: 10px;
15 | --spacing-m: 15px;
16 | --spacing-l: 20px;
17 | --spacing-xl: 40px;
18 | --spacing-xxl: 80px;
19 |
20 | --font-size-heading: 48px;
21 | --font-size-subheading: 24px;
22 | --font-size-body: 16px;
23 | --font-size-small: 12px;
24 | --font-weight-bold: bold;
25 | --font-family-body: Arial, sans-serif;
26 |
27 | --border-radius-s: 5px;
28 | --border-radius-m: 10px;
29 | --border-radius-l: 20px;
30 |
31 | --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
32 | }
33 |
34 | *,
35 | *:before,
36 | *:after {
37 | box-sizing: border-box;
38 | }
39 |
40 | /* General Styles */
41 | body {
42 | font-family: var(--font-family-body);
43 | margin: 0;
44 | padding: 0;
45 | text-align: center;
46 | }
47 |
--------------------------------------------------------------------------------
/css/search.css:
--------------------------------------------------------------------------------
1 | .search {
2 | padding: var(--spacing-xl) var(--spacing-l);
3 | text-align: left;
4 | }
5 |
6 | .search__input-wrapper {
7 | display: flex;
8 | flex-direction: column;
9 | gap: var(--spacing-l);
10 | }
11 |
12 | @media (min-width: 800px) {
13 | .search__input-wrapper {
14 | flex-direction: row;
15 | }
16 | }
17 |
18 | .search__input {
19 | flex: 1;
20 | border: 1px solid var(--colour-background-light);
21 | border-radius: var(--border-radius-s);
22 | background: var(--colour-background-light);
23 | color: var(--colour-text-dark);
24 | padding: var(--spacing-l);
25 | font-size: var(--font-size-body);
26 | font-family: var(--font-family-body);
27 | line-height: 1.5;
28 | }
29 |
30 | .search__submit {
31 | padding-left: var(--spacing-xl);
32 | padding-right: var(--spacing-xl);
33 | }
34 |
35 | .search__footer {
36 | margin-top: var(--spacing-l);
37 | display: flex;
38 | justify-content: space-between;
39 | align-items: center;
40 | }
41 |
42 | .search__result-count {
43 | font-size: var(--font-size-small);
44 | font-style: italic;
45 | }
46 |
47 | .search__loading {
48 | display: none;
49 | }
50 |
51 | .search__loading.is-loading {
52 | display: block;
53 | }
54 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Home | Ask the Brains
7 |
8 |
9 |
10 |
11 |
28 |
29 | ...
30 |
31 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/about/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | About | Ask the Brains
7 |
8 |
9 |
10 |
11 |
28 |
29 | ...
30 |
31 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/css/ask.css:
--------------------------------------------------------------------------------
1 | /* Ask Section */
2 | .ask {
3 | padding: var(--spacing-xl) var(--spacing-l);
4 | text-align: left;
5 | }
6 |
7 | .ask__label {
8 | display: block;
9 | font-size: var(--font-size-subheading);
10 | margin-bottom: var(--spacing-l);
11 | }
12 |
13 | .ask__input-wrapper {
14 | position: relative;
15 | }
16 |
17 | .ask__input {
18 | width: 100%;
19 | border: 1px solid var(--colour-background-light);
20 | border-radius: var(--border-radius-s);
21 | background: var(--colour-background-light);
22 | color: var(--colour-text-dark);
23 | padding: var(--spacing-l);
24 | font-size: var(--font-size-body);
25 | font-family: var(--font-family-body);
26 | line-height: 1.5;
27 | }
28 |
29 | .ask__button-example {
30 | position: absolute;
31 | z-index: 2;
32 | right: var(--spacing-l);
33 | top: var(--spacing-l);
34 | background: none;
35 | border: none;
36 | cursor: pointer;
37 | opacity: 1;
38 | transition: opacity 0.25s;
39 | }
40 |
41 | .ask__button-example.is-hidden {
42 | opacity: 0;
43 | pointer-events: none;
44 | }
45 |
46 | .ask__footer {
47 | margin-top: var(--spacing-l);
48 | }
49 |
50 | @media (min-width: 800px) {
51 | .ask__footer {
52 | display: flex;
53 | justify-content: space-between;
54 | align-items: center;
55 | }
56 | }
57 |
58 | .ask__char-count {
59 | font-size: var(--font-size-small);
60 | font-style: italic;
61 | }
62 |
63 | .ask__char-count.has-error {
64 | color: var(--colour-secondary);
65 | }
66 |
67 | .ask__loading {
68 | display: none;
69 | }
70 |
71 | .ask__loading.is-loading {
72 | display: block;
73 | }
74 |
--------------------------------------------------------------------------------
/css/header.css:
--------------------------------------------------------------------------------
1 | /* Header */
2 | .header {
3 | background: linear-gradient(
4 | to bottom left,
5 | var(--colour-primary),
6 | var(--colour-secondary)
7 | );
8 | color: var(--colour-text-light);
9 | padding-top: var(--spacing-xl);
10 | padding-bottom: var(--spacing-xxl);
11 | }
12 |
13 | .header__content {
14 | max-width: var(--width-max);
15 | display: flex;
16 | flex-direction: column;
17 | margin: 0 auto;
18 | justify-content: space-between;
19 | align-items: center;
20 | padding: var(--spacing-l);
21 | }
22 |
23 | @media (min-width: 800px) {
24 | .header__content {
25 | flex-direction: row;
26 | }
27 | }
28 |
29 | /* header nav */
30 |
31 | .header__nav {
32 | border-bottom: 1px solid var(--colour-text-light);
33 | padding-bottom: 5px;
34 | display: flex;
35 | gap: var(--spacing-l);
36 | margin-top: var(--spacing-l);
37 | }
38 |
39 | @media (min-width: 800px) {
40 | .header__nav {
41 | margin-top: 0;
42 | }
43 | }
44 |
45 | .header__link {
46 | color: var(--colour-text-light);
47 | text-decoration: none;
48 | position: relative;
49 | }
50 |
51 | .header__link::after {
52 | content: "";
53 | display: block;
54 | position: absolute;
55 | bottom: -5px;
56 | left: 0;
57 | right: 0;
58 | border-bottom: 2px solid var(--colour-text-light);
59 | opacity: 0;
60 | transition: all 0.25s;
61 | }
62 |
63 | .header__link:hover::after,
64 | .header__link--active::after {
65 | opacity: 1;
66 | }
67 |
68 | /* header titles */
69 |
70 | .header__titles {
71 | padding-left: var(--spacing-l);
72 | padding-right: var(--spacing-l);
73 | }
74 |
75 | .header__title {
76 | font-size: var(--font-size-heading);
77 | }
78 |
79 | .header__subtitle {
80 | font-size: var(--font-size-subheading);
81 | max-width: 700px;
82 | margin-left: auto;
83 | margin-right: auto;
84 | }
85 |
--------------------------------------------------------------------------------
/shop/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Buy the brains | Ask the Brains
7 |
8 |
9 |
10 |
11 |
31 |
32 |
49 |
50 | Products
51 |
52 |
53 |
54 |
55 |
56 |
57 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/ask/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Ask | Ask the Brains
7 |
8 |
9 |
10 |
11 |
31 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
55 | Loading...
56 |
57 |
58 | Results
59 |
60 |
61 |
62 |
63 |
64 |
65 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/js/shop.js:
--------------------------------------------------------------------------------
1 | class Shop {
2 | constructor() {
3 | this.searchContainer = document.querySelector(".search");
4 | if (this.searchContainer) {
5 | this.searchInput = this.searchContainer.querySelector(".search__input");
6 | this.searchButton = this.searchContainer.querySelector(".search__submit");
7 | this.searchResultCount = this.searchContainer.querySelector(
8 | ".search__result-count"
9 | );
10 | this.loading = this.searchContainer.querySelector(".search__loading");
11 |
12 | this.productsContainer = document.querySelector(".products");
13 | this.productsList =
14 | this.productsContainer.querySelector(".products__list");
15 | }
16 | }
17 |
18 | init() {
19 | if (!this.searchContainer) return;
20 | this.searchInput.addEventListener("input", (e) => this.checkInput(e));
21 | this.searchButton.addEventListener("click", (e) => this.search(e));
22 | this.checkInput();
23 | this.search();
24 | }
25 |
26 | checkInput() {
27 | this.searchButton.disabled = this.searchInput.value.length === 0;
28 | }
29 |
30 | async search(e) {
31 | if (e) e.preventDefault();
32 |
33 | this.loading.classList.add("is-loading");
34 | this.productsContainer.classList.remove("is-shown");
35 | this.searchResultCount.textContent = "";
36 |
37 | while (this.productsList.firstChild) {
38 | this.productsList.removeChild(this.productsList.lastChild);
39 | }
40 |
41 | const url = "../js/fake-products.json";
42 | try {
43 | const response = await fetch(url);
44 | if (!response.ok) {
45 | throw new Error(`Response status: ${response.status}`);
46 | }
47 |
48 | await setTimeout(async () => {
49 | const json = await response.json();
50 | this.processProducts(json);
51 | this.loading.classList.remove("is-loading");
52 | }, 1000);
53 | } catch (error) {
54 | console.error(error.message);
55 | this.loading.classList.remove("is-loading");
56 | }
57 | }
58 |
59 | processProducts(data) {
60 | const searchTerm = this.searchInput.value.toLowerCase();
61 | const filteredProducts = data.filter(
62 | (product) =>
63 | product.title.toLowerCase().includes(searchTerm) ||
64 | product.description.toLowerCase().includes(searchTerm)
65 | );
66 |
67 | this.searchResultCount.textContent = `${filteredProducts.length} products found`;
68 |
69 | if (filteredProducts.length > 0) {
70 | this.productsContainer.classList.add("is-shown");
71 | } else {
72 | this.productsContainer.classList.remove("is-shown");
73 | }
74 |
75 | filteredProducts.forEach((product) => {
76 | const productsItem = document.createElement("div");
77 | productsItem.classList.add("products__item");
78 | this.productsList.appendChild(productsItem);
79 |
80 | const productsItemImage = document.createElement("img");
81 | productsItemImage.classList.add("products__item-image");
82 | productsItemImage.src = product.img;
83 | productsItem.appendChild(productsItemImage);
84 |
85 | const productsItemTitle = document.createElement("h3");
86 | productsItemTitle.classList.add("products__item-title");
87 | productsItemTitle.textContent = product.title;
88 | productsItem.appendChild(productsItemTitle);
89 |
90 | const productsItemDescription = document.createElement("p");
91 | productsItemDescription.classList.add("products__item-description");
92 | productsItemDescription.textContent = product.description;
93 | productsItem.appendChild(productsItemDescription);
94 |
95 | const productsItemStars = document.createElement("p");
96 | productsItemStars.classList.add("products__item-stars");
97 | productsItemStars.textContent = "⭐".repeat(product.stars);
98 | productsItem.appendChild(productsItemStars);
99 |
100 | const productsItemPrice = document.createElement("p");
101 | productsItemPrice.classList.add("products__item-price");
102 | productsItemPrice.textContent = product.price;
103 | productsItem.appendChild(productsItemPrice);
104 | });
105 | }
106 | }
107 |
108 | // Expose an instance of the 'Shop' class
109 | export default new Shop();
110 |
--------------------------------------------------------------------------------
/js/fake-products.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "img": "https://placehold.co/600x400/0000FF/FFFFFF?text=Galactic+Telescope",
4 | "title": "Galactic Telescope",
5 | "description": "A high-powered telescope for stargazing and exploring deep space.",
6 | "stars": 5,
7 | "price": "£199.99"
8 | },
9 | {
10 | "img": "https://placehold.co/600x400/FF0000/FFFFFF?text=Martian+Rover+Toy",
11 | "title": "Martian Rover Toy",
12 | "description": "A detailed model of the latest Mars exploration rover.",
13 | "stars": 4,
14 | "price": "£29.99"
15 | },
16 | {
17 | "img": "https://placehold.co/600x400/00FF00/000000?text=Lunar+Globe",
18 | "title": "Lunar Globe",
19 | "description": "A glow-in-the-dark moon globe with detailed craters and landmarks.",
20 | "stars": 5,
21 | "price": "£14.99"
22 | },
23 | {
24 | "img": "https://placehold.co/600x400/FFFF00/000000?text=NASA+Backpack",
25 | "title": "NASA Backpack",
26 | "description": "A stylish and durable backpack with an official NASA logo.",
27 | "stars": 4,
28 | "price": "£34.99"
29 | },
30 | {
31 | "img": "https://placehold.co/600x400/000000/FFFFFF?text=Solar+System+Model",
32 | "title": "Solar System Model",
33 | "description": "An educational 3D model of our solar system with rotating planets.",
34 | "stars": 5,
35 | "price": "£24.99"
36 | },
37 | {
38 | "img": "https://placehold.co/600x400/800080/FFFFFF?text=Astronaut+Helmet",
39 | "title": "Astronaut Helmet",
40 | "description": "A replica astronaut helmet with working LED lights and a retractable visor.",
41 | "stars": 4,
42 | "price": "£49.99"
43 | },
44 | {
45 | "img": "https://placehold.co/600x400/FF4500/FFFFFF?text=Rocket+Lamp",
46 | "title": "Rocket Lamp",
47 | "description": "A decorative lamp in the shape of a launching rocket.",
48 | "stars": 4,
49 | "price": "£19.99"
50 | },
51 | {
52 | "img": "https://placehold.co/600x400/FFFFFF/0000FF?text=Black+Hole+Poster",
53 | "title": "Black Hole Poster",
54 | "description": "A stunning high-resolution print of a black hole.",
55 | "stars": 3,
56 | "price": "£9.99"
57 | },
58 | {
59 | "img": "https://placehold.co/600x400/00CED1/000000?text=Zero+Gravity+Pen",
60 | "title": "Zero Gravity Pen",
61 | "description": "A pen that writes in zero gravity, underwater, and upside down.",
62 | "stars": 5,
63 | "price": "£12.99"
64 | },
65 | {
66 | "img": "https://placehold.co/600x400/32CD32/FFFFFF?text=Moon+Base+LEGO+Set",
67 | "title": "Moon Base LEGO Set",
68 | "description": "A fun and detailed LEGO set for building your own moon base.",
69 | "stars": 5,
70 | "price": "£49.99"
71 | },
72 | {
73 | "img": "https://placehold.co/600x400/FFA500/000000?text=Comet+Puzzle",
74 | "title": "Comet Puzzle",
75 | "description": "A challenging 1000-piece puzzle of a stunning comet streaking across space.",
76 | "stars": 4,
77 | "price": "£15.99"
78 | },
79 | {
80 | "img": "https://placehold.co/600x400/00008B/FFFFFF?text=Saturn+Ring+Replica",
81 | "title": "Saturn Ring Replica",
82 | "description": "A scientifically accurate scale model of Saturn and its rings.",
83 | "stars": 4,
84 | "price": "£39.99"
85 | },
86 | {
87 | "img": "https://placehold.co/600x400/DA70D6/000000?text=Aurora+Borealis+Lamp",
88 | "title": "Aurora Borealis Lamp",
89 | "description": "A colour-changing lamp simulating the Northern Lights.",
90 | "stars": 5,
91 | "price": "£27.99"
92 | },
93 | {
94 | "img": "https://placehold.co/600x400/B22222/FFFFFF?text=Space+Trivia+Game",
95 | "title": "Space Trivia Game",
96 | "description": "A fun and educational game testing your knowledge of space.",
97 | "stars": 3,
98 | "price": "£18.99"
99 | },
100 | {
101 | "img": "https://placehold.co/600x400/2E8B57/FFFFFF?text=Galaxy+Projector",
102 | "title": "Galaxy Projector",
103 | "description": "A projector that fills your room with moving stars and nebulae.",
104 | "stars": 5,
105 | "price": "£44.99"
106 | },
107 | {
108 | "img": "https://placehold.co/600x400/8B0000/FFFFFF?text=ISS+Model+Kit",
109 | "title": "ISS Model Kit",
110 | "description": "A build-your-own model of the International Space Station.",
111 | "stars": 4,
112 | "price": "£59.99"
113 | }
114 | ]
115 |
--------------------------------------------------------------------------------
/js/ask.js:
--------------------------------------------------------------------------------
1 | class Ask {
2 | maxLength = 160;
3 |
4 | constructor() {
5 | this.askContainer = document.querySelector(".ask");
6 | if (this.askContainer) {
7 | this.askInput = this.askContainer.querySelector(".ask__input");
8 | this.exampleButton = this.askContainer.querySelector(
9 | ".ask__button-example"
10 | );
11 | this.askButton = this.askContainer.querySelector(".ask__button-ask");
12 | this.resetButton = this.askContainer.querySelector(".ask__button-reset");
13 | this.charCounter = this.askContainer.querySelector(".ask__char-count");
14 | this.loading = this.askContainer.querySelector(".ask__loading");
15 |
16 | this.resultsContainer = document.querySelector(".results");
17 | this.resultsList = this.resultsContainer.querySelector(".results__list");
18 | }
19 | }
20 |
21 | init() {
22 | if (!this.askContainer) return;
23 | this.askInput.addEventListener("input", (e) => this.checkInput(e));
24 | this.exampleButton.addEventListener("click", (e) => this.setExample(e));
25 | this.askButton.addEventListener("click", (e) => this.askClicked(e));
26 | this.resetButton.addEventListener("click", (e) => this.resetClicked(e));
27 | this.checkInput();
28 | }
29 |
30 | checkInput() {
31 | // check submission validity
32 | const charsRemaining = this.maxLength - this.askInput.value.length;
33 | if (charsRemaining < 0) {
34 | this.askButton.disabled = true;
35 | this.charCounter.classList.add("has-error");
36 | } else {
37 | this.askButton.disabled = false;
38 | this.charCounter.classList.remove("has-error");
39 | }
40 | this.charCounter.textContent = `${charsRemaining} characters remaining`;
41 |
42 | // check whether to display example button
43 | if (this.askInput.value.length === 0) {
44 | this.askButton.disabled = true;
45 | this.exampleButton.classList.remove("is-hidden");
46 | } else {
47 | this.exampleButton.classList.add("is-hidden");
48 | }
49 | }
50 |
51 | setExample(event) {
52 | event.preventDefault();
53 | console.log("setting example");
54 | this.askInput.value =
55 | "Tell me about some of the best things I could see with a telescope from Brighton (assuming it ever stops raining)";
56 | this.checkInput();
57 | }
58 |
59 | resetClicked(event) {
60 | event.preventDefault();
61 | this.askInput.value = "";
62 | this.checkInput();
63 | }
64 |
65 | async askClicked(event) {
66 | event.preventDefault();
67 | this.loading.classList.add("is-loading");
68 |
69 | const url = "../js/fake-results.json";
70 | try {
71 | const response = await fetch(url);
72 | if (!response.ok) {
73 | throw new Error(`Response status: ${response.status}`);
74 | }
75 |
76 | // fake a one second wait, use the two lines below for an instant response
77 | // const json = await response.json();
78 | // this.processResults(json);
79 |
80 | await setTimeout(async () => {
81 | const json = await response.json();
82 | this.processResults(json);
83 | this.loading.classList.remove("is-loading");
84 | }, 1000);
85 | } catch (error) {
86 | console.error(error.message);
87 | this.loading.classList.remove("is-loading");
88 | }
89 | }
90 |
91 | processResults(data) {
92 | if (data.length > 0) {
93 | this.resultsContainer.classList.add("is-shown");
94 | } else {
95 | this.resultsContainer.classList.remove("is-shown");
96 | }
97 |
98 | data.forEach((result) => {
99 | const resultsItem = document.createElement("div");
100 | resultsItem.classList.add("results__item");
101 | this.resultsList.appendChild(resultsItem);
102 |
103 | const resultsItemTitle = document.createElement("h3");
104 | resultsItemTitle.classList.add("results__item-title");
105 | resultsItemTitle.textContent = result.title;
106 | resultsItem.appendChild(resultsItemTitle);
107 |
108 | const resultsItemDescription = document.createElement("p");
109 | resultsItemDescription.classList.add("results__item-description");
110 | resultsItemDescription.textContent = result.description;
111 | resultsItem.appendChild(resultsItemDescription);
112 | });
113 | }
114 | }
115 |
116 | // Expose an instance of the 'Ask' class
117 | export default new Ask();
118 |
--------------------------------------------------------------------------------