Skip to content

Commit d56a6ea

Browse files
authored
Merge pull request #1010 from marc0olo/fix/minimal-counter-dapp
fix: frontend button unresponsive
2 parents 3d5e9f7 + bc5c809 commit d56a6ea

39 files changed

+2307
-8505
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ target/
99
*.old.did
1010
.idea
1111
.mops
12+
.env

motoko/minimal-counter-dapp/Makefile

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,17 @@ upgrade: build
2727
.PHONY: test
2828
.SILENT: test
2929
test: install
30-
dfx canister call minimal_dapp count \
30+
dfx canister call minimal_dapp_backend increment \
3131
| grep '(1 : nat)' && echo 'PASS'
32-
dfx canister call minimal_dapp getCount \
32+
dfx canister call minimal_dapp_backend increment \
33+
| grep '(2 : nat)' && echo 'PASS'
34+
dfx canister call minimal_dapp_backend getCount \
35+
| grep '(2 : nat)' && echo 'PASS'
36+
dfx canister call minimal_dapp_backend decrement \
3337
| grep '(1 : nat)' && echo 'PASS'
34-
dfx canister call minimal_dapp reset \
38+
dfx canister call minimal_dapp_backend reset \
39+
| grep '(0 : nat)' && echo 'PASS'
40+
dfx canister call minimal_dapp_backend decrement \
3541
| grep '(0 : nat)' && echo 'PASS'
3642

3743
.PHONY: clean

motoko/minimal-counter-dapp/README.md

Lines changed: 128 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ keywords: [beginner, motoko, counter]
1010

1111
## Overview
1212

13-
The example dapp shows how to build a very basic dapp with both backend and frontend, using Motoko for the backend functionality and plain HTML and JavaScript for the frontend. The dapp is a simple counter, which will increment a counter by clicking a button in the frontend.
13+
The example dapp shows how to build a very basic dapp with both backend and frontend, using Motoko for the backend functionality and plain HTML and JavaScript for the frontend. The dapp is a simple counter, which will increment, decrement or reset a counter by clicking a button in the frontend.
1414

15-
The purpose of this example dapp is to build a minimalistic dapp, based on the default dapp template, installed by `dfx` when creating a new project. The dapp is a simple website with a counter. Every time a button is pressed, a counter is incremented.
15+
The purpose of this example dapp is to build a minimalistic dapp, based on the default dapp template, installed by `dfx` when creating a new project.
1616

1717
This example covers:
1818

1919
- Create a new canister smart contract using the IC SDK (`dfx`).
2020
- Use the default project as a template as the starting point for the new project.
21-
- Add backend functions for a counter (count, get count, and reset count).
21+
- Add backend functions for a counter (increment, getCount, decreent and reset).
2222
- Implement backend functions in the frontend.
2323
- Deploy the canister smart contract locally.
2424
- Test backend with Candid UI and command line using `dfx`, and test frontend in browser.
@@ -27,10 +27,10 @@ This example covers:
2727

2828
This example requires an installation of:
2929

