-
Notifications
You must be signed in to change notification settings - Fork 133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support generating HTML fragments with multiple elements (no containing element) #228
Comments
Possible solution: @HtmlTagMarker
inline fun <T, C : TagConsumer<T>> C.fragment(crossinline block: TagConsumer<T>.() -> Unit): T {
try {
this.block()
} catch (err: Throwable) {
this.onTagError(HTMLTag("", this, emptyMap(), null, inlineTag = false, emptyTag = false), err)
}
return this.finalize()
} |
Or no, that does not work properly, it ends up calling |
"non-well-formed" sounds like the use of elements that are either missing an opening tag or a closing tag, but not both. If you only read the title of this issue, it sounds like you are trying to use a bad (non-well-formed) approach, but the thing you ask for is perfectly fine. "multiple elements with no containing element" is clearer IMO. I also need a solution for this. Every time you want to reuse code (by using a function call), you are forced to use an unwanted container element, such as a |
I am using the XML definition of well-formedness: https://www.w3.org/TR/2008/REC-xml-20081126/#sec-well-formed |
But OK, I tried to make it more clear. |
You could do something like this:
But these intermediate strings do limit performance and it's an ugly solution, especially when the number of root level elements grows. Update: or better:
I believe that sooner or later, everybody who uses kotlinx.html on an intermediate level, will sooner or later bump into this problem. The best solution would be if there was an attribute to signal that the root level element should be skipped, like:
or something like this:
|
This solutions works, but it requires a pull request. Change
(Only about 5 files have to be modified.) Then you can use this:
|
Are there any updates? I'd like to see this implemented as well. I'm down to help out if you want. |
kotlinx.html does not really support html fragments. For example, I can't respond with "<li>foo</li>". Instead I have to respond with "<body><ul><li>foo</li></ul></body>" which does not work with htmx when swapping a li element. See: Kotlin/kotlinx.html#228
I bumped into this issue when trying https://htmx.org with a ktor+kotlinx.html backend. Note that the issue does not only concern "multiple elements" as I might want to replace a single Luckily there is a workaround in https://htmx.org : using a |
It's not perfect but you can copy the inline fun FlowContent.li(classes: String? = null, crossinline block: LI.() -> Unit = {}) =
LI(attributesMapOf("class", classes), consumer).visit(block) My solution to the empty root problem: inline fun partial(crossinline block: FlowContent.() -> Unit) = createHTML {
object : FlowContent {
override val attributes = DelegatingMap(emptyMap(), this) { this@createHTML }
override val attributesEntries: Collection<Map.Entry<String, String>>
get() = this.attributes.immutableEntries
override val consumer: TagConsumer<*>
get() = this@createHTML
override val emptyTag: Boolean
get() = false
override val inlineTag: Boolean
get() = false
override val namespace: String?
get() = null
override val tagName: String
get() = ""
}.block()
} |
Came across this issue while researching. Posting my solution here if anyone needs it: import kotlinx.html.*
class FRAGMENT(override val consumer: TagConsumer<*>) : HTMLTag(
tagName = "fragment",
consumer = consumer,
initialAttributes = emptyMap(),
emptyTag = false,
inlineTag = false,
namespace = null,
), FlowContent
@HtmlTagMarker
inline fun <T, C : TagConsumer<T>> C.fragment(
crossinline block: FRAGMENT.() -> Unit = {}
): T = FragmentAwareTagConsumer(this).let { FRAGMENT(it).visitAndFinalize(it, block) }
class FragmentAwareTagConsumer<T>(private val delegate: TagConsumer<T>) : TagConsumer<T> by delegate {
override fun onTagStart(tag: Tag) {
if (tag !is FRAGMENT) delegate.onTagStart(tag)
}
override fun onTagEnd(tag: Tag) {
if (tag !is FRAGMENT) delegate.onTagEnd(tag)
}
} Usage: buildString {
appendHTML().fragment {
div {
+"Hello, world from div!"
}
}
appendHTML().fragment {
p {
+"Hello, world from paragraph!"
}
}
} |
Столкнулся с такой же проблемой. Моё решение: import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.html.*
import io.ktor.server.response.*
import kotlinx.html.*
import kotlinx.html.consumers.filter
import kotlinx.html.stream.appendHTML
suspend fun <TTemplate : Template<FlowContent>> ApplicationCall.respondFragmentTemplate(
template: TTemplate,
status: HttpStatusCode = HttpStatusCode.OK,
body: TTemplate.() -> Unit
) {
template.body()
respondFragment(status) { with(template) { apply() } }
}
suspend fun ApplicationCall.respondFragment(status: HttpStatusCode = HttpStatusCode.OK, block: FlowContent.() -> Unit) {
val text = buildString {
appendHTML().filter { if (it is FRAGMENT) SKIP else PASS }.fragment(block = block)
}
respond(TextContent(text, ContentType.Text.Html.withCharset(Charsets.UTF_8), status))
}
@Suppress("unused")
private class FRAGMENT(override val consumer: TagConsumer<*>) :
HTMLTag("fragment", consumer, emptyMap(), null, false, false), HtmlBlockTag {
}
@HtmlTagMarker
private inline fun <T, C : TagConsumer<T>> C.fragment(crossinline block: FRAGMENT.() -> Unit = {}): T =
FRAGMENT(this).visitAndFinalize(this, block) Использование: get("/fragment_template") {
call.respondFragmentTemplate(
TestTemplate()
) {}
}
get("/fragment") {
call.respondFragment {
div {
+"test"
}
}
}
class TestTemplate : Template<FlowContent> {
override fun FlowContent.apply() {
div {
+"testTemplate"
}
}
}
|
It would be useful to be able to generate HTML fragments with multiple elements without any containing element.
Use case is to generate HTML snippets on server to be requested with AJAX and inserted into the DOM on the client.
This code:
Gives this:
But I want this:
(Same issue with
<tr>
etc.)The text was updated successfully, but these errors were encountered: