Skip to content

Commit 02d320e

Browse files
authored
Document CompositeObservable
1 parent 034440b commit 02d320e

File tree

1 file changed

+79
-0
lines changed

1 file changed

+79
-0
lines changed

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,85 @@ bindings.add(binding2);
229229
bindings.dispose();
230230
```
231231

232+
###CompositeObservable
233+
234+
In UI development, it is not uncommon to have an event triggered in multiple places, or have inputs coming from multiple UI controls. Let's say you want to make a `refresh()` method callable from a `Button`, a `MenuItem`, and a <kbd>CTRL</kbd> + <kbd>R</kbd> hotkey combination.
235+
236+
```java
237+
//make refresh Button
238+
Button button = new Button("Refresh");
239+
Observable<ActionEvent> buttonClicks = JavaFxObservable.fromActionEvents(button);
240+
241+
//make refresh MenuItem
242+
MenuItem menuItem = new MenuItem("Refresh");
243+
Observable<ActionEvent> menuItemClicks = JavaFxObservable.fromActionEvents(menuItem);
244+
245+
//CTRL + R hotkeys on a TableView
246+
TableView<MyType> tableView = new TableView<>();
247+
Observable<ActionEvent> hotKeyPresses =
248+
JavaFxObservable.fromNodeEvents(tableView, KeyEvent.KEY_PRESSED)
249+
.filter(ke -> ke.isControlDown() && ke.getCode().equals(KeyCode.R))
250+
.map(ke -> new ActionEvent());
251+
252+
```
253+
254+
If you have all three components accessible in advance, you could use `Observable.merge()` to merge them all together.
255+
256+
```java
257+
Observable.merge(buttonClicks, menuItemClicks, hotKeyPresses)
258+
.subscribe(ae -> refresh());
259+
```
260+
261+
But this is preferable only if all the declarations are easily accessible. If they are in separate places throughout your UI code, this is problematic. Complex UI's are likely to be highly decoupled and have a model backing all the event flows. You cannot "add" and "remove" Observables in an `Observable.merge()` operation, and this can make designing the model rather frustrating.
262+
263+
At this point, you may be tempted to resort to a `Subject` to act as a sort of [event bus](https://github.com/google/guava/wiki/EventBusExplained) accepting inputs from any number of sources and outputs to any number of subscribers. Although this is a valid use case, Subjects are prone to abuse and can introduce many antipatterns.
264+
265+
Introducing the `CompositeObservable`. It is a tighter, safer alternative to a `Subject` or an event bus. You can `add()` and `remove()` Observables at any time from a `CompositeObservable`, and this is useful to put in an event model backing the application.
266+
267+
```java
268+
class MyEventModel {
269+
270+
private final CompositeObservable<ActionEvent> refreshRequests = new CompositeObservable<>();
271+
272+
public CompositeObservable<ActionEvent> getRefreshRequests() {
273+
return refreshRequests;
274+
}
275+
}
276+
```
277+
278+
Wherever the three controls are declared, you can `add()` the `Observable<ActionEvent>` from each control to the `CompositeObservable<ActionEvent>`. It will then merge them. You can then call the `toObservable()` to subscribe wherever those events are needed.
279+
280+
```java
281+
//make refresh Button
282+
Button button = new Button("Refresh");
283+
Observable<ActionEvent> buttonClicks = JavaFxObservable.fromActionEvents(button);
284+
myEventModel.getRefreshRequests().add(buttonClicks);
285+
286+
287+
//make refresh MenuItem
288+
MenuItem menuItem = new MenuItem("Refresh");
289+
Observable<ActionEvent> menuItemClicks = JavaFxObservable.fromActionEvents(menuItem);
290+
myEventModel.getRefreshRequests().add(menuItemClicks);
291+
292+
293+
//CTRL + R hotkeys on a TableView
294+
TableView<MyType> tableView = new TableView<>();
295+
296+
Observable<ActionEvent> hotKeyPresses =
297+
JavaFxObservable.fromNodeEvents(tableView, KeyEvent.KEY_PRESSED)
298+
.filter(ke -> ke.isControlDown() && ke.getCode().equals(KeyCode.R))
299+
.map(ke -> new ActionEvent());
300+
301+
myEventModel.getRefreshRequests().add(hotKeyPresses);
302+
303+
//subscribe to refresh events
304+
myEventModel.getRefreshRequests().subscribe(ae -> refresh());
305+
```
306+
307+
Every time you `add()` or `remove()` an `Observable` to a `CompositeObservable`, it will affect all existing Subscribers. For UI development, this is good because there is no sensitivity to the order of adding Observables and subscribing.
308+
309+
Of course, you can pass around any type `T` in a `CompositeObservable<T>` and not just `ActionEvent`. It can be = helpful to pass around entire data structures, such as `CompositeObservable<Set<MyType>>`, to relay requests and inputs between controls.
310+
232311

233312
### JavaFX Scheduler
234313

0 commit comments

Comments
 (0)