GWT DisclosurePanel


December 5, 2013 Maithilish

6.6. FactDisclosePanel

FactDisclosePanel shows or hide the Price, PB and PE graphs.

FactDisclosePanel
Figure 6.6. FactDisclosePanel


FactDisclosePanel (FDP) uses two GWT widgets, GWT DisclosurePanel and inner CaptionPanel. DisclosurePanel, contains header and a content panel, that discloses the underlying content when a user clicks the header, and CaptionPanel encloses its contents with border and a caption on upper left corner.
in.fins.client.widget/FactDisclosePanel.java

        @UiField(provided = true)
        DisclosurePanel dPanel;
        @UiField
        CaptionPanel cPanel;

        @UiConstructor
        public FactDisclosePanel(String header, String caption) {
                FinsResources images = FinsResources.INSTANCE;
                dPanel = new DisclosurePanel(images.barChart(), images.chart(), header);
                initWidget(binder.createAndBindUi(this));
                cPanel.setCaptionText(caption);
                dPanel.setContent(cPanel);
                setVisible(false);
        }

Instead of stock header, we want custom header for DisclosurePanel, and for this we use @UiField(provided = true) annotation for disclosurePanel field and instantiate it before calling UiBinder.
CaptionPanel is set as content of DisclosurePanel to which child widgets are added by implementing the HasWidget interface.
Following Ui declaration adds FDP to Snapshot.
in.fins.client.content/Snapshot.ui.xml

        <g:row>
           <g:customCell>
                <f:FactDisclosePanel header="Price" caption="Price" category="Quote">
                        <f:ChartPanel title="Price" key="Price" width="450px" 
                                      height="220px" rangeSelector="false" />
                </f:FactDisclosePanel>
           </g:customCell>

           <!-- likewise for PE and PB -->

        </g:row>

This UI declaration adds FDP to Snapshot and a ChartPanel to FDP and ChartPanel uses attributes width, rangeSelector etc to customize the chart.
HasWidgets

To enable custom widget to hold other widgets, we have to implement HasWidget interface, and FDP has to hold ChartPanel and for this it implements HasWidget. This interface enables the widget to add or remove child widgets and to enumerate the widgets it contains. It defines following methods.

  • add(Widget w) – adds a child widget.
  • clear() – removes all child widgets.
  • iterator() – returns iterator for the contained widgets.
  • remove(Widget w) – removes a child widget.
in.fins.client.widget/FactDisclosePanel.java

        @Override
        public void add(Widget w) {
                cPanel.add(w); 

                // additional logic explained later              
        }

        @Override
        public void clear() {
                cPanel.clear();
        }

        @Override
        public Iterator<Widget> iterator() {
                return cPanel.iterator();
        }

        @Override
        public boolean remove(Widget w) {
                return cPanel.remove(w);
        }

In these methods, we add (as well as remove and clear) ChartPanel widget to CaptionPanel.
DataGroupEvent and DataGroupAction

SymbolEvent that was used earlier provides data typically for a date for the entire symbol, but to plot the graph we require data for all dates between two periods for specific Fact. For example, to plot Price graph we require Facts which has key Price for a range of dates.

DataGroupEvent holds a DataGroup for a specific category with data for all dates. For price graph, it holds DataGroup for Quote category, and from DataGroup we can get List<Data> and each Data contains List<Fact> which has Price Fact.
DataGroupAction fetches DataGroup of a category and fires DataGroupEvent. Like SymbolAction, which was covered earlier, DataGroupAction is a mediator between two widgets and is responsible to fetch DataGroup from datastore.
Flow of events

Flow of event is as shown in next figure.

Flow of Events
Figure 6.7. Flow of Events


