Build dropdown list with RxJS
Dropdown list is one of the most common web UI component, but yet one of the most difficult to implement.
Recently I was working on a navigation dropdown list with animation. But this time, I implemented it with RxJS, which makes the code so much cleaner than usual javascript implementation.
So this article is going to talk about how to use RxJS to implement a dropdown list, what is RxJS in general and dropdown list example.
RxJS
RxJS is a library called Reactive Extension in Javascript. This library enable Function Reactive Programming in Javascript. Which let user can manage data or events as functional stream, and provide a series of funcational methods to manupulate stream.
It is easy to understand how function interact with streams from RxMarble or RxVision
For Example:
1 | var source = Rx.Observable.range(1, 5) |
The code above provide a stream of data from 1 to 5. We can apply map function to mutate the stream to double the value, and filter out unwanted values.subscribe
method let us register listener to listen and react when receiving data from stream and print out the numbers.
Start with marble diagram
The reason why dropdown list is hard is because it involves interaction between events and states.
Let me show a basic use case for dropdown list:
- mouse enter item
- display dropdown
- mouse leave item
- wait for n seconds before closing dropdown
- mouse move into dropdown
- check the mouse is inside dropdown, keep dropdown open
- mouse leave dropdown
- wait for n seconds before closing dropdown
- check the mouse is not inside item or dropdown, close dropdown
Usually when mouse enter item, we record the current state as ‘inside item’, and keep track of the state.
If mouse leave item but enter dropdown in some amount of time, we have to keep dropdown open, else user can never click any items on dropdown. After mouse enter dropdown, we will usually check the mouse state again to make sure we needs to keep dropdown open. Adding animation will makes this interaction more complicate.
So, how can RxJS solve this problem?
Before we implement anything, it’s better to draw the marble diagram to understand the interaction:
1 | |check |check |
So first event is mouse enter item, when mouse enter item, it opens dropdown, and will not be effected or delay by other events.
Second event is mouse leave item, which trigger close dropdown check, if mouse does not move into dropdown, than close the dropdown.
Third is mouse enter dropdown, it will be triggered before dropdown closed, keeps dropdown open. And last is mouse leave dropdown. After mouse leave, dropdown will be closed.
We can find that the mouse enter and mouse leave events are actually a pair of actions both effect the state of mouse inside/outside item.
So we can actually merge the events into this:
1 | |check |check |
A and B present the state change. We can combine the state and action triggered to a table:
State | 1 | 2 |
---|---|---|
A | X | open |
B | open | close |
So if we merge the event into single stream and combine the latest state, the events become state changes:
1 | |check |check |
So with this diagram, we can start to implement dropdown in RxJs
Implement in RxJS
First, we generate event stream from mouseenter
mouseleave
events
1 | var $ = require('jquery') |
Then, we can map the event to return true and false to present the state inside and outside, and merge mouseenter and mouseleave event into a single stream.
1 | var inMenu = mouseEnterMenuItem.map( () => { return true }) |
And the next part is to combine those 2 event stream and transform them into a single state by combineLatest
, when event happened, return with both latest value from both stream.
combineLatest
Also the inTray stream needs to start with false since the combineLatest
does not work without all the streams have values.
1 | var state = Rx.Observable.combineLatest(inMenu, inTray) |
Manipulate event stream
So we get a pretty simple and clean dropdown list now, but we did not consider the case when mouse move in and move out in short periods of time,
we want to keep dropdown open when mouse move from item to dropdown, or move from dropdown to item.
We can use debounce to only trigger event that does not change in certain amount of time.
when mouse enter and leave item, the debounce can filter out the enter event, only capture the last event in certain time range.
1 | var state = Rx.Observable.combineLatest(inMenu, inTray) |
With debounce method, we can easily create and control the event stream without any timeout
call in javascript. And also easy to modify and change.
Throttle can also be used here to make sure the animation dropdown can be finished without other event interuption.
Comments