v. 1.0.1 - Added error handler and default error handling.

Took 1 hour 47 minutes
This commit is contained in:
2022-02-01 12:02:19 +01:00
parent 88625e65a4
commit f82ef1da70
9 changed files with 181 additions and 45 deletions

View File

@@ -152,3 +152,15 @@ internal fun Element.getKompEvents(): MutableMap<String, (Event) -> Unit> {
return this.asDynamic()["komp-events"] ?: mutableMapOf()
}
internal fun Element.findElementIndex(): Int {
val childNodes = parentElement?.children
if (childNodes != null) {
for (index in 0 until childNodes.length) {
if (childNodes[index] == this) {
return index
}
}
}
return 0
}

View File

@@ -73,11 +73,13 @@ private fun ArrayList<ElementIndex>.replace(new: Node) {
private fun Node.asElement() = this as? HTMLElement
class HtmlBuilder(
val komponent: Komponent?,
parent: Element,
childIndex: Int = 0
childIndex: Int = 0,
) : HtmlConsumer {
private var currentPosition = arrayListOf<ElementIndex>()
private var inDebug = false
private var exceptionThrown = false
var currentNode: Node? = null
var root: Element? = null
@@ -225,6 +227,10 @@ class HtmlBuilder(
}
override fun onTagEnd(tag: Tag) {
if (exceptionThrown) {
return
}
while (currentPosition.currentElement() != null) {
currentPosition.currentElement()?.let {
it.parentElement?.removeChild(it)
@@ -237,31 +243,33 @@ class HtmlBuilder(
currentPosition.pop()
val setAttrs: List<String> = currentElement.asDynamic()["komp-attributes"] ?: listOf()
if (currentElement != null) {
val setAttrs: List<String> = currentElement?.asDynamic()["komp-attributes"] ?: listOf()
// remove attributes that where not set
val element = currentElement
if (element?.hasAttributes() == true) {
for (index in 0 until element.attributes.length) {
val attr = element.attributes[index]
if (attr != null) {
// remove attributes that where not set
val element = currentElement
if (element?.hasAttributes() == true) {
for (index in 0 until element.attributes.length) {
val attr = element.attributes[index]
if (attr != null) {
if (element is HTMLElement && attr.name == "data-has-focus" && "true" == attr.value) {
element.focus()
}
if (element is HTMLElement && attr.name == "data-has-focus" && "true" == attr.value) {
element.focus()
}
if (attr.name != "style" && !setAttrs.contains(attr.name)) {
if (element is HTMLInputElement) {
if (attr.name == "checkbox") {
element.checked = false
} else if (attr.name == "value") {
element.value = ""
if (attr.name != "style" && !setAttrs.contains(attr.name)) {
if (element is HTMLInputElement) {
if (attr.name == "checkbox") {
element.checked = false
} else if (attr.name == "value") {
element.value = ""
}
} else {
if (Komponent.logReplaceEvent) {
console.log("Clear attribute [${attr.name}] on $element)")
}
element.removeAttribute(attr.name)
}
} else {
if (Komponent.logReplaceEvent) {
console.log("Clear attribute [${attr.name}] on $element)")
}
element.removeAttribute(attr.name)
}
}
}
@@ -367,6 +375,43 @@ class HtmlBuilder(
currentPosition.nextElement()
}
override fun onTagError(tag: Tag, exception: Throwable) {
exceptionThrown = true
if (exception !is KomponentException) {
val position = mutableListOf<Element>()
var ce = currentElement
while(ce != null) {
position.add(ce)
ce = ce.parentElement
}
val builder = StringBuilder()
for (element in position.reversed()) {
builder.append("> ")
builder.append(element.tagName)
builder.append("[")
builder.append(element.findElementIndex())
builder.append("]")
if (element.hasAttribute("class")) {
builder.append("(")
builder.append(element.getAttribute("class"))
builder.append(")")
}
builder.append(" ")
}
throw KomponentException(
komponent,
currentElement,
tag,
builder.toString(),
exception.message ?: "error",
exception
)
} else {
throw exception
}
}
override fun finalize(): Element {
//logReplace"finalize, currentPosition: $currentPosition")
return root ?: throw IllegalStateException("We can't finalize as there was no tags")
@@ -375,7 +420,7 @@ class HtmlBuilder(
companion object {
fun create(content: HtmlBuilder.() -> Unit): Element {
val container = document.createElement("div") as HTMLElement
val consumer = HtmlBuilder(container, 0)
val consumer = HtmlBuilder(null, container, 0)
content.invoke(consumer)
return consumer.root ?: error("No root element found after render!")
}

View File

@@ -5,7 +5,6 @@ import kotlinx.html.FlowOrMetaDataOrPhrasingContent
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import org.w3c.dom.get
import kotlin.reflect.KProperty
private var currentKomponent: Komponent? = null
fun FlowOrMetaDataOrPhrasingContent.currentKomponent(): Komponent =
@@ -38,13 +37,19 @@ abstract class Komponent {
open fun create(parent: Element, childIndex: Int? = null) {
onBeforeUpdate()
val builder = HtmlBuilder(
this,
parent,
childIndex ?: parent.childNodes.length
)
currentKomponent = this
builder.render()
currentKomponent = null
try {
currentKomponent = this
builder.render()
} catch(e: KomponentException) {
errorHandler(e)
} finally {
currentKomponent = null
}
element = builder.root
updateMemoizeHash()
@@ -117,7 +122,7 @@ abstract class Komponent {
*/
open fun generateMemoizeHash(): Int? = null
internal fun refresh() {
private fun refresh() {
val currentElement = element
check(currentElement != null) {
@@ -131,14 +136,19 @@ abstract class Komponent {
childIndex = index
}
}
val consumer = HtmlBuilder(parent, childIndex)
consumer.root = null
val builder = HtmlBuilder(this, parent, childIndex)
builder.root = null
currentKomponent = this
consumer.render()
currentKomponent = null
try {
currentKomponent = this
builder.render()
} catch(e: KomponentException) {
errorHandler(e)
} finally {
currentKomponent = null
}
element = consumer.root
element = builder.root
dirty = false
}
@@ -149,6 +159,18 @@ abstract class Komponent {
companion object {
private var nextCreateIndex: Int = 1
private var updateCallback: Int? = null
private var errorHandler: (KomponentException) -> Unit = { ke ->
console.error("Render error in Komponent", ke)
ke.element?.innerHTML = """<div class="komponent-error">Render error!</div>"""
window.alert("""
Error in Komponent '${ke.komponent}', ${ke.message}
Tag: ${ke.tag.tagName}
See console log for details
Position: ${ke.position}""".trimIndent()
)
}
private var scheduledForUpdate = mutableSetOf<Komponent>()
private var interceptor: (Komponent, () -> Unit) -> Unit = { _, block -> block() }
@@ -161,6 +183,10 @@ abstract class Komponent {
component.create(parent)
}
fun setErrorHandler(handler: (KomponentException) -> Unit) {
errorHandler = handler
}
fun setUpdateInterceptor(block: (Komponent, () -> Unit) -> Unit) {
interceptor = block
}

View File

@@ -0,0 +1,18 @@
package nl.astraeus.komp
import kotlinx.html.Tag
import org.w3c.dom.Element
class KomponentException(
val komponent: Komponent?,
val element: Element?,
val tag: Tag,
val position: String,
message: String,
cause: Throwable
) : RuntimeException(message, cause) {
override fun toString(): String {
return "KompException(message='$message', tag='$tag', position='$position')"
}
}

View File

@@ -61,6 +61,8 @@ class SimpleKomponent : Komponent() {
if (hello) {
div {
+"Hello"
throw IllegalStateException("Bloe")
}
} else {
span {