forked from kscience/visionforge
Fix html input renderers
This commit is contained in:
parent
469655092e
commit
7561ddad36
@ -2,10 +2,8 @@ package space.kscience.visionforge
|
|||||||
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import space.kscience.dataforge.context.Plugin
|
import space.kscience.dataforge.context.Plugin
|
||||||
import space.kscience.dataforge.meta.Meta
|
|
||||||
import space.kscience.dataforge.meta.MetaRepr
|
import space.kscience.dataforge.meta.MetaRepr
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.dataforge.names.parseAsName
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A feedback client that communicates with a server and provides ability to propagate events and changes back to the model
|
* A feedback client that communicates with a server and provides ability to propagate events and changes back to the model
|
||||||
@ -15,25 +13,25 @@ public interface VisionClient: Plugin {
|
|||||||
|
|
||||||
public suspend fun sendEvent(targetName: Name, event: VisionEvent)
|
public suspend fun sendEvent(targetName: Name, event: VisionEvent)
|
||||||
|
|
||||||
public fun notifyPropertyChanged(visionName: Name, propertyName: Name, item: Meta?)
|
// public fun notifyPropertyChanged(visionName: Name, propertyName: Name, item: Meta?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: Meta?) {
|
//public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: Meta?) {
|
||||||
notifyPropertyChanged(visionName, propertyName.parseAsName(true), item)
|
// notifyPropertyChanged(visionName, propertyName.parseAsName(true), item)
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: Number) {
|
//public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: Number) {
|
||||||
notifyPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
|
// notifyPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: String) {
|
//public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: String) {
|
||||||
notifyPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
|
// notifyPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: Boolean) {
|
//public fun VisionClient.notifyPropertyChanged(visionName: Name, propertyName: String, item: Boolean) {
|
||||||
notifyPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
|
// notifyPropertyChanged(visionName, propertyName.parseAsName(true), Meta(item))
|
||||||
}
|
//}
|
||||||
|
|
||||||
public fun VisionClient.sendEvent(targetName: Name, payload: MetaRepr): Unit {
|
public fun VisionClient.sendEvent(targetName: Name, payload: MetaRepr): Unit {
|
||||||
context.launch {
|
context.launch {
|
||||||
|
@ -13,10 +13,7 @@ import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
|||||||
import space.kscience.dataforge.meta.toJson
|
import space.kscience.dataforge.meta.toJson
|
||||||
import space.kscience.dataforge.meta.toMeta
|
import space.kscience.dataforge.meta.toMeta
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
import space.kscience.visionforge.html.VisionOfCheckbox
|
import space.kscience.visionforge.html.*
|
||||||
import space.kscience.visionforge.html.VisionOfHtmlForm
|
|
||||||
import space.kscience.visionforge.html.VisionOfNumberField
|
|
||||||
import space.kscience.visionforge.html.VisionOfTextField
|
|
||||||
|
|
||||||
public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionContainer<Vision> {
|
public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionContainer<Vision> {
|
||||||
override val tag: PluginTag get() = Companion.tag
|
override val tag: PluginTag get() = Companion.tag
|
||||||
@ -72,9 +69,11 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionCont
|
|||||||
defaultDeserializer { SimpleVisionGroup.serializer() }
|
defaultDeserializer { SimpleVisionGroup.serializer() }
|
||||||
subclass(NullVision.serializer())
|
subclass(NullVision.serializer())
|
||||||
subclass(SimpleVisionGroup.serializer())
|
subclass(SimpleVisionGroup.serializer())
|
||||||
|
subclass(VisionOfHtmlInput.serializer())
|
||||||
subclass(VisionOfNumberField.serializer())
|
subclass(VisionOfNumberField.serializer())
|
||||||
subclass(VisionOfTextField.serializer())
|
subclass(VisionOfTextField.serializer())
|
||||||
subclass(VisionOfCheckbox.serializer())
|
subclass(VisionOfCheckbox.serializer())
|
||||||
|
subclass(VisionOfRangeField.serializer())
|
||||||
subclass(VisionOfHtmlForm.serializer())
|
subclass(VisionOfHtmlForm.serializer())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package space.kscience.visionforge.html
|
package space.kscience.visionforge.html
|
||||||
|
|
||||||
import kotlinx.html.InputType
|
import kotlinx.html.InputType
|
||||||
|
import kotlinx.html.TagConsumer
|
||||||
|
import kotlinx.html.stream.createHTML
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.*
|
||||||
@ -9,16 +11,45 @@ import space.kscience.visionforge.AbstractVision
|
|||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public abstract class VisionOfHtml: AbstractVision(){
|
public abstract class VisionOfHtml : AbstractVision() {
|
||||||
public var classes: List<String> by properties.stringList(*emptyArray())
|
public var classes: List<String> by properties.stringList(*emptyArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("html.plain")
|
||||||
|
public class VisionOfPlainHtml : VisionOfHtml() {
|
||||||
|
public var content: String? by properties.string()
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline fun VisionOfPlainHtml.content(block: TagConsumer<*>.() -> Unit) {
|
||||||
|
content = createHTML().apply(block).finalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
public enum class InputFeedbackMode{
|
||||||
|
/**
|
||||||
|
* Fire feedback event on `onchange` event
|
||||||
|
*/
|
||||||
|
ONCHANGE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire feedback event on `oninput` event
|
||||||
|
*/
|
||||||
|
ONINPUT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* provide only manual feedback
|
||||||
|
*/
|
||||||
|
NONE
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("html.input")
|
@SerialName("html.input")
|
||||||
public open class VisionOfHtmlInput(
|
public open class VisionOfHtmlInput(
|
||||||
public val inputType: String,
|
public val inputType: String,
|
||||||
|
public val feedbackMode: InputFeedbackMode = InputFeedbackMode.ONCHANGE
|
||||||
) : VisionOfHtml() {
|
) : VisionOfHtml() {
|
||||||
public var value : Value? by properties.value()
|
public var value: Value? by properties.value()
|
||||||
public var disabled: Boolean by properties.boolean { false }
|
public var disabled: Boolean by properties.boolean { false }
|
||||||
public var fieldName: String? by properties.string()
|
public var fieldName: String? by properties.string()
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,22 @@ public fun Vision.useProperty(
|
|||||||
callback: (Meta) -> Unit,
|
callback: (Meta) -> Unit,
|
||||||
): Job = useProperty(propertyName.parseAsName(), inherit, includeStyles, scope, callback)
|
): Job = useProperty(propertyName.parseAsName(), inherit, includeStyles, scope, callback)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe changes to the specific property without passing the initial value.
|
||||||
|
*/
|
||||||
|
public fun <V : Vision, T> V.onPropertyChange(
|
||||||
|
property: KProperty1<V, T>,
|
||||||
|
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties"),
|
||||||
|
callback: suspend V.(T) -> Unit,
|
||||||
|
): Job = properties.changes.onEach { name ->
|
||||||
|
if (name.startsWith(property.name.asName())) {
|
||||||
|
callback(property.get(this))
|
||||||
|
}
|
||||||
|
}.launchIn(scope)
|
||||||
|
|
||||||
public fun <V : Vision, T> V.useProperty(
|
public fun <V : Vision, T> V.useProperty(
|
||||||
property: KProperty1<V, T>,
|
property: KProperty1<V, T>,
|
||||||
scope: CoroutineScope? = manager?.context,
|
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties"),
|
||||||
callback: V.(T) -> Unit,
|
callback: V.(T) -> Unit,
|
||||||
): Job {
|
): Job {
|
||||||
//Pass initial value.
|
//Pass initial value.
|
||||||
@ -50,5 +63,5 @@ public fun <V : Vision, T> V.useProperty(
|
|||||||
if (name.startsWith(property.name.asName())) {
|
if (name.startsWith(property.name.asName())) {
|
||||||
callback(property.get(this@useProperty))
|
callback(property.get(this@useProperty))
|
||||||
}
|
}
|
||||||
}.launchIn(scope ?: error("Orphan Vision can't observe properties"))
|
}.launchIn(scope)
|
||||||
}
|
}
|
@ -26,7 +26,7 @@ public interface ElementVisionRenderer : Named {
|
|||||||
/**
|
/**
|
||||||
* Give a [vision] integer rating based on this renderer capabilities. [ZERO_RATING] or negative values means that this renderer
|
* Give a [vision] integer rating based on this renderer capabilities. [ZERO_RATING] or negative values means that this renderer
|
||||||
* can't process a vision. The value of [DEFAULT_RATING] used for default renderer. Specialized renderers could specify
|
* can't process a vision. The value of [DEFAULT_RATING] used for default renderer. Specialized renderers could specify
|
||||||
* higher value in order to "steal" rendering job
|
* higher value to "steal" rendering job
|
||||||
*/
|
*/
|
||||||
public fun rateVision(vision: Vision): Int
|
public fun rateVision(vision: Vision): Int
|
||||||
|
|
||||||
|
@ -65,20 +65,20 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
|||||||
|
|
||||||
private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value != null
|
private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value != null
|
||||||
|
|
||||||
private val mutex = Mutex()
|
// private val mutex = Mutex()
|
||||||
|
|
||||||
private val changeCollector = VisionChangeBuilder()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Communicate vision property changed from rendering engine to model
|
// /**
|
||||||
*/
|
// * Communicate vision property changed from rendering engine to model
|
||||||
override fun notifyPropertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
|
// */
|
||||||
context.launch {
|
// private fun notifyPropertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
|
||||||
mutex.withLock {
|
// context.launch {
|
||||||
changeCollector.propertyChanged(visionName, propertyName, item)
|
// mutex.withLock {
|
||||||
}
|
// changeCollector.propertyChanged(visionName, propertyName, item)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
private val eventCollector by lazy {
|
private val eventCollector by lazy {
|
||||||
MutableSharedFlow<Pair<Name, VisionEvent>>(meta["feedback.eventCache"].int ?: 100)
|
MutableSharedFlow<Pair<Name, VisionEvent>>(meta["feedback.eventCache"].int ?: 100)
|
||||||
@ -97,7 +97,7 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
|||||||
renderer.render(element, name, vision, outputMeta)
|
renderer.render(element, name, vision, outputMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startVisionUpdate(element: Element, name: Name, vision: Vision?, outputMeta: Meta) {
|
private fun startVisionUpdate(element: Element, visionName: Name, vision: Vision, outputMeta: Meta) {
|
||||||
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
|
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
|
||||||
val wsUrl = if (attr.value.isBlank() || attr.value == VisionTagConsumer.AUTO_DATA_ATTRIBUTE) {
|
val wsUrl = if (attr.value.isBlank() || attr.value == VisionTagConsumer.AUTO_DATA_ATTRIBUTE) {
|
||||||
val endpoint = resolveEndpoint(element)
|
val endpoint = resolveEndpoint(element)
|
||||||
@ -109,9 +109,10 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
|||||||
URL(attr.value)
|
URL(attr.value)
|
||||||
}.apply {
|
}.apply {
|
||||||
protocol = "ws"
|
protocol = "ws"
|
||||||
searchParams.append("name", name.toString())
|
searchParams.append("name", visionName.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
logger.info { "Updating vision data from $wsUrl" }
|
logger.info { "Updating vision data from $wsUrl" }
|
||||||
|
|
||||||
//Individual websocket for this vision
|
//Individual websocket for this vision
|
||||||
@ -125,14 +126,13 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// If change contains root vision replacement, do it
|
// If change contains root vision replacement, do it
|
||||||
if(event is VisionChange) {
|
if (event is VisionChange) {
|
||||||
event.vision?.let { vision ->
|
event.vision?.let { vision ->
|
||||||
renderVision(element, name, vision, outputMeta)
|
renderVision(element, visionName, vision, outputMeta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug { "Got $event for output with name $name" }
|
logger.debug { "Got $event for output with name $visionName" }
|
||||||
if (vision == null) error("Can't update vision because it is not loaded.")
|
|
||||||
vision.receiveEvent(event)
|
vision.receiveEvent(event)
|
||||||
} else {
|
} else {
|
||||||
logger.error { "WebSocket message data is not a string" }
|
logger.error { "WebSocket message data is not a string" }
|
||||||
@ -147,32 +147,44 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
|||||||
val feedbackAggregationTime = meta["feedback.aggregationTime"]?.int ?: 300
|
val feedbackAggregationTime = meta["feedback.aggregationTime"]?.int ?: 300
|
||||||
|
|
||||||
onopen = {
|
onopen = {
|
||||||
|
|
||||||
|
|
||||||
|
val mutex = Mutex()
|
||||||
|
|
||||||
|
val changeCollector = VisionChangeBuilder()
|
||||||
|
|
||||||
feedbackJob = visionManager.context.launch {
|
feedbackJob = visionManager.context.launch {
|
||||||
eventCollector.filter { it.first == name }.onEach {
|
//launch a separate coroutine to send events to the backend
|
||||||
|
eventCollector.filter { it.first == visionName }.onEach {
|
||||||
send(visionManager.jsonFormat.encodeToString(VisionEvent.serializer(), it.second))
|
send(visionManager.jsonFormat.encodeToString(VisionEvent.serializer(), it.second))
|
||||||
}.launchIn(this)
|
}.launchIn(this)
|
||||||
|
|
||||||
|
//launch backward property propagation
|
||||||
|
vision.properties.changes.onEach { propertyName: Name ->
|
||||||
|
changeCollector.propertyChanged(visionName, propertyName, vision.properties.getMeta(propertyName))
|
||||||
|
}.launchIn(this)
|
||||||
|
|
||||||
|
//aggregate atomic changes
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
delay(feedbackAggregationTime.milliseconds)
|
delay(feedbackAggregationTime.milliseconds)
|
||||||
val change = changeCollector[name] ?: continue
|
if (!changeCollector.isEmpty()) {
|
||||||
if (!change.isEmpty()) {
|
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
eventCollector.emit(name to change.deepCopy(visionManager))
|
eventCollector.emit(visionName to changeCollector.deepCopy(visionManager))
|
||||||
change.reset()
|
changeCollector.reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.info { "WebSocket feedback channel established for output '$name'" }
|
logger.info { "WebSocket feedback channel established for output '$visionName'" }
|
||||||
}
|
}
|
||||||
|
|
||||||
onclose = {
|
onclose = {
|
||||||
feedbackJob?.cancel()
|
feedbackJob?.cancel()
|
||||||
logger.info { "WebSocket feedback channel closed for output '$name'" }
|
logger.info { "WebSocket feedback channel closed for output '$visionName'" }
|
||||||
}
|
}
|
||||||
onerror = {
|
onerror = {
|
||||||
feedbackJob?.cancel()
|
feedbackJob?.cancel()
|
||||||
logger.error { "WebSocket feedback channel error for output '$name'" }
|
logger.error { "WebSocket feedback channel error for output '$visionName'" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,9 +253,9 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Try to load vision via websocket
|
//Try to load vision via websocket
|
||||||
element.attributes[OUTPUT_CONNECT_ATTRIBUTE] != null -> {
|
// element.attributes[OUTPUT_CONNECT_ATTRIBUTE] != null -> {
|
||||||
startVisionUpdate(element, name, null, outputMeta)
|
// startVisionUpdate(element, name, null, outputMeta)
|
||||||
}
|
// }
|
||||||
|
|
||||||
else -> error("No embedded vision data / fetch url for $name")
|
else -> error("No embedded vision data / fetch url for $name")
|
||||||
}
|
}
|
||||||
@ -252,9 +264,12 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
|||||||
|
|
||||||
override fun content(target: String): Map<Name, Any> = if (target == ElementVisionRenderer.TYPE) {
|
override fun content(target: String): Map<Name, Any> = if (target == ElementVisionRenderer.TYPE) {
|
||||||
listOf(
|
listOf(
|
||||||
numberVisionRenderer(),
|
inputVisionRenderer,
|
||||||
textVisionRenderer(),
|
checkboxVisionRenderer,
|
||||||
formVisionRenderer()
|
numberVisionRenderer,
|
||||||
|
textVisionRenderer,
|
||||||
|
rangeVisionRenderer,
|
||||||
|
formVisionRenderer
|
||||||
).associateByName()
|
).associateByName()
|
||||||
} else super<AbstractPlugin>.content(target)
|
} else super<AbstractPlugin>.content(target)
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@ package space.kscience.visionforge
|
|||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.html.InputType
|
import kotlinx.html.InputType
|
||||||
import kotlinx.html.js.input
|
import kotlinx.html.js.input
|
||||||
import kotlinx.html.js.onChangeFunction
|
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
import org.w3c.dom.HTMLFormElement
|
import org.w3c.dom.HTMLFormElement
|
||||||
import org.w3c.dom.HTMLInputElement
|
import org.w3c.dom.HTMLInputElement
|
||||||
|
import org.w3c.dom.events.Event
|
||||||
import org.w3c.dom.get
|
import org.w3c.dom.get
|
||||||
import org.w3c.xhr.FormData
|
import org.w3c.xhr.FormData
|
||||||
import space.kscience.dataforge.context.debug
|
import space.kscience.dataforge.context.debug
|
||||||
@ -14,7 +14,11 @@ import space.kscience.dataforge.context.logger
|
|||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.*
|
||||||
import space.kscience.visionforge.html.*
|
import space.kscience.visionforge.html.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes the HTML element to a given vision.
|
||||||
|
*
|
||||||
|
* @param vision The vision to subscribe to.
|
||||||
|
*/
|
||||||
private fun HTMLElement.subscribeToVision(vision: VisionOfHtml) {
|
private fun HTMLElement.subscribeToVision(vision: VisionOfHtml) {
|
||||||
vision.useProperty(VisionOfHtml::classes) {
|
vision.useProperty(VisionOfHtml::classes) {
|
||||||
classList.value = classes.joinToString(separator = " ")
|
classList.value = classes.joinToString(separator = " ")
|
||||||
@ -22,6 +26,11 @@ private fun HTMLElement.subscribeToVision(vision: VisionOfHtml) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes the HTML input element to a given vision.
|
||||||
|
*
|
||||||
|
* @param inputVision The input vision to subscribe to.
|
||||||
|
*/
|
||||||
private fun HTMLInputElement.subscribeToInput(inputVision: VisionOfHtmlInput) {
|
private fun HTMLInputElement.subscribeToInput(inputVision: VisionOfHtmlInput) {
|
||||||
subscribeToVision(inputVision)
|
subscribeToVision(inputVision)
|
||||||
inputVision.useProperty(VisionOfHtmlInput::disabled) {
|
inputVision.useProperty(VisionOfHtmlInput::disabled) {
|
||||||
@ -29,33 +38,123 @@ private fun HTMLInputElement.subscribeToInput(inputVision: VisionOfHtmlInput) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val inputVisionRenderer: ElementVisionRenderer =
|
||||||
internal fun JsVisionClient.textVisionRenderer(): ElementVisionRenderer =
|
ElementVisionRenderer<VisionOfHtmlInput>(acceptRating = ElementVisionRenderer.DEFAULT_RATING - 1) { _, vision, _ ->
|
||||||
ElementVisionRenderer<VisionOfTextField> { visionName, vision, _ ->
|
|
||||||
input {
|
input {
|
||||||
type = InputType.text
|
type = InputType.text
|
||||||
onChangeFunction = {
|
|
||||||
notifyPropertyChanged(visionName, VisionOfTextField::text.name, value)
|
|
||||||
}
|
|
||||||
}.apply {
|
}.apply {
|
||||||
|
val onEvent: (Event) -> Unit = {
|
||||||
|
vision.value = value.asValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
when (vision.feedbackMode) {
|
||||||
|
InputFeedbackMode.ONCHANGE -> onchange = onEvent
|
||||||
|
|
||||||
|
InputFeedbackMode.ONINPUT -> oninput = onEvent
|
||||||
|
InputFeedbackMode.NONE -> {}
|
||||||
|
}
|
||||||
|
|
||||||
subscribeToInput(vision)
|
subscribeToInput(vision)
|
||||||
vision.useProperty(VisionOfTextField::text) {
|
vision.useProperty(VisionOfHtmlInput::value) {
|
||||||
value = (it ?: "").asValue()
|
this@apply.value = it?.string ?: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun JsVisionClient.numberVisionRenderer(): ElementVisionRenderer =
|
internal val checkboxVisionRenderer: ElementVisionRenderer =
|
||||||
ElementVisionRenderer<VisionOfNumberField> { visionName, vision, _ ->
|
ElementVisionRenderer<VisionOfCheckbox> { _, vision, _ ->
|
||||||
|
input {
|
||||||
|
type = InputType.checkBox
|
||||||
|
}.apply {
|
||||||
|
val onEvent: (Event) -> Unit = {
|
||||||
|
vision.checked = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
when (vision.feedbackMode) {
|
||||||
|
InputFeedbackMode.ONCHANGE -> onchange = onEvent
|
||||||
|
|
||||||
|
InputFeedbackMode.ONINPUT -> oninput = onEvent
|
||||||
|
InputFeedbackMode.NONE -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeToInput(vision)
|
||||||
|
vision.useProperty(VisionOfCheckbox::checked) {
|
||||||
|
this@apply.checked = it ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val textVisionRenderer: ElementVisionRenderer =
|
||||||
|
ElementVisionRenderer<VisionOfTextField> { _, vision, _ ->
|
||||||
input {
|
input {
|
||||||
type = InputType.text
|
type = InputType.text
|
||||||
onChangeFunction = {
|
|
||||||
notifyPropertyChanged(visionName, VisionOfNumberField::value.name, value)
|
|
||||||
}
|
|
||||||
}.apply {
|
}.apply {
|
||||||
|
val onEvent: (Event) -> Unit = {
|
||||||
|
vision.text = value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
when (vision.feedbackMode) {
|
||||||
|
InputFeedbackMode.ONCHANGE -> onchange = onEvent
|
||||||
|
|
||||||
|
InputFeedbackMode.ONINPUT -> oninput = onEvent
|
||||||
|
InputFeedbackMode.NONE -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeToInput(vision)
|
||||||
|
vision.useProperty(VisionOfTextField::text) {
|
||||||
|
this@apply.value = it ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val numberVisionRenderer: ElementVisionRenderer =
|
||||||
|
ElementVisionRenderer<VisionOfNumberField> { _, vision, _ ->
|
||||||
|
input {
|
||||||
|
type = InputType.text
|
||||||
|
}.apply {
|
||||||
|
|
||||||
|
val onEvent: (Event) -> Unit = {
|
||||||
|
value.toDoubleOrNull()?.let { vision.number = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
when (vision.feedbackMode) {
|
||||||
|
InputFeedbackMode.ONCHANGE -> onchange = onEvent
|
||||||
|
|
||||||
|
InputFeedbackMode.ONINPUT -> oninput = onEvent
|
||||||
|
InputFeedbackMode.NONE -> {}
|
||||||
|
}
|
||||||
subscribeToInput(vision)
|
subscribeToInput(vision)
|
||||||
vision.useProperty(VisionOfNumberField::value) {
|
vision.useProperty(VisionOfNumberField::value) {
|
||||||
value = (it?.double ?: 0.0).asValue()
|
this@apply.valueAsNumber = it?.double ?: 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val rangeVisionRenderer: ElementVisionRenderer =
|
||||||
|
ElementVisionRenderer<VisionOfRangeField> { _, vision, _ ->
|
||||||
|
input {
|
||||||
|
type = InputType.text
|
||||||
|
min = vision.min.toString()
|
||||||
|
max = vision.max.toString()
|
||||||
|
step = vision.step.toString()
|
||||||
|
}.apply {
|
||||||
|
|
||||||
|
val onEvent: (Event) -> Unit = {
|
||||||
|
value.toDoubleOrNull()?.let { vision.number = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
when (vision.feedbackMode) {
|
||||||
|
InputFeedbackMode.ONCHANGE -> onchange = onEvent
|
||||||
|
|
||||||
|
InputFeedbackMode.ONINPUT -> oninput = onEvent
|
||||||
|
InputFeedbackMode.NONE -> {}
|
||||||
|
}
|
||||||
|
subscribeToInput(vision)
|
||||||
|
vision.useProperty(VisionOfRangeField::value) {
|
||||||
|
this@apply.valueAsNumber = it?.double ?: 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +182,7 @@ internal fun FormData.toMeta(): Meta {
|
|||||||
return DynamicMeta(`object`)
|
return DynamicMeta(`object`)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun JsVisionClient.formVisionRenderer(): ElementVisionRenderer =
|
internal val formVisionRenderer: ElementVisionRenderer =
|
||||||
ElementVisionRenderer<VisionOfHtmlForm> { visionName, vision, _ ->
|
ElementVisionRenderer<VisionOfHtmlForm> { visionName, vision, _ ->
|
||||||
|
|
||||||
val form = document.getElementById(vision.formId) as? HTMLFormElement
|
val form = document.getElementById(vision.formId) as? HTMLFormElement
|
||||||
@ -91,10 +190,10 @@ internal fun JsVisionClient.formVisionRenderer(): ElementVisionRenderer =
|
|||||||
|
|
||||||
form.subscribeToVision(vision)
|
form.subscribeToVision(vision)
|
||||||
|
|
||||||
logger.debug { "Adding hooks to form with id = '$vision.formId'" }
|
vision.manager?.logger?.debug { "Adding hooks to form with id = '$vision.formId'" }
|
||||||
|
|
||||||
vision.useProperty(VisionOfHtmlForm::values) { values ->
|
vision.useProperty(VisionOfHtmlForm::values) { values ->
|
||||||
logger.debug { "Updating form '${vision.formId}' with values $values" }
|
vision.manager?.logger?.debug { "Updating form '${vision.formId}' with values $values" }
|
||||||
val inputs = form.getElementsByTagName("input")
|
val inputs = form.getElementsByTagName("input")
|
||||||
values?.valueSequence()?.forEach { (token, value) ->
|
values?.valueSequence()?.forEach { (token, value) ->
|
||||||
(inputs[token.toString()] as? HTMLInputElement)?.value = value.toString()
|
(inputs[token.toString()] as? HTMLInputElement)?.value = value.toString()
|
||||||
@ -104,7 +203,7 @@ internal fun JsVisionClient.formVisionRenderer(): ElementVisionRenderer =
|
|||||||
form.onsubmit = { event ->
|
form.onsubmit = { event ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
val formData = FormData(form).toMeta()
|
val formData = FormData(form).toMeta()
|
||||||
notifyPropertyChanged(visionName, VisionOfHtmlForm::values.name, formData)
|
vision.values = formData
|
||||||
console.info("Sent: ${formData.toMap()}")
|
console.info("Sent: ${formData.toMap()}")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user