30-
- [x] Install the [IC SDK](https://internetcomputer.org/docs/current/developer-docs/setup/install/index.mdx).
30+
- [x] Install the [IC SDK](https://internetcomputer.org/docs/current/developer-docs/getting-started/install).
3131
- [x] Install [Node.js](https://nodejs.org/en/download/).
3232
- [x] Download and install [git.](https://git-scm.com/downloads)
33-
- [ ] Clone the example dapp project: `git clone https://github.com/dfinity/examples`
33+
- [x] Clone the example dapp project: `git clone https://github.com/dfinity/examples`
3434

3535
Begin by opening a terminal window.
3636

@@ -54,24 +54,31 @@ The output will resemble the following:
5454
Deployed canisters.
5555
URLs:
5656
Frontend canister via browser
57-
minimal_dapp_assets: http://127.0.0.1:4943/?canisterId=br5f7-7uaaa-aaaaa-qaaca-cai
57+
minimal_dapp_frontend:
58+
- http://127.0.0.1:4943/?canisterId=bd3sg-teaaa-aaaaa-qaaba-cai
59+
- http://bd3sg-teaaa-aaaaa-qaaba-cai.localhost:4943/
5860
Backend canister via Candid interface:
59-
minimal_dapp: http://127.0.0.1:4943/?canisterId=bw4dl-smaaa-aaaaa-qaacq-cai&id=be2us-64aaa-aaaaa-qaabq-cai
61+
minimal_dapp_backend: http://127.0.0.1:4943/?canisterId=be2us-64aaa-aaaaa-qaabq-cai&id=bkyz2-fmaaa-aaaaa-qaaaq-cai
6062
```
6163

62-
### Step 3: Open the `minimal_dapp_assets` URL in a web browser.
64+
### Step 3: Open the `minimal_dapp_frontend` URL in a web browser.
6365

64-
You will see a GUI interface with a button that says **Click Me!** followed by a counter number. Each time the button is clicked, the counter value will increase by 1.
66+
You will see a GUI interface with following buttons:
67+
68+
- **Increment** - On click, the counter value will increase by 1.
69+
- **Decrement** - On click, the counter value will decrease by 1.
70+
- **Reload** - On click, the counter value will be reloaded. This is useful in case the value has been changed via Candid interface.
71+
- **Reset** - On click, the counter value will be reset to 0.
6572

6673
## Architecture
6774
The three main parts of the example dapp are the backend, the Candid interface, and the frontend. This example project is based on the default project, which is created when running the `dfx new project_name` command, but most of the default project code is replaced to create the counter functionality in this project.
6875

6976
### Motoko backend
70-
The backend functions are located in the `src/minimal_dapp/main.mo` Motoko file. The backend stores the counter value and has functions to get, increment, and reset the counter value.
77+
The backend functions are located in the `src/minimal_dapp_backend/main.mo` Motoko file. The backend stores the counter value and has functions to get, increment, decrement and reset the counter value.
7178

7279

7380
#### Counter variable
74-
Three functions are created to make the counter work: `count()`, `getCount()` and `reset()`. The current counter value is stored as a number in the actor.
81+
Four functions are created to make the counter work: `increment()`, `decrement()`, `getCount()` and `reset()`. The current counter value is stored as a number in the actor.
7582

7683

7784
```javascript
@@ -80,18 +87,33 @@ actor {
8087
}
8188
```
8289

83-
#### count()
84-
The `count()` function increments the counter variable. This function is invoked when the user clicks the button on the frontend, or when the function is called through the Candid interface.
90+
#### increment()
91+
The `increment()` function increments the counter variable. This function is invoked when the user clicks the `Increment` button on the frontend, or when the function is called through the Candid interface.
8592

8693
```javascript
87-
public func count() : async Nat {
94+
public func increment() : async Nat {
8895
counter += 1;
8996
return counter;
9097
};
9198
```
9299

93100
The function returns the incremented counter variable.
94101

102+
#### decrement()
103+
The `decrement()` function decrements the counter variable. This function is invoked when the user clicks the `Decrement` button on the frontend, or when the function is called through the Candid UI.
104+
105+
```javascript
106+
public func decrement() : async Nat {
107+
// avoid trap due to Natural subtraction underflow
108+
if(counter != 0) {
109+
counter -= 1;
110+
};
111+
return counter;
112+
};
113+
```
114+
115+
The function returns the decremented counter variable.
116+
95117
#### getCount()
96118
The `getCount()` function returns the current counter value.
97119

@@ -115,66 +137,108 @@ public func reset() : async Nat {
115137
The Candid interface is automatically created, and it has a convenient UI, which provides an easy, user-friendly way to test the backend. Learn how to access the Candid UI in the **Testing** section below.
116138

117139
### Frontend
118-
The default project installed with `dfx new project_name` has an `index.html` file with page HTML and an `index.js` file with an implementation of the backend functions. These two files are modified in this example project to support the counter functionality and the backend functions.
119-
120-
#### HTML
121-
All HTML code is in the `src/minimal_dapp_assets/src/index.html` file, and most of the HTML is carried over from the default project. The button is kept and so is the section showing the result, just simplified.
122-
123-
```html
124-
<!doctype html>
125-
<html lang="en">
126-
<head>
127-
<meta charset="UTF-8">
128-
<meta name="viewport" content="width=device-width">
129-
<title>Minimal Dapp</title>
130-
<base href="/">
131-
132-
<link type="text/css" rel="stylesheet" href="main.css" />
133-
</head>
134-
<body>
135-
<img src="logo.png" alt="DFINITY logo" />
136-
<section>
137-
<button id="clickMeBtn">Click Me!</button>
138-
</section>
139-
<section id="counter"></section>
140-
</body>
141-
</html>
142-
```
143-
144-
#### Javascript
145-
Two `eventlisteners` are added to the JavaScript file, `src/minimal_dapp_assets/src/index.js`, the existing JavaScript file from the default project. One `eventlistener` is for detecting button clicks, and it's calling the `count()` function in the backend, and an `eventlistener` for page load is added to get the initial value of the counter with `getCount()`. The backend functions are imported through the Candid interface.
146-
147-
```javascript
148-
import { minimal_dapp } from "../../declarations/minimal_dapp";
149-
150-
document.addEventListener("DOMContentLoaded", async () => {
151-
const counter = await minimal_dapp.getCount();
152-
document.getElementById("counter").innerText = "Counter: " + counter;
153-
});
140+
The default project installed with `dfx new project_name` implements the logic that serves the frontend in the `src/minimal_dapp_frontend/src/App.js` file, and most of the HTML is carried over from the default project.
141+
142+
The required JavaScript code to interact with the backend canister is automatically generated by `dfx` and can be found in the `src/declarations/minimal_dapp_backend` folder. The code creates an actor that enables the frontend to call the public functions of the backend canister.
143+
144+
```js
145+
import { html, render } from 'lit-html';
146+
import { minimal_dapp_backend } from 'declarations/minimal_dapp_backend';
147+
import logo from './logo2.svg';
148+
149+
class App {
150+
counter = '';
151+
152+
constructor() {
153+
this.#init();
154+
}
155+
156+
#init = async () => {
157+
this.counter = await minimal_dapp_backend.getCount();
158+
this.#render();
159+
}
160+
161+
#increment = async (e) => {
162+
e.preventDefault();
163+
this.counter = await minimal_dapp_backend.increment();
164+
this.#render();
165+
};
166+
167+
#decrement = async (e) => {
168+
e.preventDefault();
169+
this.counter = await minimal_dapp_backend.decrement();
170+
this.#render();
171+
};
172+
173+
#reload = async (e) => {
174+
e.preventDefault();
175+
this.#init();
176+
}
177+
178+
#reset = async (e) => {
179+
e.preventDefault();
180+
this.counter = await minimal_dapp_backend.reset();
181+
this.#render();
182+
}
183+
184+
#render() {
185+
let body = html`
186+
<main>
187+
<img src="${logo}" alt="DFINITY logo" />
188+
<br />
189+
<br />
190+
<form action="#">
191+
<button id="increment-btn">Increment</button>
192+
<button id="decrement-btn">Decrement</button>
193+
<button id="reload-btn">Reload</button>
194+
<button id="reset-btn">Reset</button>
195+
</form>
196+
<section id="counter">Counter: ${this.counter}</section>
197+
</main>
198+
`;
199+
render(body, document.getElementById('root'));
200+
document.getElementById('increment-btn').addEventListener('click', this.#increment);
201+
document.getElementById('decrement-btn').addEventListener('click', this.#decrement);
202+
document.getElementById('reload-btn').addEventListener('click', this.#reload);
203+
document.getElementById('reset-btn').addEventListener('click', this.#reset);
204+
if (!this.counter) {
205+
document.getElementById('decrement-btn').disabled = true;
206+
} else {
207+
document.getElementById('decrement-btn').disabled = false;
208+
}
209+
}
210+
}
154211

155-
document.getElementById("clickMeBtn").addEventListener("click", async () => {
156-
const counter = await minimal_dapp.count();
157-
document.getElementById("counter").innerText = "Counter: " + counter;
158-
});
212+
export default App;
159213
```
160214

161215
#### `dfx`
162-
`dfx` has a subset of commands for canister operations, and one of them enables calling the public functions added to the `main.mo` file in the previous step. In the following examples the initial value is 0. `count` will increment value and return 1, `getCount` will return the current value, and `reset` will set the value to 0.
216+
`dfx` has a subset of commands for canister operations, and one of them enables calling the public functions added to the `main.mo` file in the previous step. In the following examples the initial value is 0. `increment` will increment value, `getCount` will return the current value, `decrement` will decrement the value and `reset` will set the value to 0.
163217

164-
Command usage: `dfx canister call <project> <function>`
218+
Command usage: `dfx canister call <project> <function>`
165219

166220
```bash
167-
$ dfx canister call minimal_dapp count
221+
$ dfx canister call minimal_dapp_backend increment
168222
(1 : Nat)
169223
```
170224

171225
```bash
172-
$ dfx canister call minimal_dapp getCount
226+
$ dfx canister call minimal_dapp_backend increment
227+
(2 : Nat)
228+
```
229+
230+
```bash
231+
$ dfx canister call minimal_dapp_backend getCount
232+
(2 : Nat)
233+
```
234+
235+
```bash
236+
$ dfx canister call minimal_dapp_backend decrement
173237
(1 : Nat)
174238
```
175239

176240
```bash
177-
$ dfx canister call minimal_dapp reset
241+
$ dfx canister call minimal_dapp_backend reset
178242
(0 : Nat)
179243
```
180244

@@ -183,12 +247,12 @@ The Candid interface is automatically created, and it has a convenient UI, which
183247

184248
```bash
185249
$ dfx canister id __Candid_UI
186-
r7inp-6aaaa-aaaaa-aaabq-cai
187-
$ dfx canister id minimal_dapp
188-
rrkah-fqaaa-aaaaa-aaaaq-cai
250+
be2us-64aaa-aaaaa-qaabq-cai
251+
$ dfx canister id minimal_dapp_backend
252+
bkyz2-fmaaa-aaaaa-qaaaq-cai
189253
```
190254

191-
**http://\{candid_canister_id\}.localhost:8000/?id=\<backend_canister_id\>**
255+
**http://\{candid_canister_id\}.localhost:4943/?id=\<backend_canister_id\>**
192256

193257
![Candid UI](README_images/candid_ui.png)
194258

@@ -197,8 +261,4 @@ This project is licensed under the Apache 2.0 license, see LICENSE.md for detail
197261

198262
## Security considerations and best practices
199263

200-
If you base your application on this example, we recommend you familiarize yourself with and adhere to the [security best practices](https://internetcomputer.org/docs/current/references/security/) for developing on the Internet Computer. This example may not implement all the best practices.
201-
202-
For example, the following aspects are particularly relevant for this app:
203-
* [Use HTTP asset certification and avoid serving your dApp through raw.ic0.app](https://internetcomputer.org/docs/current/developer-docs/security/security-best-practices/overview), since this app serves a frontend.
204-
* [Certify query responses if they are relevant for security](https://internetcomputer.org/docs/current/references/security/general-security-best-practices#certify-query-responses-if-they-are-relevant-for-security), since this app uses query calls.
264+
If you base your application on this example, we recommend you familiarize yourself with and adhere to the [security best practices](https://internetcomputer.org/docs/current/developer-docs/security/security-best-practices/overview) for developing on the Internet Computer. This example may not implement all the best practices.
Loading
Loading
Binary file not shown.

motoko/minimal-counter-dapp/dfx.json

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
{
22
"canisters": {
3-
"minimal_dapp": {
4-
"main": "src/minimal_dapp/main.mo",
3+
"minimal_dapp_backend": {
4+
"main": "src/minimal_dapp_backend/main.mo",
55
"type": "motoko"
66
},
7-
"minimal_dapp_assets": {
7+
"minimal_dapp_frontend": {
88
"dependencies": [
9-
"minimal_dapp"
9+
"minimal_dapp_backend"
1010
],
11-
"frontend": {
12-
"entrypoint": "src/minimal_dapp_assets/src/index.html"
13-
},
1411
"source": [
15-
"src/minimal_dapp_assets/assets",
16-
"dist/minimal_dapp_assets/"
12+
"src/minimal_dapp_frontend/dist"
1713
],
18-
"type": "assets"
14+
"type": "assets",
15+
"workspace": "minimal_dapp_frontend"
1916
}
2017
},
2118
"defaults": {
@@ -24,5 +21,6 @@
2421
"packtool": ""
2522
}
2623
},
24+
"output_env_file": ".env",
2725
"version": 1
2826
}

0 commit comments

Comments
 (0)