jac-client 0.1.0__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 +629 -0
- jac_client/docs/advanced-state.md +706 -0
- jac_client/docs/imports.md +650 -0
- jac_client/docs/lifecycle-hooks.md +554 -0
- jac_client/docs/routing.md +530 -0
- jac_client/examples/little-x/app.jac +615 -0
- jac_client/examples/little-x/package-lock.json +2840 -0
- jac_client/examples/little-x/package.json +23 -0
- jac_client/examples/little-x/submit-button.jac +8 -0
- jac_client/examples/todo-app/README.md +82 -0
- jac_client/examples/todo-app/app.jac +683 -0
- jac_client/examples/todo-app/package-lock.json +999 -0
- jac_client/examples/todo-app/package.json +22 -0
- jac_client/plugin/cli.py +328 -0
- jac_client/plugin/client.py +41 -0
- jac_client/plugin/client_runtime.jac +941 -0
- jac_client/plugin/vite_client_bundle.py +470 -0
- jac_client/tests/__init__.py +2 -0
- jac_client/tests/fixtures/button.jac +6 -0
- jac_client/tests/fixtures/client_app.jac +18 -0
- jac_client/tests/fixtures/client_app_with_antd.jac +21 -0
- jac_client/tests/fixtures/js_import.jac +30 -0
- jac_client/tests/fixtures/package-lock.json +329 -0
- jac_client/tests/fixtures/package.json +11 -0
- jac_client/tests/fixtures/relative_import.jac +13 -0
- jac_client/tests/fixtures/test_fragments_spread.jac +44 -0
- jac_client/tests/fixtures/utils.js +22 -0
- jac_client/tests/test_cl.py +360 -0
- jac_client/tests/test_create_jac_app.py +139 -0
- jac_client-0.1.0.dist-info/METADATA +126 -0
- jac_client-0.1.0.dist-info/RECORD +33 -0
- jac_client-0.1.0.dist-info/WHEEL +4 -0
- jac_client-0.1.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
# Todo App - Beginner's Guide to Building with Jac
|
|
2
|
+
|
|
3
|
+
Welcome to the Todo App example! This guide will walk you through building a full-stack web application with Jac, from setup to deployment. Jac simplifies web development by eliminating the need for separate frontend and backend technologies, HTTP clients, and complex build configurations.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📦 1. Creating the Application
|
|
8
|
+
|
|
9
|
+
### Installation
|
|
10
|
+
|
|
11
|
+
First, install the Jac client package:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install jac-client
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Create a New Jac App
|
|
18
|
+
|
|
19
|
+
Use the `jac create_jac_app` command to scaffold a new application: (* we can name our app however we want, here we are using `todo-app)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
jac create_jac_app todo-app
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This command will:
|
|
26
|
+
- Create a new directory with your project name
|
|
27
|
+
- Set up the basic project structure
|
|
28
|
+
- Initialize npm and install Vite (for development server)
|
|
29
|
+
- Create a starter `app.jac` file with a sample component
|
|
30
|
+
|
|
31
|
+
**What gets created:**
|
|
32
|
+
```
|
|
33
|
+
my-app/
|
|
34
|
+
├── app.jac # Your main application file
|
|
35
|
+
├── package.json # Node.js dependencies
|
|
36
|
+
└── node_modules/ # Dependencies (after npm install)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Running Your App
|
|
40
|
+
|
|
41
|
+
Navigate to your project directory and start the development server:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cd todo-app
|
|
45
|
+
jac serve app.jac
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This starts both:
|
|
49
|
+
- **Backend server**: Handles your Jac graph operations and walkers
|
|
50
|
+
- **Frontend development server**: Serves your React components
|
|
51
|
+
|
|
52
|
+
You can access your app at `http://localhost:8000`
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 🚪 2. Entry Point of the App
|
|
57
|
+
|
|
58
|
+
Every Jac client application needs an entry point function. This is where your app starts rendering.
|
|
59
|
+
|
|
60
|
+
### The `jac_app()` Function
|
|
61
|
+
|
|
62
|
+
At the bottom of your `app.jac` file, you'll find the entry point:
|
|
63
|
+
|
|
64
|
+
```jac
|
|
65
|
+
cl {
|
|
66
|
+
def App() -> any {
|
|
67
|
+
# Your main app component with routing
|
|
68
|
+
return <div>Hello World</div>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def jac_app() -> any {
|
|
72
|
+
return App();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Key Points:**
|
|
78
|
+
- `jac_app()` is the **required entry point** that Jac looks for
|
|
79
|
+
- It should return your root component (usually called `App`)
|
|
80
|
+
- The `cl` (client) block indicates this code runs in the browser
|
|
81
|
+
- This function gets called automatically when the page loads
|
|
82
|
+
|
|
83
|
+
**Example from Todo App:**
|
|
84
|
+
```jac
|
|
85
|
+
def App() -> any {
|
|
86
|
+
login_route = {"path": "/login", "component": lambda -> any { return LoginForm(); }, "guard": None};
|
|
87
|
+
signup_route = {"path": "/signup", "component": lambda -> any { return SignupForm(); }, "guard": None};
|
|
88
|
+
todos_route = {"path": "/todos", "component": lambda -> any { return TodoApp(); }, "guard": jacIsLoggedIn};
|
|
89
|
+
|
|
90
|
+
routes = [login_route, signup_route, todos_route];
|
|
91
|
+
router = initRouter(routes, "/login");
|
|
92
|
+
|
|
93
|
+
currentPath = router.path();
|
|
94
|
+
return <div>
|
|
95
|
+
{Nav(currentPath)}
|
|
96
|
+
{router.render()}
|
|
97
|
+
</div>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
def jac_app() -> any {
|
|
101
|
+
return App();
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 🧩 3. Creating Components
|
|
108
|
+
|
|
109
|
+
Components in Jac are functions that return JSX (JavaScript XML). They're similar to React components but written in pure Jac syntax.
|
|
110
|
+
|
|
111
|
+
### Basic Component Structure
|
|
112
|
+
|
|
113
|
+
```jac
|
|
114
|
+
cl {
|
|
115
|
+
def MyComponent() -> any {
|
|
116
|
+
return <div>
|
|
117
|
+
<h1>Hello from Jac!</h1>
|
|
118
|
+
</div>;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Component with Props
|
|
124
|
+
|
|
125
|
+
Components can accept parameters (props):
|
|
126
|
+
|
|
127
|
+
```jac
|
|
128
|
+
def TodoItem(item: dict) -> any {
|
|
129
|
+
return <li key={item.id}>
|
|
130
|
+
<span>{item.text}</span>
|
|
131
|
+
<button onClick={lambda -> None { removeTodo(item.id); }}>
|
|
132
|
+
Remove
|
|
133
|
+
</button>
|
|
134
|
+
</li>;
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Component Features:**
|
|
139
|
+
- **JSX Syntax**: Write HTML-like syntax directly in Jac
|
|
140
|
+
- **Inline Styles**: Use JavaScript objects for styling
|
|
141
|
+
- **Event Handlers**: Attach functions to user interactions
|
|
142
|
+
- **Reusability**: Components can call other components
|
|
143
|
+
|
|
144
|
+
### Example: TodoItem Component
|
|
145
|
+
|
|
146
|
+
```jac
|
|
147
|
+
def TodoItem(item: dict) -> any {
|
|
148
|
+
return <li key={item.id} style={{
|
|
149
|
+
"display": "flex",
|
|
150
|
+
"gap": "12px",
|
|
151
|
+
"alignItems": "center",
|
|
152
|
+
"padding": "12px",
|
|
153
|
+
"background": "#FFFFFF",
|
|
154
|
+
"borderRadius": "10px"
|
|
155
|
+
}}>
|
|
156
|
+
<input
|
|
157
|
+
type="checkbox"
|
|
158
|
+
checked={item.done}
|
|
159
|
+
onChange={lambda -> None { toggleTodo(item.id); }}
|
|
160
|
+
/>
|
|
161
|
+
<span style={{
|
|
162
|
+
"textDecoration": ("line-through" if item.done else "none")
|
|
163
|
+
}}>
|
|
164
|
+
{item.text}
|
|
165
|
+
</span>
|
|
166
|
+
<button onClick={lambda -> None { removeTodo(item.id); }}>
|
|
167
|
+
Remove
|
|
168
|
+
</button>
|
|
169
|
+
</li>;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Breaking it down:**
|
|
174
|
+
- `item: dict` - Component receives a dictionary (todo item) as a prop
|
|
175
|
+
- `style={{...}}` - Inline styles using JavaScript objects
|
|
176
|
+
- `checked={item.done}` - Dynamic attribute binding
|
|
177
|
+
- `onChange={lambda -> None {...}}` - Event handler using lambda
|
|
178
|
+
- `{item.text}` - Interpolation of JavaScript expressions in JSX
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 🗄️ 4. Adding State
|
|
183
|
+
|
|
184
|
+
State management in Jac uses `createState()`, which provides reactive state similar to React hooks but simpler.
|
|
185
|
+
|
|
186
|
+
### Understanding `createState()`
|
|
187
|
+
|
|
188
|
+
```jac
|
|
189
|
+
let [todoState, setTodoState] = createState({
|
|
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
|
|
200
|
+
|
|
201
|
+
### Reading State
|
|
202
|
+
|
|
203
|
+
Always call the getter as a function:
|
|
204
|
+
|
|
205
|
+
```jac
|
|
206
|
+
def TodoApp() -> any {
|
|
207
|
+
s = todoState(); # Call getter function
|
|
208
|
+
|
|
209
|
+
return <div>
|
|
210
|
+
<p>Total todos: {s.items.length}</p>
|
|
211
|
+
<p>Current filter: {s.filter}</p>
|
|
212
|
+
</div>;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Updating State
|
|
217
|
+
|
|
218
|
+
The setter merges updates with existing state:
|
|
219
|
+
|
|
220
|
+
```jac
|
|
221
|
+
# Update single property
|
|
222
|
+
setTodoState({"filter": "active"});
|
|
223
|
+
|
|
224
|
+
# Update multiple properties
|
|
225
|
+
setTodoState({
|
|
226
|
+
"filter": "completed",
|
|
227
|
+
"input": "New todo text"
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
# Update arrays (create new array)
|
|
231
|
+
s = todoState();
|
|
232
|
+
setTodoState({"items": s.items.concat([newItem])});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Important:** The setter performs a **shallow merge**, meaning:
|
|
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
|
|
241
|
+
|
|
242
|
+
```jac
|
|
243
|
+
cl {
|
|
244
|
+
# Initialize state
|
|
245
|
+
let [todoState, setTodoState] = createState({
|
|
246
|
+
"items": [],
|
|
247
|
+
"filter": "all",
|
|
248
|
+
"input": ""
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
# Read state in component
|
|
252
|
+
def TodoApp() -> any {
|
|
253
|
+
s = todoState();
|
|
254
|
+
itemsArr = s.items;
|
|
255
|
+
|
|
256
|
+
return <div>
|
|
257
|
+
<input
|
|
258
|
+
value={s.input}
|
|
259
|
+
onChange={lambda e: any -> None {
|
|
260
|
+
setTodoState({"input": e.target.value});
|
|
261
|
+
}}
|
|
262
|
+
/>
|
|
263
|
+
<div>
|
|
264
|
+
{[TodoItem(item) for item in itemsArr]}
|
|
265
|
+
</div>
|
|
266
|
+
</div>;
|
|
267
|
+
}
|
|
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
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 🎯 5. Event Handling
|
|
294
|
+
|
|
295
|
+
Event handling in Jac works just like React, but with Jac's lambda syntax.
|
|
296
|
+
|
|
297
|
+
### Basic Event Handlers
|
|
298
|
+
|
|
299
|
+
```jac
|
|
300
|
+
def Button() -> any {
|
|
301
|
+
return <button onClick={lambda -> None {
|
|
302
|
+
console.log("Button clicked!");
|
|
303
|
+
}}>
|
|
304
|
+
Click Me
|
|
305
|
+
</button>;
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Event Handlers with Event Object
|
|
310
|
+
|
|
311
|
+
```jac
|
|
312
|
+
def InputField() -> any {
|
|
313
|
+
return <input
|
|
314
|
+
type="text"
|
|
315
|
+
onChange={lambda e: any -> None {
|
|
316
|
+
console.log("Input value:", e.target.value);
|
|
317
|
+
setTodoState({"input": e.target.value});
|
|
318
|
+
}}
|
|
319
|
+
/>;
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Form Submission
|
|
324
|
+
|
|
325
|
+
```jac
|
|
326
|
+
def TodoForm() -> any {
|
|
327
|
+
return <form onSubmit={onAddTodo}>
|
|
328
|
+
<input id="todo-input" type="text" />
|
|
329
|
+
<button type="submit">Add Todo</button>
|
|
330
|
+
</form>;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async def onAddTodo(e: any) -> None {
|
|
334
|
+
e.preventDefault(); # Prevent page refresh
|
|
335
|
+
inputEl = document.getElementById("todo-input");
|
|
336
|
+
text = inputEl.value.trim();
|
|
337
|
+
|
|
338
|
+
if not text { return; }
|
|
339
|
+
|
|
340
|
+
# Handle form submission
|
|
341
|
+
new_todo = await __jacSpawn("create_todo", {"text": text});
|
|
342
|
+
# ... update state
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Common Event Types
|
|
347
|
+
|
|
348
|
+
| Event | Syntax | Use Case |
|
|
349
|
+
|-------|--------|----------|
|
|
350
|
+
| `onClick` | `onClick={lambda -> None {...}}` | Button clicks |
|
|
351
|
+
| `onChange` | `onChange={lambda e: any -> None {...}}` | Input changes |
|
|
352
|
+
| `onSubmit` | `onSubmit={lambda e: any -> None {...}}` | Form submission |
|
|
353
|
+
| `onKeyPress` | `onKeyPress={lambda e: any -> None {...}}` | Keyboard input |
|
|
354
|
+
|
|
355
|
+
### Advanced: Conditional Event Handlers
|
|
356
|
+
|
|
357
|
+
```jac
|
|
358
|
+
def FilterButton(filterType: str) -> any {
|
|
359
|
+
s = todoState();
|
|
360
|
+
isActive = s.filter == filterType;
|
|
361
|
+
|
|
362
|
+
return <button
|
|
363
|
+
onClick={lambda -> None {
|
|
364
|
+
setFilter(filterType);
|
|
365
|
+
}}
|
|
366
|
+
style={{
|
|
367
|
+
"background": ("#7C3AED" if isActive else "#FFFFFF"),
|
|
368
|
+
"color": ("#FFFFFF" if isActive else "#7C3AED")
|
|
369
|
+
}}
|
|
370
|
+
>
|
|
371
|
+
{filterType}
|
|
372
|
+
</button>;
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## ✨ 6. Magic: No More Axios/Fetch!
|
|
379
|
+
|
|
380
|
+
One of Jac's most powerful features is **seamless backend communication** without writing HTTP requests, fetch calls, or axios code.
|
|
381
|
+
|
|
382
|
+
### The `__jacSpawn()` Function
|
|
383
|
+
|
|
384
|
+
Instead of writing:
|
|
385
|
+
```javascript
|
|
386
|
+
// Traditional approach
|
|
387
|
+
const response = await fetch('/api/todos', {
|
|
388
|
+
method: 'POST',
|
|
389
|
+
headers: { 'Content-Type': 'application/json' },
|
|
390
|
+
body: JSON.stringify({ text: 'New todo' })
|
|
391
|
+
});
|
|
392
|
+
const data = await response.json();
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
You simply write:
|
|
396
|
+
```jac
|
|
397
|
+
new_todo = await __jacSpawn("create_todo", {"text": "New todo"});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### How `__jacSpawn()` Works
|
|
401
|
+
|
|
402
|
+
```jac
|
|
403
|
+
# Call a walker on the backend
|
|
404
|
+
result = await __jacSpawn(
|
|
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
|
+
);
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**Example from Todo App:**
|
|
412
|
+
```jac
|
|
413
|
+
# Create a new todo
|
|
414
|
+
new_todo = await __jacSpawn("create_todo", {"text": text});
|
|
415
|
+
|
|
416
|
+
# Toggle todo status
|
|
417
|
+
toggled_todo = await __jacSpawn("toggle_todo", {}, todo_id);
|
|
418
|
+
|
|
419
|
+
# Read all todos
|
|
420
|
+
todos = await __jacSpawn("read_todos");
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Backend Walkers
|
|
424
|
+
|
|
425
|
+
Walkers are defined in the same `app.jac` file (outside the `cl` block):
|
|
426
|
+
|
|
427
|
+
```jac
|
|
428
|
+
# Node definition (data model)
|
|
429
|
+
node Todo {
|
|
430
|
+
has text: str;
|
|
431
|
+
has done: bool = False;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
# Walker: Create a new todo
|
|
435
|
+
walker create_todo {
|
|
436
|
+
can create with `root entry {
|
|
437
|
+
new_todo = here ++> Todo(text="Example Todo");
|
|
438
|
+
report new_todo;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
# Walker: Toggle todo status
|
|
443
|
+
walker toggle_todo {
|
|
444
|
+
can toggle with Todo entry {
|
|
445
|
+
here.done = not here.done;
|
|
446
|
+
report here;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
# Walker: Read all todos
|
|
451
|
+
walker read_todos {
|
|
452
|
+
can read with `root entry {
|
|
453
|
+
visit [-->(`?Todo)];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
can report_todos with exit {
|
|
457
|
+
report here;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Complete Example: Creating a Todo
|
|
463
|
+
|
|
464
|
+
**Frontend (in `cl` block):**
|
|
465
|
+
```jac
|
|
466
|
+
async def onAddTodo(e: any) -> None {
|
|
467
|
+
e.preventDefault();
|
|
468
|
+
text = document.getElementById("todo-input").value.trim();
|
|
469
|
+
if not text { return; }
|
|
470
|
+
|
|
471
|
+
# Call backend walker - no fetch/axios needed!
|
|
472
|
+
new_todo = await __jacSpawn("create_todo", {"text": text});
|
|
473
|
+
|
|
474
|
+
# Update frontend state
|
|
475
|
+
s = todoState();
|
|
476
|
+
newItem = {
|
|
477
|
+
"id": new_todo._jac_id,
|
|
478
|
+
"text": new_todo.text,
|
|
479
|
+
"done": new_todo.done
|
|
480
|
+
};
|
|
481
|
+
setTodoState({"items": s.items.concat([newItem])});
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Backend (outside `cl` block):**
|
|
486
|
+
```jac
|
|
487
|
+
walker create_todo {
|
|
488
|
+
can create with `root entry {
|
|
489
|
+
# 'text' comes from the fields dictionary passed to __jacSpawn
|
|
490
|
+
text = context.text;
|
|
491
|
+
new_todo = here ++> Todo(text=text);
|
|
492
|
+
report new_todo;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Benefits of `__jacSpawn()`
|
|
498
|
+
|
|
499
|
+
✅ **No HTTP Configuration**: No need to set up API endpoints, CORS, or request/response formats
|
|
500
|
+
✅ **Type Safety**: Jac handles serialization automatically
|
|
501
|
+
✅ **Authentication**: Built-in token management via `jacLogin()` / `jacLogout()`
|
|
502
|
+
✅ **Error Handling**: Exceptions are properly propagated
|
|
503
|
+
✅ **Graph Operations**: Direct access to graph-based data operations
|
|
504
|
+
✅ **Less Code**: Eliminates boilerplate HTTP client code
|
|
505
|
+
|
|
506
|
+
### Authentication Helpers
|
|
507
|
+
|
|
508
|
+
Jac also provides built-in auth functions:
|
|
509
|
+
|
|
510
|
+
```jac
|
|
511
|
+
# Sign up
|
|
512
|
+
result = await jacSignup(username, password);
|
|
513
|
+
if result["success"] {
|
|
514
|
+
navigate("/todos");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
# Log in
|
|
518
|
+
success = await jacLogin(username, password);
|
|
519
|
+
if success {
|
|
520
|
+
navigate("/todos");
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
# Log out
|
|
524
|
+
jacLogout();
|
|
525
|
+
navigate("/login");
|
|
526
|
+
|
|
527
|
+
# Check if logged in
|
|
528
|
+
if jacIsLoggedIn() {
|
|
529
|
+
return <div>Welcome!</div>;
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## 🎨 Complete Example: Todo App Structure
|
|
536
|
+
|
|
537
|
+
Here's how all the pieces fit together:
|
|
538
|
+
|
|
539
|
+
```jac
|
|
540
|
+
# ============================================================================
|
|
541
|
+
# BACKEND: Data Models and Walkers
|
|
542
|
+
# ============================================================================
|
|
543
|
+
node Todo {
|
|
544
|
+
has text: str;
|
|
545
|
+
has done: bool = False;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
walker create_todo {
|
|
549
|
+
can create with `root entry {
|
|
550
|
+
new_todo = here ++> Todo(text=context.text);
|
|
551
|
+
report new_todo;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
# ============================================================================
|
|
556
|
+
# FRONTEND: Components, State, and Events
|
|
557
|
+
# ============================================================================
|
|
558
|
+
cl {
|
|
559
|
+
# State
|
|
560
|
+
let [todoState, setTodoState] = createState({
|
|
561
|
+
"items": [],
|
|
562
|
+
"filter": "all"
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
# Component
|
|
566
|
+
def TodoApp() -> any {
|
|
567
|
+
s = todoState();
|
|
568
|
+
return <div>
|
|
569
|
+
<h2>My Todos</h2>
|
|
570
|
+
<form onSubmit={onAddTodo}>
|
|
571
|
+
<input id="todo-input" type="text" />
|
|
572
|
+
<button type="submit">Add Todo</button>
|
|
573
|
+
</form>
|
|
574
|
+
{[TodoItem(item) for item in s.items]}
|
|
575
|
+
</div>;
|
|
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
|
+
}
|
|
585
|
+
|
|
586
|
+
# Entry Point
|
|
587
|
+
def jac_app() -> any {
|
|
588
|
+
return TodoApp();
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
## 🚀 Running the Todo App
|
|
596
|
+
|
|
597
|
+
To run this example:
|
|
598
|
+
|
|
599
|
+
```bash
|
|
600
|
+
# From the todo-app directory
|
|
601
|
+
jac serve app.jac
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Then visit `http://localhost:8000` in your browser.
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## 📚 Next Steps
|
|
609
|
+
|
|
610
|
+
Ready to dive deeper? Explore these advanced topics:
|
|
611
|
+
|
|
612
|
+
- **[Routing](routing.md)**: Learn about `initRouter()` for multi-page applications
|
|
613
|
+
- **[Lifecycle Hooks](lifecycle-hooks.md)**: Use `onMount()` for initialization logic
|
|
614
|
+
- **[Advanced State](advanced-state.md)**: Combine multiple `createState()` calls for complex apps
|
|
615
|
+
- **[Imports](imports.md)**: Import third-party libraries, other Jac files, and JavaScript modules
|
|
616
|
+
- **[Learn JAC](https://www.jac-lang.org)**: Explore Jac's graph-based data modeling
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## 💡 Key Takeaways
|
|
621
|
+
|
|
622
|
+
1. **Single Language**: Write frontend and backend in Jac
|
|
623
|
+
2. **No HTTP Client**: Use `__jacSpawn()` instead of fetch/axios
|
|
624
|
+
3. **Reactive State**: `createState()` manages UI updates automatically
|
|
625
|
+
4. **Component-Based**: Build reusable UI components with JSX
|
|
626
|
+
5. **Type Safety**: Jac provides type checking across frontend and backend
|
|
627
|
+
6. **Graph Database**: Built-in graph data model eliminates need for SQL/NoSQL
|
|
628
|
+
|
|
629
|
+
Happy coding with Jac! 🎉
|