Compare commits
No commits in common. "c877fcbce37cc6a4f1049a5a8ed7a2762445d739" and "469655092e8c7f871dfb610f1c34555d82da0d81" have entirely different histories.
c877fcbce3
...
469655092e
@ -2,8 +2,10 @@ 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
|
||||||
@ -13,25 +15,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,7 +13,10 @@ 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.*
|
import space.kscience.visionforge.html.VisionOfCheckbox
|
||||||
|
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
|
||||||
@ -69,11 +72,9 @@ 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,8 +1,6 @@
|
|||||||
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.*
|
||||||
@ -15,39 +13,10 @@ 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 }
|
||||||
|
@ -39,22 +39,9 @@ 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 ?: error("Orphan Vision can't observe properties"),
|
scope: CoroutineScope? = manager?.context,
|
||||||
callback: V.(T) -> Unit,
|
callback: V.(T) -> Unit,
|
||||||
): Job {
|
): Job {
|
||||||
//Pass initial value.
|
//Pass initial value.
|
||||||
@ -63,5 +50,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)
|
}.launchIn(scope ?: error("Orphan Vision can't observe properties"))
|
||||||
}
|
}
|
@ -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 to "steal" rendering job
|
* higher value in order 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?) {
|
||||||
// private fun notifyPropertyChanged(visionName: Name, propertyName: Name, item: Meta?) {
|
context.launch {
|
||||||
// context.launch {
|
mutex.withLock {
|
||||||
// mutex.withLock {
|
changeCollector.propertyChanged(visionName, propertyName, item)
|
||||||
// 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, visionName: Name, vision: Vision, outputMeta: Meta) {
|
private fun startVisionUpdate(element: Element, name: 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,10 +109,9 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
|||||||
URL(attr.value)
|
URL(attr.value)
|
||||||
}.apply {
|
}.apply {
|
||||||
protocol = "ws"
|
protocol = "ws"
|
||||||
searchParams.append("name", visionName.toString())
|
searchParams.append("name", name.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
|
||||||
@ -128,11 +127,12 @@ 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, visionName, vision, outputMeta)
|
renderVision(element, name, vision, outputMeta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug { "Got $event for output with name $visionName" }
|
logger.debug { "Got $event for output with name $name" }
|
||||||
|
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,44 +147,32 @@ 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 {
|
||||||
//launch a separate coroutine to send events to the backend
|
eventCollector.filter { it.first == name }.onEach {
|
||||||
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)
|
||||||
if (!changeCollector.isEmpty()) {
|
val change = changeCollector[name] ?: continue
|
||||||
|
if (!change.isEmpty()) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
eventCollector.emit(visionName to changeCollector.deepCopy(visionManager))
|
eventCollector.emit(name to change.deepCopy(visionManager))
|
||||||
changeCollector.reset()
|
change.reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.info { "WebSocket feedback channel established for output '$visionName'" }
|
logger.info { "WebSocket feedback channel established for output '$name'" }
|
||||||
}
|
}
|
||||||
|
|
||||||
onclose = {
|
onclose = {
|
||||||
feedbackJob?.cancel()
|
feedbackJob?.cancel()
|
||||||
logger.info { "WebSocket feedback channel closed for output '$visionName'" }
|
logger.info { "WebSocket feedback channel closed for output '$name'" }
|
||||||
}
|
}
|
||||||
onerror = {
|
onerror = {
|
||||||
feedbackJob?.cancel()
|
feedbackJob?.cancel()
|
||||||
logger.error { "WebSocket feedback channel error for output '$visionName'" }
|
logger.error { "WebSocket feedback channel error for output '$name'" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,9 +241,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")
|
||||||
}
|
}
|
||||||
@ -264,13 +252,9 @@ 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(
|
||||||
htmlVisionRenderer,
|
numberVisionRenderer(),
|
||||||
inputVisionRenderer,
|
textVisionRenderer(),
|
||||||
checkboxVisionRenderer,
|
formVisionRenderer()
|
||||||
numberVisionRenderer,
|
|
||||||
textVisionRenderer,
|
|
||||||
rangeVisionRenderer,
|
|
||||||
formVisionRenderer
|
|
||||||
).associateByName()
|
).associateByName()
|
||||||
} else super<AbstractPlugin>.content(target)
|
} else super<AbstractPlugin>.content(target)
|
||||||
|
|
||||||
|
@ -2,12 +2,11 @@ package space.kscience.visionforge
|
|||||||
|
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.html.InputType
|
import kotlinx.html.InputType
|
||||||
import kotlinx.html.div
|
|
||||||
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
|
||||||
@ -15,11 +14,7 @@ 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 = " ")
|
||||||
@ -27,11 +22,6 @@ 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) {
|
||||||
@ -39,133 +29,33 @@ private fun HTMLInputElement.subscribeToInput(inputVision: VisionOfHtmlInput) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val htmlVisionRenderer: ElementVisionRenderer =
|
|
||||||
ElementVisionRenderer<VisionOfPlainHtml> { _, vision, _ ->
|
|
||||||
div {}.also { div ->
|
|
||||||
div.subscribeToVision(vision)
|
|
||||||
vision.useProperty(VisionOfPlainHtml::content) {
|
|
||||||
div.textContent = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
}.also { htmlInputElement ->
|
onChangeFunction = {
|
||||||
val onEvent: (Event) -> Unit = {
|
notifyPropertyChanged(visionName, VisionOfTextField::text.name, value)
|
||||||
vision.value = htmlInputElement.value.asValue()
|
|
||||||
}
|
}
|
||||||
|
}.apply {
|
||||||
|
subscribeToInput(vision)
|
||||||
when (vision.feedbackMode) {
|
|
||||||
InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent
|
|
||||||
|
|
||||||
InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent
|
|
||||||
InputFeedbackMode.NONE -> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlInputElement.subscribeToInput(vision)
|
|
||||||
vision.useProperty(VisionOfHtmlInput::value) {
|
|
||||||
htmlInputElement.value = it?.string ?: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val checkboxVisionRenderer: ElementVisionRenderer =
|
|
||||||
ElementVisionRenderer<VisionOfCheckbox> { _, vision, _ ->
|
|
||||||
input {
|
|
||||||
type = InputType.checkBox
|
|
||||||
}.also { htmlInputElement ->
|
|
||||||
val onEvent: (Event) -> Unit = {
|
|
||||||
vision.checked = htmlInputElement.checked
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
when (vision.feedbackMode) {
|
|
||||||
InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent
|
|
||||||
|
|
||||||
InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent
|
|
||||||
InputFeedbackMode.NONE -> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlInputElement.subscribeToInput(vision)
|
|
||||||
vision.useProperty(VisionOfCheckbox::checked) {
|
|
||||||
htmlInputElement.checked = it ?: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val textVisionRenderer: ElementVisionRenderer =
|
|
||||||
ElementVisionRenderer<VisionOfTextField> { _, vision, _ ->
|
|
||||||
input {
|
|
||||||
type = InputType.text
|
|
||||||
}.also { htmlInputElement ->
|
|
||||||
val onEvent: (Event) -> Unit = {
|
|
||||||
vision.text = htmlInputElement.value
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
when (vision.feedbackMode) {
|
|
||||||
InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent
|
|
||||||
|
|
||||||
InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent
|
|
||||||
InputFeedbackMode.NONE -> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlInputElement.subscribeToInput(vision)
|
|
||||||
vision.useProperty(VisionOfTextField::text) {
|
vision.useProperty(VisionOfTextField::text) {
|
||||||
htmlInputElement.value = it ?: ""
|
value = (it ?: "").asValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val numberVisionRenderer: ElementVisionRenderer =
|
internal fun JsVisionClient.numberVisionRenderer(): ElementVisionRenderer =
|
||||||
ElementVisionRenderer<VisionOfNumberField> { _, vision, _ ->
|
ElementVisionRenderer<VisionOfNumberField> { visionName, vision, _ ->
|
||||||
input {
|
input {
|
||||||
type = InputType.text
|
type = InputType.text
|
||||||
}.also { htmlInputElement ->
|
onChangeFunction = {
|
||||||
|
notifyPropertyChanged(visionName, VisionOfNumberField::value.name, value)
|
||||||
val onEvent: (Event) -> Unit = {
|
|
||||||
htmlInputElement.value.toDoubleOrNull()?.let { vision.number = it }
|
|
||||||
}
|
}
|
||||||
|
}.apply {
|
||||||
when (vision.feedbackMode) {
|
subscribeToInput(vision)
|
||||||
InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent
|
|
||||||
|
|
||||||
InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent
|
|
||||||
InputFeedbackMode.NONE -> {}
|
|
||||||
}
|
|
||||||
htmlInputElement.subscribeToInput(vision)
|
|
||||||
vision.useProperty(VisionOfNumberField::value) {
|
vision.useProperty(VisionOfNumberField::value) {
|
||||||
htmlInputElement.valueAsNumber = it?.double ?: 0.0
|
value = (it?.double ?: 0.0).asValue()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val rangeVisionRenderer: ElementVisionRenderer =
|
|
||||||
ElementVisionRenderer<VisionOfRangeField> { _, vision, _ ->
|
|
||||||
input {
|
|
||||||
type = InputType.text
|
|
||||||
min = vision.min.toString()
|
|
||||||
max = vision.max.toString()
|
|
||||||
step = vision.step.toString()
|
|
||||||
}.also { htmlInputElement ->
|
|
||||||
|
|
||||||
val onEvent: (Event) -> Unit = {
|
|
||||||
htmlInputElement.value.toDoubleOrNull()?.let { vision.number = it }
|
|
||||||
}
|
|
||||||
|
|
||||||
when (vision.feedbackMode) {
|
|
||||||
InputFeedbackMode.ONCHANGE -> htmlInputElement.onchange = onEvent
|
|
||||||
|
|
||||||
InputFeedbackMode.ONINPUT -> htmlInputElement.oninput = onEvent
|
|
||||||
InputFeedbackMode.NONE -> {}
|
|
||||||
}
|
|
||||||
htmlInputElement.subscribeToInput(vision)
|
|
||||||
vision.useProperty(VisionOfRangeField::value) {
|
|
||||||
htmlInputElement.valueAsNumber = it?.double ?: 0.0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,7 +83,7 @@ internal fun FormData.toMeta(): Meta {
|
|||||||
return DynamicMeta(`object`)
|
return DynamicMeta(`object`)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val formVisionRenderer: ElementVisionRenderer =
|
internal fun JsVisionClient.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
|
||||||
@ -201,10 +91,10 @@ internal val formVisionRenderer: ElementVisionRenderer =
|
|||||||
|
|
||||||
form.subscribeToVision(vision)
|
form.subscribeToVision(vision)
|
||||||
|
|
||||||
vision.manager?.logger?.debug { "Adding hooks to form with id = '$vision.formId'" }
|
logger.debug { "Adding hooks to form with id = '$vision.formId'" }
|
||||||
|
|
||||||
vision.useProperty(VisionOfHtmlForm::values) { values ->
|
vision.useProperty(VisionOfHtmlForm::values) { values ->
|
||||||
vision.manager?.logger?.debug { "Updating form '${vision.formId}' with values $values" }
|
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()
|
||||||
@ -214,7 +104,7 @@ internal val formVisionRenderer: ElementVisionRenderer =
|
|||||||
form.onsubmit = { event ->
|
form.onsubmit = { event ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
val formData = FormData(form).toMeta()
|
val formData = FormData(form).toMeta()
|
||||||
vision.values = formData
|
notifyPropertyChanged(visionName, VisionOfHtmlForm::values.name, formData)
|
||||||
console.info("Sent: ${formData.toMap()}")
|
console.info("Sent: ${formData.toMap()}")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user