Event flow happens in two stages. Initially, when user selects a name, SymbolAction fires a SymbolEvent which is handled by DataGroupAction and assigns this symbol to a field for later use. This event is common for the entire Snapshot page, and consumed by other actions as well. Later when user expands FDP, it generates an OpenEvent and DataGroupAction handles this event to fetch DataGroup for the symbol held by its field and fires DataGroupEvent. This event is propagated to FDP and then to its child widget ChartPanel which uses DataGroup’s Data and Facts to plot the graph.
DataGroupAction fetches DataGroup when user expands the FDP, and with the help of boolean flag, symbolChanged, reuses the DataGroup on subsequent clicks until user selects another symbol.
in.fins.client.action/DataGroupAction.java

        private void execute() {
                if (symbolChanged == false && dataGroup != null) {
                        log.fine("No Symbol change. Using existing dataGroup");
                        EventBus.get().fireEventFromSource(new DataGroupEvent(dataGroup),
                                        eventSource);
                } else {
                        DataGroup newDataGroup = SymbolDatabase.getDataGroup(
                                        symbol.getName(), category, filter);
                        log.fine("Symbol Changed. Fetching dataGroup");

          ....

Wiring all of them

UiFactory of FDP and ChartPanel wire them with DataGroupAction.

in.fins.client.content/Snapshot.java

        @UiFactory
        public FactDisclosePanel factDisclosePanelFactory(String header,
                        String caption, String category) {
                DataGroupAction<DisclosurePanel> dataGroupAction = 
                      new DataGroupAction<DisclosurePanel>(
                                category, getFilter(category));
                EventBus.get().addHandlerToSource(SymbolEvent.TYPE, this,
                                dataGroupAction);

                FactDisclosePanel fdp = new FactDisclosePanel(header, caption);
                // required to make fdp visible after symbol is loaded
                EventBus.get().addHandlerToSource(SymbolEvent.TYPE, this, fdp);
                fdp.addOpenHandler(dataGroupAction);
                EventBus.get().addHandlerToSource(DataGroupEvent.TYPE, dataGroupAction,
                                fdp);
                return fdp;
        }

        @UiFactory
        public ChartPanel chartPanelFactory(String title, String key, String width,
                        String height, boolean rangeSelector) {
                ChartPanel cp = new ChartPanel(title, key, width, height, rangeSelector);
                return cp;
        }

FDP also listens to SymbolEvent make itself visible on name selection. This makes FDP visible only after symbol is loaded whereas use of NameEvent makes visible slightly before, which may look odd. Other widgets of Snapshot share the SymbolEvent and hence its use doesn’t affect the performance.
It is essential to understand that, FDP attaches open handler to DataGroupAction and in turn listens for DataGroupEvent fired by DataGroupAction.
In factory method, we are able to make FDP as a handler for the DataGroupEvent fired by the DataGroupAction, but FDP has to send it down to ChartPanel and following code accomplishes this.
in.fins.client.widget/FactDisclosePanel.java

        @Override
        public void add(Widget w) {
                cPanel.add(w);
                if (w instanceof DataGroupHandler) {
                        EventBus.get().addHandlerToSource(DataGroupEvent.TYPE, this,
                                        (DataGroupHandler) w);
                }
        }

In FDP, add() method registers all child widgets, which are instances of DataGroupHandler, as handlers of DataGroupEvent. Once FDP receives DataGroupEvent, it propagates the event to child widgets. To do so, it creates a new DataGroupEvent because it is not a good idea to handle the existing event outside this method.

Event boundary

Never refer the event received from EventBus outside its handler.
DataGroupEvent propagation
Figure 6.8. DataGroupEvent propagation


We will cover Google Chart Tools in a later chapter, and for the moment ChartPanel uses a basic VerticalPanel to display the chart data in textual format. In handler method, we traverse through List<Data> of DataGroup and add Facts with matching keys to VerticalPanel as HTML widget.
in.fins.client.widget/ChartPanel.java

        @Override
        public void onDataGroupChange(DataGroupEvent dataGroupEvent) {
                vPanel.clear();
                dataGroup = dataGroupEvent.getDataGroup();
                List<Data> dataList = dataGroup.getDataList();
                vPanel.add(new HTML(dataGroup.getCategory() + " : " + key));
                for (Data data : dataList) {
                        for (Fact fact : data.getFacts()) {
                                if (fact.getKey().equals(key)) {
                                        vPanel.add(new HTML(data.getDate() + " - "
                                                        + fact.getValue()));
                                }
                        }
                }
        }

SymbolEvent vs DataGroupEvent

FactPanel designed in the previous chapter could have used DataGroupEvent. But it uses SymbolEvent by taking datasource access and RPC calls into consideration. On name selection, widgets in Snapshot display data from multiple DataGroup in one go, and if each of these widgets use DataGroupEvent, then it involves multiple request to the server. With SymbolEvent, we will be able to get data for multiple DataGroups in single RPC.
As we progress, widgets are getting more and more sophisticated and next in line is FactTable which uses widgets from Cell Widgets group.