Skip to content

Instantly share code, notes, and snippets.

@ValeriusGC
Created April 20, 2018 05:21
Show Gist options
  • Save ValeriusGC/8e14cb42746a7c8b6fe513757686e438 to your computer and use it in GitHub Desktop.
Save ValeriusGC/8e14cb42746a7c8b6fe513757686e438 to your computer and use it in GitHub Desktop.
Extends [ViewModel] with several handy classes and properties
package com.gdetotut.sample.smartprop
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.subjects.PublishSubject
import javafx.beans.binding.BooleanBinding
import javafx.beans.property.Property
import tornadofx.*
/**
* `Smart`-conception extends [ViewModel] with handy properties like
* * reactive event [onCommit] to subscribe for commit events
* * [reset] function to return to an initial prop
* * [isResettable] method to bind with observables
*/
abstract class SmartViewModel<T : Any> : ViewModel() {
/**
* Reactive event for subscribing on [commit].
*/
val onCommit = PublishSubject.create<SmartViewModel<T>>()!!
override fun onCommit() {
onCommit.onNext(this)
}
/**
* Should get current value.
*/
abstract fun getValue(): T
/**
* Method to calculate if object can be reset.
*/
abstract fun isResettable(): BooleanBinding
/**
* Resetting with optional auto-commit.
*/
fun reset(autoCommit: Boolean = false) {
doReset()
if (autoCommit) {
commit()
}
}
protected abstract fun doReset()
}
/**
* Generic wrapper for prop to [SmartViewModel].
* Wraps prop of type T to the property and creates [SmartPropModel] based on it.
*/
class SmartPropModel<T : Any>(private val initial: T) : SmartViewModel<T>() {
/**
* PropertyDelegate
*/
private var src by property(initial)
/**
* [ObjectProperty] based on delegate
*/
fun srcProp() = getProperty<T>(SmartPropModel<T>::src)
/**
* Model's property based on [src]. Initiates later in the fabric method [smartPropModel].
* Maybe seems it is surplus field (enough [srcProp], but only with this one we can success binding.
*/
lateinit var prop: Property<T>
/**
* Getting current value
*/
override fun getValue(): T {
return prop.value
}
/**
* The prop can be reset if it does not equal to initial one.
*/
override fun isResettable(): BooleanBinding {
return booleanBinding(initial, prop) { prop.value != initial }
}
override fun doReset() {
prop.value = initial
}
}
/**
* Helper method to create [SmartPropModel].
* Due to this method we can make template building (`reified` technique).
*/
inline fun <reified T : Any> smartPropModel(initial: T, noinline op: (SmartPropModel<T>.() -> Unit)? = null)
: SmartPropModel<T> {
val spm = SmartPropModel(initial)
spm.prop = spm.bind { spm.srcProp() }
op?.invoke(spm)
return spm
}
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
// Sample for [SmartViewModel] concept
//
// Here we use template SmartPropModels that are wrapping single values
// and complex model with class User
//----------------------------------------------------------------------------------------------------------------------
/**
* Class with 2 properties initialized with default values.
*/
class User(title: String = "", check: Boolean = false) {
private var title by property(title)
fun titleProp() = getProperty<String>(User::title)
private var check by property(check)
fun checkProp() = getProperty<Boolean>(User::check)
override fun toString(): String {
return "User(title='$title', check=$check)"
}
}
/**
* [SmartViewModel] for [User].
* Here is how one can process complex [src].
*/
class SmartUserModel(private var user: User) : SmartViewModel<User>() {
private val titleInit = user.titleProp().get()!!
private val checkInit = user.checkProp().get()!!
val title = bind { user.titleProp() }
val check = bind { user.checkProp() }
override fun getValue(): User {
return user
}
/**
* Here we calculate both properties to define reset possibility.
*/
override fun isResettable(): BooleanBinding {
return booleanBinding(titleInit, title) { title.value != titleInit }
.or(booleanBinding(checkInit, check) { check.value != checkInit })
}
override fun doReset() {
title.value = titleInit
check.value = checkInit
}
override fun toString(): String {
return "SmartUserModel(titleInit='$titleInit', checkInit=$checkInit, title=${title.value}, check=${check.value}, User=$user)"
}
}
/**
* Here we use all events and observables of [SmartViewModel] to illustrate the power of this technique.
*/
class SmartModelConceptForm : View("Sample of SmartViewModel concept") {
/**
* Model of [User]
*/
private val userModel = SmartUserModel(User("User", false))
/**
* Models for single values and their list for handy iterating.
*/
val titleModel = smartPropModel("begin")
val checkModel = smartPropModel(false)
val modelList = listOf<SmartViewModel<*>>(titleModel, checkModel).observable()
private val cd = CompositeDisposable()
init {
cd.add(userModel.onCommit.subscribe(
{ println("userModel.onCommit: ${it.getValue()}") }
))
modelList.forEach {
cd.add(it.onCommit.subscribe({
println("modelList.onCommit: ${it.getValue()}")
}))
}
primaryStage.setOnHidden {
println("BYE!")
cd.clear()
}
}
override val root = vbox {
form {
spacing = 5.0
paddingAll = 5.0
fieldset("for User") {
textfield("hello") {
bind(userModel.title)
}
checkbox("check") {
bind(userModel.check)
}
}
hbox {
button("Commit") {
enableWhen(userModel.dirty)
action {
userModel.commit()
}
}
button("Rollback") {
enableWhen(userModel.dirty)
action {
userModel.rollback()
}
}
}
button("reset") {
enableWhen(userModel.isResettable())
action { userModel.reset() }
}
}
form {
spacing = 5.0
paddingAll = 5.0
fieldset("For list of single") {
textfield("hello") {
bind(titleModel.prop)
}
checkbox("check") {
bind(checkModel.prop)
}
}
hbox {
button("Commit") {
enableWhen(titleModel.dirty.or(checkModel.dirty))
action {
modelList.filter { it.isDirty }.forEach { it.commit() }
}
}
button("Rollback") {
enableWhen(titleModel.dirty.or(checkModel.dirty))
action {
modelList.filter { it.isDirty }.forEach { it.rollback() }
}
}
}
button("reset") {
enableWhen(titleModel.isResettable().or(checkModel.isResettable()))
action {
modelList.forEach {
it.reset()
}
}
}
}
}
}
class SmartPropView1App : App() {
override val primaryView = SmartModelConceptForm::class
}
fun main(args: Array<String>) {
launch<SmartPropView1App>(*args)
}
// ~Sample
//----------------------------------------------------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment