wiki:HowToCustomizeSelectionPanel

How To Customize SelectionPanel widget

Introduction

This HowTo describes how customize the SelectionPanel widget for display the results the way you want. In this HowTo, we'll see what are the two possible methods to customize this widget and make a example for each. For customize the SelectionPanel, you'll need to know the Selection API for get layers, features and values. (You can check here to get more information on the current Selection API).

First method: customize by defining a function

The simpliest one. This allow you to create a javascript function that will receive the Selection object in parameter. In this function, You'll use the Selection API for handle the features and do whatever you want with them.

Example

  1. Create the javascript function implementation
    function displaySelection(oSelection)
    {
        //TODO: this just gets the first map, we need them all
        selection = null;
        for (var mapName in oSelection) {
            selection = oSelection[mapName];
            break;
        }
        if (selection && selection.getNumElements) {
            //obtain a reference to the HTML Element that the results
            //will be placed in
            var resultElm = $('divResults');
            resultElm.innerHTML = '';
            for (var i=0; i<selection.getNumLayers(); i++) {
                var selectionLayer = selection.getLayer(i);
                var propNames = selectionLayer.getPropertyNames();
                var span = document.createElement('span');
                span.className = 'selectionResultsTitle';
                span.innerHTML = 'Layer ' + selectionLayer.getName();
                resultElm.appendChild(span);
                var table = document.createElement('table');
                table.className = 'selectionResultsTable';
                resultElm.appendChild(table);
                //set up the table header to be the property names         
                var thead = document.createElement('thead');
                table.appendChild(thead);
                var tr = document.createElement('tr');
                thead.appendChild(tr);
                for (var j=0; j<propNames.length; j++) {
                    var td = document.createElement('td');
                    td.innerHTML = propNames[j];
                    tr.appendChild(td);
                }
                //output the selection values
                var tbody = document.createElement('tbody');
                table.appendChild(tbody);
                for (var j=0; j<selectionLayer.getNumElements(); j++) {
                    var tr = document.createElement('tr');
                    tbody.appendChild(tr);
                    for (var k=0; k<propNames.length; k++) {
                        var td = document.createElement('td');
                        td.innerHTML = selectionLayer.getElementValue(j, k);
                        tr.appendChild(td);
                    }
                }
            }
        } 
    }
    

This function will produce a html table with one feature per row.

  1. Configuration needs in the ApplicationDefinition.xml You'll need to modify the SelectionPanel widget tag for enable the customization. By adding the SelectionRenderer tag in the Extension section, you're telling to the SelectionPanel to use your function instead of the default one. The widget tag should now looks like:
    <Widget xsi:type="WidgetType">
      <Name>divList</Name>
      <Type>SelectionPanel</Type>
      <StatusItem/>
      <Extension xsi:type="CustomContentType">
        <SelectionRenderer>displaySelection</SelectionRenderer>
      </Extension>
    </Widget>
    

Second method: customize by creating a renderer class

This method let you define a special behavior for the SelectionPanel. It can be a little bit more complicated than the first one but it can be very useful for those who have to make a complex behavior. It also keep a structured application using the Object Oriented programming. This method is also the best if you want to support pagination (displaying results by batches) in your results panel.

Basicly, creating a renderer class is not very complicated. You'll have to create a class that inherit from the Fusion.Widget.SelectionPanel.SelectionRenderer and implement at least these three functions: initialize, updateSelection and clearSelection. The first function is to initialize the base class and initialize all specific properties of your renderer (creating div, button, image, adding CSS, etc.). The second will be binded to the Fusion.Event.MAP_SELECTION_ON event, and the third to Fusion.Event.MAP_SELECTION_OFF.

Example 1: simple renderer

