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.
Files changed (33) hide show
  1. jac_client/docs/README.md +629 -0
  2. jac_client/docs/advanced-state.md +706 -0
  3. jac_client/docs/imports.md +650 -0
  4. jac_client/docs/lifecycle-hooks.md +554 -0
  5. jac_client/docs/routing.md +530 -0
  6. jac_client/examples/little-x/app.jac +615 -0
  7. jac_client/examples/little-x/package-lock.json +2840 -0
  8. jac_client/examples/little-x/package.json +23 -0
  9. jac_client/examples/little-x/submit-button.jac +8 -0
  10. jac_client/examples/todo-app/README.md +82 -0
  11. jac_client/examples/todo-app/app.jac +683 -0
  12. jac_client/examples/todo-app/package-lock.json +999 -0
  13. jac_client/examples/todo-app/package.json +22 -0
  14. jac_client/plugin/cli.py +328 -0
  15. jac_client/plugin/client.py +41 -0
  16. jac_client/plugin/client_runtime.jac +941 -0
  17. jac_client/plugin/vite_client_bundle.py +470 -0
  18. jac_client/tests/__init__.py +2 -0
  19. jac_client/tests/fixtures/button.jac +6 -0
  20. jac_client/tests/fixtures/client_app.jac +18 -0
  21. jac_client/tests/fixtures/client_app_with_antd.jac +21 -0
  22. jac_client/tests/fixtures/js_import.jac +30 -0
  23. jac_client/tests/fixtures/package-lock.json +329 -0
  24. jac_client/tests/fixtures/package.json +11 -0
  25. jac_client/tests/fixtures/relative_import.jac +13 -0
  26. jac_client/tests/fixtures/test_fragments_spread.jac +44 -0
  27. jac_client/tests/fixtures/utils.js +22 -0
  28. jac_client/tests/test_cl.py +360 -0
  29. jac_client/tests/test_create_jac_app.py +139 -0
  30. jac_client-0.1.0.dist-info/METADATA +126 -0
  31. jac_client-0.1.0.dist-info/RECORD +33 -0
  32. jac_client-0.1.0.dist-info/WHEEL +4 -0
  33. jac_client-0.1.0.dist-info/entry_points.txt +4 -0
