Skip to content

Commit 804f6db

Browse files
Merge pull request #2 from starfederation/adding-examples-projects
Adding examples projects
2 parents 8b33b5f + cf0f525 commit 804f6db

File tree

5 files changed

+350
-70
lines changed

5 files changed

+350
-70
lines changed

README.md

Lines changed: 33 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -41,77 +41,41 @@ The SDK offers APIs to abstract the Datastar protocol while allowing you to adap
4141
The following shows a simple implementation base of the Java `HttpServer`.
4242

4343
```kotlin
44-
45-
val server = HttpServer.create(
46-
InetSocketAddress(8080), // Port used
47-
0, // Backlog, 0 means default
48-
"/", // Path
49-
{ exchange -> // Exchange handler
50-
51-
// The `readSignals` method extracts the signals from the request.
52-
// If you use a web framework, you likely don't need this since the framework probably already handles this in its own way.
53-
// However, this method in the SDK allows you to provide your own unmarshalling strategy so you can adapt it to your preferred technology!
54-
val request: Request = adaptRequest(exchange)
55-
val signals = readSignals<EventsWrapper>(request, jsonUnmarshaller)
56-
57-
// Connect a Datastar SSE generator to the response.
58-
val response: Response = adaptResponse(exchange)
59-
val generator = ServerSentEventGenerator(response)
60-
61-
62-
// Below are some simple examples of how to use the generator.
63-
generator.patchElements(
64-
elements = "<div>Merge</div>",
65-
)
66-
67-
generator.patchSignals(
68-
signals =
69-
"""
70-
{
71-
"one":1,
72-
"two":2
73-
}
74-
""".trimIndent(),
75-
)
76-
77-
generator.executeScript(
78-
script = "alert('Hello World!')",
79-
)
80-
81-
exchange.close()
82-
}
44+
// Depending on your context, you'll need to adapt the `Request` and `Response` interfaces, as well as implementation of the `JsonUnmarshaller` type.
45+
val jsonUnmarshaller: JsonUnmarshaller<YourType> = ...
46+
val request: Request = ...
47+
val response: Response = ...
48+
49+
// The `readSignals` method extracts the signals from the request.
50+
// If you use a web framework, you likely don't need this since the framework probably already handles this in its own way.
51+
// However, this method in the SDK allows you to provide your own unmarshalling strategy so you can adapt it to your preferred technology!
52+
val request: Request = adaptRequest(exchange)
53+
val signals = readSignals<EventsWrapper>(request, jsonUnmarshaller)
54+
55+
// Connect a Datastar SSE generator to the response.
56+
val response: Response = adaptResponse(exchange)
57+
val generator = ServerSentEventGenerator(response)
58+
59+
// Below are some simple examples of how to use the generator.
60+
generator.patchElements(
61+
elements = "<div>Merge</div>",
8362
)
8463

85-
fun adaptRequest(exchange: HttpExchange): Request = object : Request {
86-
override fun bodyString() = exchange.requestBody.use { it.readAllBytes().decodeToString() }
87-
88-
override fun isGet() = exchange.requestMethod == "GET"
89-
90-
override fun readParam(string: String) =
91-
exchange.requestURI.query
92-
?.let { URLDecoder.decode(it, Charsets.UTF_8) }
93-
?.split("&")
94-
?.find { it.startsWith("$string=") }
95-
?.substringAfter("=")!!
96-
}
97-
98-
fun adaptResponse(exchange: HttpExchange): Response = object : Response {
99-
100-
override fun sendConnectionHeaders(
101-
status: Int,
102-
headers: Map<String, List<String>>,
103-
) {
104-
exchange.responseHeaders.putAll(headers)
105-
exchange.sendResponseHeaders(status, 0)
106-
}
64+
generator.patchSignals(
65+
signals =
66+
"""
67+
{
68+
"one":1,
69+
"two":2
70+
}
71+
""",
72+
)
10773

108-
override fun write(text: String) {
109-
exchange.responseBody.write(text.toByteArray())
110-
}
74+
generator.executeScript(
75+
script = "alert('Hello World!')",
76+
)
77+
```
11178

112-
override fun flush() {
113-
exchange.responseBody.flush()
114-
}
79+
### Examples
11580

116-
}
117-
```
81+
You can find runnable examples of how to use the SDK in multiple concrete web application frameworks and contexts in the [examples](examples/README.md) folder.

examples/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Datastar Kotlin Examples
2+
3+
* [About the examples](#about-the-examples)
4+
* [Running the examples](#running-the-examples)
5+
* [Prerequisites](#prerequisites)
6+
* [Java HTTP Server](#java-http-server)
7+
8+
## About the examples
9+
10+
This directory contains examples of using Datastar in Kotlin.
11+
All are a simple counter implemented using Datastar server-sent events.
12+
13+
- the front end consists in a single HTML page located in the `front` directory
14+
- each back implementation is in a separate directory, consisting in a single Kotlin file
15+
16+
## Running the examples
17+
18+
### Prerequisites
19+
20+
To make the examples work as simply as possible, each back implementation is a JBang script.
21+
22+
JBang is a tool that allows to run Kotlin scripts taking care of all the dependencies without the need to use more heavy weight tools like Maven or Gradle.
23+
You can find the installation instructions on the [official documentation](https://www.jbang.dev/documentation/jbang/latest/installation.html).
24+
25+
### Java HTTP Server
26+
27+
This example uses the plain Java `HttpServer` to serve the front end. ([code](java-httpserver/server.kt))
28+
29+
```shell
30+
cd ./java-httpserver ; jbang server.kt ; cd ..
31+
```

examples/front/counter.html

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>Counting Stars</title>
7+
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@main/bundles/datastar.js"></script>
8+
<style>
9+
* {
10+
margin: 0;
11+
padding: 0;
12+
box-sizing: border-box;
13+
}
14+
15+
body {
16+
min-height: 100vh;
17+
display: flex;
18+
justify-content: center;
19+
align-items: center;
20+
background: radial-gradient(ellipse at center, #0a0010 0%, #000000 50%);
21+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
22+
padding: 2rem;
23+
}
24+
25+
body::before {
26+
content: '';
27+
position: absolute;
28+
top: 0;
29+
left: 0;
30+
right: 0;
31+
bottom: 0;
32+
background: radial-gradient(circle at 20% 50%, rgba(120, 119, 198, 0.15) 0%, transparent 50%),
33+
radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.15) 0%, transparent 50%),
34+
radial-gradient(circle at 40% 80%, rgba(59, 130, 246, 0.2) 0%, transparent 50%);
35+
z-index: -1;
36+
}
37+
38+
.counter-card {
39+
background: linear-gradient(145deg,
40+
rgba(139, 92, 246, 0.05) 0%,
41+
rgba(59, 130, 246, 0.05) 50%,
42+
rgba(168, 85, 247, 0.05) 100%);
43+
backdrop-filter: blur(20px);
44+
border: 1px solid rgba(139, 92, 246, 0.3);
45+
border-radius: 24px;
46+
padding: 3rem 2.5rem;
47+
text-align: center;
48+
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.9),
49+
0 0 0 1px rgba(139, 92, 246, 0.2),
50+
inset 0 1px 0 rgba(255, 255, 255, 0.05),
51+
0 0 30px rgba(139, 92, 246, 0.4);
52+
min-width: 300px;
53+
position: relative;
54+
}
55+
56+
.counter-card::before {
57+
content: '';
58+
position: absolute;
59+
top: 0;
60+
left: 0;
61+
right: 0;
62+
bottom: 0;
63+
background: linear-gradient(145deg,
64+
rgba(139, 92, 246, 0.02) 0%,
65+
transparent 50%,
66+
rgba(168, 85, 247, 0.02) 100%);
67+
border-radius: 22px;
68+
z-index: -1;
69+
}
70+
71+
.counter-title {
72+
background: linear-gradient(145deg,
73+
#f8fafc 0%,
74+
#8b5cf6 25%,
75+
#3b82f6 50%,
76+
#a855f7 75%,
77+
#ec4899 100%);
78+
-webkit-background-clip: text;
79+
background-clip: text;
80+
-webkit-text-fill-color: transparent;
81+
font-size: 2rem;
82+
font-weight: 600;
83+
margin-bottom: 2rem;
84+
text-shadow: 0 0 30px rgba(139, 92, 246, 0.8),
85+
0 0 60px rgba(59, 130, 246, 0.6),
86+
0 0 90px rgba(168, 85, 247, 0.4);
87+
letter-spacing: 2px;
88+
text-transform: uppercase;
89+
filter: drop-shadow(0 0 15px rgba(139, 92, 246, 0.7));
90+
position: relative;
91+
animation: titleGlow 3s ease-in-out infinite alternate;
92+
}
93+
94+
@keyframes titleGlow {
95+
0% {
96+
filter: drop-shadow(0 0 15px rgba(139, 92, 246, 0.7));
97+
text-shadow: 0 0 30px rgba(139, 92, 246, 0.8),
98+
0 0 60px rgba(59, 130, 246, 0.6),
99+
0 0 90px rgba(168, 85, 247, 0.4);
100+
}
101+
100% {
102+
filter: drop-shadow(0 0 25px rgba(139, 92, 246, 0.9));
103+
text-shadow: 0 0 40px rgba(139, 92, 246, 1),
104+
0 0 80px rgba(59, 130, 246, 0.8),
105+
0 0 120px rgba(168, 85, 247, 0.6);
106+
}
107+
}
108+
109+
.counter-display {
110+
background: linear-gradient(145deg, #8b5cf6, #3b82f6, #a855f7);
111+
-webkit-background-clip: text;
112+
background-clip: text;
113+
-webkit-text-fill-color: transparent;
114+
font-size: 4rem;
115+
font-weight: 700;
116+
margin: 2rem 0;
117+
text-shadow: 0 0 30px rgba(139, 92, 246, 0.8);
118+
filter: drop-shadow(0 0 10px rgba(139, 92, 246, 0.5));
119+
}
120+
121+
.counter-buttons {
122+
display: flex;
123+
gap: 1rem;
124+
justify-content: center;
125+
margin-top: 2rem;
126+
}
127+
128+
.counter-btn {
129+
background: linear-gradient(145deg,
130+
rgba(139, 92, 246, 0.15) 0%,
131+
rgba(59, 130, 246, 0.15) 50%,
132+
rgba(168, 85, 247, 0.15) 100%);
133+
border: 1px solid rgba(139, 92, 246, 0.3);
134+
border-radius: 50%;
135+
color: #ffffff;
136+
cursor: pointer;
137+
font-size: 1.8rem;
138+
font-weight: bold;
139+
height: 70px;
140+
width: 70px;
141+
transition: all 0.3s ease;
142+
display: flex;
143+
align-items: center;
144+
justify-content: center;
145+
position: relative;
146+
backdrop-filter: blur(10px);
147+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3),
148+
inset 0 1px 0 rgba(255, 255, 255, 0.1);
149+
}
150+
151+
.counter-btn:hover {
152+
transform: translateY(-2px) scale(1.05);
153+
background: linear-gradient(145deg,
154+
rgba(139, 92, 246, 0.25) 0%,
155+
rgba(59, 130, 246, 0.25) 50%,
156+
rgba(168, 85, 247, 0.25) 100%);
157+
border-color: rgba(139, 92, 246, 0.5);
158+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4),
159+
0 0 15px rgba(139, 92, 246, 0.3),
160+
inset 0 1px 0 rgba(255, 255, 255, 0.15);
161+
}
162+
163+
.counter-btn:active {
164+
transform: translateY(-1px) scale(1.02);
165+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5),
166+
0 0 10px rgba(139, 92, 246, 0.4),
167+
inset 0 2px 4px rgba(0, 0, 0, 0.2);
168+
}
169+
</style>
170+
171+
</head>
172+
<body>
173+
<div class="counter-card">
174+
<h1 class="counter-title">Counting Stars</h1>
175+
<div class="counter-display" data-on-load="@get('/counter')">
176+
<span id="counter">Loading...</span>
177+
</div>
178+
<div class="counter-buttons">
179+
<button class="counter-btn" data-on-click="@post('/decrement')"></button>
180+
<button class="counter-btn" data-on-click="@post('/increment')">+</button>
181+
</div>
182+
</div>
183+
184+
</body>
185+
</html>

0 commit comments

Comments
 (0)