htmdart 0.6.0
htmdart: ^0.6.0 copied to clipboard
Build simple systems with Dart + HTMX
Htmdart combines the power of shelf, htmx, sometimes hyperscript and htmleez to give you the simplest and fastest way to build web applications in pure Dart (with a great DX too).
Before Starting #
- Make sure you're familiar with shelf as Htmdart is built on top of it
shelf_router is not necessary, htmdart has its own router
- Make sure you're familiar with hypermedia and htmx
- Check out htmleez in order to understand how you can compose and render your HTML directly in dart
- And make sure to read this essay in order to have a better idea of when to use htmx
P.S. Check out all the very informative memes about htmx and hypermedia in here
In case you'd like to add interactivity to your app you can check out:
- hyperscript which is our preferred option
- alpinejs
- and many other tools
Install #
Add to your pubspec.yaml
:
dart pub add htmdart
Hello World Example #
import 'dart:math';
import 'package:htmdart/htmdart.dart'; /// htmleez is automatically imported with htmdart
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
final router = Router()
..get("/", homePage)
..get("/random", randomNumber);
Future<void> main() async {
final server = await io.serve(router.call, 'localhost', 8080);
print('Listening on http://${server.address.host}:${server.port}');
}
Response homePage(Request _) {
return html([
body([
div([$id("number"), "0".t]),
button([
$hx.get("/random"),
"Get Random Number".t,
]),
])
]).response;
}
Response randomNumber(Request _) {
final n = Random().nextInt(100);
return div([
$id("number"),
$hx.swapOob.yes,
n.toString().t,
]).response;
}
Core Concepts #
1. HTML Responses #
HtmlResponse
Wraps a list ofHTML
components into ashelf.Response
withContent-Type: text/html; charset=utf-8
.Response myPage(Request req) { return HtmlResponse([ html([ h1(["Hello, world!".t]), ]), ]); } //You can also use the extensions Response myPage(Request req) { return html([ h1(["Hello, world!".t]) ]).response; }
- Extensions
- On a single
HTML
:myElement.response
- On a
List<HTML>
:myListOfElements.response
- On a single
2. HTMX utilities #
The hx
class provides all the standard HTMX attributes
- Simple verbs
hx.get("/path") hx.post("/submit") hx.put(...) hx.delete(...)
- Dynamic handler binding
// Let's imagine this handler Response handleNoteDetails(Request req, String noteId) {...} final router = Router()..get("/notes/<noteId>", handleNoteDetails); // Then in your elements you can automatically pick up the HTTP verb and route $hx.handle(handleNoteDetails) // In case your route has path parameters (like in this case) // you can pass them in order as a List<String> $hx.handler(handleNoteDetails, [note.id]) // You can also pass query parameters $hx.handler(handleNoteDetails, [note.id], {"fromQP": true}) // It renders to // hx-get="/notes/myNoteId?fromQP=true";
- Swap controls
$hx.swap.innerHTML $hx.swapOob.yes // out-of-band swaps
- Extras
All the available hx attributes from htmx have been added, you can see them here$hx.vals("js:{ count: … }") // inject JSON values $hx.select(".result") // response selector $hx.confirm("Are you sure?")
3. Hyperscript attribute #
Built-in support for hyperscript's attribute
div([
className("btn"),
$_("on click add .active to me then wait 500ms then remove .active"),
"Click me".t,
])
Renders ->
<div class="btn" _="on click add .active to me then wait 500ms then remove .active">Click me</div>
4. Request Extensions #
On any shelf.Request
you can check HTMX metadata:
if (request.isHx) // true if HX-Request header == "true"
if (request.hxTarget != null)
print(request.hxTriggerName);
Routing #
Use the built-in Router
(from htmdart/src/router/router.dart
) to declare routes:
final router = Router()
..notFoundHandler((req) => html([h1(["404".t])]).response)
..get("/", homePageHandler)
..post("/increment", incrementHandler);
void main() async {
final server = await io.serve(router.call, 'localhost', 8080);
print("Listening on ${server.port}");
}
- Path parameters
Response handler(Request req, String param) {...} final router = Router()..get("/handler/<param>", handler);
- Any Method
Response handler(Request req, String param) { switch(req.method) { case "GET": ... case "POST": ... } } final router = Router()..any("/any", handler);
- Grouping
final apiGroup = router.group("/api"); apiGroup.get("/items", listItems); apiGroup.post("/items", createItem);
- Static files
router.static("/public", "web/public");
- Middleware
- Built-in support for middlewares with
router.use(myMiddleware)
- Built-in pretty-logging:
prettyLogMiddleware()
- Built-in support for middlewares with
- Redirects
Redirect not defined routes with
router.redirect("/", "/login"),