Skip to content

Commit

Permalink
New ONReceiver MVP
Browse files Browse the repository at this point in the history
  • Loading branch information
marad committed May 10, 2020
1 parent 149e29d commit c50efd3
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 4 deletions.
12 changes: 10 additions & 2 deletions src/main/kotlin/kweb/Element.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import kweb.dom.element.events.ONImmediateReceiver
import kweb.dom.element.events.ONReceiver
import kweb.dom.element.read.ElementReader
import kweb.dom.style.StyleReceiver
import kweb.html.events.EventGenerator
import kweb.html.events.KeyboardEvents
import kweb.html.events.MouseEvents
import kweb.html.events.NewOnReceiver
import kweb.plugins.KwebPlugin
import kweb.state.KVal
import kweb.state.KVar
Expand All @@ -15,7 +19,8 @@ import java.util.concurrent.ConcurrentSkipListSet
import kotlin.reflect.KClass

@KWebDSL
open class Element(open val browser: WebBrowser, val creator: ElementCreator<*>?, open var jsExpression: String, val tag: String? = null, val id: String?) {
open class Element(open val browser: WebBrowser, val creator: ElementCreator<*>?, open var jsExpression: String, val tag: String? = null, val id: String?) :
EventGenerator<Element>, KeyboardEvents, MouseEvents {
constructor(element: Element) : this(element.browser, element.creator, jsExpression = element.jsExpression, tag = element.tag, id = element.id)
/*********
********* Low level methods
Expand Down Expand Up @@ -313,7 +318,7 @@ open class Element(open val browser: WebBrowser, val creator: ElementCreator<*>?
browser.evaluate(wrappedJS)
}

fun addEventListener(eventName: String, returnEventFields: Set<String> = Collections.emptySet(), retrieveJs: String?, callback: (Any) -> Unit): Element {
override fun addEventListener(eventName: String, returnEventFields: Set<String>, retrieveJs: String?, callback: (Any) -> Unit): Element {
val callbackId = Math.abs(random.nextInt())
val retrieveJs = if (retrieveJs != null) ", \"retrieved\" : ($retrieveJs)" else ""
val eventObject = "{" + returnEventFields.map { "\"$it\" : event.$it" }.joinToString(separator = ", ") + retrieveJs + "}"
Expand All @@ -331,6 +336,7 @@ open class Element(open val browser: WebBrowser, val creator: ElementCreator<*>?
return this
}


fun delete() {
execute("$jsExpression.parentNode.removeChild($jsExpression);")
}
Expand All @@ -352,6 +358,8 @@ open class Element(open val browser: WebBrowser, val creator: ElementCreator<*>?
*/
val on: ONReceiver get() = ONReceiver(this)

val newOn: NewOnReceiver<Element> get() = NewOnReceiver(this)

/**
* You can supply a javascript expression `retrieveJs` which will
* be available via [Event.retrieveJs]
Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/kweb/demos/todo/TodoApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import kweb.*
import kweb.html.events.click
import kweb.html.events.keypress
import kweb.plugins.fomanticUI.fomantic
import kweb.plugins.fomanticUI.fomanticUIPlugin
import kweb.state.*
Expand Down Expand Up @@ -138,7 +140,7 @@ class TodoApp {
}
div(fomantic.ui.action.input).new {
val input = input(InputType.text, placeholder = "Add Item")
input.on.keypress { ke ->
input.newOn.keypress { ke ->
if (ke.code == "Enter") {
handleAddItem(input, list)
}
Expand All @@ -165,7 +167,7 @@ class TodoApp {
button.new {
i(fomantic.trash.icon)
}
button.on.click {
button.newOn.click {
state.items.remove(item.value.uid)
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/main/kotlin/kweb/html/events/EventGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kweb.html.events

import java.util.*

interface EventGenerator<T> {
fun addEventListener(eventName: String, returnEventFields: Set<String> = Collections.emptySet(), retrieveJs: String?, callback: (Any) -> Unit): T
}
8 changes: 8 additions & 0 deletions src/main/kotlin/kweb/html/events/KeyboardEvents.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package kweb.html.events

import kweb.dom.element.events.ONReceiver

interface KeyboardEvents

fun <ON, T> ON.keypress(callback: (event: ONReceiver.KeyboardEvent) -> Unit) where ON: NewOnReceiver<T>, T: KeyboardEvents =
event("keypress", eventType = ONReceiver.KeyboardEvent::class, callback = callback)
8 changes: 8 additions & 0 deletions src/main/kotlin/kweb/html/events/MouseEvents.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package kweb.html.events

import kweb.dom.element.events.ONReceiver

interface MouseEvents

fun <ON, T> ON.click(callback: (event: ONReceiver.MouseEvent) -> Unit) where ON: NewOnReceiver<T>, T: KeyboardEvents =

This comment has been minimized.

Copy link
@sanity

sanity May 10, 2020

Is T: KeyboardEvents correct here, or should it be MouseEvents?

This comment has been minimized.

Copy link
@marad

marad May 11, 2020

Author Owner

Yes it should! I just copy and pasted it. I did not notice that because the Element class has both KeyboardEvents and MouseEvents so everything checks out :)

I'll have to be extra cautious about that! Goot catch!

event("click", eventType = ONReceiver.MouseEvent::class, callback = callback)
39 changes: 39 additions & 0 deletions src/main/kotlin/kweb/html/events/NewOnReceiver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package kweb.html.events

import com.github.salomonbrys.kotson.fromJson
import kweb.dom.element.events.ONReceiver
import kweb.gson
import mu.KotlinLogging
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties

class NewOnReceiver<T : EventGenerator<T>>(private val source: T, private val retrieveJs: String? = null) {

val logger = KotlinLogging.logger {}

fun event(eventName: String, returnEventFields: Set<String> = emptySet(), callback: (event: String) -> Unit): T {
source.addEventListener(eventName, returnEventFields = returnEventFields, callback = { callback(it.toString()) }, retrieveJs = retrieveJs)
return source
}

inline fun <reified U : Any> event(eventName: String, eventType: KClass<U>, crossinline callback: (event: U) -> Unit): T {
// TODO: Should probably cache this rather than do the reflection every time
val eventPropertyNames = memberProperties(eventType)
return event(eventName, eventPropertyNames) { propertiesAsString ->
val props: U = gson.fromJson(propertiesAsString)
try {
callback(props)
} catch (e: Exception) {
logger.error(e) { "Exception thrown by callback in response to $eventName event" }
}
}
}

companion object {
val memberPropertiesCache: ConcurrentHashMap<KClass<*>, Set<String>> = ConcurrentHashMap()
inline fun <reified T : Any> memberProperties(clazz: KClass<T>) =
memberPropertiesCache.get(clazz)
?: T::class.memberProperties.map { it.name }.toSet().also { memberPropertiesCache.put(clazz, it) }
}
}

0 comments on commit c50efd3

Please sign in to comment.