12 Commits

Author SHA1 Message Date
e82c4b2091 Common code initial 2022-10-14 16:56:21 +02:00
ccc07a3545 v. 1.0.8-SNAPSHOT 2022-10-14 16:55:49 +02:00
981bceacfb v. 1.0.7 upgrade to Kotlin 1.7.20 2022-10-14 16:51:06 +02:00
1b93c54cf4 Fixes 2022-08-10 20:53:06 +02:00
5f7fde44c6 Version 1.0.6
- Fix attr vs property checkbox update
2022-03-03 14:28:13 +01:00
d9d3d0f786 Version 1.0.5 2022-02-25 19:21:40 +01:00
bb8e8e0be9 Version to 1.0.5-SNAPSHOT 2022-02-25 11:55:31 +01:00
6c24547cba Version 1.0.4 2022-02-25 11:54:57 +01:00
147c934819 Fix update & replace options 2022-02-24 15:12:57 +01:00
cbf76f18a2 Add update/replace option
Took 1 hour 4 minutes
2022-02-23 21:40:57 +01:00
9a1d9ece25 v. 1.0.3 - Replace i.o. update DOM
Took 45 seconds
2022-02-07 17:37:48 +01:00
a30b0e8684 Update to snapshot
Took 4 minutes
2022-02-07 17:37:04 +01:00
8 changed files with 152 additions and 131 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ local.properties
*.ipr *.ipr
*.iws *.iws
kotlin-js-store kotlin-js-store
.idea

View File

@@ -1,12 +1,12 @@
plugins { plugins {
kotlin("multiplatform") version "1.6.10" kotlin("multiplatform") version "1.7.20"
`maven-publish` `maven-publish`
signing signing
id("org.jetbrains.dokka") version "1.5.31" id("org.jetbrains.dokka") version "1.5.31"
} }
group = "nl.astraeus" group = "nl.astraeus"
version = "1.0.2" version = "1.0.8-SNAPSHOT"
repositories { repositories {
mavenCentral() mavenCentral()

View File

@@ -1,5 +1,5 @@
#Wed Mar 04 13:29:12 CET 2020 #Wed Mar 04 13:29:12 CET 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -0,0 +1 @@
package nl.astraeus.komp

View File

@@ -65,66 +65,59 @@ fun Element.printTree(indent: Int = 0): String {
return result.toString() return result.toString()
} }
internal fun Element.clearKompAttributes() { internal fun Element.setKompAttribute(attributeName: String, value: String?) {
val attributes = this.asDynamic()["komp-attributes"] as MutableSet<String>? //val attributeName = name.lowercase()
if (value == null || value.isBlank()) {
if (attributes == null) { if (this is HTMLInputElement) {
this.asDynamic()["komp-attributes"] = mutableSetOf<String>() when (attributeName) {
} else { "checked" -> {
attributes.clear() checked = false
} }
/*
if (this is HTMLInputElement) { "class" -> {
this.checked = false className = ""
} }
} */
"value" -> {
internal fun Element.getKompAttributes(): MutableSet<String> { this.value = ""
var result: MutableSet<String>? = this.asDynamic()["komp-attributes"] as MutableSet<String>? }
else -> {
if (result == null) { removeAttribute(attributeName)
result = mutableSetOf() }
this.asDynamic()["komp-attributes"] = result
}
return result
}
internal fun Element.setKompAttribute(name: String, value: String) {
val setAttrs: MutableSet<String> = getKompAttributes()
setAttrs.add(name)
if (this is HTMLInputElement) {
when (name) {
"checked" -> {
this.checked = value == "checked"
}
"value" -> {
this.value = value
}
else -> {
setAttribute(name, value)
} }
} else {
removeAttribute(attributeName)
}
} else {
if (this is HTMLInputElement) {
when (attributeName) {
"checked" -> {
checked = "checked" == value
}
/*
"class" -> {
className = value
}
*/
"value" -> {
this.value = value
}
else -> {
setAttribute(attributeName, value)
}
}
} else if (this.getAttribute(attributeName) != value) {
setAttribute(attributeName, value)
} }
} else if (this.getAttribute(name) != value) {
setAttribute(name, value)
} }
} }
internal fun Element.clearKompEvents() { internal fun Element.clearKompEvents() {
val events = getKompEvents()
for ((name, event) in getKompEvents()) { for ((name, event) in getKompEvents()) {
removeEventListener(name, event) removeEventListener(name, event)
} }
events.clear()
val events = this.asDynamic()["komp-events"] as MutableMap<String, (Event) -> Unit>?
if (events == null) {
this.asDynamic()["komp-events"] = mutableMapOf<String, (Event) -> Unit>()
} else {
events.clear()
}
} }
internal fun Element.setKompEvent(name: String, event: (Event) -> Unit) { internal fun Element.setKompEvent(name: String, event: (Event) -> Unit) {
@@ -134,22 +127,20 @@ internal fun Element.setKompEvent(name: String, event: (Event) -> Unit) {
name name
} }
val events: MutableMap<String, (Event) -> Unit> = getKompEvents() getKompEvents()[eventName] = event
events[eventName]?.let {
println("Warn event '$eventName' already defined!")
removeEventListener(eventName, it)
}
events[eventName] = event
this.asDynamic()["komp-events"] = events
this.addEventListener(eventName, event) this.addEventListener(eventName, event)
} }
internal fun Element.getKompEvents(): MutableMap<String, (Event) -> Unit> { internal fun Element.getKompEvents(): MutableMap<String, (Event) -> Unit> {
return this.asDynamic()["komp-events"] ?: mutableMapOf() var result: MutableMap<String, (Event) -> Unit>? = this.asDynamic()["komp-events"] as MutableMap<String, (Event) -> Unit>?
if (result == null) {
result = mutableMapOf()
this.asDynamic()["komp-events"] = result
}
return result
} }
internal fun Element.findElementIndex(): Int { internal fun Element.findElementIndex(): Int {
@@ -163,4 +154,4 @@ internal fun Element.findElementIndex(): Int {
} }
return 0 return 0
} }

View File

@@ -29,7 +29,8 @@ fun FlowOrMetaDataOrPhrasingContent.currentElement(): Element =
private data class ElementIndex( private data class ElementIndex(
val parent: Node, val parent: Node,
var childIndex: Int var childIndex: Int,
var setAttr: MutableSet<String> = mutableSetOf()
) )
private fun ArrayList<ElementIndex>.currentParent(): Node { private fun ArrayList<ElementIndex>.currentParent(): Node {
@@ -48,8 +49,17 @@ private fun ArrayList<ElementIndex>.currentElement(): Node? {
return null return null
} }
private fun ArrayList<ElementIndex>.currentPosition(): ElementIndex? {
return if (this.size < 2) {
null
} else {
this[this.size - 2]
}
}
private fun ArrayList<ElementIndex>.nextElement() { private fun ArrayList<ElementIndex>.nextElement() {
this.lastOrNull()?.let { this.lastOrNull()?.let {
it.setAttr.clear()
it.childIndex++ it.childIndex++
} }
} }
@@ -64,7 +74,10 @@ private fun ArrayList<ElementIndex>.push(element: Node) {
private fun ArrayList<ElementIndex>.replace(new: Node) { private fun ArrayList<ElementIndex>.replace(new: Node) {
if (this.currentElement() != null) { if (this.currentElement() != null) {
this.currentElement()?.parentElement?.replaceChild(new, this.currentElement()!!) this.currentElement()?.parentElement?.replaceChild(
new,
this.currentElement()!!
)
} else { } else {
this.last().parent.appendChild(new) this.last().parent.appendChild(new)
} }
@@ -73,14 +86,14 @@ private fun ArrayList<ElementIndex>.replace(new: Node) {
private fun Node.asElement() = this as? HTMLElement private fun Node.asElement() = this as? HTMLElement
class HtmlBuilder( class HtmlBuilder(
val komponent: Komponent?, private val komponent: Komponent?,
parent: Element, parent: Element,
childIndex: Int = 0, childIndex: Int = 0
) : HtmlConsumer { ) : HtmlConsumer {
private var currentPosition = arrayListOf<ElementIndex>() private var currentPosition = arrayListOf<ElementIndex>()
private var inDebug = false private var inDebug = false
private var exceptionThrown = false private var exceptionThrown = false
var currentNode: Node? = null private var currentNode: Node? = null
var root: Element? = null var root: Element? = null
init { init {
@@ -94,7 +107,9 @@ class HtmlBuilder(
) { ) {
currentPosition.replace(komponent.element!!) currentPosition.replace(komponent.element!!)
if (Komponent.logRenderEvent) { if (Komponent.logRenderEvent) {
console.log("Skipped include $komponent, memoize hasn't changed") console.log(
"Skipped include $komponent, memoize hasn't changed"
)
} }
} else { } else {
komponent.create( komponent.create(
@@ -123,18 +138,21 @@ class HtmlBuilder(
} }
} }
private fun logReplace(msg: String) { private fun logReplace(msg: () -> String) {
if (Komponent.logReplaceEvent && inDebug) { if (Komponent.logReplaceEvent && inDebug) {
console.log(msg) console.log(msg.invoke())
} }
} }
override fun onTagStart(tag: Tag) { override fun onTagStart(tag: Tag) {
logReplace("onTagStart, [${tag.tagName}, ${tag.namespace}], currentPosition: $currentPosition") logReplace {
"onTagStart, [${tag.tagName}, ${tag.namespace}], currentPosition: $currentPosition"
}
currentNode = currentPosition.currentElement() currentNode = currentPosition.currentElement()
if (currentNode == null) { if (currentNode == null) {
logReplace("onTagStart, currentNode1: $currentNode") logReplace { "onTagStart, currentNode1: $currentNode" }
currentNode = if (tag.namespace != null) { currentNode = if (tag.namespace != null) {
document.createElementNS(tag.namespace, tag.tagName) document.createElementNS(tag.namespace, tag.tagName)
} else { } else {
@@ -150,8 +168,13 @@ class HtmlBuilder(
!currentNode?.asElement()?.namespaceURI.equals(tag.namespace, true) !currentNode?.asElement()?.namespaceURI.equals(tag.namespace, true)
) )
) { ) {
logReplace("onTagStart, currentElement, namespace: ${currentNode?.asElement()?.namespaceURI} -> ${tag.namespace}") logReplace {
logReplace("onTagStart, currentElement, replace: ${currentNode?.asElement()?.tagName} -> ${tag.tagName}") "onTagStart, currentElement, namespace: ${currentNode?.asElement()?.namespaceURI} -> ${tag.namespace}"
}
logReplace {
"onTagStart, currentElement, replace: ${currentNode?.asElement()?.tagName} -> ${tag.tagName}"
}
currentNode = if (tag.namespace != null) { currentNode = if (tag.namespace != null) {
document.createElementNS(tag.namespace, tag.tagName) document.createElementNS(tag.namespace, tag.tagName)
} else { } else {
@@ -159,9 +182,6 @@ class HtmlBuilder(
} }
currentPosition.replace(currentNode!!) currentPosition.replace(currentNode!!)
} else {
//logReplace"onTagStart, same node type")
} }
currentElement = currentNode as? Element ?: currentElement currentElement = currentNode as? Element ?: currentElement
@@ -172,22 +192,18 @@ class HtmlBuilder(
root = currentNode as Element root = currentNode as Element
} }
currentElement?.clearKompAttributes()
currentElement?.clearKompEvents() currentElement?.clearKompEvents()
(currentElement as? HTMLInputElement)?.checked = false
currentPosition.lastOrNull()?.setAttr?.clear()
// if currentElement = checkbox make sure it's cleared
for (entry in tag.attributesEntries) { for (entry in tag.attributesEntries) {
currentElement!!.setKompAttribute(entry.key.lowercase(), entry.value) currentElement!!.setKompAttribute(entry.key, entry.value)
} currentPosition.lastOrNull()?.setAttr?.add(entry.key)
if (tag.namespace != null) {
//logReplace"onTagStart, same node type")
(currentNode as? Element)?.innerHTML = ""
} }
} }
//logReplace"onTagStart, currentElement2: $currentNode")
currentPosition.push(currentNode!!) currentPosition.push(currentNode!!)
} }
@@ -202,28 +218,37 @@ class HtmlBuilder(
} }
} }
override fun onTagAttributeChange(tag: Tag, attribute: String, value: String?) { override fun onTagAttributeChange(
logReplace("onTagAttributeChange, ${tag.tagName} [$attribute, $value]") tag: Tag,
attribute: String,
value: String?
) {
logReplace { "onTagAttributeChange, ${tag.tagName} [$attribute, $value]" }
if (Komponent.enableAssertions) { if (Komponent.enableAssertions) {
checkTag(tag) checkTag(tag)
} }
if (value == null) { currentElement?.setKompAttribute(attribute, value)
currentElement?.removeAttribute(attribute.lowercase()) if (value == null || value.isEmpty()) {
currentPosition.currentPosition()?.setAttr?.remove(attribute)
} else { } else {
currentElement?.setKompAttribute(attribute.lowercase(), value) currentPosition.currentPosition()?.setAttr?.add(attribute)
} }
} }
override fun onTagEvent(tag: Tag, event: String, value: (Event) -> Unit) { override fun onTagEvent(
logReplace("onTagEvent, ${tag.tagName} [$event, $value]") tag: Tag,
event: String,
value: (Event) -> Unit
) {
logReplace { "onTagEvent, ${tag.tagName} [$event, $value]" }
if (Komponent.enableAssertions) { if (Komponent.enableAssertions) {
checkTag(tag) checkTag(tag)
} }
currentElement?.setKompEvent(event.lowercase(), value) currentElement?.setKompEvent(event.toLowerCase(), value)
} }
override fun onTagEnd(tag: Tag) { override fun onTagEnd(tag: Tag) {
@@ -241,43 +266,33 @@ class HtmlBuilder(
checkTag(tag) checkTag(tag)
} }
currentPosition.pop()
if (currentElement != null) { if (currentElement != null) {
val setAttrs: List<String> = currentElement?.asDynamic()["komp-attributes"] ?: listOf() val setAttrs: Set<String> = currentPosition.currentPosition()?.setAttr ?: setOf()
// remove attributes that where not set // remove attributes that where not set
val element = currentElement val element = currentElement
if (element?.hasAttributes() == true) { if (element?.hasAttributes() == true) {
for (index in 0 until element.attributes.length) { for (index in 0 until element.attributes.length) {
val attr = element.attributes[index] val attribute = element.attributes[index]
if (attr != null) { if (attribute?.name != null) {
val attr = attribute.name
if (element is HTMLElement && attr.name == "data-has-focus" && "true" == attr.value) { if (
element.focus() !setAttrs.contains(attr) &&
} attr != "style"
) {
if (attr.name != "style" && !setAttrs.contains(attr.name)) { element.setKompAttribute(attr, null)
if (element is HTMLInputElement) {
if (attr.name == "checkbox") {
element.checked = false
} else if (attr.name == "value") {
element.value = ""
} else if (attr.name == "class") {
element.className = ""
}
} else {
if (Komponent.logReplaceEvent) {
console.log("Clear attribute [${attr.name}] on $element)")
}
element.removeAttribute(attr.name)
}
} }
} }
} }
} }
} }
currentPosition.pop()
currentNode = currentPosition.currentElement()
currentElement = currentNode as? Element ?: currentElement
currentPosition.nextElement() currentPosition.nextElement()
currentElement = currentElement?.parentElement as? HTMLElement currentElement = currentElement?.parentElement as? HTMLElement
@@ -351,7 +366,10 @@ class HtmlBuilder(
//logReplace"onTagContentUnsafe, namespace: [$namespace]") //logReplace"onTagContentUnsafe, namespace: [$namespace]")
if (Komponent.unsafeMode == UnsafeMode.UNSAFE_ALLOWED || if (Komponent.unsafeMode == UnsafeMode.UNSAFE_ALLOWED ||
(Komponent.unsafeMode == UnsafeMode.UNSAFE_SVG_ONLY && namespace == "http://www.w3.org/2000/svg") (
Komponent.unsafeMode == UnsafeMode.UNSAFE_SVG_ONLY &&
namespace == "http://www.w3.org/2000/svg"
)
) { ) {
if (currentElement?.innerHTML != textContent) { if (currentElement?.innerHTML != textContent) {
currentElement?.innerHTML += textContent currentElement?.innerHTML += textContent
@@ -383,7 +401,7 @@ class HtmlBuilder(
if (exception !is KomponentException) { if (exception !is KomponentException) {
val position = mutableListOf<Element>() val position = mutableListOf<Element>()
var ce = currentElement var ce = currentElement
while(ce != null) { while (ce != null) {
position.add(ce) position.add(ce)
ce = ce.parentElement ce = ce.parentElement
} }
@@ -401,6 +419,7 @@ class HtmlBuilder(
} }
builder.append(" ") builder.append(" ")
} }
throw KomponentException( throw KomponentException(
komponent, komponent,
currentElement, currentElement,
@@ -416,13 +435,15 @@ class HtmlBuilder(
override fun finalize(): Element { override fun finalize(): Element {
//logReplace"finalize, currentPosition: $currentPosition") //logReplace"finalize, currentPosition: $currentPosition")
return root ?: throw IllegalStateException("We can't finalize as there was no tags") return root ?: throw IllegalStateException(
"We can't finalize as there was no tags"
)
} }
companion object { companion object {
fun create(content: HtmlBuilder.() -> Unit): Element { fun create(content: HtmlBuilder.() -> Unit): Element {
val container = document.createElement("div") as HTMLElement val container = document.createElement("div") as HTMLElement
val consumer = HtmlBuilder(null, container, 0) val consumer = HtmlBuilder(null, container)
content.invoke(consumer) content.invoke(consumer)
return consumer.root ?: error("No root element found after render!") return consumer.root ?: error("No root element found after render!")
} }

View File

@@ -7,6 +7,7 @@ import org.w3c.dom.HTMLElement
import org.w3c.dom.get import org.w3c.dom.get
private var currentKomponent: Komponent? = null private var currentKomponent: Komponent? = null
fun FlowOrMetaDataOrPhrasingContent.currentKomponent(): Komponent = fun FlowOrMetaDataOrPhrasingContent.currentKomponent(): Komponent =
currentKomponent ?: error("No current komponent defined! Only call from render code!") currentKomponent ?: error("No current komponent defined! Only call from render code!")
@@ -137,7 +138,6 @@ abstract class Komponent {
} }
} }
val builder = HtmlBuilder(this, parent, childIndex) val builder = HtmlBuilder(this, parent, childIndex)
builder.root = null
try { try {
currentKomponent = this currentKomponent = this

View File

@@ -1,9 +1,12 @@
package nl.astraeus.komp package nl.astraeus.komp
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.html.InputType
import kotlinx.html.classes
import kotlinx.html.div import kotlinx.html.div
import kotlinx.html.i import kotlinx.html.i
import kotlinx.html.id import kotlinx.html.id
import kotlinx.html.input
import kotlinx.html.js.onClickFunction import kotlinx.html.js.onClickFunction
import kotlinx.html.p import kotlinx.html.p
import kotlinx.html.span import kotlinx.html.span
@@ -35,6 +38,7 @@ class Child1 : Komponent() {
class Child2 : Komponent() { class Child2 : Komponent() {
override fun HtmlBuilder.render() { override fun HtmlBuilder.render() {
div { div {
id ="1234"
+"Child 2" +"Child 2"
} }
} }
@@ -50,6 +54,10 @@ class SimpleKomponent : Komponent() {
override fun HtmlBuilder.render() { override fun HtmlBuilder.render() {
div("div_class") { div("div_class") {
input(InputType.checkBox) {
name = "helloInput"
checked = hello
}
span { span {
svg { svg {
unsafe { unsafe {
@@ -61,8 +69,6 @@ class SimpleKomponent : Komponent() {
if (hello) { if (hello) {
div { div {
+"Hello" +"Hello"
throw IllegalStateException("Bloe")
} }
} else { } else {
span { span {
@@ -205,7 +211,8 @@ class TestUpdate {
fun testCreate() { fun testCreate() {
var elemTest: Element? = null var elemTest: Element? = null
val element = HtmlBuilder.create { val element = HtmlBuilder.create {
div("div_class") { div(classes = "div_class") {
classes = classes + "bla'"
id = "123" id = "123"
+"Test" +"Test"