Fusion.Widget.SelectionPanel.SimpleRenderer = OpenLayers.Class(Fusion.Widget.SelectionPanel.SelectionRenderer,
{
    initialize : function(selectionPanel) {
        // This must always be called to initialize the base class.
        Fusion.Widget.SelectionPanel.SelectionRenderer.prototype.initialize.apply(this, [selectionPanel]);
    },

    updateSelection: function() {
        this.getMap().getSelection(
            OpenLayers.Function.bind(this.renderSelection, this));
    },

    clearSelection: function() {
        // do clear stuff...
        this.oSelection = null;
    },
    
    renderSelection: function(oSelection) {
        //TODO: this just gets the first map, we need them all
        selection = null;
        for (var mapName in oSelection) {
            selection = oSelection[mapName];
            break;
        }
        if (selection && selection.getNumElements) {
            //obtain a reference to the HTML Element that the results
            //will be placed in
            var resultElm = this.oSelectionPanel.domObj;
            resultElm.innerHTML = '';
            for (var i=0; i<selection.getNumLayers(); i++) {
                var selectionLayer = selection.getLayer(i);
                var propNames = selectionLayer.getPropertyNames();
                var span = document.createElement('span');
                span.className = 'selectionResultsTitle';
                span.innerHTML = 'Layer ' + selectionLayer.getName();
                resultElm.appendChild(span);
                var table = document.createElement('table');
                table.className = 'selectionResultsTable';
                resultElm.appendChild(table);
                //set up the table header to be the property names         
                var thead = document.createElement('thead');
                table.appendChild(thead);
                var tr = document.createElement('tr');
                thead.appendChild(tr);
                for (var j=0; j<propNames.length; j++) {
                    var td = document.createElement('td');
                    td.innerHTML = propNames[j];
                    tr.appendChild(td);
                }   
                //output the selection values
                var tbody = document.createElement('tbody');
                table.appendChild(tbody);
                for (var j=0; j<selectionLayer.getNumElements(); j++) {
                    var tr = document.createElement('tr');
                    tbody.appendChild(tr);
                    for (var k=0; k<propNames.length; k++) {
                        var td = document.createElement('td');
                                            td.innerHTML = selectionLayer.getElementValue(j, k);
                        tr.appendChild(td);
                    }
                }
            }
        } 
    }    
});

And the widget tag in the ApplicationDefinition.xml

<Widget xsi:type="WidgetType">
  <Name>divResults</Name>
  <Type>SelectionPanel</Type>
  <StatusItem/>
  <Extension xsi:type="CustomContentType">
     <SelectionRenderer>Fusion.Widget.SelectionPanel.SimpleRenderer</SelectionRenderer>
  </Extension>
</Widget>

This is a very simple renderer created with the function we'd use in the first method example.I just bind the renderSelection function to handle results and display them. It's exactly the same thing that using the first method. In this simple case, you should use the first method instead than create a class.

Example 2: complex renderer

