3 Commits

Author SHA1 Message Date
fa6252832b Cleanup 2021-06-29 17:21:27 +02:00
d0442e785f VDom implementation 2021-04-05 17:21:45 +02:00
68fde339a8 Remove replace option 2021-03-31 16:17:01 +02:00
26 changed files with 1547 additions and 1356 deletions

7
.gitignore vendored
View File

@@ -1,11 +1,4 @@
# Created by .ignore support plugin (hsz.mobi) # Created by .ignore support plugin (hsz.mobi)
build
.gradle
web/js/generated web/js/generated
gradle.properties gradle.properties
local.properties local.properties
*.iml
*.ipr
*.iws
kotlin-js-store
.idea

View File

@@ -1,23 +1,27 @@
plugins { plugins {
kotlin("multiplatform") version "1.7.20" kotlin("multiplatform") version "1.4.32"
`maven-publish` `maven-publish`
signing
id("org.jetbrains.dokka") version "1.5.31"
} }
group = "nl.astraeus" group = "nl.astraeus"
version = "1.0.8-SNAPSHOT" version = "0.3.0-SNAPSHOT"
repositories { repositories {
mavenCentral() mavenCentral()
jcenter()
} }
kotlin { kotlin {
/* Targets configuration omitted.
* To find out how to configure the targets, please follow the link:
* https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets */
js(BOTH) { js(BOTH) {
browser { browser {
//produceKotlinLibrary()
testTask { testTask {
useKarma { useKarma {
useChromiumHeadless() useChromeHeadless()
} }
} }
} }
@@ -28,7 +32,7 @@ kotlin {
dependencies { dependencies {
implementation(kotlin("stdlib-common")) implementation(kotlin("stdlib-common"))
api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.3") api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2")
} }
} }
val jsMain by getting { val jsMain by getting {
@@ -44,37 +48,15 @@ kotlin {
} }
} }
extra["PUBLISH_GROUP_ID"] = group
extra["PUBLISH_VERSION"] = version
extra["PUBLISH_ARTIFACT_ID"] = name
// Stub secrets to let the project sync and build without the publication values set up
val signingKeyId: String? by project
val signingPassword: String? by project
val signingSecretKeyRingFile: String? by project
val ossrhUsername: String? by project
val ossrhPassword: String? by project
extra["signing.keyId"] = signingKeyId
extra["signing.password"] = signingPassword
extra["signing.secretKeyRingFile"] = signingSecretKeyRingFile
extra["ossrhUsername"] = ossrhUsername
extra["ossrhPassword"] = ossrhPassword
val javadocJar by tasks.registering(Jar::class) {
archiveClassifier.set("javadoc")
}
publishing { publishing {
repositories { repositories {
mavenLocal() if (project.properties["nexusUsername"] != null) {
maven { maven {
name = "releases" name = "releases"
// change to point to your repo, e.g. http://my.org/repo url = uri("http://nexus.astraeus.nl/nexus/content/repositories/releases")
setUrl("https://nexus.astraeus.nl/nexus/content/repositories/releases")
credentials { credentials {
val nexusUsername: String? by project val nexusUsername: String by project
val nexusPassword: String? by project val nexusPassword: String by project
username = nexusUsername username = nexusUsername
password = nexusPassword password = nexusPassword
@@ -82,57 +64,20 @@ publishing {
} }
maven { maven {
name = "snapshots" name = "snapshots"
// change to point to your repo, e.g. http://my.org/repo url = uri("http://nexus.astraeus.nl/nexus/content/repositories/snapshots")
setUrl("https://nexus.astraeus.nl/nexus/content/repositories/snapshots")
credentials { credentials {
val nexusUsername: String? by project val nexusUsername: String by project
val nexusPassword: String? by project val nexusPassword: String by project
username = nexusUsername username = nexusUsername
password = nexusPassword password = nexusPassword
} }
} }
maven { } else {
name = "sonatype" println("Publishing disabled properties not found.")
setUrl("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2")
credentials {
username = ossrhUsername
password = ossrhPassword
} }
} }
} publications {
val kotlinMultiplatform by getting {}
// Configure all publications
publications.withType<MavenPublication> {
// Stub javadoc.jar artifact
artifact(javadocJar.get())
// Provide artifacts information requited by Maven Central
pom {
name.set("kotlin-komponent")
description.set("Kotlin komponent")
url.set("https://github.com/rnentjes/komponent")
licenses {
license {
name.set("MIT")
url.set("https://opensource.org/licenses/MIT")
} }
} }
developers {
developer {
id.set("rnentjes")
name.set("Rien Nentjes")
email.set("info@nentjes.com")
}
}
scm {
url.set("https://github.com/rnentjes/komponent")
}
}
}
}
signing {
sign(publishing.publications)
}

View File

@@ -1,147 +0,0 @@
# Table of contents
* [Home](home.md)
* [Getting started](getting-started.md)
* [How it works](how-it-works.md)
# Getting started
To get started create a new kotlin project in intellij of the type 'Browser application'
![Create 'Browser Application' project](/docs/img/create-project.png)
Add the 'sourceSets' block with the kotlin-komponent dependency so your build.gradle.kts looks like this:
```gradle
plugins {
kotlin("js") version "1.6.10"
}
group = "com.test"
version = "1.0.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
api("nl.astraeus:kotlin-komponent-js:1.0.0")
testImplementation(kotlin("test"))
}
kotlin {
js(IR) {
binaries.executable()
browser {
commonWebpackConfig {
cssSupport.enabled = true
}
}
}
}
```
Refresh the gradle project to import the dependency.
There is now only one kotlin source file in the project called Simple.kt, it should look something like this:
```kotin
fun main() {
console.log("Hello, ${greet()}")
}
fun greet() = "world"
```
Replace the code in the file with the following for a simple click app:
```koltin
import kotlinx.browser.document
import kotlinx.html.button
import kotlinx.html.div
import kotlinx.html.hr
import kotlinx.html.js.onClickFunction
import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent
import nl.astraeus.komp.mutableCollectionState
import nl.astraeus.komp.state
import kotlin.js.Date
fun main() {
console.log("Hello, ${greet()}")
Komponent.create(document.body!!, TestKomponent())
}
fun greet() = "world"
class TestKomponent : Komponent() {
var clicks: Int = 0
val lines: MutableCollection<String> = mutableListOf()
override fun HtmlBuilder.render() {
div {
div {
+"Hello Komponent!"
}
div {
+"Clicks $clicks"
}
div {
button {
+"Click"
onClickFunction = {
clicks++
lines.add("click $clicks at ${Date()}")
requestUpdate()
}
}
}
hr()
for (line in lines) {
div {
+ line
}
}
}
}
}
```
First in the main we add our TestKomponent to the document body with the following line:
```kotlin
Komponent.create(document.body!!, TestKomponent())
```
The TestKomponent.render method will be called to render our Komponent.
As you can see events can be attached inline with the on<event>Function methods.
The requestUpdate method will call the render method again and update the page accordingly.
After building the application you will find it in /build/distributions.
In the index.html page you will find the following line:
```html
<div id="root"></div>
```
This line is not needed for kotlin-komponent.
If you like you can use some helpers that will automatically call the requestUpdate method if
the data changes, that would look like this:
```kotlin
var clicks: Int by state(0)
val lines: MutableCollection<String> = mutableCollectionState(mutableListOf())
```
In that case you can remove the requestUpdate call from the onClickFunction.
You can find a working repository of this example here: [example]()

View File

@@ -1,5 +0,0 @@
# Table of contents
* [Home](home.md)
* [Getting started](getting-started.md)
* [How it works](how-it-works.md)

View File

@@ -1,23 +0,0 @@
# Table of contents
* [Home](home.md)
* [Getting started](getting-started.md)
* [How it works](how-it-works.md)
# How it works
When the requestUpdate call is made to the [Komponent](src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt)
the update is queued in a callback. The callback will be called after the current event is handled.
If there are multiple updates requested, these are sorted so that the top Komponents get executed first.
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
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
if needed.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 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.5.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-all.zip
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

38
komp.commonMain.iml Normal file
View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="komp:commonMain" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.3.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="Common (experimental) " allPlatforms="JS []/JVM [1.6]/Native []/Native [general]" useProjectSettings="false" isTestModule="false" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin">
<newMppModelJpsModuleKind>SOURCE_SET_HOLDER</newMppModelJpsModuleKind>
<compilerSettings />
<compilerArguments>
<option name="languageVersion" value="1.4" />
<option name="apiVersion" value="1.4" />
<option name="pluginOptions">
<array />
</option>
<option name="pluginClasspaths">
<array>
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.32/49656d531bfab9d6e45d3f27bc4d03b542d6766/kotlin-scripting-jvm-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.32/6abda0fe69677f0e46e7539fd185e4bd093b7258/kotlin-scripting-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.3.8/f62be6d4cbf27781c2969867b4ed952f38378492/kotlinx-coroutines-core-1.3.8.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.32/461367948840adbb0839c51d91ed74ef4a9ccb52/kotlin-stdlib-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/ef50bfa2c0491a11dcc35d9822edbfd6170e1ea2/kotlin-stdlib-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.32/bac50b0748be017dbc13fc1cb7231b1c9da0e106/kotlin-script-runtime-1.4.32.jar" />
</array>
</option>
<option name="multiPlatform" value="true" />
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/src/commonMain" />
<orderEntry type="jdk" jdkName="Kotlin SDK" jdkType="KotlinSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.2" level="project" />
</component>
</module>

44
komp.commonTest.iml Normal file
View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="komp:commonTest" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.3.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="Common (experimental) " allPlatforms="JS []/JVM [1.6]/Native []/Native [general]" useProjectSettings="false" isTestModule="true" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin">
<newMppModelJpsModuleKind>SOURCE_SET_HOLDER</newMppModelJpsModuleKind>
<externalSystemTestTasks>
<externalSystemTestTask>jsLegacyBrowserTest|komp:jsTest|jsLegacy</externalSystemTestTask>
</externalSystemTestTasks>
<compilerSettings />
<compilerArguments>
<option name="languageVersion" value="1.4" />
<option name="apiVersion" value="1.4" />
<option name="pluginOptions">
<array />
</option>
<option name="pluginClasspaths">
<array>
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.32/49656d531bfab9d6e45d3f27bc4d03b542d6766/kotlin-scripting-jvm-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.32/6abda0fe69677f0e46e7539fd185e4bd093b7258/kotlin-scripting-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.3.8/f62be6d4cbf27781c2969867b4ed952f38378492/kotlinx-coroutines-core-1.3.8.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.32/461367948840adbb0839c51d91ed74ef4a9ccb52/kotlin-stdlib-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/ef50bfa2c0491a11dcc35d9822edbfd6170e1ea2/kotlin-stdlib-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.32/bac50b0748be017dbc13fc1cb7231b1c9da0e106/kotlin-script-runtime-1.4.32.jar" />
</array>
</option>
<option name="multiPlatform" value="true" />
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/src/commonTest" />
<orderEntry type="jdk" jdkName="Kotlin SDK" jdkType="KotlinSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="komp.commonMain" scope="TEST" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.32" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.2" level="project" />
</component>
<component name="TestModuleProperties" production-module="komp.commonMain" />
</module>

12
komp.iml Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="komp" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.version="0.3.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

518
komp.ipr Normal file
View File

@@ -0,0 +1,518 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ArtifactManager">
<artifact type="jar" name="komp-0.1.16-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="komp-0.1.16-SNAPSHOT.jar">
<element id="module-output" name="komp_main" />
</root>
</artifact>
<artifact type="jar" name="komp-0.1.17-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="komp-0.1.17-SNAPSHOT.jar">
<element id="module-output" name="komp_main" />
</root>
</artifact>
<artifact type="jar" name="komp-js-0.1.21-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="komp-js-0.1.21-SNAPSHOT.jar">
<element id="module-output" name="komp.jsMain" />
</root>
</artifact>
<artifact type="jar" name="komp-js-0.2.1-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="komp-js-0.2.1-SNAPSHOT.jar">
<element id="module-output" name="komp.jsMain" />
</root>
</artifact>
<artifact type="jar" name="komp-js-0.2.2-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="komp-js-0.2.2-SNAPSHOT.jar">
<element id="module-output" name="komp.jsMain" />
</root>
</artifact>
<artifact type="jar" name="komp-jslegacy-0.2.2-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="komp-jslegacy-0.2.2-SNAPSHOT.jar">
<element id="module-output" name="komp.jsMain" />
</root>
</artifact>
<artifact type="jar" name="komp-jslegacy-0.2.3-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="komp-jslegacy-0.2.3-SNAPSHOT.jar">
<element id="module-output" name="komp.jsMain" />
</root>
</artifact>
<artifact type="jar" name="komp-jslegacy-0.2.4-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="komp-jslegacy-0.2.4-SNAPSHOT.jar">
<element id="module-output" name="komp.jsMain" />
</root>
</artifact>
<artifact type="jar" name="komp-jslegacy-0.2.5-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="komp-jslegacy-0.2.5-SNAPSHOT.jar">
<element id="module-output" name="komp.jsMain" />
</root>
</artifact>
<artifact type="jar" name="komp-jslegacy-0.3.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/build/libs</output-path>
<root id="archive" name="komp-jslegacy-0.3.0-SNAPSHOT.jar">
<element id="module-output" name="komp.jsMain" />
</root>
</artifact>
</component>
<component name="CheckStyle-IDEA">
<option name="configuration">
<map>
<entry key="checkstyle-version" value="8.5" />
<entry key="copy-libs" value="false" />
<entry key="location-0" value="BUNDLED:(bundled):Sun Checks" />
<entry key="location-1" value="BUNDLED:(bundled):Google Checks" />
<entry key="scan-before-checkin" value="false" />
<entry key="scanscope" value="JavaOnly" />
<entry key="suppress-errors" value="false" />
</map>
</option>
</component>
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="GradleLocalSettings">
<option name="modificationStamps">
<map>
<entry key="$PROJECT_DIR$/../../DestinationSol" value="5718836739744" />
<entry key="$PROJECT_DIR$/../../kotlin/kotlin-webgl-test" value="2938763414342" />
<entry key="$PROJECT_DIR$/../../music/dawjs" value="2921352917165" />
<entry key="$PROJECT_DIR$/../../music/mto" value="7363595652795" />
<entry key="$PROJECT_DIR$/../../music/synthesis" value="2936164705438" />
<entry key="$PROJECT_DIR$/../../simple/Simple-persistence" value="2890201693583" />
<entry key="$PROJECT_DIR$/../../simple/Simple-web" value="2890212261770" />
<entry key="$PROJECT_DIR$/../../simple/SimpleDatabase" value="2890191706466" />
<entry key="$PROJECT_DIR$/../../simple/Very-simple-templates" value="2890190703434" />
<entry key="$PROJECT_DIR$/../../simple/simple-password-manager" value="8877848261147" />
<entry key="$PROJECT_DIR$/../../simple/simple-password-manager/client" value="2959282312702" />
<entry key="$PROJECT_DIR$/../../simple/simple-password-manager/server" value="2952285414000" />
<entry key="$PROJECT_DIR$/../../simple/simple-social-media" value="2954146711424" />
<entry key="$PROJECT_DIR$/../../simple/stats/simple-stats-client" value="2941044635281" />
<entry key="$PROJECT_DIR$/../../simple/stats/simple-stats-server" value="2909544379158" />
<entry key="$PROJECT_DIR$/../../work/Kos/KosConvertBranchcode" value="2904315467504" />
</map>
</option>
<option name="externalProjectsViewState">
<projects_view />
</option>
</component>
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="delegatedBuild" value="false" />
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
<component name="HotSwapAgentPluginSettingsProvider">
<option name="agentPath" value="$APPLICATION_PLUGINS_DIR$/hotswap-agent-intellij-plugin/lib/agent/hotswap-agent-1.3.0.jar" />
</component>
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
</profile>
<version value="1.0" />
</component>
<component name="MacroExpansionManager">
<option name="directoryName" value="qFcQOWli" />
</component>
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
<component name="PhpWorkspaceProjectConfiguration" backward_compatibility_performed="true" />
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/komp.iml" filepath="$PROJECT_DIR$/komp.iml" />
<module fileurl="file://$PROJECT_DIR$/komp.commonMain.iml" filepath="$PROJECT_DIR$/komp.commonMain.iml" />
<module fileurl="file://$PROJECT_DIR$/komp.commonTest.iml" filepath="$PROJECT_DIR$/komp.commonTest.iml" />
<module fileurl="file://$PROJECT_DIR$/komp.jsMain.iml" filepath="$PROJECT_DIR$/komp.jsMain.iml" />
<module fileurl="file://$PROJECT_DIR$/komp.jsTest.iml" filepath="$PROJECT_DIR$/komp.jsTest.iml" />
<module fileurl="file://$PROJECT_DIR$/komp_main.iml" filepath="$PROJECT_DIR$/komp_main.iml" group="komp" />
<module fileurl="file://$PROJECT_DIR$/komp_test.iml" filepath="$PROJECT_DIR$/komp_test.iml" group="komp" />
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" />
</component>
<component name="PropertiesComponent">
<property name="GoToClass.includeLibraries" value="false" />
<property name="GoToClass.toSaveIncludeLibraries" value="false" />
<property name="GoToFile.includeJavaFiles" value="false" />
<property name="MemberChooser.sorted" value="false" />
<property name="MemberChooser.showClasses" value="true" />
<property name="MemberChooser.copyJavadoc" value="false" />
<property name="nodejs_interpreter_path" value="/usr/bin/node" />
</component>
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="http://nexus.astraeus.nl/nexus/content/groups/public" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
</component>
<component name="RunManager">
<configuration default="true" type="Applet" factoryName="Applet">
<option name="WIDTH" value="400" />
<option name="HEIGHT" value="300" />
<option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" />
<module />
</configuration>
<configuration default="true" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" />
<option name="VM_PARAMETERS" />
<option name="PROGRAM_PARAMETERS" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="ENABLE_SWING_INSPECTOR" value="false" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<module name="" />
<envs />
</configuration>
<configuration default="true" type="JUnit" factoryName="JUnit">
<module name="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" />
<option name="MAIN_CLASS_NAME" />
<option name="METHOD_NAME" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" />
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
</configuration>
<configuration default="true" type="#org.jetbrains.idea.devkit.run.PluginConfigurationType" factoryName="Plugin">
<module name="" />
<option name="VM_PARAMETERS" value="-Xmx512m -Xms256m -XX:MaxPermSize=250m -ea" />
<option name="PROGRAM_PARAMETERS" />
<predefined_log_file id="idea.log" enabled="true" />
</configuration>
<configuration default="true" type="Remote" factoryName="Remote">
<option name="USE_SOCKET_TRANSPORT" value="true" />
<option name="SERVER_MODE" value="false" />
<option name="SHMEM_ADDRESS" value="javadebug" />
<option name="HOST" value="localhost" />
<option name="PORT" value="5005" />
</configuration>
<configuration name="&lt;template&gt;" type="TestNG" default="true" selected="false">
<option name="MAIN_CLASS_NAME" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
</configuration>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
<component name="accountSettings">
<option name="activeRegion" value="us-east-1" />
<option name="recentlyUsedRegions">
<list>
<option value="us-east-1" />
</list>
</option>
</component>
<component name="libraries-with-intellij-classes">
<option name="intellijApiContainingLibraries">
<list>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIU" />
<option name="groupId" value="com.jetbrains.intellij.idea" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIU" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIC" />
<option name="groupId" value="com.jetbrains.intellij.idea" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="ideaIC" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPY" />
<option name="groupId" value="com.jetbrains.intellij.pycharm" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPY" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPC" />
<option name="groupId" value="com.jetbrains.intellij.pycharm" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="pycharmPC" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="clion" />
<option name="groupId" value="com.jetbrains.intellij.clion" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="clion" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="riderRD" />
<option name="groupId" value="com.jetbrains.intellij.rider" />
</LibraryCoordinatesState>
<LibraryCoordinatesState>
<option name="artifactId" value="riderRD" />
<option name="groupId" value="com.jetbrains" />
</LibraryCoordinatesState>
</list>
</option>
</component>
<component name="libraryTable">
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32" type="kotlin.common">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/ef50bfa2c0491a11dcc35d9822edbfd6170e1ea2/kotlin-stdlib-common-1.4.32.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/f3e47c26a177728549b3dd2a624fec333aa17b36/kotlin-stdlib-common-1.4.32-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.32" type="kotlin.js">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.4.32/8dca0e318b64909eecfb2d00a9d045562f3e66f8/kotlin-stdlib-js-1.4.32.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-js/1.4.32/5729944dc91bf28689461fe44b9e401fd41a84a7/kotlin-stdlib-js-1.4.32-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-annotations-common:1.4.32" type="kotlin.common">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-annotations-common/1.4.32/f242cb223feb6d44e79588c26ca66bf57b0bc3a8/kotlin-test-annotations-common-1.4.32.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-annotations-common/1.4.32/57512e29ee20defe37d54ce19507d599b0f2113/kotlin-test-annotations-common-1.4.32-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-common:1.4.32" type="kotlin.common">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-common/1.4.32/a10168dd408319a4af03763ad11f12e0fc1fc5ba/kotlin-test-common-1.4.32.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-common/1.4.32/d1818d1bdc457b51289f006884a260b56a55cb77/kotlin-test-common-1.4.32-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlin:kotlin-test-js:1.4.32" type="kotlin.js">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-js/1.4.32/f4b4bf663acffeff2a664677b9e780f206f6842f/kotlin-test-js-1.4.32.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-test-js/1.4.32/8e70170db077d3f421c3fb6d24468b3938990f4a/kotlin-test-js-1.4.32-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.2" type="kotlin.common">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-common/0.7.2/7da838083337896b558cab2884c4118709a58769/kotlinx-html-metadata-0.7.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-common/0.7.2/866bbbfe0a81d49cdf19fd149adafbb938b35be4/kotlinx-html-common-0.7.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Gradle: org.jetbrains.kotlinx:kotlinx-html-js:0.7.2" type="kotlin.js">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-js/0.7.2/baf7e494e549776d0ae3e0ee225804a513f5dc26/kotlinx-html-jslegacy-0.7.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-html-js/0.7.2/b09bfd3565058174afa3fd0888743f8b8dca5406/kotlinx-html-js-0.7.2-sources.jar!/" />
</SOURCES>
</library>
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.7</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

59
komp.jsMain.iml Normal file
View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="komp:jsMain" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.3.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="JavaScript " allPlatforms="JS []" useProjectSettings="false" isTestModule="false" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin">
<dependsOnModuleNames>komp:commonMain</dependsOnModuleNames>
<sourceSets>
<sourceSet>komp.commonMain</sourceSet>
</sourceSets>
<newMppModelJpsModuleKind>COMPILATION_AND_SOURCE_SET_HOLDER</newMppModelJpsModuleKind>
<compilerSettings />
<compilerArguments>
<option name="outputFile" value="$MODULE_DIR$/build/js/packages/komp-jsLegacy/kotlin/komp-jsLegacy.js" />
<option name="noStdlib" value="true" />
<option name="sourceMap" value="true" />
<option name="metaInfo" value="true" />
<option name="target" value="v5" />
<option name="moduleKind" value="umd" />
<option name="main" value="call" />
<option name="languageVersion" value="1.4" />
<option name="apiVersion" value="1.4" />
<option name="pluginOptions">
<array />
</option>
<option name="pluginClasspaths">
<array>
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.32/bac50b0748be017dbc13fc1cb7231b1c9da0e106/kotlin-script-runtime-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.32/6abda0fe69677f0e46e7539fd185e4bd093b7258/kotlin-scripting-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.32/49656d531bfab9d6e45d3f27bc4d03b542d6766/kotlin-scripting-jvm-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/ef50bfa2c0491a11dcc35d9822edbfd6170e1ea2/kotlin-stdlib-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.32/461367948840adbb0839c51d91ed74ef4a9ccb52/kotlin-stdlib-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.3.8/f62be6d4cbf27781c2969867b4ed952f38378492/kotlinx-coroutines-core-1.3.8.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar" />
</array>
</option>
<option name="multiPlatform" value="true" />
<option name="errors">
<ArgumentParseErrors />
</option>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<output url="file://$MODULE_DIR$/build/js/packages/komp-jsLegacy/kotlin" />
<exclude-output />
<content url="file://$MODULE_DIR$/build/externals/komp-jsIr/src" />
<content url="file://$MODULE_DIR$/build/externals/komp-jsLegacy/src" />
<content url="file://$MODULE_DIR$/src/jsMain">
<sourceFolder url="file://$MODULE_DIR$/src/jsMain/kotlin" type="kotlin-source" />
</content>
<orderEntry type="jdk" jdkName="Kotlin SDK" jdkType="KotlinSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="komp.commonMain" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-js:0.7.2" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.32" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32" level="project" />
</component>
</module>

70
komp.jsTest.iml Normal file
View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="komp:jsTest" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="nl.astraeus" external.system.module.type="sourceSet" external.system.module.version="0.3.0-SNAPSHOT" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="JavaScript " allPlatforms="JS []" useProjectSettings="false" isTestModule="true" externalProjectId="komp" pureKotlinSourceFolders="$MODULE_DIR$/src/jsMain/kotlin;/home/rnentjes/Development/komp/komp/build/externals/komp-jsLegacy/src;/home/rnentjes/Development/komp/komp/build/externals/komp-jsIr/src;/home/rnentjes/Development/komp/komp/src/jsTest/kotlin">
<dependsOnModuleNames>komp:commonTest</dependsOnModuleNames>
<sourceSets>
<sourceSet>komp.commonTest</sourceSet>
<sourceSet>komp.jsMain</sourceSet>
<sourceSet>komp.commonMain</sourceSet>
</sourceSets>
<newMppModelJpsModuleKind>COMPILATION_AND_SOURCE_SET_HOLDER</newMppModelJpsModuleKind>
<externalSystemTestTasks>
<externalSystemTestTask>jsLegacyBrowserTest|komp:jsTest|jsLegacy</externalSystemTestTask>
</externalSystemTestTasks>
<compilerSettings />
<compilerArguments>
<option name="outputFile" value="$MODULE_DIR$/build/js/packages/komp-jsLegacy-test/kotlin/komp-jsLegacy-test.js" />
<option name="noStdlib" value="true" />
<option name="sourceMap" value="true" />
<option name="metaInfo" value="true" />
<option name="target" value="v5" />
<option name="moduleKind" value="umd" />
<option name="main" value="call" />
<option name="languageVersion" value="1.4" />
<option name="apiVersion" value="1.4" />
<option name="pluginOptions">
<array />
</option>
<option name="pluginClasspaths">
<array>
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.4.32/bac50b0748be017dbc13fc1cb7231b1c9da0e106/kotlin-script-runtime-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-common/1.4.32/6abda0fe69677f0e46e7539fd185e4bd093b7258/kotlin-scripting-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-scripting-jvm/1.4.32/49656d531bfab9d6e45d3f27bc4d03b542d6766/kotlin-scripting-jvm-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.4.32/ef50bfa2c0491a11dcc35d9822edbfd6170e1ea2/kotlin-stdlib-common-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.4.32/461367948840adbb0839c51d91ed74ef4a9ccb52/kotlin-stdlib-1.4.32.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.3.8/f62be6d4cbf27781c2969867b4ed952f38378492/kotlinx-coroutines-core-1.3.8.jar" />
<option value="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar" />
</array>
</option>
<option name="multiPlatform" value="true" />
<option name="errors">
<ArgumentParseErrors />
</option>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<output-test url="file://$MODULE_DIR$/build/js/packages/komp-jsLegacy-test/kotlin" />
<exclude-output />
<content url="file://$MODULE_DIR$/src/jsTest">
<sourceFolder url="file://$MODULE_DIR$/src/jsTest/kotlin" type="kotlin-test" />
</content>
<orderEntry type="jdk" jdkName="Kotlin SDK" jdkType="KotlinSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="komp.commonMain" scope="TEST" />
<orderEntry type="module" module-name="komp.commonTest" scope="TEST" production-on-test="" />
<orderEntry type="module" module-name="komp.jsMain" scope="TEST" />
<orderEntry type="module" module-name="komp.jsMain" scope="RUNTIME" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-js:0.7.2" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-test-js:1.4.32" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-js:1.4.32" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlinx:kotlinx-html-common:0.7.2" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-test-common:1.4.32" level="project" />
<orderEntry type="library" scope="TEST" name="Gradle: org.jetbrains.kotlin:kotlin-test-annotations-common:1.4.32" level="project" />
</component>
<component name="TestModuleProperties" production-module="komp.jsMain" />
</module>

View File

@@ -1,11 +1,7 @@
# Komponent # Komponent
![Gradle CI](https://github.com/rnentjes/komponent/workflows/Gradle%20CI/badge.svg)
Very simple helper to build single page apps with the Kotlin javascript target using static html builders. Very simple helper to build single page apps with the Kotlin javascript target using static html builders.
See the komp-todo repository for a basic example here: [komp-todo](https://github.com/rnentjes/komp-todo) See the komp-todo repository for a basic example here: [komp-todo](https://github.com/rnentjes/komp-todo)
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"

View File

@@ -1,2 +1,16 @@
pluginManagement {
repositories {
rootProject.name = "kotlin-komponent" maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") }
mavenCentral()
maven { setUrl("https://plugins.gradle.org/m2/") }
}
}
rootProject.name = "komp"
enableFeaturePreview("GRADLE_METADATA")

View File

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

View File

@@ -0,0 +1,253 @@
package nl.astraeus.komp
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.Node
import org.w3c.dom.events.Event
import org.w3c.dom.get
fun Element.removeKompEventListeners() {
val events = this.asDynamic().kompEvents as? MutableMap<String, (Event) -> Unit>
if (events != null) {
for ((name, event) in events) {
this.removeEventListener(name, event)
}
}
}
fun Element.removeKompEventListener(name: String) {
val events = this.asDynamic().kompEvents as? MutableMap<String, (Event) -> Unit>
if (events != null) {
val event = events[name]
this.removeEventListener(name, event)
events.remove(name)
}
}
fun Element.checkKompEventListenersEmpty() {
val events = this.asDynamic().kompEvents as? MutableMap<String, (Event) -> Unit>
if (events != null && !events.isEmpty()) {
console.log("Expected events to be empty, found:", events)
}
}
fun Element.addKompEventListener(name: String, event: (Event) -> Unit) {
var events = this.asDynamic().kompEvents as? MutableMap<String, (Event) -> Unit>
if (events == null) {
this.asDynamic().kompEvents = mutableMapOf<String, (Event) -> Unit>()
events = this.asDynamic().kompEvents as? MutableMap<String, (Event) -> Unit>
}
events?.get(name)?.let { it ->
removeEventListener(name, it)
console.log("Overwriting event $name on", this)
}
events?.put(name, event)
addEventListener(name, event)
}
object DiffPatch {
private fun updateKomponentOnNode(element: Node, newVdom: VDOMElement) {
val komponent = newVdom.komponent
if (komponent != null) {
if (Komponent.logReplaceEvent) {
console.log("Keeping oldNode, set oldNode element on Komponent", element, komponent)
}
komponent.element = element
}
}
fun updateNode(element: Node, oldVdom: VDOMElement, newVdom: VDOMElement): Node {
if (oldVdom.hash == newVdom.hash) {
if (Komponent.logReplaceEvent) {
console.log("Hashes match", oldVdom, newVdom, oldVdom.hash, newVdom.hash)
}
// no change
return element
}
if (oldVdom.type == newVdom.type && oldVdom.type == VDOMElementType.TEXT) {
if (oldVdom.content != newVdom.content) {
if (Komponent.logReplaceEvent) {
console.log("Updating text content", oldVdom, newVdom)
}
element.textContent = newVdom.content
}
return element
}
if (oldVdom.type == newVdom.type && oldVdom.type == VDOMElementType.TAG) {
if (oldVdom.content == newVdom.content) {
if (Komponent.logReplaceEvent) {
console.log("Update attributes", oldVdom.content, newVdom.content)
}
updateAttributes(element as HTMLElement, oldVdom, newVdom)
if (Komponent.logReplaceEvent) {
console.log("Update events", oldVdom.content, newVdom.content)
}
updateEvents(element as HTMLElement, oldVdom, newVdom)
if (Komponent.logReplaceEvent) {
console.log("Update children", oldVdom.content, newVdom.content)
}
updateKomponentOnNode(element, newVdom)
updateChildren(element, oldVdom, newVdom)
return element
}
}
if (Komponent.logReplaceEvent) {
console.log("Replace node (type)", newVdom.type, oldVdom, newVdom)
}
val newNode = newVdom.createElement()
updateKomponentOnNode(newNode, newVdom)
element.parentNode?.replaceChild(newNode, element)
return newNode
}
private fun updateAttributes(element: HTMLElement, oldVdom: VDOMElement, newVdom: VDOMElement) {
// removed attributes
for ((name, attr) in oldVdom.attributes) {
if (newVdom.attributes[name] == null) {
element.removeAttribute(name)
}
}
for ((name, value) in newVdom.attributes) {
val oldValue = oldVdom.attributes[name]
if (value != oldValue) {
element.setAttribute(name, value)
}
}
if (newVdom.content == "input" && oldVdom.content == "input") {
if (element is HTMLInputElement) {
element.value = newVdom.attributes["value"] ?: ""
element.checked = newVdom.attributes["checked"] == "true"
}
}
}
private fun updateChildren(
element: HTMLElement,
oldVdom: VDOMElement,
newVdom: VDOMElement
) {
var oldIndex = 0
var newIndex = 0
if (Komponent.logReplaceEvent) {
console.log(
"updateChildren HTML old(${oldVdom.childNodes.size})",
oldVdom.toString()
)
console.log(
"updateChildren HTML new(${newVdom.childNodes.size})",
newVdom.toString()
)
}
while (oldIndex < oldVdom.childNodes.size && newIndex < newVdom.childNodes.size) {
if (Komponent.logReplaceEvent) {
console.log("Update Old/new", oldIndex, newIndex)
}
val oldChildNode = oldVdom.childNodes[oldIndex]
val newChildNode = newVdom.childNodes[newIndex]
if (Komponent.logReplaceEvent) {
console.log("Update node Old/new", oldChildNode, newChildNode)
}
// scenarios:
// - hashes match, next
// - hashes don't match:
// -- old hash is down in new list
// --- delta == 1, insert new node
// -- new hash is down in old list
// --- delta == 1, remove current else swap?
// else: replace current node with new
if (oldVdom.hash != newVdom.hash &&
newChildNode.type == VDOMElementType.TAG &&
oldChildNode.type == VDOMElementType.TAG
) {
if (Komponent.logReplaceEvent) {
console.log("Hashes don't match")
}
val oldHash = oldChildNode.hash.hashCode()
val newHash = newChildNode.hash.hashCode()
val newHashIndexInOld = oldVdom.findNodeHashIndex(newHash)
val oldHashIndexInNew = newVdom.findNodeHashIndex(oldHash)
if (newHashIndexInOld == oldIndex + 1 && oldHashIndexInNew == newIndex) {
// remove
element.removeChild(element.childNodes[oldIndex]!!)
oldIndex++
continue
} else if (newHashIndexInOld == oldIndex && oldHashIndexInNew == newIndex + 1) {
// insert
element.insertBefore(newChildNode.createElement(), element.childNodes[oldIndex]!!)
newIndex++
oldIndex++
continue
}
}
// update
updateNode(element.childNodes[oldIndex]!!, oldChildNode, newChildNode)
oldIndex++
newIndex++
}
while (element.childNodes.length > newVdom.childNodes.size) {
element.childNodes[element.childNodes.length - 1]?.also {
if (Komponent.logReplaceEvent) {
console.log("Remove old node", it)
}
element.removeChild(it)
}
}
while (newIndex < newVdom.childNodes.size) {
newVdom.childNodes[newIndex].also {
element.appendChild(it.createElement())
}
newIndex++
}
}
private fun updateEvents(element: HTMLElement, oldVdom: VDOMElement, newVdom: VDOMElement) {
val oldEvents = oldVdom.events
val newEvents = newVdom.events
if (Komponent.logReplaceEvent) {
console.log("Update events", oldEvents, newEvents)
}
for ((name, event) in oldEvents) {
element.removeEventListener(name, event)
}
for ((name, event) in newEvents) {
if (Komponent.logReplaceEvent) {
console.log("Set event $event on", element)
}
element.addEventListener(name, event)
}
}
}

View File

@@ -1,157 +0,0 @@
package nl.astraeus.komp
import org.w3c.dom.Element
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.events.Event
import org.w3c.dom.get
private fun Int.asSpaces(): String {
val result = StringBuilder()
repeat(this) {
result.append(" ")
}
return result.toString()
}
fun Element.printTree(indent: Int = 0): String {
val result = StringBuilder()
result.append(indent.asSpaces())
result.append(tagName)
if (this.namespaceURI != "http://www.w3.org/1999/xhtml") {
result.append(" [")
result.append(namespaceURI)
result.append("]")
}
result.append(" (")
var first = true
if (hasAttributes()) {
for (index in 0 until attributes.length) {
if (!first) {
result.append(", ")
} else {
first = false
}
result.append(attributes[index]?.localName)
result.append("=")
result.append(attributes[index]?.value)
}
}
result.append(") {")
result.append("\n")
for ((name, event) in getKompEvents()) {
result.append(indent.asSpaces())
result.append("on")
result.append(name)
result.append(" -> ")
result.append(event)
result.append("\n")
}
for (index in 0 until childNodes.length) {
childNodes[index]?.let {
if (it is Element) {
result.append(it.printTree(indent + 2))
} else {
result.append((indent + 2).asSpaces())
result.append(it.textContent)
result.append("\n")
}
}
}
result.append(indent.asSpaces())
result.append("}\n")
return result.toString()
}
internal fun Element.setKompAttribute(attributeName: String, value: String?) {
//val attributeName = name.lowercase()
if (value == null || value.isBlank()) {
if (this is HTMLInputElement) {
when (attributeName) {
"checked" -> {
checked = false
}
/*
"class" -> {
className = ""
}
*/
"value" -> {
this.value = ""
}
else -> {
removeAttribute(attributeName)
}
}
} 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)
}
}
}
internal fun Element.clearKompEvents() {
val events = getKompEvents()
for ((name, event) in getKompEvents()) {
removeEventListener(name, event)
}
events.clear()
}
internal fun Element.setKompEvent(name: String, event: (Event) -> Unit) {
val eventName: String = if (name.startsWith("on")) {
name.substring(2)
} else {
name
}
getKompEvents()[eventName] = event
this.addEventListener(eventName, event)
}
internal fun Element.getKompEvents(): MutableMap<String, (Event) -> Unit> {
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 {
val childNodes = parentElement?.children
if (childNodes != null) {
for (index in 0 until childNodes.length) {
if (childNodes[index] == this) {
return index
}
}
}
return 0
}

View File

@@ -1,451 +1,114 @@
package nl.astraeus.komp package nl.astraeus.komp
import kotlinx.browser.document
import kotlinx.html.DefaultUnsafe import kotlinx.html.DefaultUnsafe
import kotlinx.html.Entities import kotlinx.html.Entities
import kotlinx.html.FlowOrMetaDataOrPhrasingContent
import kotlinx.html.Tag import kotlinx.html.Tag
import kotlinx.html.TagConsumer import kotlinx.html.TagConsumer
import kotlinx.html.Unsafe import kotlinx.html.Unsafe
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLSpanElement
import org.w3c.dom.Node
import org.w3c.dom.asList
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import org.w3c.dom.get
private var currentElement: Element? = null interface HtmlConsumer : TagConsumer<VDOMElement> {
fun append(node: VDOMElement)
interface HtmlConsumer : TagConsumer<Element> {
fun append(node: Element)
fun include(komponent: Komponent)
fun debug(block: HtmlConsumer.() -> Unit)
} }
fun FlowOrMetaDataOrPhrasingContent.currentElement(): Element =
currentElement ?: error("No current element defined!")
private data class ElementIndex(
val parent: Node,
var childIndex: Int,
var setAttr: MutableSet<String> = mutableSetOf()
)
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>.currentPosition(): ElementIndex? {
return if (this.size < 2) {
null
} else {
this[this.size - 2]
}
}
private fun ArrayList<ElementIndex>.nextElement() {
this.lastOrNull()?.let {
it.setAttr.clear()
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
class HtmlBuilder( class HtmlBuilder(
private val komponent: Komponent?, val baseHash: Int
parent: Element,
childIndex: Int = 0
) : HtmlConsumer { ) : HtmlConsumer {
private var currentPosition = arrayListOf<ElementIndex>() private val path = arrayListOf<VDOMElement>()
private var inDebug = false private var lastLeaved: VDOMElement? = null
private var exceptionThrown = false
private var currentNode: Node? = null
var root: Element? = null
init {
currentPosition.add(ElementIndex(parent, childIndex))
}
override fun include(komponent: Komponent) {
if (
komponent.element != null &&
!komponent.memoizeChanged()
) {
currentPosition.replace(komponent.element!!)
if (Komponent.logRenderEvent) {
console.log(
"Skipped include $komponent, memoize hasn't changed"
)
}
} else {
komponent.create(
currentPosition.last().parent as Element,
currentPosition.last().childIndex
)
}
currentPosition.nextElement()
}
override fun append(node: Element) {
currentPosition.replace(node)
currentPosition.nextElement()
}
override fun debug(block: HtmlConsumer.() -> Unit) {
val enableAssertions = Komponent.enableAssertions
Komponent.enableAssertions = true
inDebug = true
try {
block.invoke(this)
} finally {
inDebug = false
Komponent.enableAssertions = enableAssertions
}
}
private fun logReplace(msg: () -> String) {
if (Komponent.logReplaceEvent && inDebug) {
console.log(msg.invoke())
}
}
override fun onTagStart(tag: Tag) { override fun onTagStart(tag: Tag) {
logReplace { val element = VDOMElement(baseHash, tag.tagName, tag.namespace)
"onTagStart, [${tag.tagName}, ${tag.namespace}], currentPosition: $currentPosition"
}
currentNode = currentPosition.currentElement()
if (currentNode == null) {
logReplace { "onTagStart, currentNode1: $currentNode" }
currentNode = if (tag.namespace != null) {
document.createElementNS(tag.namespace, tag.tagName)
} else {
document.createElement(tag.tagName)
}
//logReplace"onTagStart, currentElement1.1: $currentNode")
currentPosition.currentParent().appendChild(currentNode!!)
} else if (
!currentNode?.asElement()?.tagName.equals(tag.tagName, true) ||
(
tag.namespace != null &&
!currentNode?.asElement()?.namespaceURI.equals(tag.namespace, true)
)
) {
logReplace {
"onTagStart, currentElement, namespace: ${currentNode?.asElement()?.namespaceURI} -> ${tag.namespace}"
}
logReplace {
"onTagStart, currentElement, replace: ${currentNode?.asElement()?.tagName} -> ${tag.tagName}"
}
currentNode = if (tag.namespace != null) {
document.createElementNS(tag.namespace, tag.tagName)
} else {
document.createElement(tag.tagName)
}
currentPosition.replace(currentNode!!)
}
currentElement = currentNode as? Element ?: currentElement
if (currentNode is Element) {
if (root == null) {
//logReplace"Setting root: $currentNode")
root = currentNode as Element
}
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, entry.value) element.setAttribute(entry.key, entry.value)
currentPosition.lastOrNull()?.setAttr?.add(entry.key)
}
} }
currentPosition.push(currentNode!!) if (path.isNotEmpty()) {
path.last().appendChild(element)
} }
private fun checkTag(tag: Tag) { path.add(element)
check(currentElement != null) {
js("debugger")
"No current tag"
}
check(currentElement?.tagName.equals(tag.tagName, ignoreCase = true)) {
js("debugger")
"Wrong current tag"
}
} }
override fun onTagAttributeChange( override fun onTagAttributeChange(tag: Tag, attribute: String, value: String?) {
tag: Tag, when {
attribute: String, path.isEmpty() -> throw IllegalStateException("No current tag")
value: String? path.last().content.toLowerCase() != tag.tagName.toLowerCase() -> throw IllegalStateException("Wrong current tag")
) { else -> path.last().let { node ->
logReplace { "onTagAttributeChange, ${tag.tagName} [$attribute, $value]" } if (value == null) {
node.removeAttribute(attribute)
if (Komponent.enableAssertions) {
checkTag(tag)
}
currentElement?.setKompAttribute(attribute, value)
if (value == null || value.isEmpty()) {
currentPosition.currentPosition()?.setAttr?.remove(attribute)
} else { } else {
currentPosition.currentPosition()?.setAttr?.add(attribute) node.setAttribute(attribute, value)
}
}
} }
} }
override fun onTagEvent( override fun onTagEvent(tag: Tag, event: String, value: (Event) -> Unit) {
tag: Tag, when {
event: String, path.isEmpty() -> throw IllegalStateException("No current tag")
value: (Event) -> Unit path.last().content.toLowerCase() != tag.tagName.toLowerCase() -> throw IllegalStateException("Wrong current tag")
) { else -> path.last().setKompEvent(event, value)
logReplace { "onTagEvent, ${tag.tagName} [$event, $value]" }
if (Komponent.enableAssertions) {
checkTag(tag)
} }
currentElement?.setKompEvent(event.toLowerCase(), value)
} }
override fun onTagEnd(tag: Tag) { override fun onTagEnd(tag: Tag) {
if (exceptionThrown) { if (path.isEmpty() || path.last().content.toLowerCase() != tag.tagName.toLowerCase()) {
return throw IllegalStateException("We haven't entered tag ${tag.tagName} but trying to leave")
} }
while (currentPosition.currentElement() != null) { lastLeaved = path.removeAt(path.lastIndex)
currentPosition.currentElement()?.let { lastLeaved?.updateChildHash()
it.parentElement?.removeChild(it)
}
}
if (Komponent.enableAssertions) {
checkTag(tag)
}
if (currentElement != null) {
val setAttrs: Set<String> = currentPosition.currentPosition()?.setAttr ?: setOf()
// remove attributes that where not set
val element = currentElement
if (element?.hasAttributes() == true) {
for (index in 0 until element.attributes.length) {
val attribute = element.attributes[index]
if (attribute?.name != null) {
val attr = attribute.name
if (
!setAttrs.contains(attr) &&
attr != "style"
) {
element.setKompAttribute(attr, null)
}
}
}
}
}
currentPosition.pop()
currentNode = currentPosition.currentElement()
currentElement = currentNode as? Element ?: currentElement
currentPosition.nextElement()
currentElement = currentElement?.parentElement as? HTMLElement
//logReplace"onTagEnd, popped: $currentElement")
} }
override fun onTagContent(content: CharSequence) { override fun onTagContent(content: CharSequence) {
//logReplace"onTagContent, [$content]") if (path.isEmpty()) {
throw IllegalStateException("No current DOM node")
check(currentElement != null) {
"No current DOM node"
} }
//logReplace"Tag content: $content") path.last().appendChild(VDOMElement(baseHash, content.toString(), type = VDOMElementType.TEXT))
if (
currentElement?.nodeType != Node.TEXT_NODE ||
currentElement?.textContent != content.toString()
) {
currentElement?.textContent = content.toString()
}
currentPosition.nextElement()
} }
override fun onTagContentEntity(entity: Entities) { override fun onTagContentEntity(entity: Entities) {
//logReplace"onTagContentEntity, [${entity.text}]") if (path.isEmpty()) {
throw IllegalStateException("No current DOM node")
check(currentElement != null) {
"No current DOM node"
} }
val s = document.createElement("span") as HTMLSpanElement // stupid hack as browsers don't support createEntityReference
s.innerHTML = entity.text path.last().appendChild(VDOMElement(baseHash, entity.text, type = VDOMElementType.ENTITY))
currentPosition.replace( }
s.childNodes.asList().firstOrNull() ?: document.createTextNode(entity.text)
) override fun append(node: VDOMElement) {
currentPosition.nextElement() path.last().appendChild(node)
} }
override fun onTagContentUnsafe(block: Unsafe.() -> Unit) { override fun onTagContentUnsafe(block: Unsafe.() -> Unit) {
with(DefaultUnsafe()) { with(DefaultUnsafe()) {
block() block()
val textContent = toString() path.last().appendChild(VDOMElement(baseHash, toString(), type = VDOMElementType.UNSAFE))
//logReplace"onTagContentUnsafe, [$textContent]")
var namespace: String? = null
if (currentPosition.currentParent().nodeType == 1.toShort()) {
val element = currentPosition.currentParent() as Element
namespace = when (Komponent.unsafeMode) {
UnsafeMode.UNSAFE_ALLOWED -> {
element.namespaceURI
}
UnsafeMode.UNSAFE_SVG_ONLY -> {
if (element.namespaceURI == "http://www.w3.org/2000/svg") {
element.namespaceURI
} else {
null
}
}
else -> {
null
}
}
}
//logReplace"onTagContentUnsafe, namespace: [$namespace]")
if (Komponent.unsafeMode == UnsafeMode.UNSAFE_ALLOWED ||
(
Komponent.unsafeMode == UnsafeMode.UNSAFE_SVG_ONLY &&
namespace == "http://www.w3.org/2000/svg"
)
) {
if (currentElement?.innerHTML != textContent) {
currentElement?.innerHTML += textContent
}
} else if (currentElement?.textContent != textContent) {
currentElement?.textContent = textContent
}
currentPosition.nextElement()
} }
} }
override fun onTagComment(content: CharSequence) { override fun onTagComment(content: CharSequence) {
//logReplace"onTagComment, [$content]") if (path.isEmpty()) {
throw IllegalStateException("No current DOM node")
check(currentElement != null) {
"No current DOM node"
}
currentElement?.appendChild(
document.createComment(content.toString())
)
currentPosition.nextElement()
} }
override fun onTagError(tag: Tag, exception: Throwable) { path.last().appendChild(VDOMElement(baseHash, content.toString(), type = VDOMElementType.COMMENT))
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( override fun finalize(): VDOMElement {
komponent, return lastLeaved ?: throw IllegalStateException("We can't finalize as there was no tags")
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"
)
} }
companion object { companion object {
fun create(content: HtmlBuilder.() -> Unit): Element { fun create(content: HtmlBuilder.() -> Unit): VDOMElement {
val container = document.createElement("div") as HTMLElement val komponent = DummyKomponent()
val consumer = HtmlBuilder(null, container) val consumer = HtmlBuilder(komponent.hashCode())
content.invoke(consumer) content.invoke(consumer)
return consumer.root ?: error("No root element found after render!") return consumer.finalize()
} }
} }
} }

View File

@@ -1,194 +1,142 @@
package nl.astraeus.komp package nl.astraeus.komp
import kotlinx.browser.document
import kotlinx.browser.window import kotlinx.browser.window
import kotlinx.html.FlowOrMetaDataOrPhrasingContent import kotlinx.html.div
import org.w3c.dom.Element import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import org.w3c.dom.get import org.w3c.dom.Node
import org.w3c.dom.css.CSSStyleDeclaration
import kotlin.reflect.KProperty
private var currentKomponent: Komponent? = null typealias CssStyle = CSSStyleDeclaration.() -> Unit
fun FlowOrMetaDataOrPhrasingContent.currentKomponent(): Komponent = class StateDelegate<T>(
currentKomponent ?: error("No current komponent defined! Only call from render code!") val komponent: Komponent,
initialValue: T
) {
var value: T = initialValue
enum class UnsafeMode { operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
UNSAFE_ALLOWED, return value
UNSAFE_DISABLED,
UNSAFE_SVG_ONLY
} }
var Element.memoizeHash: String? operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
get() { if (this.value?.equals(value) != true) {
return getAttribute("memoize-hash") this.value = value
komponent.requestUpdate()
}
}
}
inline fun <reified T> Komponent.state(initialValue: T): StateDelegate<T> = StateDelegate(this, initialValue)
fun HtmlConsumer.include(component: Komponent) {
append(component.create())
}
class DummyKomponent: Komponent() {
override fun HtmlBuilder.render() {
div {
+ "dummy"
} }
set(value) {
if (value != null) {
setAttribute("memoize-hash", value.toString())
} else {
removeAttribute("memoize-hash")
} }
} }
abstract class Komponent { abstract class Komponent {
val createIndex = getNextCreateIndex() private var createIndex = getNextCreateIndex()
private var dirty: Boolean = true private var dirty: Boolean = true
var element: Element? = null var vdom: VDOMElement? = null
var element: Node? = null
open fun create(parent: Element, childIndex: Int? = null) { val declaredStyles: MutableMap<String, CSSStyleDeclaration> = HashMap()
onBeforeUpdate()
val builder = HtmlBuilder(
this,
parent,
childIndex ?: parent.childNodes.length
)
open fun create(): VDOMElement {
val consumer = HtmlBuilder(this.createIndex)
try { try {
currentKomponent = this consumer.render()
builder.render() } catch (e: Throwable) {
} catch(e: KomponentException) { println("Exception occurred in ${this::class.simpleName}.render() call!")
errorHandler(e)
} finally { throw e
currentKomponent = null }
val result = consumer.finalize()
if (result.id.isBlank()) {
result.id = "komp_${createIndex}"
} }
element = builder.root result.komponent = this
updateMemoizeHash()
onAfterUpdate()
}
fun memoizeChanged() = element?.memoizeHash == null || element?.memoizeHash != fullMemoizeHash() vdom = result
fun updateMemoizeHash() { dirty = false
element?.memoizeHash = fullMemoizeHash()
}
private fun fullMemoizeHash(): String? { return result
val generated = generateMemoizeHash()
return if (generated != null) {
"${this::class.simpleName}:${generateMemoizeHash()}"
} else {
null
}
} }
abstract fun HtmlBuilder.render() abstract fun HtmlBuilder.render()
/**
* This method is called after the Komponent is updated
*
* note: it's also called at first render
*/
open fun onAfterUpdate() {}
/**
* This method is called before the Komponent is updated
* and before memoizeHash is checked
*
* note: it's also called at first render
*/
open fun onBeforeUpdate() {}
fun requestUpdate() { fun requestUpdate() {
dirty = true dirty = true
scheduleForUpdate(this) scheduleForUpdate(this)
} }
/** open fun style(className: String, vararg imports: CssStyle, block: CssStyle = {}) {
* Request an immediate update of this Komponent val style = (document.createElement("div") as HTMLDivElement).style
* for (imp in imports) {
* This will run immediately, make sure Komponents are not rendered multiple times imp(style)
* Any scheduled updates will be run as well }
*/ block(style)
fun requestImmediateUpdate() { declaredStyles[className] = style
dirty = true
runUpdateImmediately(this)
} }
/**
* 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 update() { open fun update() {
refresh() refresh()
} }
/** internal fun refresh() {
* If this function returns a value it will be stored and on the next render it will be compared. val oldElement = vdom
*
* The render will only happen if the hash is not null and has changed
*/
open fun generateMemoizeHash(): Int? = null
private fun refresh() { if (logRenderEvent) {
val currentElement = element console.log("Rendering", this)
}
val newElement = create()
check(currentElement != null) { element = if (oldElement != null && element != null) {
error("element is null") if (logReplaceEvent) {
console.log("DomDiffing", oldElement, newElement)
}
DiffPatch.updateNode(element!!, oldElement, newElement)
} else {
if (logReplaceEvent) {
console.log("Create", newElement)
}
newElement.createElement()
} }
val parent = currentElement.parentElement as? HTMLElement ?: error("parent is null!?") vdom = newElement
var childIndex = 0
for (index in 0 until parent.childNodes.length) {
if (parent.childNodes[index] == currentElement) {
childIndex = index
}
}
val builder = HtmlBuilder(this, parent, childIndex)
try {
currentKomponent = this
builder.render()
} catch(e: KomponentException) {
errorHandler(e)
} finally {
currentKomponent = null
}
element = builder.root
dirty = false dirty = false
} }
override fun toString(): String {
return "${this::class.simpleName}"
}
companion object { companion object {
private var nextCreateIndex: Int = 1 private var nextCreateIndex: Int = 1
private var updateCallback: Int? = null 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 scheduledForUpdate = mutableSetOf<Komponent>()
private var interceptor: (Komponent, () -> Unit) -> Unit = { _, block -> block() }
var logRenderEvent = false var logRenderEvent = false
var logReplaceEvent = false var logReplaceEvent = false
var enableAssertions = false
var unsafeMode = UnsafeMode.UNSAFE_DISABLED
fun create(parent: HTMLElement, component: Komponent) { fun create(parent: HTMLElement, component: Komponent, insertAsFirst: Boolean = false) {
component.create(parent) val vdomElement = component.create()
val element = vdomElement.createElement()
component.element = element
if (insertAsFirst && parent.childElementCount > 0) {
parent.insertBefore(element, parent.firstChild)
} else {
parent.appendChild(element)
} }
fun setErrorHandler(handler: (KomponentException) -> Unit) {
errorHandler = handler
}
fun setUpdateInterceptor(block: (Komponent, () -> Unit) -> Unit) {
interceptor = block
} }
private fun getNextCreateIndex() = nextCreateIndex++ private fun getNextCreateIndex() = nextCreateIndex++
@@ -203,11 +151,6 @@ abstract class Komponent {
} }
} }
private fun runUpdateImmediately(komponent: Komponent) {
scheduledForUpdate.add(komponent)
runUpdate()
}
private fun runUpdate() { private fun runUpdate() {
val todo = scheduledForUpdate.sortedBy { komponent -> komponent.createIndex } val todo = scheduledForUpdate.sortedBy { komponent -> komponent.createIndex }
@@ -216,32 +159,25 @@ abstract class Komponent {
} }
todo.forEach { next -> todo.forEach { next ->
interceptor(next) {
val element = next.element val element = next.element
if (element is HTMLElement) { if (element is HTMLElement) {
if (document.getElementById(element.id) != null) {
if (next.dirty) { if (next.dirty) {
if (logRenderEvent) { if (logRenderEvent) {
console.log("Update dirty ${next.createIndex}") console.log("Update dirty ${next.createIndex}")
} }
val memoizeHash = next.generateMemoizeHash()
if (next.memoizeChanged()) {
next.onBeforeUpdate()
next.update() next.update()
next.updateMemoizeHash()
next.onAfterUpdate()
} else if (logRenderEvent) {
console.log("Skipped render, memoizeHash is equal $next-[$memoizeHash]")
}
} else { } else {
if (logRenderEvent) { if (logRenderEvent) {
console.log("Skip ${next.createIndex}") console.log("Skip ${next.createIndex}")
} }
} }
} else { } else {
console.log("Komponent element is null", next, element) console.log("Komponent element has no id, ", next, element)
} }
} else {
console.log("Komponent element is null", next)
} }
} }

View File

@@ -1,18 +0,0 @@
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

@@ -1,54 +0,0 @@
package nl.astraeus.komp
inline fun <reified T> Komponent.mutableCollectionState(
initialValue: MutableCollection<T>
): MutableCollection<T> = MutableCollectionStateDelegate(
this,
initialValue
)
class MutableCollectionStateDelegate<T>(
val komponent: Komponent,
val collection: MutableCollection<T>
): MutableCollection<T> by collection {
override fun add(element: T): Boolean {
komponent.requestUpdate()
return collection.add(element)
}
override fun addAll(elements: Collection<T>): Boolean {
komponent.requestUpdate()
return collection.addAll(elements)
}
override fun clear() {
komponent.requestUpdate()
collection.clear()
}
// todo: return iterator wrapper to update at changes?
// override fun iterator(): MutableIterator<T> = collection.iterator()
override fun remove(element: T): Boolean {
komponent.requestUpdate()
return collection.remove(element)
}
override fun removeAll(elements: Collection<T>): Boolean {
komponent.requestUpdate()
return collection.removeAll(elements)
}
override fun retainAll(elements: Collection<T>): Boolean {
komponent.requestUpdate()
return collection.retainAll(elements)
}
}

View File

@@ -1,56 +0,0 @@
package nl.astraeus.komp
import kotlin.reflect.KProperty
interface Delegate<T> {
operator fun getValue(
thisRef: Any?,
property: KProperty<*>
): T
operator fun setValue(
thisRef: Any?,
property: KProperty<*>,
value: T
)
}
open class StateDelegate<T>(
val komponent: Komponent,
initialValue: T
) : Delegate<T> {
private var value: T = initialValue
init {
if (value is MutableCollection<*>) {
error("Use mutableList to create a collection!")
}
}
override operator fun getValue(
thisRef: Any?,
property: KProperty<*>
): T {
return value
}
override operator fun setValue(
thisRef: Any?,
property: KProperty<*>,
value: T
) {
if (this.value?.equals(value) != true) {
this.value = value
komponent.requestUpdate()
}
}
}
inline fun <reified T> Komponent.state(
initialValue: T
): Delegate<T> = StateDelegate(
this,
initialValue
)

View File

@@ -0,0 +1,214 @@
package nl.astraeus.komp
import kotlinx.browser.document
import org.w3c.dom.Element
import org.w3c.dom.Node
import org.w3c.dom.asList
import org.w3c.dom.events.Event
private fun attributeHash(key: String, value: String): Int =
3 * key.hashCode() +
5 * value.hashCode()
private fun MutableMap<String, String>.kompHash(): Int {
var result = 0
for ((name, value) in this) {
result += attributeHash(name, value)
}
return result
}
private fun MutableMap<String, (Event) -> Unit>.kompHash(): Int {
var result = 0
for ((name, event) in this) {
result += attributeHash(name, event.toString())
}
return result
}
private fun MutableList<VDOMElement>.kompHash(): Int {
var result = 0
for (vdom in this) {
result += 3 * vdom.hash.hashCode()
}
return result
}
enum class VDOMElementType {
TAG,
TEXT,
ENTITY,
UNSAFE,
COMMENT
}
class VDOMElementHash(
var baseHash: Int,
var contentHash: Int,
var typeHash: Int,
var namespaceHash: Int = 0,
var attributesHash: Int = 0,
var eventsHash: Int = 0,
var childIndexHash: Int = 0,
var childNodesHash: Int = 0
) {
override fun hashCode(): Int {
var result = baseHash
result = result * 7 + contentHash
result = result * 7 + typeHash
result = result * 7 + namespaceHash
result = result * 7 + attributesHash
result = result * 7 + eventsHash
result = result * 7 + childIndexHash
result = result * 7 + childNodesHash
return result
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is VDOMElementHash) return false
return other.hashCode() == this.hashCode()
}
}
class VDOMElement(
val baseHash: Int,
var content: String,
var namespace: String? = null,
var type: VDOMElementType = VDOMElementType.TAG,
) {
val attributes: MutableMap<String, String> = mutableMapOf()
val events: MutableMap<String, (Event) -> Unit> = mutableMapOf()
val childNodes: MutableList<VDOMElement> = mutableListOf()
val hash = VDOMElementHash(
baseHash,
content.hashCode(),
type.hashCode()
)
var id: String = ""
set(value) {
field = value
attributes["id"] = value
}
var komponent: Komponent? = null
fun setKompEvent(event: String, value: (Event) -> Unit) {
val eventName = if (event.startsWith("on")) {
event.substring(2)
} else {
event
}
val recalculate = events.containsKey(eventName)
events[eventName] = value
if (recalculate) {
hash.eventsHash = events.kompHash()
} else {
hash.eventsHash += attributeHash(eventName, value.toString())
}
}
fun appendChild(element: VDOMElement) {
childNodes.add(element)
//hash.childNodesHash += element.hash.hashCode()
}
fun updateChildHash() {
hash.childNodesHash = childNodes.kompHash()
}
fun removeAttribute(attr: String) {
if (attributes.containsKey(attr)) {
hash.attributesHash -= attributeHash(attr, attributes[attr] ?: "")
}
attributes.remove(attr)
}
fun setAttribute(attr: String, value: String) {
if (attributes.containsKey(attr)) {
hash.attributesHash -= attributeHash(attr, attributes[attr] ?: "")
}
if (attr.toLowerCase() == "id") {
id = value
}
attributes[attr] = value
hash.attributesHash += attributeHash(attr, value)
}
fun findNodeHashIndex(hash: Int): Int {
for ((index, node) in this.childNodes.withIndex()) {
if (node.type == VDOMElementType.TAG && node.hash.hashCode() == hash) {
return index
}
}
return -2
}
fun createElement(): Node = createActualElement()
private fun createActualElement(currentNamespace: String? = null): Node {
val result = when (type) {
VDOMElementType.TAG -> {
var tagNamespace: String? = null
val result: Element = if (namespace != null) {
tagNamespace = namespace
document.createElementNS(namespace, content)
} else {
document.createElement(content)
}
for ((name, value) in attributes) {
result.setAttribute(name, value)
}
for ((name, value) in events) {
result.addEventListener(name, value)
}
for (child in childNodes) {
result.appendChild(child.createActualElement(tagNamespace))
}
result
}
VDOMElementType.ENTITY -> {
println("Creating an entity is not supported!")
document.createTextNode(content)
}
VDOMElementType.UNSAFE,
VDOMElementType.TEXT -> {
val span = if (currentNamespace != null) {
document.createElementNS(currentNamespace, "span")
} else {
document.createElement("span")
}
span.innerHTML = content
span.childNodes.asList().firstOrNull() ?: document.createTextNode(content)
}
VDOMElementType.COMMENT -> {
document.createComment(content)
}
}
komponent?.also {
it.element = result
}
return result
}
}