@@ -0,0 +1,530 @@
1
+ # Routing in Jac: Building Multi-Page Applications
2
+
3
+ Learn how to use `initRouter()` to create multi-page applications with navigation, route guards, and dynamic routing.
4
+
5
+ ---
6
+
7
+ ## 📚 Table of Contents
8
+
9
+ - [What is Routing?](#what-is-routing)
10
+ - [Basic Routing Setup](#basic-routing-setup)
11
+ - [Route Configuration](#route-configuration)
12
+ - [Navigation](#navigation)
13
+ - [Route Guards](#route-guards)
14
+ - [Complete Example](#complete-example)
15
+ - [Advanced Patterns](#advanced-patterns)
16
+
17
+ ---
18
+
19
+ ## What is Routing?
20
+
21
+ Routing allows you to create multi-page applications where different URLs display different components. In Jac, routing is handled by the `initRouter()` function, which manages navigation using hash-based routing (e.g., `#/login`, `#/todos`).
22
+
23
+ **Key Benefits:**
24
+ - **Single Page Application (SPA)**: No page refreshes when navigating
25
+ - **URL-based Navigation**: Each view has its own URL
26
+ - **Browser History**: Back/forward buttons work automatically
27
+ - **Route Guards**: Protect routes with authentication checks
28
+ - **Reactive Updates**: Route changes automatically update components
29
+
30
+ ---
31
+
32
+ ## Basic Routing Setup
33
+
34
+ ### Setting Up the Router
35
+
36
+ ```jac
37
+ cl {
38
+ def App() -> any {
39
+ # Define routes
40
+ routes = [
41
+ {"path": "/", "component": lambda -> any { return HomeView(); }, "guard": None},
42
+ {"path": "/about", "component": lambda -> any { return AboutView(); }, "guard": None}
43
+ ];
44
+
45
+ # Initialize router with default route
46
+ router = initRouter(routes, "/");
47
+
48
+ # Render the router
49
+ return <div>
50
+ {router.render()}
51
+ </div>;
52
+ }
53
+
54
+ def jac_app() -> any {
55
+ return App();
56
+ }
57
+ }
58
+ ```
59
+
60
+ **Key Components:**
61
+ - **`routes`**: Array of route configurations
62
+ - **`initRouter()`**: Creates the router instance
63
+ - **`router.render()`**: Renders the component for the current route
64
+
65
+ ---
66
+
67
+ ## Route Configuration
68
+
69
+ Each route is a dictionary with three properties:
70
+
71
+ ### Route Structure
72
+
73
+ ```jac
74
+ route = {
75
+ "path": "/todos", # URL path
76
+ "component": lambda -> any { # Component to render
77
+ return TodoApp();
78
+ },
79
+ "guard": jacIsLoggedIn # Optional: route guard function
80
+ };
81
+ ```
82
+
83
+ ### Route Properties
84
+
85
+ | Property | Type | Required | Description |
86
+ |----------|------|----------|-------------|
87
+ | `path` | `str` | ✅ Yes | URL path (must start with `/`) |
88
+ | `component` | `function` | ✅ Yes | Function that returns a JSX component |
89
+ | `guard` | `function` | ❌ No | Function that returns `True` if route is accessible |
90
+
91
+ ### Example: Multiple Routes
92
+
93
+ ```jac
94
+ cl {
95
+ def App() -> any {
96
+ # Define all routes
97
+ home_route = {
98
+ "path": "/",
99
+ "component": lambda -> any { return HomeView(); },
100
+ "guard": None
101
+ };
102
+
103
+ login_route = {
104
+ "path": "/login",
105
+ "component": lambda -> any { return LoginForm(); },
106
+ "guard": None
107
+ };
108
+
109
+ todos_route = {
110
+ "path": "/todos",
111
+ "component": lambda -> any { return TodoApp(); },
112
+ "guard": jacIsLoggedIn # Requires authentication
113
+ };
114
+
115
+ profile_route = {
116
+ "path": "/profile",
117
+ "component": lambda -> any { return ProfileView(); },
118
+ "guard": jacIsLoggedIn # Requires authentication
119
+ };
120
+
121
+ # Combine all routes
122
+ routes = [home_route, login_route, todos_route, profile_route];
123
+
124
+ # Initialize router with default route
125
+ router = initRouter(routes, "/");
126
+
127
+ return <div>
128
+ {Nav(router.path())}
129
+ {router.render()}
130
+ </div>;
131
+ }
132
+ }
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Navigation
138
+
139
+ Jac provides multiple ways to navigate between routes.
140
+
141
+ ### Using the `navigate()` Runtime Function
142
+
143
+ ```jac
144
+ cl {
145
+ async def handleLogin(e: any) -> None {
146
+ e.preventDefault();
147
+ success = await jacLogin(username, password);
148
+ if success {
149
+ navigate("/todos"); # Global navigate function
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ ### Using the `Link` Component
156
+
157
+ The `Link` component creates clickable links that navigate to routes:
158
+
159
+ ```jac
160
+ cl {
161
+ def Nav() -> any {
162
+ return <nav>
163
+ <Link href="/">Home</Link>
164
+ <Link href="/todos">Todos</Link>
165
+ <Link href="/profile">Profile</Link>
166
+ </nav>;
167
+ }
168
+ }
169
+ ```
170
+
171
+ **Link Component Features:**
172
+ - Automatic hash-based navigation
173
+ - Updates URL without page refresh
174
+ - Triggers route changes and component updates
175
+
176
+ ### Complete Navigation Example
177
+
178
+ ```jac
179
+ cl {
180
+ def Nav(currentPath: str) -> any {
181
+ return <nav style={{
182
+ "display": "flex",
183
+ "gap": "16px",
184
+ "padding": "12px"
185
+ }}>
186
+ <Link href="/" style={{
187
+ "color": ("#7C3AED" if currentPath == "/" else "#111827"),
188
+ "textDecoration": "none",
189
+ "fontWeight": ("700" if currentPath == "/" else "500")
190
+ }}>
191
+ Home
192
+ </Link>
193
+ <Link href="/todos" style={{
194
+ "color": ("#7C3AED" if currentPath == "/todos" else "#111827"),
195
+ "textDecoration": "none",
196
+ "fontWeight": ("700" if currentPath == "/todos" else "500")
197
+ }}>
198
+ Todos
199
+ </Link>
200
+ <button onClick={lambda -> None {
201
+ navigate("/login");
202
+ }}>
203
+ Logout
204
+ </button>
205
+ </nav>;
206
+ }
207
+
208
+ def App() -> any {
209
+ routes = [/* routes */];
210
+ router = initRouter(routes, "/");
211
+ currentPath = router.path();
212
+
213
+ return <div>
214
+ {Nav(currentPath)}
215
+ {router.render()}
216
+ </div>;
217
+ }
218
+ }
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Route Guards
224
+
225
+ Route guards protect routes by checking conditions (like authentication) before allowing access.
226
+
227
+ ### How Guards Work
228
+
229
+ 1. **Guard Function**: A function that returns `True` (allow) or `False` (deny)
230
+ 2. **Automatic Check**: Router checks the guard when the route matches
231
+ 3. **Access Denied**: If guard returns `False`, shows "Access Denied" instead of the component
232
+
233
+ ### Basic Guard Example
234
+
235
+ ```jac
236
+ cl {
237
+ def App() -> any {
238
+ public_route = {
239
+ "path": "/public",
240
+ "component": lambda -> any { return PublicView(); },
241
+ "guard": None # No guard - always accessible
242
+ };
243
+
244
+ protected_route = {
245
+ "path": "/private",
246
+ "component": lambda -> any { return PrivateView(); },
247
+ "guard": jacIsLoggedIn # Requires authentication
248
+ };
249
+
250
+ routes = [public_route, protected_route];
251
+ router = initRouter(routes, "/");
252
+
253
+ return <div>{router.render()}</div>;
254
+ }
255
+ }
256
+ ```
257
+
258
+ ### Custom Guard Functions
259
+
260
+ You can create custom guard functions:
261
+
262
+ ```jac
263
+ cl {
264
+ # Check if user is admin
265
+ def isAdmin() -> bool {
266
+ if not jacIsLoggedIn() {
267
+ return False;
268
+ }
269
+ user = getCurrentUser(); # Your custom function
270
+ return user.role == "admin";
271
+ }
272
+
273
+ # Check if user has specific permission
274
+ def hasPermission(permission: str) -> bool {
275
+ if not jacIsLoggedIn() {
276
+ return False;
277
+ }
278
+ user = getCurrentUser();
279
+ return permission in user.permissions;
280
+ }
281
+
282
+ def App() -> any {
283
+ admin_route = {
284
+ "path": "/admin",
285
+ "component": lambda -> any { return AdminView(); },
286
+ "guard": isAdmin # Custom guard
287
+ };
288
+
289
+ settings_route = {
290
+ "path": "/settings",
291
+ "component": lambda -> any { return SettingsView(); },
292
+ "guard": lambda -> bool { return hasPermission("settings:edit"); } # Inline guard
293
+ };
294
+
295
+ routes = [admin_route, settings_route];
296
+ router = initRouter(routes, "/");
297
+
298
+ return <div>{router.render()}</div>;
299
+ }
300
+ }
301
+ ```
302
+
303
+ ### Redirecting on Guard Failure
304
+
305
+ You can automatically redirect when a guard fails:
306
+
307
+ ```jac
308
+ cl {
309
+ def protectedGuard() -> bool {
310
+ if not jacIsLoggedIn() {
311
+ navigate("/login"); # Redirect to login
312
+ return False;
313
+ }
314
+ return True;
315
+ }
316
+
317
+ def App() -> any {
318
+ protected_route = {
319
+ "path": "/todos",
320
+ "component": lambda -> any { return TodoApp(); },
321
+ "guard": protectedGuard
322
+ };
323
+
324
+ routes = [protected_route];
325
+ router = initRouter(routes, "/login");
326
+
327
+ return <div>{router.render()}</div>;
328
+ }
329
+ }
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Complete Example
335
+
336
+ Here's a complete routing example from the Todo App:
337
+
338
+ ```jac
339
+ cl {
340
+ def Nav(route: str) -> any {
341
+ if not jacIsLoggedIn() or route == "/login" or route == "/signup" {
342
+ return None;
343
+ }
344
+ return <nav style={{
345
+ "background": "#FFFFFF",
346
+ "padding": "12px",
347
+ "boxShadow": "0 1px 2px rgba(17,24,39,0.06)"
348
+ }}>
349
+ <div style={{
350
+ "maxWidth": "960px",
351
+ "margin": "0 auto",
352
+ "display": "flex",
353
+ "gap": "16px",
354
+ "alignItems": "center"
355
+ }}>
356
+ <Link href="/todos" style={{"textDecoration": "none"}}>
357
+ <span style={{
358
+ "color": "#111827",
359
+ "fontWeight": "800",
360
+ "fontSize": "18px"
361
+ }}>📝 My Todos</span>
362
+ </Link>
363
+ <button
364
+ onClick={logout_action}
365
+ style={{
366
+ "marginLeft": "auto",
367
+ "padding": "8px 12px",
368
+ "background": "#FFFFFF",
369
+ "color": "#374151",
370
+ "border": "1px solid #E5E7EB",
371
+ "borderRadius": "18px",
372
+ "cursor": "pointer"
373
+ }}
374
+ >
375
+ Logout
376
+ </button>
377
+ </div>
378
+ </nav>;
379
+ }
380
+
381
+ def App() -> any {
382
+ # Define routes
383
+ login_route = {
384
+ "path": "/login",
385
+ "component": lambda -> any { return LoginForm(); },
386
+ "guard": None
387
+ };
388
+
389
+ signup_route = {
390
+ "path": "/signup",
391
+ "component": lambda -> any { return SignupForm(); },
392
+ "guard": None
393
+ };
394
+
395
+ todos_route = {
396
+ "path": "/todos",
397
+ "component": lambda -> any { return TodoApp(); },
398
+ "guard": jacIsLoggedIn # Protected route
399
+ };
400
+
401
+ # Initialize router
402
+ routes = [login_route, signup_route, todos_route];
403
+ router = initRouter(routes, "/login"); # Default to login page
404
+
405
+ # Get current path for navigation
406
+ currentPath = router.path();
407
+
408
+ return <div style={{
409
+ "minHeight": "95vh",
410
+ "background": "#F7F8FA",
411
+ "padding": "24px"
412
+ }}>
413
+ {Nav(currentPath)}
414
+ <div style={{
415
+ "maxWidth": "960px",
416
+ "margin": "0 auto",
417
+ "padding": "20px"
418
+ }}>
419
+ {router.render()}
420
+ </div>
421
+ </div>;
422
+ }
423
+
424
+ def jac_app() -> any {
425
+ return App();
426
+ }
427
+ }
428
+ ```
429
+
430
+ ---
431
+
432
+ ## Advanced Patterns
433
+
434
+ ### Getting Current Route
435
+
436
+ Use `router.path()` to get the current route:
437
+
438
+ ```jac
439
+ cl {
440
+ def App() -> any {
441
+ routes = [/* routes */];
442
+ router = initRouter(routes, "/");
443
+
444
+ currentPath = router.path(); # Get current route
445
+
446
+ # Use currentPath for conditional rendering
447
+ return <div>
448
+ {Nav(currentPath)}
449
+ {router.render()}
450
+ </div>;
451
+ }
452
+ }
453
+ ```
454
+
455
+ ### Programmatic Navigation
456
+
457
+ Navigate programmatically from anywhere:
458
+
459
+ ```jac
460
+ cl {
461
+ async def handleLogin(e: any) -> None {
462
+ e.preventDefault();
463
+ success = await jacLogin(username, password);
464
+ if success {
465
+ router = __jacReactiveContext.router;
466
+ if router {
467
+ router.navigate("/todos");
468
+ }
469
+ }
470
+ }
471
+
472
+ def logout_action() -> None {
473
+ jacLogout();
474
+ navigate("/login"); # Or use router.navigate()
475
+ }
476
+ }
477
+ ```
478
+
479
+ ### Dynamic Route Parameters (Future)
480
+
481
+ While Jac's router uses hash-based routing without path parameters, you can pass state through navigation:
482
+
483
+ ```jac
484
+ cl {
485
+ def navigateToTodo(id: str) -> None {
486
+ # Store ID in state before navigating
487
+ setTodoId(id);
488
+ navigate("/todo-detail");
489
+ }
490
+
491
+ def TodoDetailView() -> any {
492
+ id = todoId();
493
+ todo = getTodoById(id); # Fetch todo by ID
494
+ return <div>{todo.text}</div>;
495
+ }
496
+ }
497
+ ```
498
+
499
+ ### 404 Handling
500
+
501
+ The router automatically shows a 404 message if no route matches:
502
+
503
+ ```jac
504
+ # If user navigates to /unknown-route
505
+ # Router automatically renders: "404 - Route not found: /unknown-route"
506
+ ```
507
+
508
+ ---
509
+
510
+ ## Best Practices
511
+
512
+ 1. **Default Route**: Always provide a sensible default route in `initRouter()`
513
+ 2. **Route Guards**: Use guards for protected routes instead of checking in components
514
+ 3. **Link Components**: Use `Link` component instead of manual hash manipulation
515
+ 4. **Guard Functions**: Keep guard functions simple and focused
516
+ 5. **Navigation**: Use `navigate()` for programmatic navigation
517
+ 6. **Current Path**: Use `router.path()` for conditional rendering based on current route
518
+
519
+ ---
520
+
521
+ ## Summary
522
+
523
+ - **Routing**: Use `initRouter()` to create multi-page applications
524
+ - **Routes**: Configure routes with `path`, `component`, and optional `guard`
525
+ - **Navigation**: Use `Link` component or `navigate()` function
526
+ - **Guards**: Protect routes with guard functions
527
+ - **Current Route**: Access current route with `router.path()`
528
+
529
+ Routing in Jac is simple, reactive, and powerful! 🚀
530
+