jac-client 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jac_client/docs/README.md +232 -172
- jac_client/docs/advanced-state.md +1012 -452
- jac_client/docs/asset-serving/intro.md +209 -0
- jac_client/docs/assets/pipe_line-v2.svg +32 -0
- jac_client/docs/assets/pipe_line.png +0 -0
- jac_client/docs/file-system/intro.md +90 -0
- jac_client/docs/guide-example/intro.md +117 -0
- jac_client/docs/guide-example/step-01-setup.md +260 -0
- jac_client/docs/guide-example/step-02-components.md +416 -0
- jac_client/docs/guide-example/step-03-styling.md +478 -0
- jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
- jac_client/docs/guide-example/step-05-local-state.md +530 -0
- jac_client/docs/guide-example/step-06-events.md +750 -0
- jac_client/docs/guide-example/step-07-effects.md +469 -0
- jac_client/docs/guide-example/step-08-walkers.md +534 -0
- jac_client/docs/guide-example/step-09-authentication.md +586 -0
- jac_client/docs/guide-example/step-10-routing.md +540 -0
- jac_client/docs/guide-example/step-11-final.md +964 -0
- jac_client/docs/imports.md +538 -46
- jac_client/docs/lifecycle-hooks.md +517 -297
- jac_client/docs/routing.md +487 -357
- jac_client/docs/styling/intro.md +250 -0
- jac_client/docs/styling/js-styling.md +373 -0
- jac_client/docs/styling/material-ui.md +346 -0
- jac_client/docs/styling/pure-css.md +305 -0
- jac_client/docs/styling/sass.md +409 -0
- jac_client/docs/styling/styled-components.md +401 -0
- jac_client/docs/styling/tailwind.md +303 -0
- jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
- jac_client/examples/asset-serving/css-with-image/README.md +91 -0
- jac_client/examples/asset-serving/css-with-image/app.jac +67 -0
- jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
- jac_client/examples/asset-serving/css-with-image/package.json +28 -0
- jac_client/examples/asset-serving/css-with-image/styles.css +27 -0
- jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -0
- jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
- jac_client/examples/asset-serving/image-asset/README.md +119 -0
- jac_client/examples/asset-serving/image-asset/app.jac +43 -0
- jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
- jac_client/examples/asset-serving/image-asset/package.json +28 -0
- jac_client/examples/asset-serving/image-asset/styles.css +27 -0
- jac_client/examples/asset-serving/image-asset/vite.config.js +29 -0
- jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
- jac_client/examples/asset-serving/import-alias/README.md +83 -0
- jac_client/examples/asset-serving/import-alias/app.jac +57 -0
- jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
- jac_client/examples/asset-serving/import-alias/package.json +28 -0
- jac_client/examples/asset-serving/import-alias/vite.config.js +29 -0
- jac_client/examples/basic/.babelrc +9 -0
- jac_client/examples/basic/README.md +16 -0
- jac_client/examples/basic/app.jac +16 -0
- jac_client/examples/basic/package.json +27 -0
- jac_client/examples/basic/vite.config.js +28 -0
- jac_client/examples/basic-auth/.babelrc +9 -0
- jac_client/examples/basic-auth/README.md +16 -0
- jac_client/examples/basic-auth/app.jac +308 -0
- jac_client/examples/basic-auth/package.json +27 -0
- jac_client/examples/basic-auth/vite.config.js +28 -0
- jac_client/examples/basic-auth-with-router/.babelrc +9 -0
- jac_client/examples/basic-auth-with-router/README.md +60 -0
- jac_client/examples/basic-auth-with-router/app.jac +464 -0
- jac_client/examples/basic-auth-with-router/package.json +28 -0
- jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
- jac_client/examples/basic-full-stack/.babelrc +9 -0
- jac_client/examples/basic-full-stack/README.md +18 -0
- jac_client/examples/basic-full-stack/app.jac +320 -0
- jac_client/examples/basic-full-stack/package.json +28 -0
- jac_client/examples/basic-full-stack/vite.config.js +28 -0
- jac_client/examples/css-styling/js-styling/.babelrc +9 -0
- jac_client/examples/css-styling/js-styling/README.md +183 -0
- jac_client/examples/css-styling/js-styling/app.jac +63 -0
- jac_client/examples/css-styling/js-styling/package.json +28 -0
- jac_client/examples/css-styling/js-styling/styles.js +100 -0
- jac_client/examples/css-styling/js-styling/vite.config.js +28 -0
- jac_client/examples/css-styling/material-ui/.babelrc +9 -0
- jac_client/examples/css-styling/material-ui/README.md +16 -0
- jac_client/examples/css-styling/material-ui/app.jac +82 -0
- jac_client/examples/css-styling/material-ui/package.json +32 -0
- jac_client/examples/css-styling/material-ui/vite.config.js +28 -0
- jac_client/examples/css-styling/pure-css/.babelrc +9 -0
- jac_client/examples/css-styling/pure-css/README.md +16 -0
- jac_client/examples/css-styling/pure-css/app.jac +63 -0
- jac_client/examples/css-styling/pure-css/package.json +28 -0
- jac_client/examples/css-styling/pure-css/styles.css +112 -0
- jac_client/examples/css-styling/pure-css/vite.config.js +28 -0
- jac_client/examples/css-styling/sass-example/.babelrc +9 -0
- jac_client/examples/css-styling/sass-example/README.md +16 -0
- jac_client/examples/css-styling/sass-example/app.jac +63 -0
- jac_client/examples/css-styling/sass-example/package.json +29 -0
- jac_client/examples/css-styling/sass-example/styles.scss +158 -0
- jac_client/examples/css-styling/sass-example/vite.config.js +28 -0
- jac_client/examples/css-styling/styled-components/.babelrc +9 -0
- jac_client/examples/css-styling/styled-components/README.md +16 -0
- jac_client/examples/css-styling/styled-components/app.jac +66 -0
- jac_client/examples/css-styling/styled-components/package.json +29 -0
- jac_client/examples/css-styling/styled-components/styled.js +91 -0
- jac_client/examples/css-styling/styled-components/vite.config.js +28 -0
- jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
- jac_client/examples/css-styling/tailwind-example/README.md +16 -0
- jac_client/examples/css-styling/tailwind-example/app.jac +64 -0
- jac_client/examples/css-styling/tailwind-example/global.css +1 -0
- jac_client/examples/css-styling/tailwind-example/package.json +30 -0
- jac_client/examples/css-styling/tailwind-example/vite.config.js +30 -0
- jac_client/examples/full-stack-with-auth/.babelrc +9 -0
- jac_client/examples/full-stack-with-auth/README.md +16 -0
- jac_client/examples/full-stack-with-auth/app.jac +735 -0
- jac_client/examples/full-stack-with-auth/package.json +28 -0
- jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
- jac_client/examples/with-router/.babelrc +9 -0
- jac_client/examples/with-router/README.md +17 -0
- jac_client/examples/with-router/app.jac +323 -0
- jac_client/examples/with-router/package.json +28 -0
- jac_client/examples/with-router/vite.config.js +28 -0
- jac_client/plugin/cli.py +95 -179
- jac_client/plugin/client.py +111 -2
- jac_client/plugin/client_runtime.jac +183 -890
- jac_client/plugin/vite_client_bundle.py +185 -205
- jac_client/tests/__init__.py +0 -1
- jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
- jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
- jac_client/tests/fixtures/cl_file/app.jac +15 -0
- jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
- jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
- jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
- jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
- jac_client/tests/fixtures/spawn_test/app.jac +133 -0
- jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
- jac_client/tests/test_asset_examples.py +339 -0
- jac_client/tests/test_cl.py +345 -151
- jac_client/tests/test_create_jac_app.py +41 -45
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
- jac_client-0.2.1.dist-info/RECORD +140 -0
- jac_client/examples/little-x/package-lock.json +0 -2840
- jac_client/examples/todo-app/README.md +0 -82
- jac_client/examples/todo-app/app.jac +0 -683
- jac_client/examples/todo-app/package-lock.json +0 -999
- jac_client/examples/todo-app/package.json +0 -22
- jac_client-0.1.0.dist-info/RECORD +0 -33
- /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
- {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
jac_client/docs/README.md
CHANGED
|
@@ -6,9 +6,39 @@ Welcome to the Todo App example! This guide will walk you through building a ful
|
|
|
6
6
|
|
|
7
7
|
## 📦 1. Creating the Application
|
|
8
8
|
|
|
9
|
+
### Prerequisites
|
|
10
|
+
|
|
11
|
+
Before installing Jac client, you need to have **Node.js** installed on your system.
|
|
12
|
+
|
|
13
|
+
#### Installing Node.js
|
|
14
|
+
|
|
15
|
+
**For Linux users:**
|
|
16
|
+
|
|
17
|
+
Visit [https://nodejs.org/en/download](https://nodejs.org/en/download) and follow the instructions to install Node.js using **nvm** (Node Version Manager) with **npm**.
|
|
18
|
+
|
|
19
|
+
Select:
|
|
20
|
+
- **Platform**: Linux
|
|
21
|
+
- **Package Manager**: nvm
|
|
22
|
+
- **Package**: npm
|
|
23
|
+
|
|
24
|
+
Then follow the installation commands provided on that page.
|
|
25
|
+
|
|
26
|
+
**For macOS users:**
|
|
27
|
+
|
|
28
|
+
Download and install Node.js from [https://nodejs.org/en/download](https://nodejs.org/en/download) by selecting your operating system.
|
|
29
|
+
|
|
30
|
+
**Verify Installation:**
|
|
31
|
+
|
|
32
|
+
After installation, verify Node.js and npm are installed correctly:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
node -v
|
|
36
|
+
npm -v
|
|
37
|
+
```
|
|
38
|
+
|
|
9
39
|
### Installation
|
|
10
40
|
|
|
11
|
-
|
|
41
|
+
Once Node.js is installed, install the Jac client package:
|
|
12
42
|
|
|
13
43
|
```bash
|
|
14
44
|
pip install jac-client
|
|
@@ -57,48 +87,63 @@ You can access your app at `http://localhost:8000`
|
|
|
57
87
|
|
|
58
88
|
Every Jac client application needs an entry point function. This is where your app starts rendering.
|
|
59
89
|
|
|
60
|
-
### The `
|
|
90
|
+
### The `app()` Function
|
|
61
91
|
|
|
62
|
-
|
|
92
|
+
Inside your `cl` block, define a function called `app()`:
|
|
63
93
|
|
|
64
94
|
```jac
|
|
65
|
-
cl {
|
|
66
|
-
def App() -> any {
|
|
67
|
-
# Your main app component with routing
|
|
68
|
-
return <div>Hello World</div>;
|
|
69
|
-
}
|
|
95
|
+
cl import from react {useState}
|
|
70
96
|
|
|
71
|
-
|
|
72
|
-
|
|
97
|
+
cl {
|
|
98
|
+
def app() -> any {
|
|
99
|
+
let [count, setCount] = useState(0);
|
|
100
|
+
return <div>
|
|
101
|
+
<h1>Hello, World!</h1>
|
|
102
|
+
<p>Count: {count}</p>
|
|
103
|
+
<button onClick={lambda e: any -> None { setCount(count + 1); }}>
|
|
104
|
+
Increment
|
|
105
|
+
</button>
|
|
106
|
+
</div>;
|
|
73
107
|
}
|
|
74
108
|
}
|
|
75
109
|
```
|
|
76
110
|
|
|
77
111
|
**Key Points:**
|
|
78
|
-
- `
|
|
79
|
-
- It
|
|
112
|
+
- `app()` is the **required entry point** that Jac looks for
|
|
113
|
+
- It must be defined inside a `cl { }` block (client-side code)
|
|
80
114
|
- The `cl` (client) block indicates this code runs in the browser
|
|
81
115
|
- This function gets called automatically when the page loads
|
|
116
|
+
- You can define other components and helper functions in the same `cl` block
|
|
82
117
|
|
|
83
|
-
**Example
|
|
118
|
+
**Example with Multiple Components:**
|
|
84
119
|
```jac
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
{Nav(currentPath)}
|
|
96
|
-
{router.render()}
|
|
97
|
-
</div>;
|
|
98
|
-
}
|
|
120
|
+
cl import from react {useState, useEffect}
|
|
121
|
+
|
|
122
|
+
cl {
|
|
123
|
+
def TodoList(todos: list) -> any {
|
|
124
|
+
return <ul>
|
|
125
|
+
{todos.map(lambda todo: any -> any {
|
|
126
|
+
return <li key={todo._jac_id}>{todo.text}</li>;
|
|
127
|
+
})}
|
|
128
|
+
</ul>;
|
|
129
|
+
}
|
|
99
130
|
|
|
100
|
-
def
|
|
101
|
-
|
|
131
|
+
def app() -> any {
|
|
132
|
+
let [todos, setTodos] = useState([]);
|
|
133
|
+
|
|
134
|
+
useEffect(lambda -> None {
|
|
135
|
+
async def loadTodos() -> None {
|
|
136
|
+
result = root spawn read_todos();
|
|
137
|
+
setTodos(result.reports if result.reports else []);
|
|
138
|
+
}
|
|
139
|
+
loadTodos();
|
|
140
|
+
}, []);
|
|
141
|
+
|
|
142
|
+
return <div>
|
|
143
|
+
<h1>My Todos</h1>
|
|
144
|
+
<TodoList todos={todos} />
|
|
145
|
+
</div>;
|
|
146
|
+
}
|
|
102
147
|
}
|
|
103
148
|
```
|
|
104
149
|
|
|
@@ -179,112 +224,104 @@ def TodoItem(item: dict) -> any {
|
|
|
179
224
|
|
|
180
225
|
---
|
|
181
226
|
|
|
182
|
-
## 🗄️ 4. Adding State
|
|
183
|
-
|
|
184
|
-
State management in Jac uses `createState()`, which provides reactive state similar to React hooks but simpler.
|
|
227
|
+
## 🗄️ 4. Adding State with React Hooks
|
|
185
228
|
|
|
186
|
-
|
|
229
|
+
Jac uses React hooks for state management. You can use all standard React hooks by importing them:
|
|
187
230
|
|
|
188
231
|
```jac
|
|
189
|
-
|
|
190
|
-
"items": [],
|
|
191
|
-
"filter": "all",
|
|
192
|
-
"input": ""
|
|
193
|
-
});
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
**How it works:**
|
|
197
|
-
- `createState()` returns a tuple: `[getter, setter]`
|
|
198
|
-
- **Getter** (`todoState`): A function that returns the current state value
|
|
199
|
-
- **Setter** (`setTodoState`): A function that updates the state and triggers re-renders
|
|
232
|
+
cl import from react { useState, useEffect }
|
|
200
233
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
234
|
+
cl {
|
|
235
|
+
def Counter() -> any {
|
|
236
|
+
let [count, setCount] = useState(0);
|
|
204
237
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
238
|
+
useEffect(lambda -> None {
|
|
239
|
+
console.log("Count changed:", count);
|
|
240
|
+
}, [count]);
|
|
208
241
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
242
|
+
return <div>
|
|
243
|
+
<h1>Count: {count}</h1>
|
|
244
|
+
<button onClick={lambda e: any -> None {
|
|
245
|
+
setCount(count + 1);
|
|
246
|
+
}}>
|
|
247
|
+
Increment
|
|
248
|
+
</button>
|
|
249
|
+
</div>;
|
|
250
|
+
}
|
|
213
251
|
}
|
|
214
252
|
```
|
|
215
253
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
# Update multiple properties
|
|
225
|
-
setTodoState({
|
|
226
|
-
"filter": "completed",
|
|
227
|
-
"input": "New todo text"
|
|
228
|
-
});
|
|
254
|
+
**Available React Hooks:**
|
|
255
|
+
- `useState` - For state management
|
|
256
|
+
- `useEffect` - For side effects
|
|
257
|
+
- `useRef` - For refs
|
|
258
|
+
- `useContext` - For context
|
|
259
|
+
- `useCallback`, `useMemo` - For performance optimization
|
|
260
|
+
- And more...
|
|
229
261
|
|
|
230
|
-
|
|
231
|
-
s = todoState();
|
|
232
|
-
setTodoState({"items": s.items.concat([newItem])});
|
|
233
|
-
```
|
|
262
|
+
### State Management Example
|
|
234
263
|
|
|
235
|
-
|
|
236
|
-
- Existing properties are preserved
|
|
237
|
-
- Only specified properties are updated
|
|
238
|
-
- Arrays and objects are replaced, not merged deeply
|
|
239
|
-
|
|
240
|
-
### Complete State Example
|
|
264
|
+
Here's a complete example showing state management in a todo app:
|
|
241
265
|
|
|
242
266
|
```jac
|
|
267
|
+
cl import from react {useState, useEffect}
|
|
268
|
+
|
|
243
269
|
cl {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
270
|
+
def app() -> any {
|
|
271
|
+
let [todos, setTodos] = useState([]);
|
|
272
|
+
let [input, setInput] = useState("");
|
|
273
|
+
let [filter, setFilter] = useState("all");
|
|
274
|
+
|
|
275
|
+
# Load todos on mount
|
|
276
|
+
useEffect(lambda -> None {
|
|
277
|
+
async def loadTodos() -> None {
|
|
278
|
+
result = root spawn read_todos();
|
|
279
|
+
setTodos(result.reports if result.reports else []);
|
|
280
|
+
}
|
|
281
|
+
loadTodos();
|
|
282
|
+
}, []);
|
|
283
|
+
|
|
284
|
+
# Add new todo
|
|
285
|
+
async def addTodo() -> None {
|
|
286
|
+
if not input.trim() { return; }
|
|
287
|
+
response = root spawn create_todo(text=input.trim());
|
|
288
|
+
new_todo = response.reports[0][0];
|
|
289
|
+
setTodos(todos.concat([new_todo]));
|
|
290
|
+
setInput("");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
# Filter todos
|
|
294
|
+
def getFilteredTodos() -> list {
|
|
295
|
+
if filter == "active" {
|
|
296
|
+
return todos.filter(lambda todo: any -> bool { return not todo.done; });
|
|
297
|
+
} elif filter == "completed" {
|
|
298
|
+
return todos.filter(lambda todo: any -> bool { return todo.done; });
|
|
299
|
+
}
|
|
300
|
+
return todos;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
filteredTodos = getFilteredTodos();
|
|
255
304
|
|
|
256
305
|
return <div>
|
|
306
|
+
<h1>My Todos</h1>
|
|
257
307
|
<input
|
|
258
|
-
value={
|
|
259
|
-
onChange={lambda e: any -> None {
|
|
260
|
-
|
|
308
|
+
value={input}
|
|
309
|
+
onChange={lambda e: any -> None { setInput(e.target.value); }}
|
|
310
|
+
onKeyPress={lambda e: any -> None {
|
|
311
|
+
if e.key == "Enter" { addTodo(); }
|
|
261
312
|
}}
|
|
262
313
|
/>
|
|
314
|
+
<button onClick={addTodo}>Add</button>
|
|
315
|
+
|
|
263
316
|
<div>
|
|
264
|
-
{
|
|
317
|
+
{filteredTodos.map(lambda todo: any -> any {
|
|
318
|
+
return <div key={todo._jac_id}>
|
|
319
|
+
<span>{todo.text}</span>
|
|
320
|
+
</div>;
|
|
321
|
+
})}
|
|
265
322
|
</div>
|
|
266
323
|
</div>;
|
|
267
324
|
}
|
|
268
|
-
|
|
269
|
-
# Update state in event handler
|
|
270
|
-
async def onAddTodo(e: any) -> None {
|
|
271
|
-
e.preventDefault();
|
|
272
|
-
text = document.getElementById("todo-input").value.trim();
|
|
273
|
-
|
|
274
|
-
if not text { return; }
|
|
275
|
-
|
|
276
|
-
# Create todo on backend
|
|
277
|
-
new_todo = await __jacSpawn("create_todo", {"text": text});
|
|
278
|
-
|
|
279
|
-
# Update frontend state
|
|
280
|
-
s = todoState();
|
|
281
|
-
newItem = {
|
|
282
|
-
"id": new_todo._jac_id,
|
|
283
|
-
"text": new_todo.text,
|
|
284
|
-
"done": new_todo.done
|
|
285
|
-
};
|
|
286
|
-
setTodoState({"items": s.items.concat([newItem])});
|
|
287
|
-
}
|
|
288
325
|
}
|
|
289
326
|
```
|
|
290
327
|
|
|
@@ -310,11 +347,14 @@ def Button() -> any {
|
|
|
310
347
|
|
|
311
348
|
```jac
|
|
312
349
|
def InputField() -> any {
|
|
350
|
+
let [value, setValue] = useState("");
|
|
351
|
+
|
|
313
352
|
return <input
|
|
314
353
|
type="text"
|
|
354
|
+
value={value}
|
|
315
355
|
onChange={lambda e: any -> None {
|
|
316
356
|
console.log("Input value:", e.target.value);
|
|
317
|
-
|
|
357
|
+
setValue(e.target.value);
|
|
318
358
|
}}
|
|
319
359
|
/>;
|
|
320
360
|
}
|
|
@@ -338,7 +378,8 @@ async def onAddTodo(e: any) -> None {
|
|
|
338
378
|
if not text { return; }
|
|
339
379
|
|
|
340
380
|
# Handle form submission
|
|
341
|
-
|
|
381
|
+
response = root spawn create_todo(text=text);
|
|
382
|
+
new_todo = response.reports[0][0];
|
|
342
383
|
# ... update state
|
|
343
384
|
}
|
|
344
385
|
```
|
|
@@ -355,13 +396,12 @@ async def onAddTodo(e: any) -> None {
|
|
|
355
396
|
### Advanced: Conditional Event Handlers
|
|
356
397
|
|
|
357
398
|
```jac
|
|
358
|
-
def FilterButton(filterType: str) -> any {
|
|
359
|
-
|
|
360
|
-
isActive = s.filter == filterType;
|
|
399
|
+
def FilterButton(filterType: str, currentFilter: str, onFilterChange: any) -> any {
|
|
400
|
+
isActive = currentFilter == filterType;
|
|
361
401
|
|
|
362
402
|
return <button
|
|
363
403
|
onClick={lambda -> None {
|
|
364
|
-
|
|
404
|
+
onFilterChange(filterType);
|
|
365
405
|
}}
|
|
366
406
|
style={{
|
|
367
407
|
"background": ("#7C3AED" if isActive else "#FFFFFF"),
|
|
@@ -379,7 +419,7 @@ def FilterButton(filterType: str) -> any {
|
|
|
379
419
|
|
|
380
420
|
One of Jac's most powerful features is **seamless backend communication** without writing HTTP requests, fetch calls, or axios code.
|
|
381
421
|
|
|
382
|
-
### The `
|
|
422
|
+
### The `spawn` Syntax
|
|
383
423
|
|
|
384
424
|
Instead of writing:
|
|
385
425
|
```javascript
|
|
@@ -394,30 +434,32 @@ const data = await response.json();
|
|
|
394
434
|
|
|
395
435
|
You simply write:
|
|
396
436
|
```jac
|
|
397
|
-
|
|
437
|
+
response = root spawn create_todo(text="New todo");
|
|
398
438
|
```
|
|
399
439
|
|
|
400
|
-
### How `
|
|
440
|
+
### How `spawn` Works
|
|
401
441
|
|
|
402
442
|
```jac
|
|
403
|
-
# Call a walker on
|
|
404
|
-
result =
|
|
405
|
-
walker_name, # Name of the walker to execute
|
|
406
|
-
fields, # Dictionary of parameters to pass
|
|
407
|
-
node_id # Optional: specific node ID (defaults to "root")
|
|
408
|
-
);
|
|
443
|
+
# Call a walker on a node
|
|
444
|
+
result = node_reference spawn walker_name(parameters);
|
|
409
445
|
```
|
|
410
446
|
|
|
447
|
+
**Syntax:**
|
|
448
|
+
- `node_reference` - The node to spawn the walker on (commonly `root` for the root node, or a node ID)
|
|
449
|
+
- `spawn` - The spawn keyword
|
|
450
|
+
- `walker_name` - Name of the walker to execute
|
|
451
|
+
- `parameters` - Parameters to pass to the walker (as function arguments)
|
|
452
|
+
|
|
411
453
|
**Example from Todo App:**
|
|
412
454
|
```jac
|
|
413
|
-
# Create a new todo
|
|
414
|
-
|
|
455
|
+
# Create a new todo (spawn on root node)
|
|
456
|
+
response = root spawn create_todo(text=text);
|
|
415
457
|
|
|
416
|
-
# Toggle todo status
|
|
417
|
-
|
|
458
|
+
# Toggle todo status (spawn on specific todo node)
|
|
459
|
+
id spawn toggle_todo();
|
|
418
460
|
|
|
419
461
|
# Read all todos
|
|
420
|
-
todos =
|
|
462
|
+
todos = root spawn read_todos();
|
|
421
463
|
```
|
|
422
464
|
|
|
423
465
|
### Backend Walkers
|
|
@@ -433,8 +475,9 @@ node Todo {
|
|
|
433
475
|
|
|
434
476
|
# Walker: Create a new todo
|
|
435
477
|
walker create_todo {
|
|
478
|
+
has text: str;
|
|
436
479
|
can create with `root entry {
|
|
437
|
-
new_todo = here ++> Todo(text=
|
|
480
|
+
new_todo = here ++> Todo(text=self.text);
|
|
438
481
|
report new_todo;
|
|
439
482
|
}
|
|
440
483
|
}
|
|
@@ -469,7 +512,8 @@ async def onAddTodo(e: any) -> None {
|
|
|
469
512
|
if not text { return; }
|
|
470
513
|
|
|
471
514
|
# Call backend walker - no fetch/axios needed!
|
|
472
|
-
|
|
515
|
+
response = root spawn create_todo(text=text);
|
|
516
|
+
new_todo = response.reports[0][0];
|
|
473
517
|
|
|
474
518
|
# Update frontend state
|
|
475
519
|
s = todoState();
|
|
@@ -485,16 +529,16 @@ async def onAddTodo(e: any) -> None {
|
|
|
485
529
|
**Backend (outside `cl` block):**
|
|
486
530
|
```jac
|
|
487
531
|
walker create_todo {
|
|
532
|
+
has text: str;
|
|
488
533
|
can create with `root entry {
|
|
489
|
-
# 'text' comes from the
|
|
490
|
-
|
|
491
|
-
new_todo = here ++> Todo(text=text);
|
|
534
|
+
# 'text' comes from the walker parameter
|
|
535
|
+
new_todo = here ++> Todo(text=self.text);
|
|
492
536
|
report new_todo;
|
|
493
537
|
}
|
|
494
538
|
}
|
|
495
539
|
```
|
|
496
540
|
|
|
497
|
-
### Benefits of `
|
|
541
|
+
### Benefits of `spawn`
|
|
498
542
|
|
|
499
543
|
✅ **No HTTP Configuration**: No need to set up API endpoints, CORS, or request/response formats
|
|
500
544
|
✅ **Type Safety**: Jac handles serialization automatically
|
|
@@ -502,6 +546,7 @@ walker create_todo {
|
|
|
502
546
|
✅ **Error Handling**: Exceptions are properly propagated
|
|
503
547
|
✅ **Graph Operations**: Direct access to graph-based data operations
|
|
504
548
|
✅ **Less Code**: Eliminates boilerplate HTTP client code
|
|
549
|
+
✅ **Natural Syntax**: Call walkers on nodes using intuitive syntax
|
|
505
550
|
|
|
506
551
|
### Authentication Helpers
|
|
507
552
|
|
|
@@ -546,8 +591,9 @@ node Todo {
|
|
|
546
591
|
}
|
|
547
592
|
|
|
548
593
|
walker create_todo {
|
|
594
|
+
has text: str;
|
|
549
595
|
can create with `root entry {
|
|
550
|
-
new_todo = here ++> Todo(text=
|
|
596
|
+
new_todo = here ++> Todo(text=self.text);
|
|
551
597
|
report new_todo;
|
|
552
598
|
}
|
|
553
599
|
}
|
|
@@ -555,37 +601,41 @@ walker create_todo {
|
|
|
555
601
|
# ============================================================================
|
|
556
602
|
# FRONTEND: Components, State, and Events
|
|
557
603
|
# ============================================================================
|
|
604
|
+
cl import from react {useState}
|
|
605
|
+
|
|
558
606
|
cl {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
607
|
+
def app() -> any {
|
|
608
|
+
let [todos, setTodos] = useState([]);
|
|
609
|
+
let [input, setInput] = useState("");
|
|
610
|
+
|
|
611
|
+
# Event Handler
|
|
612
|
+
async def addTodo() -> None {
|
|
613
|
+
if not input.trim() { return; }
|
|
614
|
+
response = root spawn create_todo(text=input.trim());
|
|
615
|
+
new_todo = response.reports[0][0];
|
|
616
|
+
setTodos(todos.concat([new_todo]));
|
|
617
|
+
setInput("");
|
|
618
|
+
}
|
|
619
|
+
|
|
568
620
|
return <div>
|
|
569
621
|
<h2>My Todos</h2>
|
|
570
|
-
<
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
# Event Handler
|
|
579
|
-
async def onAddTodo(e: any) -> None {
|
|
580
|
-
e.preventDefault();
|
|
581
|
-
text = document.getElementById("todo-input").value;
|
|
582
|
-
new_todo = await __jacSpawn("create_todo", {"text": text});
|
|
583
|
-
# Update state...
|
|
584
|
-
}
|
|
622
|
+
<input
|
|
623
|
+
value={input}
|
|
624
|
+
onChange={lambda e: any -> None { setInput(e.target.value); }}
|
|
625
|
+
onKeyPress={lambda e: any -> None {
|
|
626
|
+
if e.key == "Enter" { addTodo(); }
|
|
627
|
+
}}
|
|
628
|
+
/>
|
|
629
|
+
<button onClick={addTodo}>Add Todo</button>
|
|
585
630
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
631
|
+
<div>
|
|
632
|
+
{todos.map(lambda todo: any -> any {
|
|
633
|
+
return <div key={todo._jac_id}>
|
|
634
|
+
<span>{todo.text}</span>
|
|
635
|
+
</div>;
|
|
636
|
+
})}
|
|
637
|
+
</div>
|
|
638
|
+
</div>;
|
|
589
639
|
}
|
|
590
640
|
}
|
|
591
641
|
```
|
|
@@ -609,19 +659,29 @@ Then visit `http://localhost:8000` in your browser.
|
|
|
609
659
|
|
|
610
660
|
Ready to dive deeper? Explore these advanced topics:
|
|
611
661
|
|
|
612
|
-
- **[Routing](routing.md)**:
|
|
613
|
-
- **[Lifecycle Hooks](lifecycle-hooks.md)**: Use `onMount()` for initialization logic
|
|
614
|
-
- **[Advanced State](advanced-state.md)**:
|
|
615
|
-
- **[Imports](imports.md)**: Import third-party libraries, other Jac files, and JavaScript modules
|
|
662
|
+
- **[Routing](routing.md)**: Build multi-page apps with declarative routing (`<Router>`, `<Routes>`, `<Route>`)
|
|
663
|
+
- **[Lifecycle Hooks](lifecycle-hooks.md)**: Use `onMount()` and React hooks for initialization logic
|
|
664
|
+
- **[Advanced State](advanced-state.md)**: Manage complex state with React hooks and context
|
|
665
|
+
- **[Imports](imports.md)**: Import third-party libraries (React, Ant Design, Lodash), other Jac files, and JavaScript modules
|
|
616
666
|
- **[Learn JAC](https://www.jac-lang.org)**: Explore Jac's graph-based data modeling
|
|
617
667
|
|
|
668
|
+
## 🎓 Examples
|
|
669
|
+
|
|
670
|
+
Check out the `examples/` directory for working applications:
|
|
671
|
+
|
|
672
|
+
- **[basic](../../examples/basic/)**: Simple counter app using React hooks
|
|
673
|
+
- **[with-router](../../examples/with-router/)**: Multi-page app with declarative routing
|
|
674
|
+
- **[little-x](../../examples/little-x/)**: Social media app with third-party libraries
|
|
675
|
+
- **[todo-app](../../examples/todo-app/)**: Full-featured todo app with authentication
|
|
676
|
+
- **[basic-full-stack](../../examples/basic-full-stack/)**: Full-stack app with backend integration
|
|
677
|
+
|
|
618
678
|
---
|
|
619
679
|
|
|
620
680
|
## 💡 Key Takeaways
|
|
621
681
|
|
|
622
682
|
1. **Single Language**: Write frontend and backend in Jac
|
|
623
|
-
2. **No HTTP Client**: Use `
|
|
624
|
-
3. **Reactive State**:
|
|
683
|
+
2. **No HTTP Client**: Use `spawn` syntax instead of fetch/axios
|
|
684
|
+
3. **Reactive State**: React hooks manage UI updates automatically
|
|
625
685
|
4. **Component-Based**: Build reusable UI components with JSX
|
|
626
686
|
5. **Type Safety**: Jac provides type checking across frontend and backend
|
|
627
687
|
6. **Graph Database**: Built-in graph data model eliminates need for SQL/NoSQL
|