View File

@@ -1,247 +1,144 @@
package nl.astraeus.komp package nl.astraeus.komp
import kotlinx.browser.document import kotlinx.html.*
import kotlinx.html.InputType
import kotlinx.html.classes
import kotlinx.html.div
import kotlinx.html.i
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.span
import kotlinx.html.svg
import kotlinx.html.table
import kotlinx.html.td
import kotlinx.html.tr
import kotlinx.html.unsafe
import org.w3c.dom.Element import org.w3c.dom.Element
import org.w3c.dom.HTMLDivElement import org.w3c.dom.HTMLElement
import org.w3c.dom.get
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertTrue
class TestKomponent : Komponent() { fun nodesEqual(element: Element, vdom: VDOMElement): Boolean {
override fun HtmlBuilder.render() { if (element.childNodes.length != vdom.childNodes.size) {
div { return false
+"Test" }
} if (element.attributes.length != vdom.attributes.size) {
return false
}
for ((name, value) in vdom.attributes) {
if (value != element.getAttribute(name)) {
return false
} }
} }
for ((index, child) in vdom.childNodes.withIndex()) {
if (index < element.childNodes.length) {
val elementChild = element.childNodes[index]
class Child1 : Komponent() { if (child.type == VDOMElementType.TAG) {
override fun HtmlBuilder.render() { if (!nodesEqual(elementChild as HTMLElement, child)) {
div { return false
+"Child 1"
} }
} else if (child.type == VDOMElementType.TEXT) {
if (child.content != element.textContent) {
return false
} }
} }
class Child2 : Komponent() {
override fun HtmlBuilder.render() {
div {
id ="1234"
+"Child 2"
}
}
}
class SimpleKomponent : Komponent() {
var hello = true
var append = HtmlBuilder.create {
p {
+"Appended"
}
}
override fun HtmlBuilder.render() {
div("div_class") {
input(InputType.checkBox) {
name = "helloInput"
checked = hello
}
span {
svg {
unsafe {
+"""
<p bla>
""".trimIndent()
}
}
if (hello) {
div {
+"Hello"
}
} else { } else {
span { return false
+"Good bye"
}
}
}
div {
if (hello) {
id = "123"
+"div text"
} else {
+"div text goodbye"
}
onClickFunction = if (hello) {
{
println("onClick")
}
} else {
{
println("onClick 2")
}
}
}
if (hello) {
span {
+"2nd span"
}
}
//append(append)
if (hello) {
include(Child1())
} else {
include(Child2())
}
//append(append)
} }
} }
} return true
class IncludeKomponent(
var text: String = "My Text"
) : Komponent() {
override fun generateMemoizeHash(): Int = text.hashCode()
override fun HtmlBuilder.render() {
span {
+text
}
}
}
class ReplaceKomponent : Komponent() {
val includeKomponent = IncludeKomponent()
var includeSpan = true
override fun generateMemoizeHash(): Int = includeSpan.hashCode() * 7 + includeKomponent.generateMemoizeHash()
override fun HtmlBuilder.render() {
div {
+"Child 2"
div {
if (includeSpan) {
span {
i("fas fa-eye") {
+"span1"
}
}
span {
i("fas fa-eye") {
+"span2"
}
}
span {
i("fas fa-eye") {
+"span3"
}
}
}
include(includeKomponent)
}
}
}
} }
class TestUpdate { class TestUpdate {
@Test @Test
fun testUpdateWithEmpty() { fun testCompare() {
val div = document.createElement("div") as HTMLDivElement val dom1 = HtmlBuilder.create {
val rk = ReplaceKomponent() div {
div(classes = "bla") {
Komponent.logRenderEvent = true span {
+" Some Text "
Komponent.create(div, rk)
println("ReplaceKomponent: ${div.printTree()}")
rk.requestImmediateUpdate()
println("ReplaceKomponent: ${div.printTree()}")
rk.requestImmediateUpdate()
println("ReplaceKomponent: ${div.printTree()}")
rk.includeSpan = false
rk.requestImmediateUpdate()
println("ReplaceKomponent: ${div.printTree()}")
rk.includeSpan = true
rk.includeKomponent.text = "New Text"
rk.requestImmediateUpdate()
println("ReplaceKomponent: ${div.printTree()}")
} }
@Test
fun testSimpleKomponent() {
val sk = SimpleKomponent()
val div = document.createElement("div") as HTMLDivElement
Komponent.create(div, sk)
println("SimpleKomponent: ${div.printTree()}")
sk.hello = false
sk.requestImmediateUpdate()
println("SimpleKomponent updated: ${div.printTree()}")
}
@Test
fun testCreate() {
var elemTest: Element? = null
val element = HtmlBuilder.create {
div(classes = "div_class") {
classes = classes + "bla'"
id = "123"
+"Test"
span("span_class") {
+"Span"
elemTest = currentElement()
}
table { table {
tr { tr {
td { td {
+"column 1" +"Table column"
} }
}
}
}
}
}
val dom2 = HtmlBuilder.create {
div {
span {
id = "123"
+"New dom!"
}
input {
value = "bla"
}
}
}
var element = dom1.createElement()
assertTrue(nodesEqual(element as Element, dom1), "Created dom not equal to original")
element = DiffPatch.updateNode(element, dom1, dom2) as HTMLElement
assertTrue(nodesEqual(element, dom2), "Updated dom not equal to original")
}
@Test
fun testCompare2() {
val dom1 = HtmlBuilder.create {
div {
div(classes = "bla") {
span {
+" Some Text "
}
table {
tr {
th {
+"Header"
}
}
tr {
td { td {
+"column 2" +"Table column"
}
} }
} }
} }
} }
} }
println("Element: ${element.printTree()}") val dom2 = HtmlBuilder.create {
println("divTst: ${elemTest?.printTree()}") div {
println("span class: ${ div {
elemTest?.getAttributeNames()?.joinToString span {
{ "," } +"Other text"
}" }
) }
span {
id = "123"
+"New dom!"
}
input {
value = "bla"
onClickFunction = {
println("Clickerdyclick!")
}
}
}
}
Komponent.logRenderEvent = true
var element = dom1.createElement()
assertTrue(nodesEqual(element as Element, dom1), "Created dom not equal to original")
element = DiffPatch.updateNode(element, dom1, dom2) as HTMLElement
assertTrue(nodesEqual(element, dom2), "Updated dom not equal to original")
} }
} }