Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

Commit

Permalink
feat: navigation callbacks (#75)
Browse files Browse the repository at this point in the history
* implements: navigation callbacks (events); operations object and array; expressions on initial value of states. Renamed params on navigation to state.

* detekt
  • Loading branch information
Tiagoperes committed Jan 12, 2023
1 parent f2fb0f1 commit 542681a
Show file tree
Hide file tree
Showing 16 changed files with 565 additions and 19 deletions.
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
The Nimbus SDUI is:

1. A solution for applications that need to have some of its user interface (UI) driven by the backend, i.e. Server Driven UI (SDUI).
1. A protocol for serializing the content and behavior of a UI into JSON so it can be sent by a backend sever and interpreted by the front-end.
1. A protocol for serializing the content and behavior of a UI into JSON so it can be sent by a backend server and interpreted by the front-end.
1. A set of libraries that implements this protocol.

An application that uses Nimbus will have:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package br.com.zup.nimbus.core

import br.com.zup.nimbus.core.scope.CommonScope
import br.com.zup.nimbus.core.tree.ServerDrivenEvent

/**
* A scope for the current view in a navigator.
Expand All @@ -30,6 +31,7 @@ class ServerDrivenView(
* The states in this scope. Useful for creating view parameters in the navigation.
*/
states: List<ServerDrivenState>? = null,
val events: List<ServerDrivenEvent>? = null,
/**
* A description for this view. Suggestion: the URL used to load the content of this view or "json", if a local json
* string was used to load it.
Expand All @@ -44,7 +46,7 @@ class ServerDrivenView(
getNavigator: () -> ServerDrivenNavigator,
): CommonScope(parent = nimbus, states = states) {
constructor(nimbus: Nimbus, getNavigator: () -> ServerDrivenNavigator):
this(nimbus, null, null, getNavigator)
this(nimbus, null, null, null, getNavigator)

val navigator = getNavigator()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package br.com.zup.nimbus.core.network

import br.com.zup.nimbus.core.tree.ServerDrivenEvent

data class ViewRequest(
/**
* The URL to send the request to. When it starts with "/", it's relative to the BaseUrl.
Expand All @@ -38,7 +40,8 @@ data class ViewRequest(
*/
val fallback: Map<String, Any?>? = null,
/**
* The map of state ids and its values that will be used on the next page.
* The map of states and their values that will be used on the next page.
*/
val params: Map<String, Any?>? = null,
val state: Map<String, Any?>? = null,
val events: List<ServerDrivenEvent>? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@

package br.com.zup.nimbus.core.tree.dynamic.node

import br.com.zup.nimbus.core.Nimbus
import br.com.zup.nimbus.core.scope.CloneAfterInitializationError
import br.com.zup.nimbus.core.scope.DoubleInitializationError
import br.com.zup.nimbus.core.ServerDrivenState
import br.com.zup.nimbus.core.dependency.CommonDependency
import br.com.zup.nimbus.core.dependency.Dependency
import br.com.zup.nimbus.core.dependency.Dependent
import br.com.zup.nimbus.core.scope.CommonScope
import br.com.zup.nimbus.core.scope.LazilyScoped
import br.com.zup.nimbus.core.scope.Scope
import br.com.zup.nimbus.core.scope.closestScopeWithType
import br.com.zup.nimbus.core.tree.ServerDrivenNode
import br.com.zup.nimbus.core.tree.dynamic.container.NodeContainer
import br.com.zup.nimbus.core.tree.dynamic.container.PropertyContainer
Expand Down Expand Up @@ -67,9 +71,28 @@ open class DynamicNode(
hasChanged = true
}

/**
* Compute the values of states that have been provided expressions as their initial values.
*/
private fun initializeDependentStates() {
if (states?.isEmpty() != false) return
val expressionParser = closestScopeWithType<Nimbus>()?.expressionParser ?: return

states?.forEach { state ->
val value = state.get()
if (value is String && expressionParser.containsExpression(value)) {
val parsed = expressionParser.parseString(value, true)
if (parsed is LazilyScoped<*>) parsed.initialize(this)
if (parsed is Dependent) parsed.update()
state.setSilently(parsed.getValue())
}
}
}

override fun initialize(scope: Scope) {
if (parent != null) throw DoubleInitializationError()
parent = scope
initializeDependentStates()
propertyContainer?.initialize(this)
childrenContainer?.initialize(this)
propertyContainer?.addDependent(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import br.com.zup.nimbus.core.network.ServerDrivenHttpMethod
import br.com.zup.nimbus.core.network.ViewRequest
import br.com.zup.nimbus.core.ActionTriggeredEvent
import br.com.zup.nimbus.core.deserialization.AnyServerDrivenData
import br.com.zup.nimbus.core.deserialization.SerializationError
import br.com.zup.nimbus.core.ui.action.error.ActionExecutionError
import br.com.zup.nimbus.core.ui.action.error.ActionDeserializationError

Expand All @@ -35,10 +34,12 @@ private fun requestFromEvent(event: ActionEvent, isPushOrPresent: Boolean): View
val headers = properties.get("headers").asMapOrNull()?.mapValues { it.value.asString() }
val body = attemptJsonSerialization(properties.get("body"), event)
val fallback = properties.get("fallback").asMapOrNull()?.mapValues { it.value.asAnyOrNull() }
val params = if (isPushOrPresent) properties.get("params").asMapOrNull()?.mapValues { it.value.asAnyOrNull() }
val state = if (isPushOrPresent) properties.get("state").asMapOrNull()?.mapValues { it.value.asAnyOrNull() }
else null
val events = if (isPushOrPresent) properties.get("events").asMapOrNull()?.map { it.value.asEvent() }
else null
if (properties.hasError()) throw ActionDeserializationError(event, properties)
return ViewRequest(url, method, headers, body, fallback, params)
return ViewRequest(url, method, headers, body, fallback, state, events)
}

private fun pushOrPresent(event: ActionTriggeredEvent, isPush: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2023 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package br.com.zup.nimbus.core.ui.action

import br.com.zup.nimbus.core.ActionTriggeredEvent
import br.com.zup.nimbus.core.deserialization.AnyServerDrivenData
import br.com.zup.nimbus.core.ui.action.error.ActionDeserializationError

internal fun triggerViewEvent(event: ActionTriggeredEvent) {
val data = AnyServerDrivenData(event.action.properties)
val nameOfEventToTrigger = data.get("event").asString()
val valueForEventToTrigger = data.get("value").asAnyOrNull()
if (data.hasError()) {
throw ActionDeserializationError(event, data)
}
val eventToTrigger = event.scope.view.events?.find { it.name == nameOfEventToTrigger }
if (eventToTrigger == null) {
event.scope.nimbus.logger.error("Can't trigger view event named \"$nameOfEventToTrigger\" because the current " +
"view has no such event.")
} else {
eventToTrigger.run(valueForEventToTrigger)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ import br.com.zup.nimbus.core.ui.action.present
import br.com.zup.nimbus.core.ui.action.push
import br.com.zup.nimbus.core.ui.action.sendRequest
import br.com.zup.nimbus.core.ui.action.setState
import br.com.zup.nimbus.core.ui.action.triggerViewEvent
import br.com.zup.nimbus.core.ui.operations.registerArrayOperations
import br.com.zup.nimbus.core.ui.operations.registerLogicOperations
import br.com.zup.nimbus.core.ui.operations.registerNumberOperations
import br.com.zup.nimbus.core.ui.operations.registerObjectOperations
import br.com.zup.nimbus.core.ui.operations.registerOtherOperations
import br.com.zup.nimbus.core.ui.operations.registerStringOperations

Expand All @@ -42,6 +44,7 @@ val coreUILibrary = UILibrary("")
.addAction("popTo") { popTo(it) }
.addAction("present") { present(it) }
.addAction("dismiss") { dismiss(it) }
.addAction("triggerViewEvent") { triggerViewEvent(it) }
.addAction("log") { log(it) }
.addAction("sendRequest") { sendRequest(it) }
.addAction("setState") { setState(it) }
Expand All @@ -56,5 +59,6 @@ val coreUILibrary = UILibrary("")
registerNumberOperations(this)
registerOtherOperations(this)
registerStringOperations(this)
registerObjectOperations(this)
this
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import br.com.zup.nimbus.core.ui.UILibrary

internal fun registerArrayOperations(library: UILibrary) {
library
.addOperation("array") { it }
.addOperation("insert") {
val arguments = AnyServerDrivenData(it)
val list = if (arguments.at(0).isList()) (arguments.at(0).value as List<*>).toMutableList()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2023 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package br.com.zup.nimbus.core.ui.operations

import br.com.zup.nimbus.core.ui.UILibrary

internal fun registerObjectOperations(library: UILibrary) {
library
.addOperation("object") {
val objectMap = mutableMapOf<String, Any?>()
for (i in it.indices step 2) {
objectMap[it.getOrNull(i).toString()] = it.getOrNull(i + 1)
}
objectMap
}
.addOperation("entries") {
val result = it.firstOrNull()?.let { map ->
if (map is Map<*, *>) map.entries.map { entry -> mapOf("key" to entry.key, "value" to entry.value) }
else null
}
result ?: emptyList<Any>()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package br.com.zup.nimbus.core.ui.operations

import br.com.zup.nimbus.core.deserialization.AnyServerDrivenData
import br.com.zup.nimbus.core.ui.UILibrary
import br.com.zup.nimbus.core.utils.Null
import br.com.zup.nimbus.core.utils.compareTo
Expand All @@ -28,7 +27,7 @@ private fun areNumbersEqual(left: Any?, right: Any?): Boolean {
return leftNumber.compareTo(rightNumber) == 0
}

@Suppress("ComplexMethod", "LongMethod")
@Suppress("ComplexMethod")
internal fun registerOtherOperations(library: UILibrary) {
library
.addOperation("contains"){
Expand Down Expand Up @@ -85,11 +84,4 @@ internal fun registerOtherOperations(library: UILibrary) {
else -> Null.isNull(collection)
}
}
.addOperation("entries"){
val result = it.firstOrNull()?.let { map ->
if (map is Map<*, *>) map.entries.map { entry -> mapOf("key" to entry.key, "value" to entry.value) }
else null
}
result ?: emptyList<Any>()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ class ObservableNavigator(
}

override fun push(request: ViewRequest) {
val states = request.params?.map { ServerDrivenState(it.key, it.value) }
val view = ServerDrivenView(nimbus, states = states, description = request.url) { this }
val states = request.state?.map { ServerDrivenState(it.key, it.value) }
val view = ServerDrivenView(nimbus, states = states, events = request.events, description = request.url) { this }
testScope.launch {
try {
val tree = nimbus.viewClient.fetch(request)
Expand Down
Loading

0 comments on commit 542681a

Please sign in to comment.