How to improve my code to show a data array in a table, with TornadoFX? - kotlin

This is my way to display an array of data:
private val data = observableArrayList(
arrayOf("AAA", "111"),
arrayOf("BBB", "222"),
arrayOf("CCC", "333")
)
class HelloWorld : View() {
override val root = tableview<Array<String>>(data) {
column("name") { cellDataFeatures: TableColumn.CellDataFeatures<Array<String>, String> ->
SimpleStringProperty(cellDataFeatures.value[0])
}
column("value") { cellDataFeatures: TableColumn.CellDataFeatures<Array<String>, String> ->
SimpleStringProperty(cellDataFeatures.value[1])
}
}
}
It works but the code is quite complex. Is there any better way to do it?
(Maybe define a class to hold the data will make it much simpler, but I just want to test some uncommon cases)
Update:
A complete demo project for this: https://github.com/javafx-demos/tornadofx-tableview-array-data-demo

Here is a simpler way of defining your columns:
class HelloWorld : View() {
override val root = tableview(data) {
column<Array<String>, String>("name", { it.value[0].toProperty() })
column<Array<String>, String>("value", { it.value[1].toProperty() })
}
}
That said, using a specialized data structure would yield less headache :)
An alternative approach would be to configure just the cell item type and then a value factory:
column("name", String::class) {
value { it.value[0] }
}
column("value", String::class) {
value { it.value[1] }
}

Related

Should I get rid of big switch case?