Fusion.Widget.SelectionPanel.SelectionRendererHorizontal = OpenLayers.Class(Fusion.Widget.SelectionPanel.SelectionRenderer,
{
    initialize : function(selectionPanel) {
        Fusion.Widget.SelectionPanel.SelectionRenderer.prototype.initialize.apply(this, [selectionPanel]);
   
        var d = document.createElement('div');
        this.featureDiv = document.createElement('div');
        this.featureDiv.innerHTML = 'No Selection';
        Element.addClassName(this.featureDiv, 'noSelection');
        d.appendChild(this.featureDiv);

        if (this.iResultsPerPage != 0) {
            this.previousButton = document.createElement('img');
            this.previousButton.src = this.oSelectionPanel.previousIcon;
            this.previousButton.style.position = "absolute";
            this.previousButton.style.left = "0px";
            this.previousButton.style.padding = "3px";
            Event.observe(this.previousButton, 'click',
                          OpenLayers.Function.bind(this.renderLayers, this, 'prev'));
            this.nextButton = document.createElement('img');
            this.nextButton.src = this.oSelectionPanel.nextIcon;
            this.nextButton.style.position = "absolute";
            this.nextButton.style.right = "0px";
            this.nextButton.style.padding = "3px";
            Event.observe(this.nextButton, 'click',
                          OpenLayers.Function.bind(this.renderLayers, this, 'next'));
            
            d.appendChild(this.previousButton);
            d.appendChild(this.nextButton);
        }

        Element.addClassName(this.featureDiv, 'selectionPanelContent');
        Fusion.addWidgetStyleSheet(this.oSelectionPanel.getLocation() + 'SelectionPanel/SelectionPanel.css');
        this.oSelectionPanel.domObj.appendChild(d);
    },

    updateSelection: function() {
        this.getMap().getSelection(
            OpenLayers.Function.bind(this.renderSelection, this));
    },

    clearSelection: function() {
        this.oSelection = null;
        Element.addClassName(this.featureDiv, 'noSelection');
        this.featureDiv.innerHTML = OpenLayers.i18n('noSelection');
    },
    
    renderSelection: function(oSelection) {
        //TODO: this just gets the first map, we need them all
        this.oSelection = null;
        for (var mapName in oSelection) {
            this.oSelection = oSelection[mapName];
            break;
        }
        this.resetPageIndexes();
        this.renderLayers("next");
    },
    
    renderLayers: function(renderingPage) {
        if (!this.oSelection) {
            return;
        }
        
        Element.removeClassName(this.featureDiv, 'noSelection');
        this.featureDiv.innerHTML = '';
        
        var nLayers = this.oSelection.getNumLayers();
        for (var i=0; i<nLayers; i++) {
            var table = document.createElement('table');
            table.style.borderLeft = "1px solid #CCCCCC";
            table.style.marginBottom = "10px";
            var layerObj = this.oSelection.getLayer(i);
            var aNames = layerObj.getPropertyNames();
            //find the legend label from the Map layer objects
            var mapLayers = this.getMap().aMaps[0].aLayers; //TODO: allow multiple maps
            var labelName = layerObj.getName();
            for (var j=0; j<mapLayers.length; ++j) {
                if (mapLayers[j].layerName == labelName) {
                    labelName = mapLayers[j].legendLabel;
                    break;
                }
            }
            
            var thead = document.createElement('thead');
            var tr = document.createElement('tr');
            var th = document.createElement('th');
            th.innerHTML = labelName;
            th.colSpan=aNames.length;
            th.style.textAlign = "center";
            tr.appendChild(th);
            thead.appendChild(tr);
            tr = document.createElement('tr');
            for (var j=0; j<aNames.length; j++) {
                th = document.createElement('th');
                th.innerHTML = aNames[j];
                th.style.textAlign = "center";
                tr.appendChild(th);
            }
            thead.appendChild(tr);
            table.appendChild(thead);
            var tbody = document.createElement('tbody');
            var page = (renderingPage == 'next') ? this.getNextPage(layerObj): this.getPreviousPage(layerObj);
            this.renderFeatures(page,tbody);
            table.appendChild(tbody);
            this.featureDiv.appendChild(table);
        }

    },

    renderFeatures: function(page, dom) {
        if (!page)
            return;

        for (var i=0; i<page.length; i++) {
            var tr = document.createElement('tr');
            if (i%2) {
                Element.addClassName(tr, 'oddRow');
            }
            for (var j=0; j<page[i].length; j++) {
                var td = document.createElement('td');
                td.innerHTML = page[i][j];
                tr.appendChild(td);
            }
            dom.appendChild(tr);            
        }
    }
});

And the widget tag in the ApplicationDefinition.xml

<Widget xsi:type="WidgetType">
  <Name>divResults</Name>
  <Type>SelectionPanel</Type>
  <StatusItem/>
  <Extension xsi:type="CustomContentType">
    <NextImageUrl>images/icon_next.png</NextImageUrl>
    <PreviousImageUrl>images/icon_previous.png</PreviousImageUrl>
    <ResultsPerPage>10</ResultsPerPage>
    <SelectionRenderer>Fusion.Widget.SelectionPanel.SelectionRendererHorizontal</SelectionRenderer>
  </Extension>
</Widget>

As you can see, this example is more complex. I use the initialize method for initialize my html element, add some css styles and bind some events to the appropriate function of this class. I also add a previous/next button for navigate page to page because this renderer support the pagination. So, i've set the ResultsPerPage to 10 in the ApplicationDefinition.xml and all my pages will not exceed this limit.

Pagination

The pagination is available for everyone who use a renderer for customize the SelectionPanel.

ApplicationDefinition parameter: ResultsPerPage: The number of desired results by batch.

This is the current API for get results by batches.

getNextPage():

return an array that contains the next batch of results.

getPreviousPage():

return an array that contains the previous batch of results.

resetPageIndexes():

This reset the page indexes for the pagination system and have to be called by your renderer on each selection updated.

Last modified 11 years ago Last modified on Sep 16, 2008 7:23:00 AM