forked from kscience/visionforge
Implement and test input elements
This commit is contained in:
parent
05b87857f4
commit
f40bed7bb9
59
demo/playground/src/jvmMain/kotlin/controlVision.kt
Normal file
59
demo/playground/src/jvmMain/kotlin/controlVision.kt
Normal file
@ -0,0 +1,59 @@
|
||||
package space.kscience.visionforge.examples
|
||||
|
||||
import kotlinx.html.p
|
||||
import space.kscience.visionforge.VisionControlEvent
|
||||
import space.kscience.visionforge.html.*
|
||||
import space.kscience.visionforge.onClick
|
||||
|
||||
|
||||
fun main() = serve {
|
||||
|
||||
val events = ArrayDeque<VisionControlEvent>(10)
|
||||
|
||||
val html = VisionOfPlainHtml()
|
||||
|
||||
fun pushEvent(event: VisionControlEvent) {
|
||||
events.addFirst(event)
|
||||
if (events.size >= 10) {
|
||||
events.removeLast()
|
||||
}
|
||||
html.content {
|
||||
events.forEach { event ->
|
||||
p {
|
||||
text(event.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vision {
|
||||
htmlCheckBox {
|
||||
checked = true
|
||||
onValueChange(context) {
|
||||
pushEvent(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vision {
|
||||
htmlRangeField(1, 10) {
|
||||
numberValue = 4
|
||||
onValueChange(context) {
|
||||
pushEvent(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vision {
|
||||
button("Click me"){
|
||||
onClick(context){
|
||||
pushEvent(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
vision(html)
|
||||
}
|
@ -8,14 +8,13 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaRepr
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.Value
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
|
||||
@Serializable
|
||||
@SerialName("control")
|
||||
public abstract class VisionControlEvent : VisionEvent, MetaRepr {
|
||||
public sealed class VisionControlEvent : VisionEvent, MetaRepr {
|
||||
public abstract val meta: Meta
|
||||
|
||||
override fun toMeta(): Meta = meta
|
||||
@ -24,30 +23,41 @@ public abstract class VisionControlEvent : VisionEvent, MetaRepr {
|
||||
public interface ControlVision : Vision {
|
||||
public val controlEventFlow: SharedFlow<VisionControlEvent>
|
||||
|
||||
public fun dispatchControlEvent(event: VisionControlEvent)
|
||||
public suspend fun dispatchControlEvent(event: VisionControlEvent)
|
||||
|
||||
override fun receiveEvent(event: VisionEvent) {
|
||||
override suspend fun receiveEvent(event: VisionEvent) {
|
||||
if (event is VisionControlEvent) {
|
||||
dispatchControlEvent(event)
|
||||
} else super.receiveEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param payload The optional payload associated with the click event.
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("control.click")
|
||||
public class VisionClickEvent(public val payload: Meta = Meta.EMPTY) : VisionControlEvent() {
|
||||
override val meta: Meta get() = Meta { ::payload.name put payload }
|
||||
public class VisionClickEvent(override val meta: Meta) : VisionControlEvent() {
|
||||
public val payload: Meta? by meta.node()
|
||||
public val name: Name? get() = meta["name"].string?.parseAsName()
|
||||
|
||||
override fun toString(): String = meta.toString()
|
||||
}
|
||||
|
||||
public fun VisionClickEvent(payload: Meta = Meta.EMPTY, name: Name? = null): VisionClickEvent = VisionClickEvent(
|
||||
Meta {
|
||||
VisionClickEvent::payload.name put payload
|
||||
VisionClickEvent::name.name put name.toString()
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
public interface ClickControl : ControlVision {
|
||||
/**
|
||||
* Create and dispatch a click event
|
||||
*/
|
||||
public fun click(builder: MutableMeta.() -> Unit = {}) {
|
||||
public suspend fun click(builder: MutableMeta.() -> Unit = {}) {
|
||||
dispatchControlEvent(VisionClickEvent(Meta(builder)))
|
||||
}
|
||||
}
|
||||
@ -64,7 +74,18 @@ public fun ClickControl.onClick(scope: CoroutineScope, block: suspend VisionClic
|
||||
public class VisionValueChangeEvent(override val meta: Meta) : VisionControlEvent() {
|
||||
|
||||
public val value: Value? get() = meta.value
|
||||
|
||||
/**
|
||||
* The name of a control that fired the event
|
||||
*/
|
||||
public val name: Name? get() = meta["name"]?.string?.parseAsName()
|
||||
|
||||
override fun toString(): String = meta.toString()
|
||||
}
|
||||
|
||||
public fun VisionValueChangeEvent(value: Value?): VisionValueChangeEvent =
|
||||
VisionValueChangeEvent(Meta { this.value = value })
|
||||
public fun VisionValueChangeEvent(value: Value?, name: Name? = null): VisionValueChangeEvent = VisionValueChangeEvent(
|
||||
Meta {
|
||||
this.value = value
|
||||
name?.let { set("name", it.toString()) }
|
||||
}
|
||||
)
|
||||
|
@ -50,7 +50,7 @@ public interface Vision : Described {
|
||||
/**
|
||||
* Receive and process a generic [VisionEvent].
|
||||
*/
|
||||
public fun receiveEvent(event: VisionEvent) {
|
||||
public suspend fun receiveEvent(event: VisionEvent) {
|
||||
if(event is VisionChange) update(event)
|
||||
else manager?.logger?.warn { "Undispatched event: $event" }
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.dataforge.context.Plugin
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaRepr
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
|
||||
@ -32,10 +30,4 @@ public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: St
|
||||
|
||||
public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: Boolean) {
|
||||
notifyPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
|
||||
}
|
||||
|
||||
public fun VisionClient.sendMetaEvent(targetName: Name, payload: MetaRepr): Unit {
|
||||
context.launch {
|
||||
sendEvent(targetName, VisionMetaEvent(payload.toMeta()))
|
||||
}
|
||||
}
|
@ -69,12 +69,14 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionCont
|
||||
defaultDeserializer { SimpleVisionGroup.serializer() }
|
||||
subclass(NullVision.serializer())
|
||||
subclass(SimpleVisionGroup.serializer())
|
||||
subclass(VisionOfPlainHtml.serializer())
|
||||
subclass(VisionOfHtmlInput.serializer())
|
||||
subclass(VisionOfNumberField.serializer())
|
||||
subclass(VisionOfTextField.serializer())
|
||||
subclass(VisionOfCheckbox.serializer())
|
||||
subclass(VisionOfRangeField.serializer())
|
||||
subclass(VisionOfHtmlForm.serializer())
|
||||
subclass(VisionOfHtmlButton.serializer())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.html.DIV
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.stream.createHTML
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -27,8 +29,10 @@ public class VisionOfPlainHtml : VisionOfHtml() {
|
||||
public var content: String? by properties.string()
|
||||
}
|
||||
|
||||
public inline fun VisionOfPlainHtml.content(block: TagConsumer<*>.() -> Unit) {
|
||||
content = createHTML().apply(block).finalize()
|
||||
public fun VisionOfPlainHtml.content(block: DIV.() -> Unit) {
|
||||
content = createHTML().apply {
|
||||
div(block = block)
|
||||
}.finalize()
|
||||
}
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
@ -54,15 +58,7 @@ public enum class InputFeedbackMode {
|
||||
NONE
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.input")
|
||||
public open class VisionOfHtmlInput(
|
||||
public val inputType: String,
|
||||
public val feedbackMode: InputFeedbackMode = InputFeedbackMode.ONCHANGE,
|
||||
) : VisionOfHtml(), ControlVision {
|
||||
public var value: Value? by properties.value()
|
||||
public var disabled: Boolean by properties.boolean { false }
|
||||
public var fieldName: String? by properties.string()
|
||||
public abstract class VisionOfHtmlControl: VisionOfHtml(), ControlVision{
|
||||
|
||||
@Transient
|
||||
private val mutableControlEventFlow = MutableSharedFlow<VisionControlEvent>()
|
||||
@ -70,14 +66,32 @@ public open class VisionOfHtmlInput(
|
||||
override val controlEventFlow: SharedFlow<VisionControlEvent>
|
||||
get() = mutableControlEventFlow
|
||||
|
||||
override fun dispatchControlEvent(event: VisionControlEvent) {
|
||||
if(event is VisionValueChangeEvent){
|
||||
this.value = event.value
|
||||
}
|
||||
mutableControlEventFlow.tryEmit(event)
|
||||
override suspend fun dispatchControlEvent(event: VisionControlEvent) {
|
||||
mutableControlEventFlow.emit(event)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.input")
|
||||
public open class VisionOfHtmlInput(
|
||||
public val inputType: String,
|
||||
public val feedbackMode: InputFeedbackMode = InputFeedbackMode.ONCHANGE,
|
||||
) : VisionOfHtmlControl() {
|
||||
public var value: Value? by properties.value()
|
||||
public var disabled: Boolean by properties.boolean { false }
|
||||
public var fieldName: String? by properties.string()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger [callback] on each value change
|
||||
*/
|
||||
public fun VisionOfHtmlInput.onValueChange(
|
||||
scope: CoroutineScope = manager?.context ?: error("Coroutine context is not resolved for $this"),
|
||||
callback: suspend VisionValueChangeEvent.() -> Unit,
|
||||
): Job = controlEventFlow.filterIsInstance<VisionValueChangeEvent>().onEach(callback).launchIn(scope)
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
public inline fun VisionOutput.htmlInput(
|
||||
inputType: String,
|
||||
@ -110,7 +124,7 @@ public inline fun VisionOutput.htmlCheckBox(
|
||||
@Serializable
|
||||
@SerialName("html.number")
|
||||
public class VisionOfNumberField : VisionOfHtmlInput(InputType.number.realValue) {
|
||||
public var number: Number? by properties.number(key = VisionOfHtmlInput::value.name.asName())
|
||||
public var numberValue: Number? by properties.number(key = VisionOfHtmlInput::value.name.asName())
|
||||
}
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
@ -125,14 +139,14 @@ public class VisionOfRangeField(
|
||||
public val max: Double,
|
||||
public val step: Double = 1.0,
|
||||
) : VisionOfHtmlInput(InputType.range.realValue) {
|
||||
public var number: Number? by properties.number(key = VisionOfHtmlInput::value.name.asName())
|
||||
public var numberValue: Number? by properties.number(key = VisionOfHtmlInput::value.name.asName())
|
||||
}
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
public inline fun VisionOutput.htmlRangeField(
|
||||
min: Double,
|
||||
max: Double,
|
||||
step: Double = 1.0,
|
||||
min: Number,
|
||||
max: Number,
|
||||
step: Number = 1.0,
|
||||
block: VisionOfRangeField.() -> Unit = {},
|
||||
): VisionOfRangeField = VisionOfRangeField(min, max, step).apply(block)
|
||||
): VisionOfRangeField = VisionOfRangeField(min.toDouble(), max.toDouble(), step.toDouble()).apply(block)
|
||||
|
||||
|
@ -8,6 +8,8 @@ import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.node
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.visionforge.ClickControl
|
||||
|
||||
/**
|
||||
* @param formId an id of the element in rendered DOM, this form is bound to
|
||||
@ -16,7 +18,7 @@ import space.kscience.dataforge.meta.node
|
||||
@SerialName("html.form")
|
||||
public class VisionOfHtmlForm(
|
||||
public val formId: String,
|
||||
) : VisionOfHtml() {
|
||||
) : VisionOfHtmlControl() {
|
||||
public var values: Meta? by properties.node()
|
||||
}
|
||||
|
||||
@ -26,4 +28,21 @@ public fun <R> TagConsumer<R>.bindForm(
|
||||
): R = form {
|
||||
this.id = visionOfForm.formId
|
||||
builder()
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
@SerialName("html.button")
|
||||
public class VisionOfHtmlButton : VisionOfHtmlControl(), ClickControl {
|
||||
public var label: String? by properties.string()
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
public inline fun VisionOutput.button(
|
||||
text: String,
|
||||
block: VisionOfHtmlButton.() -> Unit = {},
|
||||
): VisionOfHtmlButton = VisionOfHtmlButton().apply {
|
||||
label = text
|
||||
block()
|
||||
}
|
@ -135,7 +135,9 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
||||
}
|
||||
|
||||
logger.debug { "Got $event for output with name $visionName" }
|
||||
vision.receiveEvent(event)
|
||||
context.launch {
|
||||
vision.receiveEvent(event)
|
||||
}
|
||||
} else {
|
||||
logger.error { "WebSocket message data is not a string" }
|
||||
}
|
||||
@ -262,7 +264,8 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
||||
numberVisionRenderer,
|
||||
textVisionRenderer,
|
||||
rangeVisionRenderer,
|
||||
formVisionRenderer
|
||||
formVisionRenderer,
|
||||
buttonVisionRenderer
|
||||
).associateByName()
|
||||
} else super<AbstractPlugin>.content(target)
|
||||
|
||||
|
@ -0,0 +1,90 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.html.ButtonType
|
||||
import kotlinx.html.js.button
|
||||
import org.w3c.dom.HTMLFormElement
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.xhr.FormData
|
||||
import space.kscience.dataforge.context.debug
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.html.VisionOfHtmlButton
|
||||
import space.kscience.visionforge.html.VisionOfHtmlForm
|
||||
|
||||
|
||||
internal fun FormData.toMeta(): Meta {
|
||||
@Suppress("UNUSED_VARIABLE") val formData = this
|
||||
//val res = js("Object.fromEntries(formData);")
|
||||
val `object` = js("{}")
|
||||
//language=JavaScript
|
||||
js(
|
||||
"""
|
||||
formData.forEach(function(value, key){
|
||||
// Reflect.has in favor of: object.hasOwnProperty(key)
|
||||
if(!Reflect.has(object, key)){
|
||||
object[key] = value;
|
||||
return;
|
||||
}
|
||||
if(!Array.isArray(object[key])){
|
||||
object[key] = [object[key]];
|
||||
}
|
||||
object[key].push(value);
|
||||
});
|
||||
"""
|
||||
)
|
||||
return DynamicMeta(`object`)
|
||||
}
|
||||
|
||||
|
||||
public fun VisionClient.sendMetaEvent(targetName: Name, payload: MetaRepr): Unit {
|
||||
context.launch {
|
||||
sendEvent(targetName, VisionMetaEvent(payload.toMeta()))
|
||||
}
|
||||
}
|
||||
|
||||
internal val formVisionRenderer: ElementVisionRenderer =
|
||||
ElementVisionRenderer<VisionOfHtmlForm> { name, client, vision, _ ->
|
||||
|
||||
val form = document.getElementById(vision.formId) as? HTMLFormElement
|
||||
?: error("An element with id = '${vision.formId} is not a form")
|
||||
|
||||
form.subscribeToVision(vision)
|
||||
|
||||
vision.manager?.logger?.debug { "Adding hooks to form with id = '$vision.formId'" }
|
||||
|
||||
vision.useProperty(VisionOfHtmlForm::values) { values ->
|
||||
vision.manager?.logger?.debug { "Updating form '${vision.formId}' with values $values" }
|
||||
val inputs = form.getElementsByTagName("input")
|
||||
values?.valueSequence()?.forEach { (token, value) ->
|
||||
(inputs[token.toString()] as? HTMLInputElement)?.value = value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
form.onsubmit = { event ->
|
||||
event.preventDefault()
|
||||
val formData = FormData(form).toMeta()
|
||||
client.sendMetaEvent(name, formData)
|
||||
console.info("Sent: ${formData.toMap()}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
internal val buttonVisionRenderer: ElementVisionRenderer =
|
||||
ElementVisionRenderer<VisionOfHtmlButton> { name, client, vision, _ ->
|
||||
button(type = ButtonType.button).also { button ->
|
||||
button.subscribeToVision(vision)
|
||||
button.onclick = {
|
||||
client.context.launch {
|
||||
client.sendEvent(name, VisionClickEvent(name = name))
|
||||
}
|
||||
}
|
||||
vision.useProperty(VisionOfHtmlButton::label) {
|
||||
button.innerHTML = it ?: ""
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,21 +1,17 @@
|
||||
package space.kscience.visionforge
|
||||
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.dom.clear
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.html.js.input
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.HTMLFormElement
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.xhr.FormData
|
||||
import space.kscience.dataforge.context.debug
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.Value
|
||||
import space.kscience.dataforge.meta.asValue
|
||||
import space.kscience.dataforge.meta.double
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.html.*
|
||||
|
||||
@ -24,7 +20,7 @@ import space.kscience.visionforge.html.*
|
||||
*
|
||||
* @param vision The vision to subscribe to.
|
||||
*/
|
||||
private fun HTMLElement.subscribeToVision(vision: VisionOfHtml) {
|
||||
internal fun HTMLElement.subscribeToVision(vision: VisionOfHtml) {
|
||||
vision.useProperty(VisionOfHtml::classes) {
|
||||
classList.value = classes.joinToString(separator = " ")
|
||||
}
|
||||
@ -33,7 +29,7 @@ private fun HTMLElement.subscribeToVision(vision: VisionOfHtml) {
|
||||
|
||||
private fun VisionClient.sendInputEvent(name: Name, value: Value?) {
|
||||
context.launch {
|
||||
sendEvent(name, VisionValueChangeEvent(value))
|
||||
sendEvent(name, VisionValueChangeEvent(value, name))
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,43 +47,39 @@ private fun HTMLInputElement.subscribeToInput(inputVision: VisionOfHtmlInput) {
|
||||
|
||||
internal val htmlVisionRenderer: ElementVisionRenderer =
|
||||
ElementVisionRenderer<VisionOfPlainHtml> { _, _, vision, _ ->
|
||||
div {}.also { div ->
|
||||
div().also { div ->
|
||||
div.subscribeToVision(vision)
|
||||
vision.useProperty(VisionOfPlainHtml::content) {
|
||||
div.clear()
|
||||
div.append {
|
||||
|
||||
}
|
||||
div.textContent = it
|
||||
if (it != null) div.innerHTML = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val inputVisionRenderer: ElementVisionRenderer =
|
||||
ElementVisionRenderer<VisionOfHtmlInput>(
|
||||
acceptRating = ElementVisionRenderer.DEFAULT_RATING - 1
|
||||
) { name, client, vision, _ ->
|
||||
input {
|
||||
type = InputType.text
|
||||
}.also { htmlInputElement ->
|
||||
val onEvent: (Event) -> Unit = {
|
||||
client.sendInputEvent(name, htmlInputElement.value.asValue())
|
||||
}
|
||||
internal val inputVisionRenderer: ElementVisionRenderer = ElementVisionRenderer<VisionOfHtmlInput>(
|
||||
acceptRating = ElementVisionRenderer.DEFAULT_RATING - 1
|
||||
) { name, client, vision, _ ->
|
||||
input {
|
||||
type = InputType.text
|
||||
}.also { htmlInputElement ->
|
||||
val onEvent: (Event) -> Unit = {
|
||||
client.sendInputEvent(name, htmlInputElement.value.asValue())
|
||||
}
|
||||
|
||||
|
||||
when (vision.feedbackMode) {
|
||||
InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent
|
||||
when (vision.feedbackMode) {
|
||||
InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent
|
||||
|
||||
InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent
|
||||
InputFeedbackMode.NONE -> {}
|
||||
}
|
||||
InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent
|
||||
InputFeedbackMode.NONE -> {}
|
||||
}
|
||||
|
||||
htmlInputElement.subscribeToInput(vision)
|
||||
vision.useProperty(VisionOfHtmlInput::value) {
|
||||
htmlInputElement.value = it?.string ?: ""
|
||||
}
|
||||
htmlInputElement.subscribeToInput(vision)
|
||||
vision.useProperty(VisionOfHtmlInput::value) {
|
||||
htmlInputElement.value = it?.string ?: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val checkboxVisionRenderer: ElementVisionRenderer =
|
||||
ElementVisionRenderer<VisionOfCheckbox> { name, client, vision, _ ->
|
||||
@ -95,7 +87,7 @@ internal val checkboxVisionRenderer: ElementVisionRenderer =
|
||||
type = InputType.checkBox
|
||||
}.also { htmlInputElement ->
|
||||
val onEvent: (Event) -> Unit = {
|
||||
client.sendInputEvent(name, htmlInputElement.value.asValue())
|
||||
client.sendInputEvent(name, htmlInputElement.checked.asValue())
|
||||
}
|
||||
|
||||
|
||||
@ -140,7 +132,7 @@ internal val textVisionRenderer: ElementVisionRenderer =
|
||||
internal val numberVisionRenderer: ElementVisionRenderer =
|
||||
ElementVisionRenderer<VisionOfNumberField> { name, client, vision, _ ->
|
||||
input {
|
||||
type = InputType.text
|
||||
type = InputType.number
|
||||
}.also { htmlInputElement ->
|
||||
|
||||
val onEvent: (Event) -> Unit = {
|
||||
@ -165,7 +157,7 @@ internal val numberVisionRenderer: ElementVisionRenderer =
|
||||
internal val rangeVisionRenderer: ElementVisionRenderer =
|
||||
ElementVisionRenderer<VisionOfRangeField> { name, client, vision, _ ->
|
||||
input {
|
||||
type = InputType.text
|
||||
type = InputType.range
|
||||
min = vision.min.toString()
|
||||
max = vision.max.toString()
|
||||
step = vision.step.toString()
|
||||
@ -189,53 +181,3 @@ internal val rangeVisionRenderer: ElementVisionRenderer =
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun FormData.toMeta(): Meta {
|
||||
@Suppress("UNUSED_VARIABLE") val formData = this
|
||||
//val res = js("Object.fromEntries(formData);")
|
||||
val `object` = js("{}")
|
||||
//language=JavaScript
|
||||
js(
|
||||
"""
|
||||
formData.forEach(function(value, key){
|
||||
// Reflect.has in favor of: object.hasOwnProperty(key)
|
||||
if(!Reflect.has(object, key)){
|
||||
object[key] = value;
|
||||
return;
|
||||
}
|
||||
if(!Array.isArray(object[key])){
|
||||
object[key] = [object[key]];
|
||||
}
|
||||
object[key].push(value);
|
||||
});
|
||||
"""
|
||||
)
|
||||
return DynamicMeta(`object`)
|
||||
}
|
||||
|
||||
internal val formVisionRenderer: ElementVisionRenderer =
|
||||
ElementVisionRenderer<VisionOfHtmlForm> { name, client, vision, _ ->
|
||||
|
||||
val form = document.getElementById(vision.formId) as? HTMLFormElement
|
||||
?: error("An element with id = '${vision.formId} is not a form")
|
||||
|
||||
form.subscribeToVision(vision)
|
||||
|
||||
vision.manager?.logger?.debug { "Adding hooks to form with id = '$vision.formId'" }
|
||||
|
||||
vision.useProperty(VisionOfHtmlForm::values) { values ->
|
||||
vision.manager?.logger?.debug { "Updating form '${vision.formId}' with values $values" }
|
||||
val inputs = form.getElementsByTagName("input")
|
||||
values?.valueSequence()?.forEach { (token, value) ->
|
||||
(inputs[token.toString()] as? HTMLInputElement)?.value = value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
form.onsubmit = { event ->
|
||||
event.preventDefault()
|
||||
val formData = FormData(form).toMeta()
|
||||
client.sendMetaEvent(name, formData)
|
||||
console.info("Sent: ${formData.toMap()}")
|
||||
false
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user