Bind an observable property to a collection function?

2k views Asked by At

I have a class Market which contains a collection of MarketUpdate objects called m_updates. For the UI I am using type-safe builders to create columns in a tableview like so:

override val root = tableview<Market> {
    val sortedMarketList = SortedList<Market>(markets)
    sortedMarketList.comparatorProperty().bind(this.comparatorProperty())
    items = sortedMarketList
...
column("Strikes", Market::m_strikes)
...

The m_strikes property is just a SimpleIntegerProperty directly owned by a Market object. However, I need to be able to build columns like these:

...
column("Created At", Market::m_updates::first::m_time)
...

...
column("Last Update", Market::m_updates::last::m_time)
...

where m_time is a SimpleLongProperty owned by a MarketUpdate object. When a Market object is updated, a new MarketUpdate object is added to the end of the m_updates collection. This means that the binding needs to automatically transition from one object to another, and that the tableview needs to be notified and update itself to reflect the data in the new object. I think binding by way of the first() and last() functions of the collection as described above captures the idea in a very simple way, but it won't compile.

There are many properties like m_strikes and m_time. How can I achieve this gracefully?

1

There are 1 answers

10
Edvin Syse On BEST ANSWER

If I understand your use case, what you want to do is to create an observable value that represents the time property for the first and last updates in a given Market object. To do that, you can create an objectBinding based on the updates list inside of each Market object, then extract the first() or last() element's timeProperty. In the following example, the TableView will update as soon as you augment the updates list in any Market object.

Bear in mind that the example requires each Market to have at least one update. If this isn't your case, make sure to handle null accordingly.

class Market {
    val updates = FXCollections.observableArrayList<MarketUpdate>()
}

class MarketUpdate {
    val timeProperty = SimpleObjectProperty(LocalDateTime.now())
}

class MarketList : View("Markets") {
    val markets = FXCollections.observableArrayList<Market>()
    val data = SortedFilteredList<Market>(markets)

    override val root = borderpane {
        prefWidth = 500.0

        center {
            tableview(markets) {
                column<Market, LocalDateTime>("Created at", { objectBinding(it.value.updates) { first() }.select { it!!.timeProperty } })
                column<Market, LocalDateTime>("Last update", { objectBinding(it.value.updates) { last() }.select { it!!.timeProperty } })
            }
        }
        bottom {
            toolbar {
                // Click to add an update to the first entry
                button("Add update").action {
                    markets.first().updates.add(MarketUpdate())
                }
            }
        }
    }

    init {
        // Add some test entries
        markets.addAll(
                Market().apply { updates.addAll(MarketUpdate(), MarketUpdate(), MarketUpdate()) },
                Market().apply { updates.addAll(MarketUpdate(), MarketUpdate(), MarketUpdate()) },
                Market().apply { updates.addAll(MarketUpdate(), MarketUpdate(), MarketUpdate()) },
                Market().apply { updates.addAll(MarketUpdate(), MarketUpdate(), MarketUpdate()) }
        )
    }
}

I've used a SortedFilteredList to make it easier to deal with sorting. The reason sort works here, is that the columns are actually represented by LocalDateTime values.

Resulting UI

I hope this gives you some ideas :)