initial commit

This commit is contained in:
Hartmut Bauer committed 2026-10-09 17:13:39 +02:00
commit d84861949f
92 files changed
+8425

No files matched your search

+202
View File
@@ -0,0 +1,202 @@
package net.keinekekse.app.books.ui
import io.ktor.htmx.html.*
import kotlinx.coroutines.runBlocking
import kotlinx.html.*
import net.keinekekse.app.books.db.BookRepository
import net.keinekekse.app.books.model.*
import net.keinekekse.app.books.model.Tag
fun FlowContent.bookEdit(book: Book, booksRepo: BookRepository) {
val publishers: List<Publisher> = runBlocking {
booksRepo.allPublishers()
}
val series: List<Series> = runBlocking {
booksRepo.allSeries()
}
val artists: List<Artist> = runBlocking {
booksRepo.allArtists()
}
val tags: List<Tag> = runBlocking {
booksRepo.allTags()
}
div(classes = "flex flex-col m-8") {
form {
attributes.hx {
post = navBooksEdit.second
target = "#response"
swap = "innerHTML"
}
div(classes = "flex flex-row mb-5 h-9") {
div(classes = "w-30 ") {
+"Titel:"
}
input(classes = "$cssTextInput w-150") {
name = "title"
tabIndex = "1"
value = book.title
}
}
div(classes = "flex flex-row mb-5 h-9") {
div(classes = "w-30 ") {
+"Originaltitel:"
}
input(classes = "$cssTextInput w-150") {
name = "originaltitle"
tabIndex = "2"
value = book.originalTitle ?: ""
}
}
div(classes = "flex flex-row mb-5 h-9") {
div(classes = "w-30 ") {
+"Untertitel:"
}
input(classes = "$cssTextInput w-150") {
name = "subtitle"
tabIndex = "3"
value = book.subTitle ?: ""
}
}
div(classes = "flex flex-row mb-5 h-9") {
div(classes = "w-30") {
+"Sprache"
}
select(classes = "$cssTextInput w-16") {
name = "language"
tabIndex = "4"
for (language in Languages.entries) {
option {
if (language == book.language)
selected = true
value = language.ordinal.toString()
+language.name
}
}
}
div(classes = "ml-9 pr-3 ") {
+"Erscheinungsjahr:"
}
input(classes = "$cssTextInput w-16") {
name = "year"
tabIndex = "5"
value = book.year.toString()
}
div(classes = "ml-8 pr-3") {
+"Jahr der Originalausgabe:"
}
input(classes = "$cssTextInput w-16") {
name = "originalyear"
tabIndex = "6"
value = book.originalYear?.toString() ?: ""
}
}
div(classes = "flex flex-row mb-5 h-9") {
div(classes = "w-30") {
+"ISBN"
}
input(classes = "$cssTextInput w-40") {
name = "isbn"
tabIndex = "7"
value = book.isbn
}
div(classes = "ml-14 pr-3") {
+"Verlag"
}
select(classes = "$cssTextInput w-74") {
name = "publisher"
tabIndex = "8"
option {
value = "0"
+"---"
}
for (pub in publishers) {
option {
if (pub == book.publisher)
selected = true
value = pub.id.toString()
+pub.name
}
}
}
a(classes = cssFormIcon) {
tabIndex = "9"
title = "Verlag hinzufügen"
materialSymbolFormButton("add_box")
}
}
div(classes = "flex flex-row mb-5 h-9") {
div(classes = "w-30") {
+"Serie"
}
select(classes = "$cssTextInput w-98") {
name = "series"
tabIndex = "10"
option {
value = "0"
+"---"
}
for (serie in series) {
option {
if (serie == book.series)
selected = true
value = serie.id.toString()
+serie.name
}
}
}
a(classes = cssFormIcon) {
tabIndex = "11"
title = "Serie hinzufügen"
materialSymbolFormButton("add_box")
}
div(classes = "ml-13 pr-3 ") {
+"Index"
}
input(classes = "$cssTextInput w-16") {
name = "seriesindex"
tabIndex = "12"
value = book.seriesIndex?.toString() ?: ""
}
}
div(classes = "flex flex-row mb-5 h-9") {
// artists tags
// | | | |
// | | | |
}
div(classes = "flex flex-row mb-5 h-9") {
// evtl. switch
// Teil von Sammelband _____v / enthält (-> Link)
// comment
}
button(classes = "$cssButtonPri block mt-8") {
tabIndex = "20"
type = ButtonType.submit
+"Save"
}
}
div {
id = "response"
}
}
}
@@ -0,0 +1,97 @@
package net.keinekekse.app.books.ui
import kotlinx.html.*
import net.keinekekse.app.VERSION_books
import net.keinekekse.app.core.ui.cssFullscreen
import net.keinekekse.app.core.ui.stdHead
fun FlowContent.programName() {
a(classes = "flex") {
href = navBooks.second
title = "v ${VERSION_books.fullVersion}"
img(classes = "h-14 my-2 $bgHeaderIcon") {
src = "/static/books.png"
}
div(classes = "self-center text-right ml-3") {
div {
div(classes = "text-3xl") {
+VERSION_books.displayName
}
div(classes = "text-xs") {
+VERSION_books.version
}
}
}
}
}
fun HTML.booksPage(title: String, breadcrumbs: Map<String, String>, block: DIV.() -> Unit = {}) {
// val booksDb = Dependencies.getBooksDb()
LOG.info(" ### building test page")
// var breadcrumbMap = emptyMap<String, String>()
// if (breadcrumbs.isEmpty()) {
// breadcrumbMap = breadcrumbs.plus(Pair("Start", ""))
// } else {
// breadcrumbMap = mapOf(Pair("Start", "/books")).plus(breadcrumbs)
// }
head {
stdHead(title)
}
body {
// page
div(classes = "$cssFullscreen flex-col") {
// header
div(classes = "flex flex-row justify-between py-2 px-5 $bgHeader $textHeader") {
// header left
div(classes = "flex flex-row") {
programName()
div(classes = "ml-10 flex self-end pr-5 pt-2 border-t-1 items-baseline") {
a(classes = cssHeaderLink) {
href = "/"
materialSymbol("home")
// +"Start"
}
div(classes = "px-3") { +">" }
breadcrumbs.forEach { (name, url) ->
if (url.isNotEmpty()) {
a(classes = cssHeaderLink) {
href = url
+name
}
div(classes = "px-3") { +">" }
} else {
div {
+name
}
}
}
}
}
// header right TODO nötig?
// div(classes = "flex") {
// div(classes = "self-center text-xl") {
// materialSymbol("Settings")
// }
// }
}
div(classes = "flex flex-row flex-auto overflow-hidden") {
block()
}
// footer
div(classes = "p-1 text-sm $bgFooter") {
div(classes = "col") {
+"some footer content"
}
}
}
}
}
+137
View File
@@ -0,0 +1,137 @@
package net.keinekekse.app.books.ui
import io.ktor.util.logging.*
import net.keinekekse.app.books.db.BookRepository
import net.keinekekse.app.books.db.schema.*
import net.keinekekse.app.books.model.Languages
import org.jetbrains.exposed.v1.jdbc.Database
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
val logger = KtorSimpleLogger("net.keinekekse.app.books.ui")
suspend fun dbTest(db: Database, booksRepo: BookRepository) {
// val db = Dependencies.getBooksDb()
// val repo = Dependencies.getBookRepository()
// DB-Test
transaction(db) {
logger.debug("start DB reset - drop")
// create if not exists
SchemaUtils.drop(
PublisherTable,
TagsTable,
TagsToBookTable,
SeriesTable,
ArtistToBookTable,
ArtistRolesTable,
ArtistsTable,
BooksTable,
CalibreBookInfoTable
)
logger.debug("create tables")
SchemaUtils.create(
PublisherTable,
TagsTable,
TagsToBookTable,
SeriesTable,
ArtistRolesTable,
ArtistsTable,
ArtistToBookTable,
BooksTable,
CalibreBookInfoTable
)
logger.debug("Tables created")
val auth = ArtistRoleDAO.new { role = "Autor" }
ArtistRoleDAO.new { role = "Übersetzer" }
val kia1 = ArtistDAO.new {
name = "Aigner"
givenName = "Sebastian"
}
val kia2 = ArtistDAO.new {
name = "Elizarov"
givenName = "Roman"
}
val kia3 = ArtistDAO.new {
name = "Isakova"
givenName = "Svetlana"
}
val kia4 = ArtistDAO.new {
name = "Jemerov"
givenName = "Dmitry"
}
val kp1 = ArtistDAO.new {
name = "Soshin"
givenName = "Alexey"
}
val pub1 = PublisherDAO.new {
name = "Manning"
}
val pub2 = PublisherDAO.new {
name = "Packt Publishing"
}
val kia = BookDAO.new {
title = "Kotlin in Action, 2nd Edition"
originalTitle = ""
subTitle = ""
year = 2024
originalYear = 2024
seriesIndex = 0
language = Languages.EN
isbn = "9781617296823"
publisher = pub1
comments = ""
}
val kp = BookDAO.new {
title = "Kotlin Design Patterns and Best Practices, 3rd Edition"
originalTitle = ""
subTitle = ""
year = 2024
originalYear = 2024
seriesIndex = 0
language = Languages.EN
isbn = "9781617298823"
publisher = pub2
comments = ""
}
ArtistToBookDAO.new {
book = kia
role = auth
artist = kia1
}
ArtistToBookDAO.new {
book = kia
role = auth
artist = kia2
}
ArtistToBookDAO.new {
book = kia
role = auth
artist = kia3
}
ArtistToBookDAO.new {
book = kia
role = auth
artist = kia4
}
ArtistToBookDAO.new {
book = kp
role = auth
artist = kp1
}
}
val books = booksRepo.allBooks()
books.forEach { book ->
logger.debug("### ${book.title}:")
book.artists.forEach { atb ->
logger.debug("### ${atb.role.role}: ${atb.artist.givenName} ${atb.artist.name}")
}
}
}
+119
View File
@@ -0,0 +1,119 @@
package net.keinekekse.app.books.ui
import kotlinx.html.FlowContent
import kotlinx.html.div
import kotlinx.html.span
import kotlinx.html.style
import net.keinekekse.app.books.isbnScanResultPath
import kotlin.io.encoding.Base64
// * colors *
const val bgHeader = "bg-orange-500"
const val textHeader = "text-orange-100"
const val bgHeaderIcon = "bg-orange-100"
const val bgFooter = "bg-orange-50"
const val bgLeftColumn = "bg-slate-200"
const val bgRightColumn = "bg-slate-50"
const val bgThead = "bg-slate-200"
const val borderTable = "border-slate-400"
const val bgStdButton = "bg-taupe-500"
const val bgStdButtonHover = "hover:bg-taupe-700"
const val bgStdButtonFocus = "focus:bg-taupe-700"
const val bgPriButton = "bg-orange-400"
const val bgPriButtonHover = "hover:bg-orange-600"
const val bgPriButtonFocus = "focus:bg-orange-600"
const val bgSecButton = "bg-indigo-400"
const val bgSecButtonHover = "hover:bg-indigo-600"
const val bgSecButtonFocus = "focus:bg-indigo-600"
const val textLink = "text-orange-400"
const val textLinkHover = "hover:text-orange-600"
const val textLinkFocus = "focus:text-orange-600"
const val bgLinkHover = "hover:bg-orange-200"
const val bgLinkFocus = "focus:bg-orange-200"
// * button & link *
const val cssButton = "cursor-pointer rounded-md px-4 py-2"
const val cssButtonStd =
"text-white $bgStdButton $bgStdButtonHover $cssButton $bgStdButtonFocus"
const val cssButtonPri =
"text-white $bgPriButton $bgPriButtonHover $cssButton $bgPriButtonFocus"
const val cssButtonSec =
"text-white $bgSecButton $bgSecButtonHover $cssButton $bgSecButtonFocus"
const val cssHeaderLink =
"font-bold px-1 $textLinkHover $textLinkFocus $bgLinkHover $bgLinkFocus rounded-md cursor-pointer m-0 no-underline"
const val cssLink =
"font-bold $textLink $textLinkHover $textLinkFocus $bgLinkHover $bgLinkFocus rounded-md cursor-pointer m-0 no-underline"
const val cssLinkText =
"px-1 $cssLink"
const val cssLinkIcon =
"px-2 $cssLink "
const val cssFormIcon =
"font-bold text-slate-400 $textLinkHover $textLinkFocus $bgLinkHover $bgLinkFocus rounded-md " +
"cursor-pointer m-0 px-2 pt-1 no-underline "
const val cssTextInput =
"rounded-sm border-1 border-slate-400 px-2 py-1 focus:border-orange-400 focus:border-4" +
" invalid:focus:bg-red-200 invalid:focus:border-red-500 invalid:focus:border-5" +
" invalid:bg-red-200 invalid:border-red-500 invalid:border-3 "
const val _cssTextInput =
"rounded-sm border-b-1 px-2 py-1 focus:border-b-orange-400 focus:border-b-4" +
" invalid:focus:bg-red-200 invalid:focus:border-red-500 invalid:focus:border-5" +
" invalid:bg-red-200 invalid:border-red-500 invalid:border-3 "
// * fixed table header *
const val cssFixedTHead = "fixed-table-header $bgThead "
// m-1 -> margin (--spacing * 1) -> --spacing = .25em = 4px => m-1 = 4px, m-2 = 8px
// Navigation
val navBooks = "Bücher" to "/books"
val navBooksEdit = "Buch bearbeiten" to "/books/edit"
val navBooksNew = "Neue Bücher" to "/books/new"
val navBooksScan = "ISBN Scan" to "/books/new/scan"
val navBooksScanFetch = "" to isbnScanResultPath // has to be shared with :web-Project
val navBooksScanEdit = "Scan bearbeiten" to "/books/new/editscan"
fun FlowContent.materialSymbol(symbol: String, styles: String = "") {
// Den Icon via Tailwind formatieren klappt nicht. Daher muss z. B. font-size oder
// font-weight per style= gesetzt werden.
span(classes = "material-symbols-outlined align-text-bottom") {
style = styles
+symbol
}
}
fun FlowContent.materialSymbolButton(symbol: String, styles: String = "") {
div(classes = "$cssLinkIcon inline-block") {
materialSymbol(symbol, styles)
}
}
fun FlowContent.materialSymbolFormButton(symbol: String, styles: String = "") {
div(classes = " inline-block") {
materialSymbol(symbol, styles)
}
}
/**
* Converts a byte array representing an image into a Base64 encoded Data URL string.
*
* Use in &lt;img src="..."&gt;
*
* @param image A nullable byte array containing the binary data of the image.
* When null or empty, an empty string is returned.
* @return A Data URL string containing the Base64 encoded image data,
* or an empty string if the input is null or empty.
*/
fun embedImageURL(image: ByteArray?): String =
if (image != null && image.isNotEmpty()) {
val base64String =
Base64.encode(image) // kotlin.io.encoding.Base64
val mimeType = "image/jpeg"
"data:$mimeType;base64,$base64String"
} else ""
@@ -0,0 +1,72 @@
package net.keinekekse.app.books.ui
import kotlinx.html.HTML
import kotlinx.html.div
import kotlinx.html.img
import kotlinx.html.span
import net.keinekekse.app.books.db.BookRepository
import net.keinekekse.app.books.model.Book
import net.keinekekse.app.books.model.CalibreBookInfo
import net.keinekekse.app.books.model.Languages
import net.keinekekse.app.books.model.Publisher
fun HTML.importEditPage(bookInfo: CalibreBookInfo?, booksRepo: BookRepository) {
val dataUrl = embedImageURL(bookInfo?.cover)
booksPage(
navBooksScanEdit.first,
mapOf(navBooks, navBooksNew, Pair(navBooksScanEdit.first, ""))
) {
div(classes = "flex flex-col flex-auto") {
div(classes = "flex flex-row h-60 m-8 w-3/4") {
img(classes = "inline-block h-60 pr-6 ") {
src = dataUrl
}
div {
div {
span(classes = "inline-block w-25 ") { +"ISBN: " }
+(bookInfo?.isbn ?: "")
}
div {
span(classes = "inline-block w-25") { +"Titel: " }
+(bookInfo?.title ?: "")
}
div {
span(classes = "inline-block w-25") { +"Autor(en): " }
+(bookInfo?.authors ?: "")
}
div {
span(classes = "inline-block w-25") { +"Verlag: " }
+(bookInfo?.publisher ?: "")
}
div {
span(classes = "inline-block w-25") { +"Jahr: " }
+bookInfo?.published.toString()
}
div {
span(classes = "inline-block w-25") { +"Tags: " }
+(bookInfo?.tags ?: "")
}
div {
span(classes = "inline-block w-25") { +"Identifiers: " }
+(bookInfo?.identifiers ?: "")
}
div {
span(classes = "inline-block w-25") { +"Comment: " }
+(bookInfo?.comments ?: "")
}
}
}
bookEdit(
Book(
title = bookInfo?.title ?: "",
year = bookInfo?.published ?: 0,
isbn = bookInfo?.isbn ?: "",
publisher = Publisher(0, ""),
language = bookInfo?.language ?: Languages.DE
), booksRepo
)
}
}
}
@@ -0,0 +1,110 @@
package net.keinekekse.app.books.ui
import kotlinx.html.*
import net.keinekekse.app.books.model.CalibreBookInfo
fun HTML.importPage(books: List<CalibreBookInfo>) {
// head {
// stdHead("ISBN Scannen", false)
// }
// body {
booksPage(navBooksNew.first, mapOf(navBooks, Pair(navBooksNew.first, ""))) {
div(classes = "basis-64 px-1 py-2 text-center $bgLeftColumn") {
div {
+"some content on the left "
}
div(classes = "my-6") {
a(classes = cssButtonPri) {
href = navBooksScan.second
+"ISBN Scan"
}
}
}
// content right
div(classes = "flex flex-auto $bgRightColumn") {
div(classes = "flex flex-col flex-auto") {
div {
// +"some heading"
}
importTable(books)
}
}
}
}
fun FlowContent.importTable(books: List<CalibreBookInfo>) {
div(classes = "flex-auto overflow-auto px-1 pb-1") {
table {
id = "importtable"
thead(classes = "$cssFixedTHead ") {
tr {
th(classes = "py-3 px-2") { +"ISBN" }
th(classes = "py-3 px-2") { +"Titel" }
th(classes = "py-3 px-2") { +"Autoren" }
th(classes = "py-3 px-2") { +"Verlag" }
th(classes = "py-3 px-2") { +"Jahr" }
th(classes = "py-3 px-2") { +"Sprache" }
th(classes = "py-3 px-2") { +" " }
}
}
tbody {
for ((id, title, authors, publisher, tags, language, published, identifiers, isbn, comment, cover) in books) {
tr {
td(classes = "border-t-1 $borderTable px-2 py-1") {
+isbn
}
td(classes = "border-t-1 $borderTable px-2 py-1") {
+title
}
td(classes = "border-t-1 $borderTable px-2 py-1") {
+authors
}
td(classes = "border-t-1 $borderTable px-2 py-1") {
+publisher
}
td(classes = "border-t-1 $borderTable px-2 py-1") {
+published.toString()
}
td(classes = "border-t-1 $borderTable px-2 py-1") {
+language.name
}
td(classes = "border-t-1 $borderTable px-2 py-1 text-nowrap") {
a {
href = "${navBooksScanEdit.second}/$id"
materialSymbolButton("Edit")
}
materialSymbolButton("Delete")
}
}
tr {
td {}
td {
colSpan = "5"
details(classes = "mb-2") {
attributes["name"] = "comments"
summary(classes = "text-sm opacity-50") {
+"Tags, Comments"
}
div(classes = "flex flex-row") {
img(classes = "inline-block h-30 pr-6 ") {
src = embedImageURL(cover)
}
div {
+tags
br {}
+identifiers
br {}
+comment
}
}
}
}
}
}
}
}
}
}
@@ -0,0 +1,94 @@
package net.keinekekse.app.books.ui
import kotlinx.html.*
fun HTML.scanPage() {
// head {
// stdHead("ISBN Scannen", false)
// }
// body {
booksPage(
navBooksScan.first,
mapOf(navBooks, navBooksNew, Pair(navBooksScan.first, ""))
) {
style {
type = "text/css"
+"""
button {
background-color: var(--color-orange-400);
color: white;
padding: 8px 16px;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: var(--color-orange-600);
}
h3 {
font-weight: bold;
margin: 10px;
}
""".trimIndent()
}
div(classes = "m-4 overflow-auto flex-auto") {
h3 { +"ISBN / Barcode erfassen" }
// 1. Kamera-Ausgabebereich
div {
id = "camera"
style = "width: 400px;"
}
// 2. Zielbereich für das Buch-Ergebnis (wird per HTMX befüllt)
div {
id = "scan-result"
// p { +"Bitte Barcode vor die Kamera halten..." }
}
}
// 3. JavaScript zur Initialisierung & HTMX-Bridge
script {
unsafe {
raw(
"""
document.addEventListener('DOMContentLoaded', function () {
renderScanner("camera");
});
""".trimIndent()
)
}
}
}
}
fun TagConsumer<Appendable>.isbnResultCallback(bookInfo: String, dataUrl: String) {
div {
pre {
// gefundene Daten anzeigen -> Bearbeitung später
+bookInfo
}
if (dataUrl.isNotEmpty()) {
div {
// img(src = "/books/testimage/$isbn")
img(src = dataUrl) {
style = "height: 200px; margin-bottom: 10px;"
}
}
}
button {
style = "margin-left: 10px; font-size: 16px;"
onClick = "renderScanner('camera')"
+"Nächtes Buch"
}
button {
style = "margin-left: 10px; font-size: 16px;"
type = ButtonType.button
onClick = "location.href='${navBooksNew.second}'"
+"Fertig"
}
}
}
+279
View File
@@ -0,0 +1,279 @@
@file:OptIn(ExperimentalKtorApi::class)
package net.keinekekse.app.books.ui
import io.ktor.htmx.*
import io.ktor.htmx.html.*
import io.ktor.util.logging.*
import io.ktor.utils.io.*
import kotlinx.html.*
import net.keinekekse.app.core.config.LOGOUT_PATH
import org.jetbrains.exposed.v1.jdbc.Database
import kotlin.random.Random
val LOG = KtorSimpleLogger("net.keinekekse.app.books.ui.TestPage")
//fun FlowContent.programName() {
//
// a(classes = "flex") {
// href = "/"
// title = "v ${VERSION_books.fullVersion}"
//
// img(classes = "h-14 my-2 $bgHeaderIcon") {
// src = "/static/books.png"
// }
// div(classes = "self-center text-right ml-3") {
// div {
// div(classes = "text-3xl") {
// +VERSION_books.displayName
// }
// div(classes = "text-xs") {
// +VERSION_books.version
// }
// }
// }
// }
//}
//
//fun HTML.testPage(booksDb: Database) {
//// val booksDb = Dependencies.getBooksDb()
// LOG.info(" ### building test page")
//
// head {
// stdHead("Test Page")
// }
//
// body {
// // page
// div(classes = "$cssFullscreen flex-col") {
// // header
// div(classes = "flex flex-row justify-between py-2 px-5 $bgHeader $textHeader") {
// // header left
// div(classes = "flex flex-row") {
// programName()
// div(classes = "ml-10 flex self-center") {
// +"MENÜ"
// }
// }
// // header right
// div(classes = "flex") {
// div(classes = "self-center text-xl") {
// materialSymbol("Settings")
// }
// }
// }
fun HTML.testPage(booksDb: Database) {
booksPage("Books-DB", mapOf(Pair(navBooks.first, ""))) {
// content left
div(classes = "basis-64 px-1 py-2 text-center $bgLeftColumn") {
div {
+"some content on the left "
}
div {
button(classes = "$cssButtonStd m-2") {
attributes.hx {
// TODO
get = "/books/testdb"
target = "#testdb"
swap = HxSwap.innerHtml
trigger = "click"
}
+"DB test"
}
}
div {
button(classes = "$cssButtonPri m-2 focus:outline-none focus:outline-orange-800") {
+"primary button"
}
}
div {
+"text text "
button(classes = "$cssLinkText ") { +"click me" }
+" test test"
}
div {
button(classes = "$cssButtonSec m-2") { +"secondary button" }
}
div {
+"Database: ${booksDb.vendor} ${booksDb.version}"
}
div {
form(action = LOGOUT_PATH, method = FormMethod.post) {
button(classes = cssLinkText) {
type = ButtonType.submit
+"Logout"
}
}
}
div {
id = "testdb"
}
div(classes = "mt-6") {
// script {
// unsafe {
// raw(
// """
// function toggleField(hideObj,showObj){
// hideObj.disabled=true;
// hideObj.style.display='none';
// showObj.disabled=false;
// showObj.style.display='inline';
// showObj.focus();
// }
// """.trimIndent()
// )
// }
//
// }
// fkt. nur so lala (onBlur!), lässt sich das hinbiegen?
// form {
// attributes.hx {
// post = "/books/testform"
// target = "#testForm"
// swap = HxSwap.innerHtml
// }
// name = "BrowserSurvey"
// select {
// name = "browser"
// onChange = """
// if(this.options[this.selectedIndex].value=='customOption'){
// toggleField(this,this.nextSibling);
// this.selectedIndex='0';
// }
// """.trimIndent()
// label { +"Browser: " }
// option {
// value = "customOption"
// +"[type a custom value]"
// }
// option {
// value = "vChrome"
// +"Chrome"
// }
// option {
// value = "vFirefox"
// +"Firefox"
// }
// option {
// value = "vInternetExplorer"
// +"Internet Explorer"
// }
// option {
// value = "vOpera"
// +"Opera"
// }
// }
// input {
// type = InputType.text
// name = "browser"
// style = "display:none;"
// disabled = true
// onBlur = """
// if(this.value==null || this.value==''){toggleField(this,this.previousSibling);}
// """.trimIndent()
// }
// input(type = InputType.submit) {
// value = "Submit"
// }
// }
// klappt, es fehlt halt die "value" -> alpha search ?!
form {
attributes.hx {
post = "/books/testform"
target = "#testForm"
swap = HxSwap.innerHtml
}
input(
classes = cssTextInput,
type = InputType.text
) {
name = "testInput"
list = "lst"
minLength = "2"
dataList {
id = "lst"
option(content = "alpha")
option(content = "beta")
option(content = "gamma")
}
}
input(type = InputType.submit) {
value = "Submit"
}
}
div {
id = "testForm"
}
}
}
// content right
div(classes = "flex flex-auto $bgRightColumn") {
div(classes = "flex flex-col flex-auto") {
// div {
// +"some heading"
// }
testTable()
}
}
}
}
// // footer
// div(classes = "p-1 text-sm $bgFooter") {
// div(classes = "col") {
// +"some footer content"
// }
// }
// }
//// }
//
// }
//}
fun FlowContent.testTable() {
div(classes = "flex-auto overflow-auto px-1 pb-1") {
table {
id = "testtable"
thead(classes = "$cssFixedTHead ") {
tr {
th(classes = "py-3 px-2") { +"#" }
th(classes = "py-3 px-2") { +"Alias" }
th(classes = "py-3 px-2") { +"Score" }
th(classes = "py-3 px-2") {
a {
href = navBooksNew.second
title = navBooksNew.first
materialSymbolButton(
"auto_stories",
"font-weight: bold; font-size: 2em"
)
// library_add
}
}
}
}
tbody {
for (i in 0..100) {
tr {
td(classes = "border-t-1 $borderTable px-2 py-1") {
+i.toString()
}
td(classes = "border-t-1 $borderTable px-2 py-1") {
+Random.nextLong().toString()
}
td(classes = "border-t-1 $borderTable px-2 py-1") {
+Random.nextLong().toString()
}
td(classes = "border-t-1 $borderTable px-2 py-1 text-nowrap") {
materialSymbolButton("Edit")
materialSymbolButton("Delete")
}
}
}
}
}
}
}