diff --git a/client/src/main/kotlin/com/r3corda/client/model/Models.kt b/client/src/main/kotlin/com/r3corda/client/model/Models.kt index 0d65d6a183..605c2c4226 100644 --- a/client/src/main/kotlin/com/r3corda/client/model/Models.kt +++ b/client/src/main/kotlin/com/r3corda/client/model/Models.kt @@ -19,12 +19,51 @@ import kotlin.reflect.KProperty * This allows decoupling of UI logic from stream initialisation and provides us with a central place to inspect data * flows. It also allows detecting of looping logic by constructing a stream dependency graph TODO do this. * - * General rule of thumb: A stream/observable should be a model if it may be reused several times. - * * Usage: * // Inject service -> client event stream * private val serviceToClient: EventStream by eventStream(WalletMonitorModel::serviceToClient) * + * Each Screen code should have a code layout like this: + * + * class Screen { + * val root = (..) + * + * [ inject UI elements using fxid()/inject() ] + * + * [ inject observable dependencies using observable()/eventSink() etc] + * + * [ define screen-specific observables ] + * + * init { + * [ wire up UI elements ] + * } + * } + * + * For example if I wanted to display a list of all USD cash states: + * class USDCashStatesScreen { + * val root: Pane by fxml() + * + * val usdCashStatesListView: ListView by fxid("USDCashStatesListView") + * + * val cashStates: ObservableList by observableList(ContractStateModel::cashStates) + * + * val usdCashStates = cashStates.filter { it.(..).currency == USD } + * + * init { + * Bindings.bindContent(usdCashStatesListView.items, usdCashStates) + * usdCashStatesListView.setCellValueFactory(somethingsomething) + * } + * } + * + * The UI code can just assume that the cash state list comes from somewhere outside. The initialisation of that + * observable is decoupled, it may be mocked or be streamed from the network etc. + * + * Later on we may even want to move all screen-specific observables to a separate Model as well (like usdCashStates) - this + * would allow moving all of the aggregation logic to e.g. a different machine, all the UI will do is inject these and wire + * them up with the UI elements. + * + * Another advantage of this separation is that once we start adding a lot of screens we can still track data dependencies + * in a central place as opposed to ad-hoc wiring up the observables. */ inline fun observable(noinline observableProperty: (M) -> Observable) = @@ -56,6 +95,10 @@ inline fun observableListReadOnly(noinline observableListPr object Models { private val modelStore = HashMap, Any>() + + /** + * Holds a class->dependencies map that tracks what screens are depending on what model. + */ private val dependencyGraph = HashMap, MutableSet>>() fun initModel(klass: KClass) = modelStore.getOrPut(klass) { klass.java.newInstance() }