Skip to content

Commit

Permalink
Add HTMX transition
Browse files Browse the repository at this point in the history
  • Loading branch information
daviddenton committed Sep 18, 2024
1 parent e644057 commit eb1ca6c
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 95 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ modules and features:
- `http4k-core` <-- main HTTP library
- `http4k-config` <-- for 12-factor configuration via environmental properties
- `http4k-connect-amazon-s3` <-- replaces the Java AWS SDK with a lightweight http4k client
- `http4k-template-handlebars` <-- for templating
- `http4k-htmx` <-- for HTMX support
- `http4k-template-rocker` <-- for templating
- `http4k-multipart` <-- multipart form uploads
- `http4k-testing-hamkrest` <-- for test assertions

Expand Down
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
dependencies {
api("org.http4k:http4k-core")
api("org.http4k:http4k-config")
api("org.http4k:http4k-htmx")
api("org.http4k:http4k-multipart")
api("org.http4k:http4k-template-rocker")
api("org.http4k:http4k-connect-amazon-s3")
Expand Down
6 changes: 4 additions & 2 deletions app/src/main/kotlin/http4kbox/Http4kBox.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ import http4kbox.endpoints.Index
import http4kbox.endpoints.Upload
import org.http4k.config.Environment
import org.http4k.core.HttpHandler
import org.http4k.core.Method.DELETE
import org.http4k.core.Method.GET
import org.http4k.core.Method.POST
import org.http4k.core.then
import org.http4k.filter.ServerFilters.CatchAll
import org.http4k.routing.bind
import org.http4k.routing.htmxWebjars
import org.http4k.routing.routes

fun Http4kBox(env: Environment, http: HttpHandler): HttpHandler {
val fs = FileStorage(env, http)
return CatchAll().then(
routes(
"/{id}/delete" bind POST to Delete(fs),
"/{id}" bind GET to Get(fs),
htmxWebjars(),
"/{id}" bind routes(DELETE to Delete(fs), GET to Get(fs)),
"/" bind routes(POST to Upload(fs), GET to Index(fs))
)
)
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/kotlin/http4kbox/endpoints/Delete.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import http4kbox.FileName
import http4kbox.FileStorage
import org.http4k.core.HttpHandler
import org.http4k.core.Response
import org.http4k.core.Status
import org.http4k.core.Status.Companion.OK
import org.http4k.lens.Path
import org.http4k.lens.value

fun Delete(fs: FileStorage): HttpHandler {
val fileName = Path.value(FileName).of("id")
return { fs.delete(fileName(it)).run { Response(Status.SEE_OTHER).header("location", "/") } }
return { fs.delete(fileName(it)).run { Response(OK) } }
}
180 changes: 92 additions & 88 deletions app/src/main/resources/http4kbox/endpoints/ListFiles.rocker.html
Original file line number Diff line number Diff line change
@@ -1,94 +1,98 @@
@args (Iterable<http4kbox.FileName> files)
<head>
<title>http4kbox</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css"
integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.2.0/min/dropzone.min.css"
integrity="sha256-e47xOkXs1JXFbjjpoRr1/LhVcqSzRmGmPqsrUQeVs+g=" crossorigin="anonymous"/>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"
integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.2.0/min/dropzone.min.js"
integrity="sha256-eO9VTVeZLaplH86Iwt8l39+l7GZpLOTsVWYziS5oY0Q=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js"
integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb"
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"
integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn"
crossorigin="anonymous"></script>
<style>
body {
padding-top: 100px;
padding-left: 30px;
padding-right: 30px;
}
@args (Iterable
<http4kbox.FileName> files)
<head>
<title>http4kbox</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css"
integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.2.0/min/dropzone.min.css"
integrity="sha256-e47xOkXs1JXFbjjpoRr1/LhVcqSzRmGmPqsrUQeVs+g=" crossorigin="anonymous"/>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"
integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.2.0/min/dropzone.min.js"
integrity="sha256-eO9VTVeZLaplH86Iwt8l39+l7GZpLOTsVWYziS5oY0Q=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js"
integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb"
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"
integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn"
crossorigin="anonymous"></script>
<script src="/htmx.min.js"></script>
<style>
body {
padding-top: 100px;
padding-left: 30px;
padding-right: 30px;
}

.logo {
max-height: 30px;
vertical-align: bottom;
}
.logo {
max-height: 30px;
vertical-align: bottom;
}

.text {
max-width: 50%;
}
.text {
max-width: 50%;
}

.upload {
float: right;
}
</style>
</head>
<nav class="navbar navbar-toggleable-md navbar-inverse bg-inverse fixed-top">
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"
data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="https://github.com/http4k/http4kbox"><img class="logo" src="https://www.http4k.org/img/octocat-inverted-64.png"></a>
.upload {
float: right;
}

<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto"></ul>
<div class="error"><a href="https://http4k.org"><img class="logo"
src="https://www.http4k.org/img/logo_1100x200_white_on_black_trans.png"/></a>
tr.htmx-swapping td {
opacity: 0;
transition: opacity 0.5s ease-out;
}
</style>
</head>
<nav class="navbar navbar-toggleable-md navbar-inverse bg-inverse fixed-top">
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"
data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="https://github.com/http4k/http4kbox"><img class="logo" src="https://www.http4k.org/img/octocat-inverted-64.png"></a>

<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto"></ul>
<div class="error"><a href="https://http4k.org"><img class="logo"
src="https://www.http4k.org/img/logo_1100x200_white_on_black_trans.png"/></a>
</div>
</div>
</div>
</nav>
<body>
<span class="upload">
<form class="dropzone" method="POST" action="/"/>
</form>
</nav>
<body>
<span class="upload">
<form class="dropzone" method="POST" action="/"></form>
</span>
<div>
<h2>http4kbox</h2>
<p class="text">This is a simple Dropbox clone written using <a href="https://http4k.org">http4k</a> and <a href="https://connect.http4k.org">http4k Connect</a>, backed by an
<a href="https://aws.amazon.com/s3">S3</a> bucket.
</p>
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@for ((file) : files) {
<tr>
<td><a id="file-@file" href="/@file">@file</a></td>
<td>
<form method="POST" action="/@file/delete">
<button>delete</button>
</form>
</td>
</tr>
}
</tbody>
</table>
</div>
</body>
<script type="text/javascript">
$(function () {
Dropzone.forElement(".dropzone").on('queuecomplete', function () {
location.reload();
})
});
</script>
</html>
<div>
<h2>http4kbox</h2>
<p class="text">This is a simple Dropbox clone written using <a href="https://http4k.org">http4k</a>, <a href="https://connect.http4k.org">http4k Connect</a>, <a href="https://htmx.org">HTMX</a>, backed by an
<a href="https://aws.amazon.com/s3">S3</a> bucket.
</p>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
<tbody id="fileList" hx-confirm="Are you sure?" hx-target="closest tr" hx-swap="outerHTML swap:0.5s">
@for ((file) : files) {
<tr id="file-@file">
<td><a href="/@file" target="_blank">@file</a></td>
<td>
<button hx-delete="/@file">delete</button>
</td>
</tr>
}
</tbody>
</table>
</div>
</body>
<script type="text/javascript">
$(function () {
Dropzone.forElement(".dropzone").on('queuecomplete', function () {
location.reload();
})
});
</script>
</html>
6 changes: 4 additions & 2 deletions app/src/test/kotlin/functional/Http4kboxTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.http4k.connect.amazon.s3.FakeS3
import org.http4k.connect.amazon.s3.createBucket
import org.http4k.core.ContentType.Companion.MultipartFormWithBoundary
import org.http4k.core.ContentType.Companion.TEXT_PLAIN
import org.http4k.core.Method.DELETE
import org.http4k.core.Method.GET
import org.http4k.core.Method.POST
import org.http4k.core.MultipartFormBody
Expand All @@ -20,6 +21,7 @@ import org.http4k.core.Status.Companion.NOT_FOUND
import org.http4k.core.Status.Companion.OK
import org.http4k.core.Status.Companion.SEE_OTHER
import org.http4k.core.with
import org.http4k.filter.debug
import org.http4k.hamkrest.hasBody
import org.http4k.hamkrest.hasStatus
import org.http4k.lens.Header.CONTENT_TYPE
Expand All @@ -29,7 +31,7 @@ import org.junit.jupiter.api.Test
class Http4kboxTest {
private val http4kbox = Http4kBox(TestSettings, FakeS3().apply {
s3Client().createBucket(TestSettings[AWS_BUCKET], TestSettings[AWS_REGION])
})
}.debug())

@Test
fun `can list files`() {
Expand Down Expand Up @@ -78,7 +80,7 @@ class Http4kboxTest {

private fun getFile(key: String) = http4kbox(Request(GET, "/$key"))
private fun listFiles() = http4kbox(Request(GET, "/"))
private fun deleteFile(key: String) = http4kbox(Request(POST, "/$key/delete"))
private fun deleteFile(key: String) = http4kbox(Request(DELETE, "/$key"))

private fun uploadFile(name: String, content: String) {
val file = MultipartFormFile(name, TEXT_PLAIN, content.byteInputStream())
Expand Down

0 comments on commit eb1ca6c

Please sign in to comment.