Created
April 20, 2018 05:21
-
-
Save ValeriusGC/8e14cb42746a7c8b6fe513757686e438 to your computer and use it in GitHub Desktop.
Extends [ViewModel] with several handy classes and properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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