I have a factory which includes many HTML attribute generators which returns one of them based on the type of attribute, so I wanted to see if there is a better way of doing this.
class AttributeHtmlGeneratorFactory {
fun create(property: String): AttributeHtmlGenerator {
when (property) {
"animation" -> {
return AnimationHtmlGenerator()
}
...
"left", "top" -> {
return PositionHtmlGenerator()
}
...
"scaleX" , "scaleY", ... , "direction" -> {
return UnusedAttributesHtmlGenerator()
}
this when switch has like 20 switch cases in it.
this is the interface which all these classes are using
interface AttributeHtmlGenerator {
fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel ): String
}
and this is where and how I'm using all of these:
var result = ""
HtmlComponentDataModel::class.memberProperties.forEach { member ->
val generator = AttributeHtmlGeneratorFactory().create(member.name)
result = result.plus(generator.generateHtml(member, component))
}
return result
also, this is a simple implementation of the interface:
class ButtonFillHtmlGenerator : AttributeHtmlGenerator {
override fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel): String {
var result = ""
member.get(component)?.let {
result = result.plus("background-color:${it};")
}
return result
}
}
is there anyway to make this better?
If you just want to reformat the when statement, I suggest you you do like this:
fun create(property: String): AttributeHtmlGenerator = when (property)
{
"animation" -> AnimationHtmlGenerator()
"left", "top" -> PositionHtmlGenerator()
"scaleX", "scaleY", "direction" -> UnusedAttributesHtmlGenerator()
else -> error("No generator found for property $property")
}
If you want to split this logic across modules, you would use a Map.
class AttributeHtmlGeneratorFactory {
private val generatorMap = mutableMapOf<String, () -> AttributeHtmlGenerator>()
init {
assignGeneratorToProperties("animation") { AnimationHtmlGenerator() }
assignGeneratorToProperties("left", "top") { PositionHtmlGenerator() }
}
fun create(property: String): AttributeHtmlGenerator {
return generatorMap[property]?.invoke() ?: error("No generator found for property $property")
}
fun assignGeneratorToProperties(vararg properties: String, provider: () -> AttributeHtmlGenerator) {
properties.forEach {
generatorMap[it] = provider
}
}
}
This way you can call assignGeneratorToProperties in parts of the code and thus split the initialization logic.
Performance-wise, when/if-else statements are really performant when you have a few cases but a HashMap outperforms them for a lot of elements. You decide what to use depending on your case.

Delegate a function to all other interfaces

I have an interface:
interface EmployeeActions
interface SalesEmployeeActions : EmployeeActions {
fun onSaleRequest()
//..more functions
}
interface HREmployeeActions : EmployeeActions {
}
...//more EmployeeActions
Now the way I am using this in the class is basically an aggregation where I will have many of these objects and I want to call stuff on them all together.
class FakeEmployeeDb {
val employees: MutableList<EmployeeActions> = mutableListOf()
private val salesEmployees:
get() = employees.filterIsInstance<SalesEmployeeActions>()
....
val salesManager = object: SalesEmployeeAction {
fun onSaleRequest() {
salesEmployees.forEach { it.onSaleRequest() }
}
... //more functions
}
private val .... // other Managers
// also at some point employees are added using this API...
fun addEmployee(employee: EmployeeActions) {
employees.add(employee)
}
}
Question:
Is there a kotlin-y way of writing this code in a scalable way?
In the real case, the client will only call APIs on these xyzManager objects and the job of each of them is to simply pass that information down to all employees.

Elegant way of doing if else statement with a Flux

I have the following kotlin method which currently uses a non-reactive mongodb repository (spring data) which returns a list.
fun register(userRegistration: UserRegistration) {
val existing = userRepository.findByTokenOrUserId(userRegistration.deviceToken, userRegistration.userId)
var alreadyRegistered = false
if (existing.isNotEmpty()) {
existing.forEach {
if (!isAlreadyRegistered(it, userRegistration)) {
userRepository.delete(it)
} else {
alreadyRegistered = true
}
}
}
if (!alreadyRegistered) {
val pnUser = PnUser(userRegistration.userId, userRegistration.deviceToken, userRegistration.region, userRegistration.locale, userRegistration.deviceType, userRegistration.osVersion, userRegistration.appVersion, userRegistration.timezone)
userRepository.save(pnUser)
}
}
How can I have the same behavior in an elegant way if userRepository.findByTokenOrUserId would return a Flux of PnUser instead of a List?
Thanks
As #Markus pointed out the most elegant way would be to split the Flux using Flux.groupBy and then doing the logic for each key.
Check out this answer for more information:
This doesn't do exactly the same but for me it will do:
fun registerUser(userRegistration: UserRegistration) {
userReactiveRepository.findByTokenOrUserId(userRegistration.userId, userRegistration.userId).map {
if (!isAlreadyRegistered(it, userRegistration)) {
userReactiveRepository.delete(it).map {
val pnUser = PnUser(userRegistration.userId, userRegistration.deviceToken, userRegistration.region, userRegistration.locale, userRegistration.deviceType, userRegistration.osVersion, userRegistration.appVersion, userRegistration.timezone)
userReactiveRepository.save(pnUser)
}
}
}
}

How do we compose queries in Kotlin Exposed?

I would like to create something similar to Ruby's ActiveRecord Scopes using Kotlin Exposed.
For example I would like to break the following query up so that the first part acts like a scope.
This query returns what I want.
val m1 = Measurement.wrapRows(Measurements.innerJoin(Runs).select {
((exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
})) and Measurements.name.eq("someName")
I would like to use this part as a scope:
val q1 = Measurements.innerJoin(Runs).select {
((exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
}))
}
and then be able to refine the query using the q1 "scope"
so something like this:
val q2 = q1.having { Measurements.name.eq("someName") } // which does not work
Ultimately I would like to push this down into either the Measurements object or the Measurement class so I can do something like this
Measurement.withDefaultRegion.where( Measurements.name.eq("someName")
I was able to get what I wanted by adding a couple of functions to the model's companion object.
The first one provides the "scope"
fun defaultRegion() :Op<Boolean> {
return Op.build {(exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
})}
}
The second function does the query using the scope and any refinements passed in and returns a "collection" of objects.
fun withDefaultRegionAnd( refinedBy: (SqlExpressionBuilder.()->Op<Boolean>)) : SizedIterable<Measurement> {
return Measurement.wrapRows(Measurements.innerJoin(Runs).select(Measurement.defaultRegion() and SqlExpressionBuilder.refinedBy() ))
}
At the client level I can simply do this:
val measurements = Measurement.withDefaultRegionAnd { Measurements.name.eq("someName") }
Here are the nearly table object and entity classes:
object Measurements : IntIdTable("measurements") {
val sequelId = integer("id").primaryKey()
val run = reference("run_id", Runs)
// more properties
}
class Measurement(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Measurement>(Measurements) {
fun defaultRegion() :Op<Boolean> {
return Op.build {(exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
})}
}
fun withDefaultRegionAnd( refinedBy: (SqlExpressionBuilder.()->Op<Boolean>)) : SizedIterable<Measurement> {
return Measurement.wrapRows(Measurements.innerJoin(Runs).select(Measurement.defaultRegion() and SqlExpressionBuilder.refinedBy() ))
}
}
var run by Run referencedOn Measurements.run
var name by Measurements.name
// more properties
}

