25 Commits

Author SHA1 Message Date
3c41535870 Version 1.2.3-SNAPSHOT 2024-03-24 12:27:22 +01:00
1b2dd7f43a Version 1.2.2 2024-03-24 12:24:36 +01:00
ad42f33142 Version 1.2.2-SNAPSHOT 2024-01-24 16:36:51 +01:00
06a1e9956e Upgrade to Kotlin 1.9.22, , kotlinx-html 0.11.0, version 1.2.1 2024-01-24 14:53:44 +01:00
5cc4826e65 Upgrade to Kotlin 1.9.0, kotlinx-html 0.9.1, version 1.1.1. 2023-07-19 16:18:49 +02:00
a1f1f3bb38 Add some documentation 2022-10-14 20:09:43 +02:00
4954382f96 Update kotlinx-html to 0.7.5 2022-10-14 20:08:16 +02:00
419886bed0 Merge remote-tracking branch 'origin/master' 2022-10-14 17:13:31 +02:00
6b5a9bbe57 v. 1.0.8-SNAPSHOT 2022-10-14 17:12:59 +02:00
a4d5017651 v. 1.0.7 upgrade to Kotlin 1.7.20 2022-10-14 17:12:59 +02:00
fd6d643b45 Fixes 2022-10-14 17:12:53 +02:00
81ebbc250f Version 1.0.6
- Fix attr vs property checkbox update
2022-10-14 17:11:31 +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
6cc2389b2f Merge branch '1.0.4-SNAPSHOT' 2022-04-14 13:39:50 +02:00
8981e976ed Fix currentKomponent call 2022-04-14 13:32:54 +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
15 changed files with 328 additions and 233 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,19 +1,19 @@
plugins { plugins {
kotlin("multiplatform") version "1.6.10" kotlin("multiplatform") version "1.9.23"
`maven-publish` id("maven-publish")
signing id("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.2.3-SNAPSHOT"
repositories { repositories {
mavenCentral() mavenCentral()
} }
kotlin { kotlin {
js(BOTH) { js(IR) {
browser { browser {
testTask { testTask {
useKarma { useKarma {
@@ -22,20 +22,37 @@ kotlin {
} }
} }
} }
/*
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
//moduleName = project.name
browser()
mavenPublication {
groupId = group as String
pom { name = "${project.name}-wasm-js" }
}
}
@OptIn(ExperimentalKotlinGradlePluginApi::class)
applyDefaultHierarchyTemplate {
common {
group("jsCommon") {
withJs()
// TODO: switch to `withWasmJs()` after upgrade to Kotlin 2.0
withWasm()
}
}
}
*/
sourceSets { sourceSets {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
implementation(kotlin("stdlib-common")) api("org.jetbrains.kotlinx:kotlinx-html:0.11.0")
api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.3")
}
}
val jsMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
} }
} }
val jsMain by getting
val jsTest by getting { val jsTest by getting {
dependencies { dependencies {
implementation(kotlin("test-js")) implementation(kotlin("test-js"))
@@ -71,25 +88,25 @@ publishing {
maven { maven {
name = "releases" name = "releases"
// change to point to your repo, e.g. http://my.org/repo // change to point to your repo, e.g. http://my.org/repo
setUrl("https://nexus.astraeus.nl/nexus/content/repositories/releases") setUrl("https://reposilite.astraeus.nl/releases")
credentials { credentials {
val nexusUsername: String? by project val reposiliteUsername: String? by project
val nexusPassword: String? by project val reposilitePassword: String? by project
username = nexusUsername username = reposiliteUsername
password = nexusPassword password = reposilitePassword
} }
} }
maven { maven {
name = "snapshots" name = "snapshots"
// change to point to your repo, e.g. http://my.org/repo // change to point to your repo, e.g. http://my.org/repo
setUrl("https://nexus.astraeus.nl/nexus/content/repositories/snapshots") setUrl("https://reposilite.astraeus.nl/snapshots")
credentials { credentials {
val nexusUsername: String? by project val reposiliteUsername: String? by project
val nexusPassword: String? by project val reposilitePassword: String? by project
username = nexusUsername username = reposiliteUsername
password = nexusPassword password = reposilitePassword
} }
} }
maven { maven {
@@ -136,3 +153,23 @@ publishing {
signing { signing {
sign(publishing.publications) sign(publishing.publications)
} }
tasks.named<Task>("signJsPublication") {
dependsOn(tasks.named<Task>("publishKotlinMultiplatformPublicationToMavenLocal"))
}
tasks.named<Task>("publishJsPublicationToReleasesRepository") {
dependsOn(tasks.named<Task>("signKotlinMultiplatformPublication"))
}
tasks.named<Task>("publishKotlinMultiplatformPublicationToMavenLocalRepository") {
dependsOn(tasks.named<Task>("signJsPublication"))
}
tasks.named<Task>("publishKotlinMultiplatformPublicationToReleasesRepository") {
dependsOn(tasks.named<Task>("signJsPublication"))
}
tasks.named<Task>("publishKotlinMultiplatformPublicationToSonatypeRepository") {
dependsOn(tasks.named<Task>("signJsPublication"))
}

View File

@@ -56,7 +56,7 @@ fun greet() = "world"
Replace the code in the file with the following for a simple click app: Replace the code in the file with the following for a simple click app:
```koltin ```kotlin
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.html.button import kotlinx.html.button
import kotlinx.html.div import kotlinx.html.div
@@ -144,4 +144,4 @@ the data changes, that would look like this:
In that case you can remove the requestUpdate call from the onClickFunction. In that case you can remove the requestUpdate call from the onClickFunction.
You can find a working repository of this example here: [example]() You can find a working repository of this example here: [kotlin-komponent-start](https://github.com/rnentjes/kotlin-komponent-start)

View File

@@ -3,3 +3,4 @@
* [Home](home.md) * [Home](home.md)
* [Getting started](getting-started.md) * [Getting started](getting-started.md)
* [How it works](how-it-works.md) * [How it works](how-it-works.md)

View File

@@ -15,8 +15,8 @@ This way there will not be double updates of the same komponent.
The render call will be invoked and every html builder function (div, span etc.) will call the The render call will be invoked and every html builder function (div, span etc.) will call the
different HtmlBuilder functions like onTagStart, onTagAttributeChange etc. different HtmlBuilder functions like onTagStart, onTagAttributeChange etc.
In these functions the HtmlBuilder will compare the dom against the call being made and it will update the DOM In these functions the HtmlBuilder will compare the dom against the call being made, and it will update the DOM
if needed. as needed.

View File

Before

Width:  |  Height:  |  Size: 184 KiB

After

Width:  |  Height:  |  Size: 184 KiB

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-8.4-all.zip
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -9,3 +9,5 @@ See the komp-todo repository for a basic example here: [komp-todo](https://githu
For a more complete example take a look at the simple-password-manager repository: [simple-password-manager](https://github.com/rnentjes/simple-password-manager) For a more complete example take a look at the simple-password-manager repository: [simple-password-manager](https://github.com/rnentjes/simple-password-manager)
Available on maven central: "nl.astraeus:kotlin-komponent-js:1.0.0" Available on maven central: "nl.astraeus:kotlin-komponent-js:1.0.0"
Some getting started documentation can be found [here](docs/getting-started.md)

View File

@@ -1,8 +1,9 @@
package nl.astraeus.komp package nl.astraeus.komp
import org.w3c.dom.events.Event
import org.w3c.dom.Element import org.w3c.dom.Element
import org.w3c.dom.HTMLInputElement import org.w3c.dom.HTMLInputElement
import org.w3c.dom.events.Event import org.w3c.dom.events.EventListener
import org.w3c.dom.get import org.w3c.dom.get
private fun Int.asSpaces(): String { private fun Int.asSpaces(): String {
@@ -65,66 +66,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 +128,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 {

View File

@@ -0,0 +1,64 @@
package nl.astraeus.komp
import org.w3c.dom.Node
import org.w3c.dom.get
data class ElementIndex(
val parent: Node,
var childIndex: Int,
var setAttr: MutableSet<String> = mutableSetOf()
) {
override fun toString(): String {
return "${parent.nodeName}[$childIndex]"
}
}
fun ArrayList<ElementIndex>.currentParent(): Node {
this.lastOrNull()?.let {
return it.parent
}
throw IllegalStateException("currentParent should never be null!")
}
fun ArrayList<ElementIndex>.currentElement(): Node? {
this.lastOrNull()?.let {
return it.parent.childNodes[it.childIndex]
}
return null
}
fun ArrayList<ElementIndex>.currentPosition(): ElementIndex? {
return if (this.size < 2) {
null
} else {
this[this.size - 2]
}
}
fun ArrayList<ElementIndex>.nextElement() {
this.lastOrNull()?.let {
it.setAttr.clear()
it.childIndex++
}
}
fun ArrayList<ElementIndex>.pop() {
this.removeLast()
}
fun ArrayList<ElementIndex>.push(element: Node) {
this.add(ElementIndex(element, 0))
}
fun ArrayList<ElementIndex>.replace(new: Node) {
if (this.currentElement() != null) {
this.currentElement()?.parentElement?.replaceChild(
new,
this.currentElement()!!
)
} else {
this.last().parent.appendChild(new)
}
}

View File

@@ -13,7 +13,6 @@ import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSpanElement import org.w3c.dom.HTMLSpanElement
import org.w3c.dom.Node import org.w3c.dom.Node
import org.w3c.dom.asList import org.w3c.dom.asList
import org.w3c.dom.events.Event
import org.w3c.dom.get import org.w3c.dom.get
private var currentElement: Element? = null private var currentElement: Element? = null
@@ -27,60 +26,18 @@ interface HtmlConsumer : TagConsumer<Element> {
fun FlowOrMetaDataOrPhrasingContent.currentElement(): Element = fun FlowOrMetaDataOrPhrasingContent.currentElement(): Element =
currentElement ?: error("No current element defined!") currentElement ?: error("No current element defined!")
private data class ElementIndex(
val parent: Node,
var childIndex: Int
)
private fun ArrayList<ElementIndex>.currentParent(): Node {
this.lastOrNull()?.let {
return it.parent
}
throw IllegalStateException("currentParent should never be null!")
}
private fun ArrayList<ElementIndex>.currentElement(): Node? {
this.lastOrNull()?.let {
return it.parent.childNodes[it.childIndex]
}
return null
}
private fun ArrayList<ElementIndex>.nextElement() {
this.lastOrNull()?.let {
it.childIndex++
}
}
private fun ArrayList<ElementIndex>.pop() {
this.removeLast()
}
private fun ArrayList<ElementIndex>.push(element: Node) {
this.add(ElementIndex(element, 0))
}
private fun ArrayList<ElementIndex>.replace(new: Node) {
if (this.currentElement() != null) {
this.currentElement()?.parentElement?.replaceChild(new, this.currentElement()!!)
} else {
this.last().parent.appendChild(new)
}
}
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
private var firstTag: Boolean = true
var root: Element? = null var root: Element? = null
init { init {
@@ -94,9 +51,18 @@ 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 {
// current element should become parent
/*
val ce = komponent.element
if (ce != null) {
append(ce as Element)
}
*/
komponent.create( komponent.create(
currentPosition.last().parent as Element, currentPosition.last().parent as Element,
currentPosition.last().childIndex currentPosition.last().childIndex
@@ -123,25 +89,28 @@ 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 {
document.createElement(tag.tagName) document.createElement(tag.tagName)
} }
//logReplace"onTagStart, currentElement1.1: $currentNode") logReplace { "onTagStart, currentElement1.1: $currentNode" }
currentPosition.currentParent().appendChild(currentNode!!) currentPosition.currentParent().appendChild(currentNode!!)
} else if ( } else if (
!currentNode?.asElement()?.tagName.equals(tag.tagName, true) || !currentNode?.asElement()?.tagName.equals(tag.tagName, true) ||
@@ -150,8 +119,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,74 +133,81 @@ class HtmlBuilder(
} }
currentPosition.replace(currentNode!!) currentPosition.replace(currentNode!!)
} else {
//logReplace"onTagStart, same node type")
} }
currentElement = currentNode as? Element ?: currentElement currentElement = currentNode as? Element ?: currentElement
if (currentNode is Element) { if (currentNode is Element) {
if (root == null) { if (firstTag) {
//logReplace"Setting root: $currentNode") logReplace { "Setting root: $currentNode" }
root = currentNode as Element root = currentNode as Element
firstTag = false
} }
currentElement?.clearKompAttributes()
currentElement?.clearKompEvents() currentElement?.clearKompEvents()
// if currentElement = checkbox make sure it's cleared
(currentElement as? HTMLInputElement)?.checked = false
currentPosition.lastOrNull()?.setAttr?.clear()
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!!)
} }
private fun checkTag(tag: Tag) { private fun checkTag(source: String, tag: Tag) {
check(currentElement != null) { check(currentElement != null) {
js("debugger") js("debugger;")
"No current tag" "No current tag ($source)"
} }
check(currentElement?.tagName.equals(tag.tagName, ignoreCase = true)) { check(currentElement?.tagName.equals(tag.tagName, ignoreCase = true)) {
js("debugger") js("debugger;")
"Wrong current tag" "Wrong current tag ($source), got: ${tag.tagName} expected ${currentElement?.tagName}"
} }
} }
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("onTagAttributeChange", 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: (kotlinx.html.org.w3c.dom.events.Event) -> Unit
) {
logReplace { "onTagEvent, ${tag.tagName} [$event, $value]" }
if (Komponent.enableAssertions) { if (Komponent.enableAssertions) {
checkTag(tag) checkTag("onTagEvent", tag)
} }
currentElement?.setKompEvent(event.lowercase(), value) currentElement?.setKompEvent(event.lowercase(), value.asDynamic())
} }
override fun onTagEnd(tag: Tag) { override fun onTagEnd(tag: Tag) {
logReplace {
"onTagEnd, [${tag.tagName}, ${tag.namespace}], currentPosition: $currentPosition"
}
if (exceptionThrown) { if (exceptionThrown) {
return return
} }
@@ -238,46 +219,36 @@ class HtmlBuilder(
} }
if (Komponent.enableAssertions) { if (Komponent.enableAssertions) {
checkTag(tag) checkTag("onTagEnd", 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 +322,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
@@ -377,13 +351,13 @@ class HtmlBuilder(
currentPosition.nextElement() currentPosition.nextElement()
} }
override fun onTagError(tag: Tag, exception: Throwable) { fun onTagError(tag: Tag, exception: Throwable) {
exceptionThrown = true exceptionThrown = true
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 +375,7 @@ class HtmlBuilder(
} }
builder.append(" ") builder.append(" ")
} }
throw KomponentException( throw KomponentException(
komponent, komponent,
currentElement, currentElement,
@@ -416,13 +391,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!")
@@ -110,8 +111,25 @@ abstract class Komponent {
* *
* HTMLBuilder.render() is called 1st time the component is rendered, after that this * HTMLBuilder.render() is called 1st time the component is rendered, after that this
* method will be called * method will be called
*
* @deprecated
*/ */
open fun update() { @Deprecated(
"Deprecated to avoid confusing with requestUpdate, use renderUpdate instead",
ReplaceWith("renderUpdate"),
level = DeprecationLevel.WARNING
)
protected fun update() {
refresh()
}
/**
* This function can be overwritten if you know how to update the Komponent yourself
*
* HTMLBuilder.render() is called 1st time the component is rendered, after that this
* method will be called
*/
open fun renderUpdate() {
refresh() refresh()
} }
@@ -137,7 +155,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
@@ -228,7 +245,7 @@ abstract class Komponent {
if (next.memoizeChanged()) { if (next.memoizeChanged()) {
next.onBeforeUpdate() next.onBeforeUpdate()
next.update() next.renderUpdate()
next.updateMemoizeHash() next.updateMemoizeHash()
next.onAfterUpdate() next.onAfterUpdate()
} else if (logRenderEvent) { } else if (logRenderEvent) {

View File

@@ -31,7 +31,7 @@ class MutableCollectionStateDelegate<T>(
} }
// todo: return iterator wrapper to update at changes? // todo: return iterator wrapper to update at changes?
// override fun iterator(): MutableIterator<T> = collection.iterator() //override fun iterator(): MutableIterator<T> = collection.iterator()
override fun remove(element: T): Boolean { override fun remove(element: T): Boolean {
komponent.requestUpdate() komponent.requestUpdate()

View File

@@ -14,8 +14,8 @@ interface Delegate<T> {
property: KProperty<*>, property: KProperty<*>,
value: T value: T
) )
}
}
open class StateDelegate<T>( open class StateDelegate<T>(
val komponent: Komponent, val komponent: Komponent,
@@ -51,6 +51,6 @@ open class StateDelegate<T>(
inline fun <reified T> Komponent.state( inline fun <reified T> Komponent.state(
initialValue: T initialValue: T
): Delegate<T> = StateDelegate( ): Delegate<T> = StateDelegate(
this, this,
initialValue initialValue
) )

View File

@@ -1,9 +1,13 @@
package nl.astraeus.komp package nl.astraeus.komp
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.html.DIV
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 +39,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 +55,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 +70,6 @@ class SimpleKomponent : Komponent() {
if (hello) { if (hello) {
div { div {
+"Hello" +"Hello"
throw IllegalStateException("Bloe")
} }
} else { } else {
span { span {
@@ -119,7 +126,7 @@ class IncludeKomponent(
} }
class ReplaceKomponent : Komponent() { class ReplaceKomponent : Komponent() {
val includeKomponent = IncludeKomponent() val includeKomponent = IncludeKomponent("Other text")
var includeSpan = true var includeSpan = true
override fun generateMemoizeHash(): Int = includeSpan.hashCode() * 7 + includeKomponent.generateMemoizeHash() override fun generateMemoizeHash(): Int = includeSpan.hashCode() * 7 + includeKomponent.generateMemoizeHash()
@@ -130,20 +137,8 @@ class ReplaceKomponent : Komponent() {
div { div {
if (includeSpan) { if (includeSpan) {
span { for (index in 0 ..< 3) {
i("fas fa-eye") { extracted(index)
+"span1"
}
}
span {
i("fas fa-eye") {
+"span2"
}
}
span {
i("fas fa-eye") {
+"span3"
}
} }
} }
@@ -151,6 +146,14 @@ class ReplaceKomponent : Komponent() {
} }
} }
} }
private fun HtmlBuilder.extracted(index: Int) {
span {
i("fas fa-eye") {
+ ("span" + (index+1))
}
}
}
} }
class TestUpdate { class TestUpdate {
@@ -205,7 +208,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"