Changeset 5480


Ignore:
Timestamp:
13/07/09 14:29:35 (10 years ago)
Author:
pnorton
Message:

Added the legend back into the UI and added an object to cache the WMC javascript objects.

Location:
cowsclient/branches/qesdi/cowsclient
Files:
3 added
9 edited

Legend:

Unmodified
Added
Removed
  • cowsclient/branches/qesdi/cowsclient/controllers/wmsviz.py

    r5457 r5480  
    3939            return self.removeViewItem(self.inputs['removeItem']) 
    4040         
     41         
     42        log.debug("self.inputs = %s" % (self.inputs,)) 
    4143        # check if we're doing an AJAX callback to get some WMC data 
    4244        if 'REQUEST' in self.inputs: 
  • cowsclient/branches/qesdi/cowsclient/lib/wmc_util.py

    r5457 r5480  
    6565    try: 
    6666        #req = urllib2.Request(endpoint,urllib.urlencode(request.params),{'Cookie': request.headers.get('Cookie', '')}) 
     67        log.debug("endpoint = %s" % (endpoint,)) 
    6768        req = urllib2.Request(endpoint, None, {'Cookie': request.headers.get('Cookie', '')}) 
    6869        url = req.get_full_url() 
     
    8586    log.info('Getting WebMapContext from endpoint: ' + endpoint) 
    8687    #urlstring=('%s&request=GetContext'%(str(endpoint))) 
    87     urlstring = str(endpoint) 
     88    urlstring=('%s?request=GetContext&service=WMS'%(str(endpoint))) 
     89#    urlstring = str(endpoint) 
    8890    log.info("urlstring=%s" % (urlstring,)) 
    8991    #cookies are passed to enable authorisation mechanisms e.g. ndg security 
  • cowsclient/branches/qesdi/cowsclient/public/js

    • Property svn:ignore set to
      OpenLayers
  • cowsclient/branches/qesdi/cowsclient/public/js/layerControl.js

    r5457 r5480  
    3636     *  NB, this control must include a method, updateDomainDiv(OpenLayers.Bounds) 
    3737     */ 
    38     initialize: function(treeDiv, layerDiv, coordControl)  
     38    initialize: function(treeDiv, layerDiv, wmcRetriever, newEndpointInputId, addNewEndpointBtnId )  
    3939    { 
    4040                WMSC.log("Initialising Control"); 
    4141                this.treeDiv = treeDiv; 
    4242                this.layerDiv = layerDiv; 
    43                 this.coordControl = coordControl; 
    44  
     43                 
     44                this.wmcRetriever = wmcRetriever; 
     45                 
     46                this.endpointInputBox = $(newEndpointInputId); 
     47                this.addNewEndpointBtn = $(addNewEndpointBtnId); 
     48                this.addNewEndpointBtn.onclick = this.onNewEndpointClick.bindAsEventListener(this); 
     49                 
    4550                this.events = new OpenLayers.Events(this, $(this.treeDiv), 
    4651                                            this.EVENT_TYPES); 
     
    7075                                CRS: 'CRS:84',  
    7176                                transparent: 'true',  
    72                                 styles: 'contour',  
     77                                styles: 'grid',  
    7378                                cmap: 'jet',  
    7479                                time:'1905-01-15T00:00:00.0' }; 
     
    131136    addListeners: function() 
    132137    { 
    133         for (var i = 0; i <= this.tree.getRoot().children.length; i++) 
     138        for (var i = 0; i < this.tree.getRoot().children.length; i++) 
    134139        { 
    135                 // get index of child 
    136140                var index = this.tree.getRoot().children[i].index; 
    137141                var delIcon = document.getElementById("delIcon_" + index); 
     
    154158                treeNode.label = this._createNodeLabel("...loading", treeNode.index); 
    155159                                              
    156                 var bindDataToTree = function(xhr)  
    157                 { 
    158                 var wmc = new WMSC.WebMapContext(xhr.responseXML.documentElement); 
     160                var bindDataToTree = function(wmc)  
     161                { 
    159162                var tree = this._addWMCTree(wmc,  
    160163                        treeNode.data,  
     
    167170                this.addListeners(); 
    168171                }; 
    169  
    170                 var params = {REQUEST: 'GetWebMapContext', 
    171                                           ENDPOINT: wmcEndpoint}; 
    172  
    173                 // invoke the GetWebMapContext call asynchronously via AJAX 
    174                 new Ajax.Request('',  
    175                         {parameters: params, 
    176                  method: "get", 
    177                  onSuccess: bindDataToTree.bindAsEventListener(this) 
    178                         }); 
     172                 
     173                successFn =bindDataToTree.bindAsEventListener(this); 
     174                 
     175                this.wmcRetriever.getWMC(wmcEndpoint, successFn); 
     176//               
     177//              var params = {REQUEST: 'GetWebMapContext', 
     178//                                        ENDPOINT: wmcEndpoint}; 
     179// 
     180//              // invoke the GetWebMapContext call asynchronously via AJAX 
     181//              new Ajax.Request('',  
     182//                      {parameters: params, 
     183//               method: "get", 
     184//               onSuccess: bindDataToTree.bindAsEventListener(this) 
     185//                      }); 
    179186    }, 
    180187     
     
    232239    _addWMCTree: function(wmc, nodeData, parentNode, treeNode)  
    233240    { 
    234                 nodeData.label = wmc.getTitle(); 
     241                nodeData.label = wmc.getTitle() + " (" + wmc.getSubLayers()[0].getEndpoint() + ")"; 
    235242                nodeData.layer = wmc.getTitle(); 
    236243                nodeData.abstract = wmc.getTitle(); 
     
    366373                         
    367374                        // update the selections control to match the selected layer 
    368                         this.coordControl.updateDomainDiv(this._selectedLayer.data.layerData.getDimensions()); 
     375                this.coordControl.updateDomainDiv(this._selectedLayer.data.layerData.getDimensions()); 
    369376 
    370377                        // now refresh the displayed map layers - this is done in the BaseMap.updateVisLayer fn 
     
    430437         
    431438        var node = this._selectedTreeNode; 
    432         var newLayer = this.makeNewLayer(node.data.wmcEndpoint); 
     439        var newLayer = this.makeNewLayer(node.data.wmcEndpoint, node.data.layer); 
    433440         
    434441        this.events.triggerEvent("NEW_LAYER", {layer:newLayer}); 
     
    501508    }, 
    502509     
    503     makeNewLayer: function(url) { 
    504         var layer = new OpenLayers.Layer.WMS("Layer", url, this.defaultParams); 
    505  
     510    makeNewLayer: function(url, layerName) { 
     511        var layer = new OpenLayers.Layer.WMS("#" + this.idIndex + " " + layerName,  
     512                        url, this.defaultParams); 
     513 
     514        layer.params['LAYERS'] = layerName 
    506515        layer.id = url + "_" + layer.name + "_" + this.idIndex; 
    507516 
     
    535544    { 
    536545                return value; 
    537     } 
     546    }, 
     547     
     548    onNewEndpointClick: function(evt) { 
     549        var element = Event.element(evt); 
     550        this.addWebMapContext(this.endpointInputBox.value); 
     551    }, 
     552     
    538553} 
    539554 
  • cowsclient/branches/qesdi/cowsclient/public/js/layerList.js

    r5457 r5480  
    4141                this._dragList = document.getElementById(dragListId); 
    4242            this._layers = []; 
    43             this.events = new OpenLayers.Events(this, this.propertiesDiv, this.EVENTS_RAISED); 
     43            this.events = new OpenLayers.Events(this, this._dragList, this.EVENTS_RAISED); 
    4444             
    4545            this.target = new YAHOO.util.DDTarget(dragListId); 
    46              
     46            this.removeLayerBtn = document.getElementById('btn_remove_selected_layer'); 
     47            addHTMLEventListener( this.removeLayerBtn, 'click', this._onRemoveClick, this); 
    4748        }, 
    4849     
     
    5657 
    5758        _onItemClick: function(event, target) { 
    58                 this._selectItem(target); 
    59                 this.events.triggerEvent("SELECTED_LAYER_CHANGED", {layer:this.getSelectedLayer()});             
     59                if (! this._isSelected(target)) { 
     60                        this._selectItem(target); 
     61                        this._triggerSelectedLayerChange();                      
     62                }                
    6063        }, 
    6164         
    6265        _onDragEnd: function(item){ 
    63                 this._selectItem(item); 
    64                 this.events.triggerEvent("SELECTED_LAYER_CHANGED", {layer:this.getSelectedLayer()}); 
     66                if (! this._isSelected(item)) { 
     67                        this._selectItem(item); 
     68                        this._triggerSelectedLayerChange();                      
     69                } 
     70                this._triggerLayerOrderChange(); 
     71        }, 
     72         
     73        _onRemoveClick: function(event, target) { 
     74                this._removeSelectedItem(); 
    6575                this._triggerLayerOrderChange(); 
    6676        }, 
     
    195205        this.events.triggerEvent("LAYER_ORDER_CHANGED", {layers:this._getOrderedLayers()});      
    196206    }, 
     207     
     208    _triggerSelectedLayerChange: function() { 
     209//      console.log("triggering SELECTED_LAYER_CHANGED at" +new Date()); 
     210        this.events.triggerEvent("SELECTED_LAYER_CHANGED", {layer:this.getSelectedLayer()}); 
     211    }, 
    197212 
    198213    _getOrderedLayers: function() { 
  • cowsclient/branches/qesdi/cowsclient/public/js/layerParameters.js

    r5457 r5480  
    1 LayerParameters = function(propertiesDivId, selectionFormId) { 
     1 
     2LayerParameters = function(propertiesDivId, selectionFormId, wmcRetriever) { 
    23    this.propertiesDiv = document.getElementById(propertiesDivId); 
    34    this.selectionForm = document.getElementById(selectionFormId); 
    45    this.currentLayer = null; 
    5     this.hideControls() 
     6    this.wmcRetriever = wmcRetriever; 
     7     
     8    this.dimFormID = 'WMSC_dimForm'; 
     9    this.hideControls(); 
     10 
     11     
     12    this.events = new OpenLayers.Events(this, null, this.EVENTS_RAISED); 
     13     
     14    this._selectionEventHandlers = {}; 
     15     
    616} 
    717 
    818LayerParameters.prototype = { 
    9  
     19         
     20        EVENTS_RAISED: ['LAYER_PROPERTY_CHANGED'], 
     21         
    1022    addChangedListeners: function() { 
    1123 
    1224        for (i in this.selectionForm.elements) { 
    13             addHTMLEventListener(this.selectionForm.elements[i], 'change', this.onSelectionChange, this); 
     25                element = this.selectionForm.elements[i]; 
     26            handler = addHTMLEventListener(element, 'change', this.onSelectionChange, this); 
     27            this._selectionEventHandlers[element.id] = handler; 
     28        } 
     29 
     30    }, 
     31     
     32    removeChangedListeners: function() { 
     33 
     34        for (i in this.selectionForm.elements) { 
     35                element = this.selectionForm.elements[i]; 
     36                handler = this._selectionEventHandlers[element.id]; 
     37                 
     38                if (handler != null) { 
     39                        removeHTMLEventListener(element, 'change', handler); 
     40                } 
    1441        } 
    1542 
     
    2451            this.currentLayer.redraw(); 
    2552        } 
     53 
     54        this.events.triggerEvent("LAYER_PROPERTY_CHANGED", {layer:this.currentLayer}); 
     55         
    2656    }, 
    2757 
     
    3161 
    3262    onSelectedLayerChanged: function(e) { 
     63         
     64        alert("selected layer has changed") 
     65        if (this.currentLayer != null) { 
     66                this.removeChangedListeners(); 
     67        } 
    3368         
    3469        this.currentLayer = e.layer; 
     
    5489            elements[i].value = val; 
    5590        } 
     91         
     92        var elements = $(this.dimFormID).elements; 
     93         
     94        for (var i=0; i< elements.length; i++){ 
     95            param = elements[i].id.substr(7); 
    5696 
     97            val = this.currentLayer.params[param.toUpperCase()]; 
     98            elements[i].value = val; 
     99        } 
     100         
     101        $('layer_url').innerHTML = this.currentLayer.url; 
     102         
    57103    }, 
    58104 
    59105    hideControls: function() { 
    60106        this.propertiesDiv.style.display = 'none'; 
     107         
     108        //clear the select controls 
    61109    }, 
    62110 
    63111    showControls: function() { 
    64112        this.propertiesDiv.style.display = 'block'; 
    65     } 
     113         
     114        successFn =this.buildControls.bindAsEventListener(this); 
     115         
     116        this.wmcRetriever.getWMC(this.currentLayer.url, successFn) 
     117 
     118    }, 
     119     
     120    buildControls: function(wmc) { 
     121                         
     122        //find the dimensions for the layer 
     123        var wmcLayer = this.searchSubLayers(wmc.getSubLayers()); 
     124                 
     125        //build the select controls 
     126                this.updateDomainDiv(wmcLayer.getDimensions()); 
     127    }, 
     128     
     129    searchSubLayers: function(subLayers){ 
     130        var wmcLayer = null; 
     131         
     132        for (var i=0; i<subLayers.length; i++) { 
     133                 
     134                        if (subLayers[i].getName() == this.currentLayer.params['LAYERS']){ 
     135                                wmcLayer = subLayers[i]; 
     136                                break; 
     137                        } 
     138                } 
     139         
     140        return wmcLayer; 
     141    }, 
     142     
     143    updateDomainDiv: function(dims)  
     144    { 
     145        this.currentDims = dims; 
     146        this._selectedDims={}; //added by domlowe - this clears out any dims left over from previous layers 
     147        this.wmsParams={}; //and same for wms params 
     148                var dimId, dimText, div, i; 
     149 
     150                $(this.dimFormID).innerHTML = ''; 
     151                for (id in dims)  
     152                { 
     153                div = document.createElement('div'); 
     154                div.innerHTML = '<b>'+dims[id].getName()+'</b> ' 
     155                select = document.createElement('select'); 
     156                select.name = id; 
     157                select.id = 'select_' + id 
     158                extent = dims[id].getExtent(); 
     159 
     160                    this.wmsParams[id] = extent[0]; 
     161                    this._selectedDims[id] = this.getDimensionText(dims[id], extent[0]); 
     162             
     163                for (i=0; i<extent.length; i++)  
     164                { 
     165                                option = document.createElement('option'); 
     166                                option.innerHTML = this.getDimensionText(dims[id], extent[i]); 
     167                                // Required for IE6 
     168                                option.value = extent[i]; 
     169                                select.appendChild(option); 
     170                } 
     171 
     172                addHTMLEventListener(select, 'change', this.onSelectionChange, this); 
     173 
     174                div.appendChild(select); 
     175                 
     176                $(this.dimFormID).appendChild(div); 
     177                } 
     178    }, 
     179   
     180     
     181    getDimensionText: function(dim, value) { 
     182        return value; 
     183    }     
    66184} 
  • cowsclient/branches/qesdi/cowsclient/public/js/utils.js

    r5457 r5480  
    88        element.attachEvent("on"+eventName, scopedEventHandler); 
    99    } 
     10     
     11    return scopedEventHandler; // this is useful if you want to remove the handler later 
    1012} 
     13 
     14removeHTMLEventListener = function(element, eventName, handler) { 
     15 
     16    if(element.removeEventListener) { 
     17        element.removeEventListener(eventName, handler, false); 
     18    } else if(element.detachEvent) { 
     19        element.detachEvent("on"+eventName, scopedEventHandler); 
     20    } 
     21} 
     22 
     23 
    1124 
    1225 
  • cowsclient/branches/qesdi/cowsclient/public/layout/drag_drop_style.css

    r5457 r5480  
    1414    position: relative; 
    1515    width: 200px;  
    16     height:240px; 
     16    height:200px; 
    1717    background: #f7f7f7; 
    1818    border: 1px solid gray; 
  • cowsclient/branches/qesdi/cowsclient/templates/wmsviz.html

    r5457 r5480  
    4848<!-- script src="$g.server/js/dimensionControl.js"/ --> 
    4949<script src="$g.server/js/mapControl.js"/> 
    50 <!--  script src="$g.server/js/layerControl.js"></script -->  
     50<script src="$g.server/js/layerControl.js"></script>  
    5151<script src="$g.server/js/capabilities.js"></script> 
    5252<script src="$g.server/js/wcs.js"></script> 
     
    5555<script src="$g.server/js/layerList.js"></script> 
    5656<script src="$g.server/js/layerParameters.js"></script> 
    57 <script src="$g.server/js/wmsSelect.js"></script> 
     57<script src="$g.server/js/legendContainer.js"></script> 
    5858<script src="$g.server/js/boundsControl.js"></script> 
    5959<script src="$g.server/js/utils.js"></script> 
     60<script src="$g.server/js/wmcRetriever.js"></script> 
    6061 
    6162<!-- END: WMSC library --> 
     
    7071from pylons import session  
    7172?>     
    72      
     73 
     74function alertWMC(wmc) { 
     75        //alert("wmc = " + wmc); 
     76} 
     77 
    7378function init()  
    7479{ 
     80 
     81    var wmcRetriever = new WMCRetriever(); 
     82                 
    7583        var layerList = new LayerList('layer_list'); 
    76     var layerParameters = new LayerParameters('parameters_select', 'selection_form'); 
    77      
    78     var wmsSelect = new WMSSelector('select_endpoints', 'add_layer', 
    79             {format: 'image/png', version: '1.3.0', layers:'tmp', CRS: 'CRS:84', transparent: 'true', 
    80              styles: 'contour', cmap: 'jet', time:'1905-01-15T00:00:00.0' } 
    81             ); 
    82  
     84    var layerParameters = new LayerParameters('parameters_select', 'selection_form', wmcRetriever); 
     85 
     86    var legendContainer = new LegendContainer('legend'); 
     87     
    8388    var boundsControl = new WMSC.BoundsControl('dims'); 
    8489    //var coordControl = new WMSC.DimControl('dims'); 
    85     //var layerControl = new WMSC.VisAppLayers('layerTree', 'layerLeaves', coordControl);   
    86      
    87     layerList.addSelectorHandlers(wmsSelect.events); 
     90     
     91    var coordControl = null; 
     92    var layerControl = new WMSC.VisAppLayers('layerTree', 'layerLeaves',wmcRetriever, 'new_endpoint', 'add_new_endpoint');   
     93     
     94    layerList.addSelectorHandlers(layerControl.events); 
    8895    layerParameters.addLayerListHandlers(layerList.events); 
     96    legendContainer.addLayerListHandlers(layerList.events); 
     97    legendContainer.addLayerParametersHandlers(layerParameters.events); 
    8998     
    9099    <for py:for="i in session['viewItems']" class="dataset" py:strip="True"> 
    91 //        <span py:if="i.wmcURL" py:strip="True"> 
    92 //            layerControl.addWebMapContext("${i.wmcURL}"); 
    93 //        </span> 
     100        <span py:if="i.wmcURL" py:strip="True"> 
     101            layerControl.addWebMapContext("${i.wmcURL}"); 
     102        </span> 
    94103    </for>  
    95104     
     
    105114    if (document.getElementById('wcsdownload') == null) { 
    106115        //createDownloadButton(wcsdownloadDiv);     
    107         }     
     116        } 
     117     
    108118} 
    109119 
     
    194204                <th>Dataset  <span py:replace="helpIcon('dataset_help')"/>  
    195205                </th>  
    196                 <th>Layer <span py:replace="helpIcon('layer_help')"/></th>   
     206                <th>Layer <span py:replace="helpIcon('layer_help')"/></th> 
     207                <th>Properties</th>     
    197208                </tr> 
    198209                <tr> 
     
    218229                </tr> 
    219230                <tr> 
    220                         <td class="controlPanel"><div class="controlContent" id="layerTree"> 
    221                             Please wait while datasets load</div> 
     231                        <td class="controlPanel"> 
     232                             <div class="controlContent" id="layerTree"> 
     233                                 Please wait while datasets load 
     234                             </div> 
     235                <div> 
     236                    New Endpoint:<input type="text" id="new_endpoint" > </input> <input type="button" id="add_new_endpoint" value="Add"/> 
     237                </div>                        
    222238                        </td> 
    223239                        <td class="controlPanel"> 
    224240                            <div class="controlContent" id="layerLeaves"> 
    225                                 <div id="layerMessage" class="layerMessage"> 
    226                                     <span py:if="'viewItems' not in session" py:strip="True"> 
    227                                         Please select a dataset to view - from the 'Selections' tab. 
    228                                     </span> 
    229                                     <span py:if="'viewItems' in session" py:strip="True"> 
    230                                         Expand a dataset and select a layer to view 
    231                                     </span> 
    232                                 </div> 
     241 
    233242                                <div class="workarea"> 
    234243                                    <ul class="draglist" id="layer_list"> 
    235244                                    </ul> 
     245                                    <input type="button" value="Remove Selected" id="btn_remove_selected_layer" /> 
    236246                                </div> 
    237247                            </div> 
    238248                        </td> 
     249            <td class="controlPanel"> 
     250                            <div id="parameters_select"> 
     251                             
     252                            <form id='selection_form'> 
     253                             
     254                               URL: <span id='layer_url'> </span> <br /> 
     255                             
     256                                Layers:     
     257                               <select id="select_layers" disabled="disabled"> 
     258                                    <option value="tmp">Temperature</option> 
     259                                    <option value="pre">Precipitation</option> 
     260                                    <option value="tmx">Max Temp</option> 
     261                                    <option value="cld">Cloud Cover</option> 
     262                                </select> <br/> 
     263                         
     264                                Style : 
     265                                <select id="select_styles"> 
     266                                  <option value="grid">Grid cells</option> 
     267                                  <option value="contour">Contour Lines</option> 
     268                                </select> <br/> 
     269                         
     270                                Cmap: 
     271                                <select id="select_cmap"> 
     272                                    <option value="jet">jet</option> 
     273                                    <option value="bone">bone</option> 
     274                                    <option value="winter">winter</option> 
     275                                    <option value="copper">copper</option> 
     276                                </select> <br/> 
     277                            </form> 
     278                             
     279                         
     280                            Dimensions: 
     281                            <form id="WMSC_dimForm"> 
     282                            </form> 
     283                            </div> 
     284            </td> 
    239285                </tr> 
    240286        </table> 
    241287         
    242288         
    243 <div id='wms_layers'> 
    244  
    245    <select id="select_endpoints"> 
    246         <option value="http://localhost:5000/clim_10/wms">http://localhost:5000/clim_10/wms</option> 
    247         <option value="http://localhost:5000/clim_30/wms">http://localhost:5000/clim_30/wms</option> 
    248     </select> <br/> 
    249     <input type="button" value="Add" id="add_layer" /> 
    250  
    251 </div> 
    252      
    253         <div id="parameters_select"> 
    254     <form id='selection_form'> 
    255         Layers: 
    256        <select id="select_layers"> 
    257             <option value="tmp">Temperature</option> 
    258             <option value="pre">Precipitation</option> 
    259             <option value="tmx">Max Temp</option> 
    260             <option value="cld">Cloud Cover</option> 
    261         </select> <br/> 
    262  
    263         Style : 
    264         <select id="select_styles"> 
    265           <option value="grid">Grid cells</option> 
    266           <option value="contour">Contour Lines</option> 
    267         </select> <br/> 
    268  
    269         <!-- only a shortened time list --> 
    270         Time: 
    271         <select id="select_time"> 
    272             <option value="1905-01-15T00:00:00.0">1905-01-15T00:00:00.0</option> 
    273             <option value="1905-02-15T00:00:00.0">1905-02-15T00:00:00.0</option> 
    274             <option value="1905-03-15T00:00:00.0">1905-03-15T00:00:00.0</option> 
    275             <option value="1905-04-15T00:00:00.0">1905-04-15T00:00:00.0</option> 
    276             <option value="1905-05-15T00:00:00.0">1905-05-15T00:00:00.0</option> 
    277             <option value="1905-06-15T00:00:00.0">1905-06-15T00:00:00.0</option> 
    278             <option value="1905-07-15T00:00:00.0">1905-07-15T00:00:00.0</option> 
    279             <option value="1905-08-15T00:00:00.0">1905-08-15T00:00:00.0</option> 
    280             <option value="1905-09-15T00:00:00.0">1905-09-15T00:00:00.0</option> 
    281             <option value="1905-10-15T00:00:00.0">1905-10-15T00:00:00.0</option> 
    282             <option value="1975-11-15T00:00:00.0">1975-11-15T00:00:00.0</option> 
    283             <option value="1985-12-15T00:00:00.0">1985-12-15T00:00:00.0</option> 
    284         </select> <br/> 
    285  
    286         Cmap: 
    287         <select id="select_cmap"> 
    288             <option value="jet">jet</option> 
    289             <option value="bone">bone</option> 
    290             <option value="winter">winter</option> 
    291             <option value="copper">copper</option> 
    292         </select> <br/> 
    293     </form> 
    294         </div> 
     289 
     290     
     291 
    295292 
    296293</div> 
Note: See TracChangeset for help on using the changeset viewer.