Skip to content
This repository was archived by the owner on Dec 11, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/webapp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<link rel="stylesheet" href="search-meal-style.css"></link>
<link rel="stylesheet" href="style.css"></link>
<script src="https://unpkg.com/vue"></script>
<script src="service.js"></script>
<script src="search-meal-script.js"></script>
<script src="script.js"></script>
<script type="module" src="main.js"></script>
Expand All @@ -29,4 +30,4 @@
</div>
</div>
</body>
</html>
</html>
16 changes: 6 additions & 10 deletions src/main/webapp/meal-page-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Alias the service.
// Use the `RealMealService` to actually query the backend.
const service = FakeMealService;

function fetchMealInfo() {
// use mapping /meal.html?id=<id>
// fetches form server by action meal/<id>
Expand All @@ -22,14 +26,7 @@ function fetchMealInfo() {
window.location.replace("error.html");
}

fetch(`/meal/${id}`)
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
window.location.replace("error.html");
}
return response.json();
})
service.getOne(id)
.then((meal) => {
const { title, description, ingredients, type } = meal;
const titleElement = document.getElementById("title");
Expand All @@ -54,8 +51,7 @@ function redirectToSimilar() {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const pageId = urlParams.get("id") ?? 0;
fetch(`/meal/similar?id=${pageId.toString()}`)
.then((response) => response.json())
service.getSimilar(pageId.toString())
.then((id) => {
const url = `/meal.html?id=${id.toString()}`;
window.location.replace(url);
Expand Down
3 changes: 2 additions & 1 deletion src/main/webapp/meal.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<script src="https://unpkg.com/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyA0FR7vWT5PKPh-Sq95zAjcWXBFXRYl5xI&libraries=places"></script>
<script src="service.js"></script>
<script src="meal-page-script.js"></script>
<script src="script.js"></script>
<script type="module" src="main.js"></script>
Expand Down Expand Up @@ -39,4 +40,4 @@ <h2>Explore on a map:</h2>
</div>
</div>
</body>
</html>
</html>
38 changes: 0 additions & 38 deletions src/main/webapp/script.js

This file was deleted.

6 changes: 4 additions & 2 deletions src/main/webapp/search-meal-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// See a comment in meal-page-script.js
const service = FakeMealService;

function searchMeal() {
const searchLine = getQueryParam("query");
fetch(`/meal?query=${searchLine}`)
.then((response) => response.json())
service.query(searchLine)
.then((dishes) => {
const amount = document.getElementById('amount-block');
const isSingular = dishes.length == 1;
Expand Down
3 changes: 2 additions & 1 deletion src/main/webapp/search-results.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<link rel="stylesheet" href="style.css"></link>
<script src="https://unpkg.com/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.js"></script>
<script src="service.js"></script>
<script src="search-meal-script.js"></script>
<script src="script.js"></script>
<script type="module" src="main.js"></script>
Expand All @@ -30,4 +31,4 @@
</div>
</div>
</body>
</html>
</html>
75 changes: 75 additions & 0 deletions src/main/webapp/service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// This module exposes objects responsible for communicating with the backend.
// One "proper" version, talking to the real Java service, and one fake
// variant, returning hardcoded values, useful for local testing.
//
// All communication with the service should be done through one of the objects
// below.
// All exported objects should implement the same interface (the same set of
// public methods, each with the exact same signature). If we used TypeScript,
// we'd have defined an
// [interface](https://www.typescriptlang.org/docs/handbook/interfaces.html).
//
// NOTE(zajonc): the reason I've introduced this layer is because I wanted to
// detach the frontend part from the backend.
// Ideally, we'd like to be able to run a simple HTTP server serving the
// frontend, without the need to spin up Tomcat / Google App Engine.
// By mocking the service calls, we get rid of a strong dependency on the
// server.
// (TLDR: it's easier to test a Vue app this way).
// It's often a good idea to have a clear split between the frontend and the
// backend, though. In many cases they may even belong to separate
// repositories. You may even have multiple frontend apps for the same backend.

// Builds HTTP requests and forwards them to the right endpoint.
// NOTE: I wasn't really able to test it - I might have messed something up here.
const RealMealService = {
getOne: (mealId) =>
fetch(`/meal/${mealId}`).then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
window.location.replace("error.html");
}
return response.json();
}),
getSimilar: (mealId) =>
fetch(`/meal/similar?id=${mealId}`).then((response) => response.json()),
query: (queryContent) =>
fetch(`/meal?query=${queryContent}`).then((response) => response.json()),
};

// Fake variant, returning hardcoded values.
// No real network requests are made to the service.
// All methods return `Promise`s, for compatibility with the real service.
const FakeMealService = {
getOne: (mealId) => {
if (mealId == 1) {
return Promise.resolve(fakeMealA);
} else if (mealId == 2) {
return Promise.resolve(fakeMealB);
} else {
return Promise.resolve(null);
}
},
// If current meal is A, suggest B. Suggest A otherwise.
getSimilar: (mealId) =>
Promise.resolve(mealId == fakeMealA.id ? fakeMealB.id : fakeMealA.id),
// Return all known meals, regardless of the query.
query: (queryContent) => Promise.resolve([fakeMealA, fakeMealB]),
};

// NOTE(zajonc): I'm not too creative :/
const fakeMealA = {
id: 1,
title: "MealA",
description: "MealADescription",
ingredients: ["Ingredient1, Ingredient2"],
type: "MealAType",
};

const fakeMealB = {
id: 2,
title: "MealB",
description: "MealBDescription",
ingredients: ["Ingredient3, Ingredient4"],
type: "MealBType",
};
54 changes: 54 additions & 0 deletions src/main/webapp/vue-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# vue-app

A simple rewrite of the basic functionality of the client, using Vue.js for all frontend code.
Some features are most likely missing. The main goal is to set up and use a usable environment
for working with modern frontend apps.

## SPA

The result is a [Single Page App](https://en.wikipedia.org/wiki/Single-page_application).

The main difference between a SPA and a regular, oldschool app, is that there is only one html file
serving all subpages. It's content is dynamically rendered based on some magic JS code, usually provided
by a framework (here: Vue.js).

## Development

*Important*
To run any of the tools described below, you need to install [NodeJS](https://nodejs.org/en/) on your machine.

From a developer's perspective, one of the key differences between the old approach (including js files in a
HTML file, which populate the global namespace) and the modern approach used in this directory, is the introduction
of a build step - the files we write are not the same files that are seen by the browser.

A browser is able to render HTML files, which may define script dependencies as `<script>` tags. If we
defined every small piece of logic in a separate file, we'd have to include hundreds `script` tags in the final HTML.

What we can do instead is work on small modules during development, and then have a script bundle them together into
a single js file sent to the browser.
[Webpack](https://webpack.js.org/) is the most popular such tool.

There's many other fancy things we can do with our JS files, including:

- testing - https://jestjs.io/
- transpiling from TypeScript - https://www.typescriptlang.org/
- linting (statically checking for errors in code) - https://eslint.org/
... and many, many more.

## Vue-CLI

Not going into details, managing all those tools also gets unwieldy. Luckily, there's tools bundling those tools.
One such tool, tailored for working with Vue.js is [Vue-CLI](https://cli.vuejs.org/).

## meal-assistant-client
All client source code is stored under meal-assistant-client.
A large portion of the code has been generated by running `vue create meal-assistant-client`.
vue-cli produces a comprehensible README file. See meal-assistant-client/README.md for details how to use
the app.

### TLDR
```
cd meal-assistant-client
npm run install # install all dependencies. They'll be installed to node_modules/.
npm run serve # compile the bundle and start a local server. The app will be accessible at localhost:8080
```
23 changes: 23 additions & 0 deletions src/main/webapp/vue-app/meal-assistant-client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist


# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
29 changes: 29 additions & 0 deletions src/main/webapp/vue-app/meal-assistant-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# meal-assistant-client

## Project setup

```
npm install
```

### Compiles and hot-reloads for development

```
npm run serve
```

### Compiles and minifies for production

```
npm run build
```

### Lints and fixes files

```
npm run lint
```

### Customize configuration

See [Configuration Reference](https://cli.vuejs.org/config/).
3 changes: 3 additions & 0 deletions src/main/webapp/vue-app/meal-assistant-client/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};
4 changes: 4 additions & 0 deletions src/main/webapp/vue-app/meal-assistant-client/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
preset: "@vue/cli-plugin-unit-jest",
testMatch: ["**/*.(spec|test).js"],
};
Loading