TornadoFX how to add validation while editing TableView

Consider folowing example:
class Item(name: String, number: Int) {
val nameProperty = SimpleStringProperty(name)
var name by nameProperty
val numberProperty by lazy { SimpleIntegerProperty(number) }
var number by numberProperty
}
class MainView : View("Example") {
val items = listOf(Item("One", 1), Item("Two", 2)).observable()
override val root = vbox {
tableview(items) {
column("Name", Item::nameProperty).makeEditable()
column("Number", Item::numberProperty).makeEditable(NumberStringConverter())
enableCellEditing()
}
}
}
How can I add a validator while editing cells? Is the only way to do that is to add rowExpander with some textfield and try to validate a model there?
You can either implement your own cellfactory and return a cell that shows a textfield bound to a ViewModel when in edit mode and an label if not. Alternatively, if you're fine with always displaying a textfield, you can use cellFormat and bind the current item to an ItemModel so you can attach validation:
class ItemModel(item: Item) : ItemViewModel<Item>(item) {
val name = bind(Item::nameProperty)
val number = bind(Item::numberProperty)
}
class MainView : View("Example") {
val items = listOf(Item("One", 1), Item("Two", 2)).observable()
override val root = vbox {
tableview(items) {
column("Name", Item::nameProperty).makeEditable()
column("Number", Item::numberProperty).cellFormat {
val model = ItemModel(rowItem)
graphic = textfield(model.number, NumberStringConverter()) {
validator {
if (model.number.value == 123) error("Invalid number") else null
}
}
}
}
}
}
It will look like this:
While it works, it's sort of wasteful since the nodes are recreated frequently. I would recommend approach number one if performance is a concern, until we get cellFragment support for TableView like we have for ListView.
EDIT: I implemented cellFragment support, so it's possible to create a more robust solution which will show a label when not in edit mode and a validating textfield when you enter edit mode.
class ItemModel : ItemViewModel<Item>() {
val name = bind(Item::nameProperty)
val number = bind(Item::numberProperty)
}
class MainView : View("Example") {
val items = listOf(Item("One", 1), Item("Two", 2)).observable()
override val root = vbox {
tableview(items) {
column("Name", Item::nameProperty).makeEditable()
column("Number", Item::numberProperty).cellFragment(NumberEditor::class)
}
}
}
class NumberEditor : TableCellFragment<Item, Number>() {
// Bind our ItemModel to the rowItemProperty, which points to the current Item
val model = ItemModel().bindToRowItem(this)
override val root = stackpane {
textfield(model.number, NumberStringConverter()) {
removeWhen(editingProperty.not())
validator {
if (model.number.value == 123L) error("Invalid number") else null
}
// Call cell.commitEdit() only if validation passes
action {
if (model.commit()) {
cell?.commitEdit(model.number.value)
}
}
}
// Label is visible when not in edit mode, and always shows committed value (itemProperty)
label(itemProperty) {
removeWhen(editingProperty)
}
}
// Make sure we rollback our model to avoid showing the last failed edit
override fun startEdit() {
model.rollback()
}
}
This will be possible starting from TornadoFX 1.7.9.