I designed both the Add Transaction
and Add Category
pages to be similar. Both pages utilize the form
tag. Inside the form element, I created two columns to separate the label and the input fields.
Svelte Directory Structure#
In Svelte, the directory represents the URL. For example, if a website has a page /foo/bar
, we need to create a new directory in Svelte src/routes/foo/bar
and create a +page.svelte
and any other files needed.
In this project, the URL to add transaction and add category is /categories/add
and /transactions/add
. So, we need to create new directories categories/add
and transactions/add
.
After that, we need to create a +page.svelte
in each directory to define the page layout. +page.server.js
and categories.scss
are not mandatory for every page, but we use it to enhance the experience. +page.server.js
is a file that contains code that will be executed by the frontend server, while categories.scss
is a Sassy CSS file.
Add Transaction Page#
This is what the Add Transaction
page looks like. And I will show the content of +page.svelte
of Add Transaction
page:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| <script>
export let data;
const { categories } = data;
</script>
<style src="./transaction.scss"></style>
<main class="admin__main">
<h2>Add Transaction</h2>
<form method= "POST" class="theForm" action="?/addTransaction">
<label class="theForm__label" for="amount">Amount</label>
<input type="text" class ="theForm__input" placeholder="Insert amount" name="amount" id="amount" required>
<label class="theForm__label" for="category_id">Category</label>
<select class ="theForm__input" name = "category_id" id ="category_id" required>
{#each categories as category}
<option value="{category.category_id}">{category.category_name}</option>
{/each}
</select>
<label class="theForm__label" for="transaction_date">Date</label>
<input type="date" class ="theForm__input" id="transaction_date" name="transaction_date" required>
<label class="theForm__label" for="note">Notes</label>
<textarea placeholder="Insert notes" id="note" class ="theForm__input" name="note" rows="5" cols="30"></textarea>
<button class ="theForm__input theForm__button">Submit</button>
</form>
</main>
|
Let’s break the code down.
1
2
3
4
| <script>
export let data;
const { categories } = data;
</script>
|
This is the code to store the category list into categories
variable. Where to get the category list data ? I will get back to it later.
The rest of the code is a code to import SCSS file and the HTML layout of the page. The form
action in the code above redirects to ?/addTransaction
. It means the form will call a Svelte’s form action named addTransaction
. Form action is a feature in Svelte to intercept the default handler with its own handler. It is implemented in +page.server.js
.
1
2
3
4
5
| <select class ="theForm__input" name = "category_id" id ="category_id" required>
{#each categories as category}
<option value="{category.category_id}">{category.category_name}</option>
{/each}
</select>
|
This code will iterate the categories
variable obtained from the script above and transform each of them into a dropdown option.
Now I will show the content of +page.server.js
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| import 'dotenv-expand/config'
export const load = async ({fetch}) => {
const fetchCategories = async() => {
const categoryRes = await fetch(`${process.env.HOST_URL}/users/${process.env.USER_ID}/categories/list`)
const categoryData = await categoryRes.json()
return categoryData.category_list
}
return {
categories: fetchCategories()
}
}
export const actions = {
addTransaction: async ({request}) => {
const formData = await request.formData()
const amount = formData.get('amount')
const categoryId = formData.get('category_id')
const transactionDate = formData.get('transaction_date')
const notes = formData.get('note')
const toSend = {
amount: amount,
category_id: categoryId,
transaction_date: transactionDate,
note: notes ? notes: null
}
const addRes = await fetch(`${process.env.HOST_URL}/users/${process.env.USER_ID}/transactions/add`, {
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(toSend)
})
const add = await addRes.json()
console.log (JSON.stringify({
toSend
}), add)
}
}
|
Let’s breakdown the code
1
| import 'dotenv-expand/config'
|
I use dotenv-expand library in this project. It enables the system to read configurations from .env
file.
1
2
3
4
5
6
7
8
9
10
11
12
| export const load = async ({fetch}) => {
const fetchCategories = async() => {
const categoryRes = await fetch(`${process.env.HOST_URL}/users/${process.env.USER_ID}/categories/list`)
const categoryData = await categoryRes.json()
return categoryData.category_list
}
return {
categories: fetchCategories()
}
}
|
load
is a function that is executed by Svelte before the page loads. In this code snippet, it will get a list of categories from the backend server and return the result to +page.svelte
. Remember this code snippet ?
const { categories } = data;
The result of load
function is exported to the code above.
What about the ${process.env.HOST_URL}
and ${process.env.USER_ID}
values ? process.env
is a syntax to retrieve configuration data from .env
file, where I store the HOST_URL
and USER_ID
variables. In short, I use values stored in .env
file to construct a URL.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| export const actions = {
addTransaction: async ({request}) => {
const formData = await request.formData()
const amount = formData.get('amount')
const categoryId = formData.get('category_id')
const transactionDate = formData.get('transaction_date')
const notes = formData.get('note')
const toSend = {
amount: amount,
category_id: categoryId,
transaction_date: transactionDate,
note: notes ? notes: null
}
const addRes = await fetch(`${process.env.HOST_URL}/users/${process.env.USER_ID}/transactions/add`, {
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(toSend)
})
const add = await addRes.json()
console.log (JSON.stringify({
toSend
}), add)
}
}
|
This is the code to export actions. The addTransaction
function defines /?addTransaction
action in Add Transaction form. addTransaction
function sends an HTTP request to the backend server to add a new transaction and log both the request and the result.
Add Category Page#
The layout of Add Category
page is similar to Add Transaction
page. It has an input for name, dropdown for type of category, and a color picker for category color.
Let’s take a look at the +page.svelte
content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| <style src="./categories.scss"></style>
<main class="admin__main">
<h2>Add Category</h2>
<form method= "POST" class="theForm" action="?/addCategory">
<label class="theForm__label" for="name">Name</label>
<input type="text" class ="theForm__input" placeholder="Insert name" name="name" id="name" required>
<label class="theForm__label" for="transaction_type">Type</label>
<select class ="theForm__input" name = "transaction_type" id ="transaction_type" required>
<option value="EARNING">EARNING</option>
<option value="EXPENSE">EXPENSE</option>
</select>
<label class="theForm__label" for="color">Color</label>
<input type="color" class ="theForm__input" id="color" name="color" required>
<button class ="theForm__input theForm__button">Submit</button>
</form>
</main>
|
There is no import script here because there is nothing to get before the page loads.
Now, let’s take a look of the content of +page.server.js
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| import 'dotenv-expand/config'
export const actions = {
addCategory: async ({request}) => {
const formData = await request.formData()
const name = formData.get('name')
const transactionType = formData.get('transaction_type')
const color = formData.get('color')
const toSend = {
category_name: name,
category_color: color,
transaction_type: transactionType
}
const res = await fetch(`${process.env.HOST_URL}/users/${process.env.USER_ID}/categories/add`, {
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(toSend)
})
const response = await res.json()
return;
}
}
|
Similar to Add Transaction
page, there is a form action that handles a form action, which is addCategory
. It creates an HTTP request to add a new category to the backend server. Unlike Add Transaction
page, there is no load function implemented.
Resources#
I put several links to get more understanding about the concept mentioned here:
- Svelte’s form actions: link
- Page load using Svelte: link
- Dotenv-expand library: link
- Sass: link
- Tabunganku Frontend repository: link