
/******************** Begin clientsrc\annotation\OpenLayers\Layer\Annotations.js ********************/
/* Copyright (c) 2008 Avencia, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Layer.js
 * @requires OpenLayers/Layer/Markers.js
 */

/**
 * Class: OpenLayers.Layer.Annotations
 * Draw divs as 'boxes' on the layer. 
 *
 * Inherits from:
 *  - <OpenLayers.Layer.Markers>
 */
OpenLayers.Layer.Annotations = OpenLayers.Class(OpenLayers.Layer.Markers, {

    /**
     * Constructor: OpenLayers.Layer.Annotations
     *
     * Parameters:
     * name - {String} 
     * options - {Object} Hashtable of extra options to tag onto the layer
     */
    initialize: function (name, options) {
        OpenLayers.Layer.Markers.prototype.initialize.apply(this, arguments);
    },
    
    /** 
     * Method: drawMarker
     * Calculate the pixel location for the marker, create it, and 
     *    add it to the layer's div.
     *    This is overridden so we can render the div first and have
     *    access to its properties for calculated offsets
     *
     * Parameters:
     * marker - {<OpenLayers.Marker>} 
     */
    drawMarker: function(marker) {
        var px = this.map.getLayerPxFromLonLat(marker.lonlat);
        if (!px) {
            marker.display(false);
        } else {
            var markerDiv = marker.draw(px);

            if (!marker.drawn) {
                this.div.appendChild(markerDiv);
                
                //We calculate offsets here and not on the annotation marker because the element height 
                //and width are not set until it is actually rendered to the DOM (above)
                if(marker.calculateOffset) {
                    px = px.offset(marker.calculateOffset(marker.div));
                    OpenLayers.Util.modifyDOMElement(marker.div, null, px);
                }
                
                marker.px = px;
                marker.drawn = true;
            }
        }
    },
    
    /**
     * APIMethod: removeMarker 
     * 
     * Parameters:
     * marker - {<OpenLayers.Marker.Box>} 
     */
    removeMarker: function(marker) {
        if (this.markers && this.markers.length) {
            OpenLayers.Util.removeItem(this.markers, marker);
            if ((marker.div !== null) && (marker.div.parentNode == this.div) ) {
                this.div.removeChild(marker.div);
                marker.drawn = false;
            }
        }
    },

    CLASS_NAME: "OpenLayers.Layer.Annotations"
});

/********************   End clientsrc\annotation\OpenLayers\Layer\Annotations.js ********************/


/******************** Begin clientsrc\annotation\OpenLayers\Marker\Annotation.js ********************/
/* Copyright (c) 2008 Avencia, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */


/**
 * @requires OpenLayers/Marker.js
 */

/**
 * Class: OpenLayers.Marker.Annotation
 * Note that this class is not implemented to handle changes to its attributes after
 * initialization.  This object is currently not mutable.
 *
 * Inherits from:
 *  - <OpenLayers.Marker> 
 */
OpenLayers.Marker.Annotation = OpenLayers.Class(OpenLayers.Marker, {

    /** 
     * Property: text 
     * {String} the annotation text
     */
    text: null,

    /** 
     * Property: div 
     * {DOMElement} 
     */
    div: null,
    
    /** 
     * Property: px 
     * {<OpenLayers.Pixel>} 
     */
    px: null,
    
    /** 
     * Property: calculateOffset 
     * {Function(px, div)} function to calculate offset where the annotation should be placed.
     * ie. the pixel value that you want to put into OpenLayers.Pixel.offset(yourPixelHere) method
     * Returns a new {OpenLayers.Pixel}
     */
     calculateOffset: null,

    /** 
     * Constructor: OpenLayers.Marker.Annotation
     *
     * Parameters:
     * text - {String} the annotation text
     * {Function(px, div)} function to calculate offset where the annotation should be placed.
     *  ie. the pixel value that you want to put into OpenLayers.Pixel.offset(yourPixelHere) method
     * options - {Object} An optional object whose properties will be set on the annotation.
     */
    initialize: function(text, lonlat, calculateOffset, options) {
        this.text = text;
        this.lonlat = lonlat;
        this.calculateOffset = calculateOffset;
        this.div = OpenLayers.Util.createDiv();

        //Default settings, can be overridden
        this.div.style.whiteSpace = 'nowrap';
        this.div.className = 'olAnnotationMarker';

        
        if (options) {
            var option, styleProp;
            for (option in options) {
                if (options.hasOwnProperty(option)) {
                    
                    //style is readonly so we handle it specifically
                    if (option === 'style') {
                        for (styleProp in options.style) {
                            if (options.style.hasOwnProperty(styleProp)) {
                                this.div.style[styleProp] = options.style[styleProp];
                            }
                        }
                    
                    }
                    else {
                        this.div[option] = options[option];
                    }
                }
            }
        }

        //Default settings, CANNOT be overridden
        this.div.innerHTML = text;
        this.div.style.overflow = 'hidden';
        this.events = new OpenLayers.Events(this, this.div, null);
    },

    /**
     * Method: destroy 
     */    
    destroy: function() {
        this.text = null;
        this.lonlat = null;
        this.div = null;
        this.calculateOffset = null;

        OpenLayers.Marker.prototype.destroy.apply(this, arguments);
    },

    /** 
    * Method: draw
    * 
    * Parameters:
    * px - {<OpenLayers.Pixel>} 
    * 
    * Returns: 
    * {DOMElement} A new DOM Image with this marker�s icon set at the 
    *         location passed-in
    */
    draw: function(px) {
        if (this.div && px) {
            OpenLayers.Util.modifyDOMElement(this.div, null, px);
            return this.div;
        }
    }, 
    
    /**
     * Method: moveTo
     * move div to passed in px and use calculateOffset if available.
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>} 
     */
    moveTo: function (px) {
        if (this.div && px) {
            this.px = px;
            this.lonlat = this.map.getLonLatFromLayerPx(this.px);
            
            if (this.calculateOffset) {
                this.px = this.px.offset(this.calculateOffset(this.div));
            }
            OpenLayers.Util.modifyDOMElement(this.div, null, this.px);            
        }
    },
    
    /**
     * Method: display
     * Hide or show the annotation marker
     * 
     * Parameters:
     * display - {Boolean} 
     */
    display: function(display) {
        this.div.style.display = (display) ? "" : "none";
    },

    CLASS_NAME: "OpenLayers.Marker.Annotation"
});


/********************   End clientsrc\annotation\OpenLayers\Marker\Annotation.js ********************/


/******************** Begin clientsrc\util\avencia.util.js ********************/
var Avencia = {};

(function(av) {

	av.isArray = function(o) {
		/// <summary>Determines whether the argument is an array.</summary>
		/// http://thinkweb2.com/projects/prototype/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
		return Object.prototype.toString.call(o) === '[object Array]';
	};
	
	av.isStringTruthy = function(str) {
		/// <summary>Determines whether the string represents a falsy value.</summary>

        if (str === 'false') {
		    return false;
		} else if (str === '') {
		    return false;
		} else if (str === 'NaN') {
		    return false;
		} else if (str === 'null') {
		    return false;
		} else if (str === 'undefined') {
		    return false;
		} else if (str === '0') {
		    return false;
        }

		return true;
	};

    av.log = function(msg) {
        /// <summary>Ensures the console exists before attempting to log.</summary>
        if (window.console && window.console.log) {
            console.log(msg);
        }
    };

	// JS base object augmentations
	String.prototype.trim = function() {
		/// <summary>Trims all whitespace from the front and end of this string</summary>
		return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
	};
})(Avencia);
/********************   End clientsrc\util\avencia.util.js ********************/


/******************** Begin clientsrc\viewer\mapviewer.core.js ********************/
var mapviewer = {};

Ext.BLANK_IMAGE_URL = 'client/extjs/resources/images/default/s.gif';

String.prototype.trim = function() {
    /// <summary>Trims all whitespace from the front and end of this string</summary>
    return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
};

String.prototype.htmlEncode = function() {
    /// <summary>HTML encodes this string</summary>
    var div = document.createElement('div');
    var text = document.createTextNode(this);
    div.appendChild(text);
    return div.innerHTML;
};

Ext.Panel.prototype.removeItems = function() {
    /// <summary>Removes each item from this Panel</summary>
    if (this.items && this.items.items) {
        Ext.each(this.items.items, function(item, index, all) {
            this.remove(all[0]);
        }, this);
    }
};
/********************   End clientsrc\viewer\mapviewer.core.js ********************/


/******************** Begin clientsrc\viewer\mapviewer.orderer.js ********************/
// <summary>an object that let's us order things with an alpha character string by incrementing each time it is used.</summary>
// <parameter name="start" type="number">(Optional) Allows the object to start at a value greated than 0 ("A").
//      This can be useful if you need to keep several different lists ordered independently.  
//      E.g. if you knew you had a max length of 50 in each list you could seed each orderer with a start of 0, 50, 100, etc 
//      to make sure the objects are ordered within each list, but the order of the lists is maintained.</parameter>
mapviewer.orderer = function(start) {
    // this list of "indexes" is the list of character positions from right to left
    // so, in the example: 'NN[n]', "n" is _indexes[0], and there would have to be 3 values in the _indexes array
    var _indexes = [-1];
    // next, the list of possible values (alpha in this, to make sorting easy)
    var _list = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];

    // this updates the current index for one or more values in _indexes
    // the position refers to index inside of the _indexes array, 
    // which also refers to the character index of the return value from right to left
    function _update(position) {
        // this first check is for recursion, if someone is trying to update a value outside 
        // the current range in the _indexes array, we need to push on a new value
        if (position > _indexes.length - 1) {
            _indexes.push(0);
        } else {
            // increment the value of the index in the array at the position specified
            _indexes[position]++;
            // if that increment pushed the value over the length of the array, we need to reset it back to 0, 
            // and increment the value of the next index (e.g. if the current values refer to a string "Z", 
            // we want to reset "Z" to "A" and add another "A" to get "AA"
            if (_indexes[position] > _list.length - 1) {
                _indexes[position] = 0;
                _update(position + 1);
            }
        }
    }

    // each time this function is called, we start the update process over by attempting to increment 
    // the first value in _indexes (the right-most character).  Subsequent values in _indexes will get updated by recursion
    function _next() {
        _update(0);
        // once all the _indexes have been updated, loop over each one and manually construct a character that
        // is represented by the _indexes value in the _list.
        var n = '';
        for (var i = 0; i < _indexes.length; i++) {
            n = _list[_indexes[i]] + n;
        }
        return n;
    }

    // if the object was constructed with a starting value, run the next process to get there.
    if (start > 0) {
        for (var s = 0; s < start-1; s++) {
            _next();
        }
    }

    // construct a return object, exposting the "next" function.
    var _self = {
        next: function() {
            return _next();
        }
    };

    return _self;
};
/********************   End clientsrc\viewer\mapviewer.orderer.js ********************/


/******************** Begin clientsrc\viewer\mapviewer.identify.config.js ********************/
mapviewer.identify = mapviewer.identify || {};
mapviewer.identify.config = {
    UNITS: 'ft',
    // a generic function that takes a data object (literal), turns it into rendered key/value pairs 
    // based on a field mapping object.  it also sets the category and automatically sets a sort order value.
    fieldsToRecords: function(data, renderers, fields, category, orderStart) {    
        var recs = [], orderer = new mapviewer.orderer(orderStart);
        for(var f = 0; f < fields.length; f++) {
            var field = fields[f];
            var renderArgs = [data, renderers, field.r];
            for(var k = 0; k < field.keys.length; k++) {
                renderArgs.push(field.keys[k]);
            }
            var rendereredValue = mapviewer.identify.config.renderResult.apply(this, renderArgs);
            
            if (rendereredValue) {
                recs.push({ Key:field.label, Value:rendereredValue, Category:category, SortOrder:orderer.next() });
            }
        }
        return recs;
    },
    renderResult: function(dataObj, renderers, rendererKey, valueKey) {
        var retVal = '', rendererArgs = [ dataObj[valueKey] ];

        //If the key was never in the data object, don't render anything
        if (dataObj.hasOwnProperty(valueKey)) {
            if(arguments.length > 4) {
                for(var i = 4; i < arguments.length; i++) {
                    var objValue = dataObj[arguments[i]];
                    rendererArgs.push(objValue);
                }
            }
        
            if(renderers[rendererKey]) {
                retVal = renderers[rendererKey].apply(mapviewer.identify.config, rendererArgs);
            } else {
                retVal = dataObj[valueKey];
            }
            
            retVal = renderers.noValue(retVal);
        }
        
        return retVal;
    },
    resultRenderers: {
        address: function(house, extension, unit, stprefix, stname, sttype, stsuffix) {
            var address = '';
            if(house && house.toString().trim().length > 0) {
                address += house;
            }
            if(extension && extension.toString().trim().length > 0) {
                address += ' - ' + extension;
            }
            if(unit && unit.toString().trim().length > 0) {
                address += ' ' + unit;
            }
            if(stprefix && stprefix.toString().trim().length > 0) {
                address += ' ' + stprefix;
            }
            if(stname && stname.toString().trim().length > 0) {
                address += ' ' + stname;
            }
            if(sttype && sttype.toString().trim().length > 0) {
                address += ' ' + sttype;
            }
            if(stsuffix && stsuffix.toString().trim().length > 0) {
                address += ' ' + stsuffix;
            }
            
            return address;
        },
        noValue: function(val) {
            if(typeof val === 'string') {
                val = val.trim();
            }
            return val || '[No Value]';
        },
        yesNo: function(yesNoVal) {
            var v = '';
            if(yesNoVal !== undefined) {
                v = yesNoVal ? 'Yes' : 'No';
            } else if(typeof yesNoVal === 'boolean' || typeof yesNoVal === 'number') {
                v = yesNoVal ? 'Yes' : 'No';
            } else if(typeof yesNoVal === 'string') {
                v = yesNoVal === '1' || yesNoVal.toLowerCase() === 'true';
            }
            return v;
        },
        date: function(dateVal) {
            var d = '';
            if(dateVal.getDate) {
                var dt = dateVal.getDate(), m = dateVal.getMonth() + 1;
                var day = (dt < 10 ? '0' : '') + dt;
                var month = (m < 10 ? '0' : '') + m;
                
                d = month+'-'+day+'-'+dateVal.getFullYear();
            }
            return d;
        },
        dollar: function(dollarVal) {
            var n = '';
            if(typeof dollarVal === 'number') {
                n = '$' + dollarVal.toFixed(2);
             }
            return n;
        },
        length: function(lengthVal) {
            var n = '';
            if(typeof lengthVal === 'number') {
                n = Math.round(lengthVal, 1);
                n += ' ' + mapviewer.identify.config.UNITS;
             }
            return n;
        },
        area: function(areaVal) {
            var n = '';
            if(typeof areaVal === 'number') {
                n = Math.round(areaVal, 1);
                n += ' ' + mapviewer.identify.config.UNITS + '<sup>2</sup>';
             }
            return n;
        },
        elevation: function(bottom, top, flag) {
            var elev = '';
            if(flag && flag == 1) {
                if(typeof bottom === 'number') {
                    elev = bottom.toFixed(2);
                }
                if(typeof top === 'number') {
                    if(elev) { elev += ' to '; }
                    elev += top.toFixed(2);
                }
            }
            return elev;
        },
        parcelStatus: function(status) {
            var retVal = '[Unknown]';
            switch (status) {
                case 1: retVal = 'Active'; break;
                case 2: retVal = 'Inactive'; break;
                case 3: retVal = 'Remainder'; break;
                case 9: retVal = 'Merged'; break;
                default: break;
            }
            return retVal;
        },
        merge: function(tokenizedStr, tokens) {
            // Looks for tokens like {ADDRESS} or {1} in tokenizedStr and merges
            // in the provided tokens.
            var s = tokenizedStr.trim();
            $.each(tokens, function(i, token) {
                var key;
                if (isNaN(parseInt(i, 10))) {
                    //i is a string based key
                    key = i;
                } else {
                    //i is an index. Use a 1-based index.
                    key = i+1;
                }
                
                s = s.replace('{' + key + '}', token);
            });
            
            return s;
        },
        externalLink: function(href, title, address, xCoord, yCoord) {
            href = this.resultRenderers.merge(href, {
                ADDRESS: address,
                X: xCoord,
                Y: yCoord
            });
            
            //Make sure we merged all of the tokens
            if (href.indexOf('{') === -1 && href.indexOf('}') === -1) {
                return '<a title="' + title + '" href="' + encodeURI(href) + '" target="_blank">' + title + '</a>';
            }
            
            return null;
        }
    },
    updateResultFeature: function(resultFeature) {
        resultFeature.Display = {
            Title: 'Result Feature',
            Data: resultFeature.Attributes
        };
        return resultFeature;
    }
};
/********************   End clientsrc\viewer\mapviewer.identify.config.js ********************/


/******************** Begin clientsrc\viewer\mapviewer.applicationManager.js ********************/
mapviewer.applicationManager = function(options) {
    ///<summary>Location for all application code and central broker for all interactions.</summary>
    ///<param name="options">Initialization options.</param>
    ///<returns>mapviewer.applicationManager</returns>
    var _self = {},
        _mapManager,
        _layoutManager,
        _components;

    _options = options || {};
    _components = options.components || {};

    function parseStateString(stateStr) {
        ///<summary>Parses a state string and returns a state object.</summary>
        var state = {};
        var componentList = stateStr.split('/');
        
        $.each(componentList, function(i, component) {
            //Component parsing variables
            var comName, comVal, comArray;
            
            if (Avencia.isStringTruthy(component)) {
                comArray = component.split(':');
                comName = comArray[0];
                comVal = comArray[1];
                
                if (_components[comName]) {
                    state[comName] = _components[comName].parseStateString(comVal);
                }
            }
        });
        
        return state;
    }
    
    _self.init = function() {
        ///<summary>Initialize the manager.</summary>
        ///<returns>mapviewer.applicationManager</returns>

        //Construct managers
        if (_options.layout.manager) {
            _layoutManager = _options.layout.manager(_options.layout);
        } else {
            _layoutManager = mapviewer.layoutManager(_options.layout);
        }
        
        if (_options.map.manager) {
            _mapManager = _options.map.manager(_options.map);
        } else {
            _mapManager = mapviewer.mapManager(_options.map);
        }
        
        //Initialize managers
        _layoutManager.init(_self);
        _mapManager.init(_self, 'map');

        //Initialize components
        for (var component in _components) {
            if (_components.hasOwnProperty(component)) {
                _components[component].init(_self, component).render();
            }
        }
        
        _components.map = _mapManager;

        //hashchange must be bound before hash.init
        $(_self).hashchange(function (e, newState) {
            //The hash value has changed, but we don't know who did it. It could have been
            //a user action on a component, the back button, or a manual url change. We have
            //no way of knowing. But a back button or manual url change will result in a 
            //new hash that IS NOT EQUAL to the current state string.
            
            var outOfSync, parsedState;
            var oldState = _self.getStateString();
            
            if (newState) {
                outOfSync = (oldState !== newState);
                if (outOfSync) {
                    //The hash was manually changed or someone clicked the back/fwd button. 
                    //Find out what changed and update the component(s) state object AND
                    //update the UI.
                    parsedState = parseStateString(newState);
                    
                    $.each(_components, function(name, component) {
                        if (component.getStateString() !== component.parseState(parsedState[name])) {
                            //setState will not update if the new state is the same
                            component.setState(parsedState[name], true);
                            //Update the UI of each changed component
                            component.syncUIState();
                        }
                    });
                }
            }
        });
        
        $.hash.init('blank.html');

        $(_self).bind('state', function(){
            //A user modified the state of the application, now we're updating the hash.
            $.hash.go(_self.getStateString());
        });
    };
    
    _self.getLayoutManager = function() {
        ///<summary>Get access to the layoutManager</summary>
        ///<returns>mapviewer.layoutManager</returns>
        return _layoutManager;
    };
    
    _self.getMapManager = function() {
        ///<summary>Get access to the mapManager</summary>
        ///<returns>mapviewer.mapManager</returns>
        return _mapManager;
    };
    
    _self.getComponent = function(key) {
        ///<summary>Get access to a component by its key</summary>
        ///<returns>Object</returns>
    
        return _components[key];
    };
    
    _self.getState = function() {
        ///<summary>Returns the state object of all of the components (including map).</summary>
        var state = {};
        $.each(_components, function(name, component) {
            state[name] = component.getState();
        });
        
        return state;
    };

    _self.getStateString = function() {
        ///<summary>Returns the state string of all of the components (including map).</summary>
        var stateStr = '';
        $.each(_components, function(name, component) {
            var comState;
            if (name) {
                comState = component.getStateString();
                
                if (comState) {
                    stateStr += '/' + name + ':' + component.getStateString();
                }
            }
        });
        
        return stateStr;
    };

    return _self;
};
/********************   End clientsrc\viewer\mapviewer.applicationManager.js ********************/


/******************** Begin clientsrc\viewer\mapviewer.mapManager.js ********************/
mapviewer.mapManager = function(options) {
    ///<summary>Location for all application map code and 
    ///         central broker for all map interaction.</summary>
    ///<param name="options">Initialization options.</param>
    ///<returns>mapviewer.mapManager</returns>
    var _defaults = {};
    var _appManager, _map, _layerIds = {};
    var _base = mapviewer.component($.extend(_defaults, options));
    var _self = $.extend({}, _base);
    var _preventStateEvents = false;
    var _serviceInfo = _self.options.serviceInfo;
    
    _self.baseClassName = _base.className;
    _self.className = 'mapviewer.mapManager';

    //Avoid pink tiles
    OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
    OpenLayers.Util.onImageLoadErrorColor = 'transparent';

    function extendMapOptions(options) {
        ///<summary>Default map options based on the service info from
        ///         the AGS REST API.</summary>
        var retVal, serviceOptions = {};
        
        if (_serviceInfo) {
            serviceOptions = {
                maxExtent: new OpenLayers.Bounds(_serviceInfo.fullExtent.xmin,
                                                 _serviceInfo.fullExtent.ymin,
                                                 _serviceInfo.fullExtent.xmax,
                                                 _serviceInfo.fullExtent.ymax),
                projection: 'ESPG:' + _serviceInfo.spatialReference.wkid
            };
        }
        
        return $.extend(serviceOptions, options.mapOptions);
    }

    _self.init = function(am) {
        ///<summary>Initialize the manager.</summary>
        ///<param name="am">The application manager.</param>
        ///<returns>mapviewer.mapManager</returns>
        _appManager = am;
        _base.init(_appManager, id);
    
        //Init map
        var newOptions = $.extend(_self.options.mapOptions, {
            eventListeners: { 
                'moveend': function(evt) {
                    //This is tricky because clicking the back sets the extent and triggers this
                    //event. We need to prevent that event to triggering another state change event.

                    _self.setState({ bbox: evt.object.getExtent().toBBOX() }, _preventStateEvents);
                    _preventStateEvents = false;
                }
            }
        });
        
        _self.options.mapOptions = extendMapOptions(_self.options);
        _map = new OpenLayers.Map(_self.options.contentEl, _self.options.mapOptions);
        
        //Init user-defined layers
        _map.addLayer(_self.options.baseLayer.layerObj);
        //Track individual IDs for each layers
        _layerIds[_self.options.baseLayer.layerObj.name] = _self.options.baseLayer.layerIds;

        for (i = 0; _self.options.overlayLayers && i < _self.options.overlayLayers.length; i++) {
            _map.addLayer(options.overlayLayers[i].layerObj);
            //Track individual IDs for each layers
            _layerIds[_self.options.overlayLayers[i].name] = _self.options.overlayLayers[i].layerIds;
        }

        //Init user-defined controls
        for (i = 0; i < _self.options.controls.length; i++) {
            _map.addControl(_self.options.controls[i]);
        }
        
        _map.zoomToMaxExtent();
        
        return _self;
    };
    
    _self.syncUIState = function() {
        /// <summary>Syncs the component UI to match the current state.</summary>
        
        var state = _self.getState();
        if (state) {
            _preventStateEvents = true;
            _map.zoomToExtent(OpenLayers.Bounds.fromString(state.bbox));
        }
    };

    _self.getMap = function() {
        /// <summary>Gets the OpenLayers.Map object created during init</summary>
        /// <returns>OpenLayers.Map</returns>
        return _map;
    };
    
    _self.getLayerId = function(layerName) {
        /// <summary>Gets the layer id from the layer name.</summary>
        /// <returns>The integer layer id.</returns>
        var retVal;
        
        if (_serviceInfo && _serviceInfo.layers) {
            $.each(_serviceInfo.layers, function(i, layerObj) {
                if (layerObj.name === layerName) {
                    retVal = layerObj.id;
                    return false;
                }
            });
        }
        
        return parseInt(retVal, 10);
    };

    _self.setLayerFilter = function(layerObjName, layerId, filter) {
        /// <summary>Gets the component array originally passed into the init method</summary>
        /// <param name="layerObjName" type="string">The OpenLayers.Layer name property</param>
        /// <param name="layerId" type="string">The id of the layer in the OpenLayers layer param</param>
        /// <param name="filter" type="string">The AGS layer definition</param>

        var layer = _map.getLayersByName(layerObjName)[0];
        layer.setLayerFilter(layerId, filter);
    };

    _self.clearLayerFilter = function(layerObjName, layerId) {
        /// <summary>Gets the component array originally passed into the init method</summary>
        /// <param name="layerObjName" type="string">The OpenLayers.Layer name property</param>
        /// <param name="layerId" type="string">The id of the layer in the OpenLayers layer param</param>

        var layer = _map.getLayersByName(layerObjName)[0];
        layer.clearLayerFilter(layerId);
    };

    _self.showLayer = function(layerObjName, layerId, preventRefresh) {
        /// <summary>Make a layer on an OpenLayers.Layer object visible</summary>
        /// <param name="layerObjName" type="string">The OpenLayers.Layer name property</param>
        /// <param name="layerId" type="string">The id of the layer in the OpenLayers layer param</param>
        /// <param name="preventRefresh" type="bool">A flag to prevent the map from refreshing</param>

        _self.setLayerVisiblity(true, layerObjName, layerId, preventRefresh);
    };

    _self.hideLayer = function(layerObjName, layerId, preventRefresh) {
        /// <summary>Make a layer on an OpenLayers.Layer object invisible</summary>
        /// <param name="layerObjName" type="string">The OpenLayers.Layer name property</param>
        /// <param name="layerId" type="string">The id of the layer in the OpenLayers layer param</param>
        /// <param name="preventRefresh" type="bool">A flag to prevent the map from refreshing</param>

        _self.setLayerVisiblity(false, layerObjName, layerId, preventRefresh);
    };

    _self.setLayerVisiblity = function(show, layerObjName, layerId, preventRefresh) {
        /// <summary>Make a layer on an OpenLayers.Layer object invisible</summary>
        /// <param name="visible" type="bool">A flag to indicate visibility</param>
        /// <param name="layerObjName" type="string">The OpenLayers.Layer name property</param>
        /// <param name="layerId" type="string">The id of the layer in the OpenLayers layer param</param>
        /// <param name="preventRefresh" type="bool">A flag to prevent the map from refreshing</param>

        _layerIds[layerObjName][layerId] = show;

        if (!preventRefresh) {
            _self.refreshLayerVisiblity(layerObjName);
        }
    };

    _self.refreshLayerVisiblity = function(layerObjName) {
        /// <summary>Refeshes the map with the current layer visiblity state</summary>
        /// <param name="layerObjName" type="string">Confines the refresh to a single OpenLayers layer object</param>

        var i;
        function refresh(layer) {
            layer.mergeNewParams({ layers: mapviewer.mapManager.makeLayersString(_layerIds[layer.name]) });
            layer.redraw();
        }

        if (layerObjName) {
            refresh(_map.getLayersByName(layerObjName)[0]);
        }
        else {
            for (i = 0; i < _map.layers.length; i++) {
                refresh(_map.layers[i]);
            }
        }
    };
    
    return _self;
};

// This is being created like a 'static' helper function.  In other words,
// we're declaring it directly on the function mapviewer.component.manager, not
// on an instance returned by mapviewer.component.manager().
mapviewer.mapManager.makeLayersString = function(layerIds) {
    /// <summary>Given the layerIds object, which should look like this:
    /// {
    ///     //LayerID = visibility
    ///     1 = true;
    ///     2 = false;
    ///     3 = true;
    /// }
    /// construct the correctly-formatted "layers" parameter for the
    /// OpenLayers layer we're working with.</summary>
    /// <returns>A string that can be set as the "layers" parameter on the
    ///          OpenLayers layer's options object.</returns>

    var retVal = 'show:';
    var layerId;
    for (layerId in layerIds) {
        if (layerIds.hasOwnProperty(layerId)) {
            // The value associated with the ID is the visibility 
            if (layerIds[layerId]) {
                // append a ,
                retVal += ',' + layerId;
            }
        }
    }
    return retVal;
};
/********************   End clientsrc\viewer\mapviewer.mapManager.js ********************/


/******************** Begin clientsrc\viewer\mapviewer.component.js ********************/
mapviewer.component = mapviewer.component || {};

mapviewer.component = function(options) {
    var _self = {};
    var _options = $.extend({}, options);
    var _state = null;
    var _appManager;
    var _id;
    var _olControl;

    _self.init = function(appManager, id) {
        /// <summary>Base initialization of the component.</summary>
        _appManager = appManager;
        _id = id;
    };
    
    _self.render = function() {
        /// <summary>Renders the component to the DOM. Abstract.</summary>
        throw { name: 'NotImplemented', message: this.className + ' does not implement render().' };
    };

    _self.setOpenLayersControl = function(olControl) {
        /// <summary>Set the OpenLayers Control that should be added to the toolbar.</summary>
        /// <returns>OpenLayers.Control</returns>
        _olControl = olControl;
    };
    
    _self.getOpenLayersControl = function() {
        /// <summary>The OpenLayers Control that should be added to the toolbar.</summary>
        /// <returns>OpenLayers.Control</returns>
        return _olControl;
    };
    
    _self.syncUIState = function() {
        /// <summary>Syncs the component UI to match the current state. Abstract.</summary>
        throw { name: 'NotImplemented', message: this.className + ' does not implement syncUIState().' };
    };
    
    _self.setState = function(state, preventTrigger) {
        /// <summary>Sets the component specific state. In order to be compatible with
        ///          getStateString, this should be and object of String:String or 
        ///          String:Array.</summary>
        var event;
        
        if (_self.getStateString() !== _self.parseState(state)) {
            if (state) {
                _state = $.extend({}, state);
            } else {
                _state = null;
            }
            
            event = 'state';
            if (_appManager && !preventTrigger) {
                $(_appManager).trigger(event);
                
                if (_id) {
                    $(_appManager).trigger(event + '.' + _id);
                }
            }
        }
    };
    
    _self.getState = function() {
        /// <summary>Returns the component specific state. In order to be compatible with
        ///          getStateString, this should be and object of String:String or 
        ///          String:Array.</summary>
        var state = null;
        if (_state) {
            state = $.extend({}, _state);
        }
        
        return state;
    };
    
    _self.getStateString = function() {
        /// <summary>Returns the component specific state as a URL safe string.</summary>
        return _self.parseState(_state);
    };
    
    _self.parseState = function(stateObj) {
        /// <summary>Parses the component specific state object as a URL safe string.</summary>
        /// <param name="stateObj">State object to parse.</param>
        var retVal = null;

        if (stateObj) {
            $.each(stateObj, function(key, val) {
                var stateStr;
                
                if (val || val === false) {
                    if (retVal) {
                        retVal += ';';
                    } else {
                        retVal = '';
                    }
                    
                    if($.isArray(val)) {
                        stateStr = '';
                        $.each(val, function(i, item) {
                            stateStr += encodeURIComponent(item) + '+';
                        });
                        
                        //So we know that this is an array when we parse the string
                        if (!stateStr) { 
                            stateStr =  '+'; 
                        }
                    } else {
                        stateStr = encodeURIComponent(val);
                    }
                    
                    retVal += key + '=' + stateStr;
                }
            });
        }
        
        return retVal;
    };
    
    _self.parseStateString = function(stateStr) {
        /// <summary>Parses the state string into a component specific state object.</summary>
        /// <param name="stateStr">State string to parse.</param>

        var stateName, stateVal, stateList, stateArray, stateValArray;
        var state = {};
                
        if (Avencia.isStringTruthy(stateStr)) {
            stateList = stateStr.split(';');
            $.each(stateList, function(j, stateItem) {
                stateArray = stateItem.split('=');
                stateName = stateArray[0];
                stateVal = stateArray[1];
                
                if (Avencia.isStringTruthy(stateName)) {
                    if (stateVal) {
                        if (stateVal.indexOf('+') > -1) {
                            if (stateVal.indexOf('+') === 0 && stateVal.length === 1) {
                                stateVal = [];
                            } else if (stateVal.indexOf('+') > -1) {
                                stateValArray = [];
                                $.each(stateVal.split('+'), function(k, stateValItem) {
                                    if (stateValItem) {
                                        stateValArray.push(decodeURIComponent(stateValItem));
                                    }
                                });
                                stateVal = stateValArray;
                            }
                        }
                        else {
                            stateVal = decodeURIComponent(stateVal);
                        
                            if (stateVal === 'true') {
                                stateVal = true;
                            } else if (stateVal === 'false') {
                                stateVal = false;
                            } else if (stateVal === 'null') {
                                stateVal = null;
                            } else if (stateVal === 'undefined') {
                                stateVal = myUndefined;
                            } else if (stateVal === 'NaN') {
                                stateVal = NaN;
                            } else if ((/^\d+\.?\d*$/.test(stateVal)) && parseFloat(stateVal)) {
                                stateVal = parseFloat(stateVal);
                            }
                        }
                        
                        state[stateName] = stateVal;
                    }
                }
            });
        }
        
        return state;
    };
        
    _self.options = _options;
    
    _self.className = 'mapviewer.component';
    
    return _self;
};
/********************   End clientsrc\viewer\mapviewer.component.js ********************/


/******************** Begin clientsrc\viewer\mapviewer.component.identify.js ********************/
mapviewer.component = mapviewer.component || {};

mapviewer.component.identify = function(options) {
    /// <summary>Implements the identify component</summary>
    var _defaults = {};
    var _appManager, _mapManager, _layoutManager;
    var _base = mapviewer.component($.extend(_defaults, options));
    var _self = $.extend({}, _base);

    _self.baseClassName = _base.className;
    _self.className = 'mapviewer.component.identify';

    var _layer;
    var _panel;
    var _defaultMessage = _self.options.defaultMsg || '<img alt="Identify Tool Icon" src="client/viewer/css/images/tool_identify.gif" style="float: left;"/><span>Use the Identify tool or search field to select a parcel on the map.</span>';
    var _notFoundMessage = '<span>No parcels were found.</span>';
    var _loadingMessage = '<div class="loading-indicator">Loading...</div>';
    var _errorMessage = '<span class="error">An error occurred while identifying parcels.  Please try again..</span>';
    var _searchText = 'Address/Intersection/RegMap ID';
    var _searchLinkClass = 'searchLink';
    //Keyed functions that will render a feature attribute value
    var _resultRenderers = {};
    //Maps a feature attribute key to a renderer key
    var _updateResultFeature;
    var _recordFields = [
            { name: 'key', mapping: 'Key' },
            { name: 'value', mapping: 'Value' },
            { name: 'category', mapping: 'Category' },
            { name: 'sort', mapping: 'SortOrder' }
        ];
    var _parcelInfoRecord = Ext.data.Record.create(_recordFields);
    var _ajaxReqId;

    //Use custom control if provided
    if (_self.options.control) {
        _self.setOpenLayersControl(_self.options.control);
    } else {
        var control = new OpenLayers.Control(
        {
            title: 'Select and click a parcel to see details.',
            displayClass: 'olControlIdentify',
            handler: new OpenLayers.Handler.Click(
                this,
                {
                    'click': function(evt) {
                        var px = new OpenLayers.Pixel(evt.xy.x, evt.xy.y);
                        var lonlat = _mapManager.getMap().getLonLatFromPixel(px);
                        _self.identifyParcel(lonlat);
                    }
                },
                {
                    'single': true,
                    'double': false,
                    'pixelTolerance': 0,
                    'stopSingle': false,
                    'stopDouble': false
                }
            )
        });
        _self.setOpenLayersControl(control);
    }

    _self.init = function(am, id) {
        /// <summary>Initializes the identify component by adding an OpenLayers.Layer.Vector
        ///          layer to the map of the component manager and adds a new accordion panel
        ///          to the tools tab panel (also accessed via the component manager).</summary>
        _appManager = am;
        _base.init(_appManager, id);
        _mapManager = _appManager.getMapManager();
        _layoutManager = _appManager.getLayoutManager();
        
        return _self;
    };
    
    _self.render = function(){
        /// <summary>Renders the component to the DOM.</summary>
        
        // Create and add an Annotations layer
        _layer = new OpenLayers.Layer.Vector("Parcel Layer");
        _mapManager.getMap().addLayer(_layer);

        // Create and add the accordion panel to the layout
        _panel = new Ext.Panel({
            id: _layoutManager.getOption('PREFIX') + 'ParcelDataPanel',
            title: 'Identify Parcel',
            layout: 'fit',
            border: false,
            html: '<div id="identifyMsg">' + _defaultMessage + '</div>'
        });
        
        if (_self.options && _self.options.resultRenderers) {
            Ext.apply(_resultRenderers, _self.options.resultRenderers, mapviewer.identify.config.resultRenderers);
        }

        if (_self.options && _self.options.updateResultFeature) {
            _updateResultFeature =  _self.options.updateResultFeature;
        }
        else {
            _updateResultFeature = mapviewer.identify.config.updateResultFeature;
        }

        // Option provided to bind events to a search field
        if (_self.options && _self.options.search && _self.options.search.fieldId) {
            var searchBoxField = Ext.get(_self.options.search.fieldId);
            if (_self.options.search.defaultSearchTerm) {
                searchBoxField.dom.value = _self.options.search.defaultSearchTerm;
            } else {
                searchBoxField.dom.value = _searchText;
            }

            //Clear the search box on focus, when appropriate
            searchBoxField.on('focus', function(e, txt) {
                if (txt.value === _searchText) {
                    txt.value = '';
                    searchBoxField.addClass('active');
                } else {
                    txt.select();
                }
            });

            //Bind Search text field blur event
            searchBoxField.on('blur', function(e, txt) {
                if (!txt.value) {
                    txt.value = _searchText;
                    searchBoxField.removeClass('active');
                }
            });

            //Bind Search text keyup event - search on enter.
            //Doing this manually since we're not using a form.
            searchBoxField.on('keyup', function(e, txt) {
                if (e.keyCode == 13) {
                    _self.identifyParcel(txt.value, true);
                }
            });

            // Option provided to bind events to a search button
            if (_self.options.search.buttonId) {
                Ext.get(_self.options.search.buttonId).on('click', function(e, btn) {
                    _self.identifyParcel(Ext.get(_self.options.search.fieldId).dom.value, true);
                });
            }
        }
        
        //Require that a id panel be provided
        if (_self.options.target) {
            Ext.getCmp(_self.options.target).add(_panel);
            Ext.getCmp(_self.options.target).doLayout();
        }    
    };
    
    function clearParcelData() {
        if (_ajaxReqId) {
            Ext.Ajax.abort(_ajaxReqId);
            _ajaxReqId = null;
        }
        
        //Remove parcel features, if any
        if (_layer.features.length > 0) {
            _layer.removeFeatures(_layer.features);
        }

        //Remove panel items, if any
        _panel.removeItems();

        _panel.doLayout();
    }
    
    _self.syncUIState = function() {
        /// <summary>Syncs the component UI to match the current state.</summary>

        var state = _self.getState();

        if (state) {
            if (state.x && state.y) {
                _self.identifyParcel(new OpenLayers.LonLat(state.x, state.y), true);
            } else if (state.search) {
                _self.identifyParcel(state.search, true);
                Ext.get(_self.options.search.fieldId).dom.value = state.search;
            } else {
                clearParcelData();
                Ext.get(_self.options.search.fieldId).dom.value = _searchText;
                _panel.getEl().child('#identifyMsg').update(_defaultMessage);
            }
        } else {
            clearParcelData();
            Ext.get(_self.options.search.fieldId).dom.value = _searchText;
            _panel.getEl().child('#identifyMsg').update(_defaultMessage);
        }
    };
    
    _self.identifyParcel = function(searchBy, zoom) {
        /// <summary>Creates and displays an annotation both on the map using OpenLayers.Marker.Annotation 
        ///          and tracks each annotation in the panel created during initialization.  Annotations
        ///          can be edited and deleted via the controls on the panel.</summary>
        /// <param name="searchBy" type="object or string">An OpenLayers.LonLat object or a term to use
        ///                                                to search for in the ULRS (that finds an xy).</param>
        /// <param name="zoom" type="boolean">Whether to zoom to that location after updating the map and panel.</param>

        var idParams = {};

        if (searchBy) {
            if (searchBy.CLASS_NAME === 'OpenLayers.LonLat') {
                idParams.x = searchBy.lon;
                idParams.y = searchBy.lat;
            } else {
                idParams.search = searchBy;
            }
        
            //A way to indicate what is being identified
            _self.setState(idParams);

            //Focus (and render)
            _layoutManager.toolFocus(_layoutManager.getOption('PREFIX') + 'ParcelDataPanel');

            //Remove any existing panels and update message to "loading"
            clearParcelData();
            _panel.getEl().child('#identifyMsg').update(_loadingMessage);

            //Ask the handler for parcel data for the point or search term
            _ajaxReqId = Ext.Ajax.request({
                method: 'GET',
                url: _self.options.search.serviceUrl,
                autoAbort: true,
                params: idParams,
                success: function(res, req) {
                    //Got a response, now refresh the panel
                    _ajaxReqId = null;
                    _self.refreshParcelData(Ext.decode(res.responseText), zoom);
                },
                failure: function(res, req) {
                    //Failed, show the error message
                    _ajaxReqId = null;
                    _panel.getEl().child('#identifyMsg').update(_errorMessage);
                }
            });
        }
    };

    _self.refreshParcelData = function(queryResult, zoom) {
        /// <summary>Refreshes the displayed parcel data, both on the map and the panel</summary>
        /// <param name="queryResult" type="object">All parcel data returned from the handler for the
        ///                                        search term or xy.</param>
        /// <param name="zoom" type="boolean">Whether to zoom to that location after updating the map and panel.</param>

        var i, j, parcelDataContentPanel, startCollapsed;
        var wktParser = new OpenLayers.Format.WKT();
        var geometry;

        clearParcelData();

        //Did we get anything back?
        if (queryResult.Envelope && queryResult.Features) {

            //Did we get some parcels back?
            if (queryResult.Features.length > 0) {

                //Collapse everything if we got more then one parcel back
                startCollapsed = (queryResult.Features.length > 1);

                for (j = 0; j < queryResult.Features.length; j++) {

                    //Did we get any data back to display in the panel?
                    if (queryResult.Features[j] && queryResult.Features[j].Attributes) {
                        // Set a new 'Display' variable that contains the feature 'Title' 
                        // and updates the Attributes to a "Simplified" set of key/value pairs to display                    
                        queryResult.Features[j].Display = _updateResultFeature.call(queryResult.Features[j], queryResult.Features[j].Attributes, _resultRenderers);

                        //Make the content panel if needed
                        if (!parcelDataContentPanel) {
                            parcelDataContentPanel = new Ext.Panel({
                                id: _layoutManager.getOption('PREFIX') + 'ParcelDataContentPanel',
                                // Our layout is a table with only one column.
                                layout: 'table',
                                layoutConfig: { columns: 1 },
                                // Make sure everything in our table is WEST_CONTENT_WIDTH pixels wide.
                                defaults: { width: _layoutManager.getOption('WEST_CONTENT_WIDTH') },
                                // Our panel needs to be wide enough to have a scroll bar.
                                width: _layoutManager.getOption('WEST_WIDTH'),
                                // Allow the outer container to control this panel's height.
                                autoHeight: false,
                                // We want scroll bars if the contents are longer than this panel.
                                autoScroll: true,
                                border: false
                            });
                        }

                        //Add overview map image and data grid
                        parcelDataContentPanel.add(initParcelDataContentItem(queryResult.Features[j], startCollapsed));

                        geometry = wktParser.read(queryResult.Features[j].Attributes.Shape);

                        //Do we have a shape to show on the map?
                        if (geometry) {
                            geometry.style = {
                                fillColor: '#0075BD',
                                fillOpacity: 0.4,
                                strokeColor: '#0075BD',
                                strokeOpacity: 1,
                                strokeWidth: 1
                            };

                            //Add feature to the map
                            _layer.addFeatures(geometry);
                        }
                    }
                }

                // add the content to the accordion panel.
                if (parcelDataContentPanel) {
                    _panel.add(parcelDataContentPanel);
                }
            }

            _panel.getEl().child('#identifyMsg').update(_defaultMessage);

            //Should I zoom to this extent?
            if (zoom) {
                _mapManager.getMap().zoomToExtent(
                    new OpenLayers.Bounds(queryResult.Envelope.XMin, queryResult.Envelope.YMin,
                                          queryResult.Envelope.XMax, queryResult.Envelope.YMax)
                );
            }
        }
        else {
            _panel.getEl().child('#identifyMsg').update(_notFoundMessage);
        }

        _panel.doLayout();
    };
    
    function initParcelDataContentItem(resultFeature, startCollapsed) {
        /// <summary>Returns a panel with a map thumbnail and data grid </summary>
        /// <param name="resultFeature" type="object">The parcel data</param>
        /// <param name="startCollapsed" type="boolean">Should the panel(s) be collapsed</param>
        /// <returns>Ext.Panel</returns>

        var thumbParams = encodeURI('?geometries=[{geom:"' + resultFeature.Attributes.Shape +
                                    '", linecolor:"#0075bd",fillcolor:"#0075bd",fillopacity:0.4}]' +
                                    '&width=' + _layoutManager.getOption('WEST_CONTENT_WIDTH') +
                                    '&height=' + 150 +
                                    '&xmin=' + resultFeature.BufferedEnvelope.XMin +
                                    '&ymin=' + resultFeature.BufferedEnvelope.YMin +
                                    '&xmax=' + resultFeature.BufferedEnvelope.XMax +
                                    '&ymax=' + resultFeature.BufferedEnvelope.YMax)
                                    .replace(/#/g, '%23');
        // The One Panel, has everything about a single parcel.
        return new Ext.Panel({
            title: resultFeature.Display.Title,
            // Allow width to be forced by containing panel.
            autoWidth: false,
            collapsible: true,
            animCollapse: false,
            titleCollapse: true,
            collapsed: startCollapsed,
            // No borders on content items.
            defaults: { border: false },
            items: [
                //Map Thumbnail
                new Ext.Panel({
                    html: '<img src="handlers/viewer/parcel-thumbnail.ashx' + thumbParams +
                                    '" ext:qtip="Click to zoom to the parcel" />',
                    listeners: {
                        render: function(c) {
                            c.body.on('click', function() {
                                _mapManager.getMap().zoomToExtent(
                                    new OpenLayers.Bounds(resultFeature.BufferedEnvelope.XMin, resultFeature.BufferedEnvelope.YMin,
                                                          resultFeature.BufferedEnvelope.XMax, resultFeature.BufferedEnvelope.YMax)
                                );
                            });
                        },
                        scope: this
                    }
                }),
                //Data Grid
                initParcelDataGrid(resultFeature)
            ]
        });
    }

    function initParcelDataGrid(parcelData) {
        /// <summary>Returns a data grid populated with parcel data</summary>
        /// <param name="parcelData" type="object">The parcel data</param>
        /// <returns>Ext.grid.GridPanel</returns>

        var keyWidth = 120;
        var valWidth = 190;

        //How to read the parcel data
        var parcelDataReader = new Ext.data.JsonReader({
                id: _layoutManager.getOption('PREFIX') + 'GridReader'
            },
            _parcelInfoRecord
        );

        // Define GridPanel 
        var grid = new Ext.grid.GridPanel({
            store: new Ext.data.GroupingStore({
                reader: parcelDataReader,
                data: parcelData.Display.Data,
                sortInfo: { field: 'sort', direction: 'ASC' },
                groupField: 'category'
            }),
            columns: [
                { dataIndex:'key', width:keyWidth },
                { dataIndex:'value', width:valWidth },
                { dataIndex:'category', hidden:true },
                { dataIndex:'sort', hidden:true }
            ],
            view: new Ext.grid.GroupingView({
                forceFit: true,
                scrollOffset: 0,
                groupTextTpl: '{text}',
                showGroupName: false,
                startCollapsed: false
            }),
            // not setting width works fine in firefox, but defaults to some absurdly large
            // width in IE (like 10,000 pixels).  Hopefully this will be fixed in the 2.0
            // release, it is something that has been discussed in the forum at:
            // http://extjs.com/forum/showthread.php?t=15508        
            width: _layoutManager.getOption('WEST_CONTENT_WIDTH'),
            autoHeight: true,
            disableSelection: true,
            enableHdMenu: false,
            listeners: {
                mousedown: function(grid) {
                    //This is an Ext hack to allow IE user to select the text in a grid cell.
                    //FF is handled in the CSS
                    $('div.x-grid3-cell-inner[unselectable]').attr('unselectable', 'off');
                }
            }
        });
        
        // AdditionalInfos are functions that can be called after the initial DataGrid is constructed, 
        // that can add more data asynchronously.
        // Each "AdditionalInfo" function takes the "Display" object as its context (this), 
        // and a required callback function.  The callback function should be called by the 
        // AdditionalInfo function after it is finished processing whatever data it needs, 
        // and should pass back an array objects that conform to the _parceInfoRecord Ext Record object.
        
        function additionalInfoCallback(toAdd) {
            // inside the callback function, which will fire after the additionalInfo function is finished
            // check to make sure the returned object is roughly an Array and has some items.
            if(toAdd && toAdd.length && toAdd.length > 0) {
                var recs = [];
                // loop over each object and turn it into a _parceInfoRecord object.
                for(var r = 0; r < toAdd.length; r++) {
                    // this is kind of hacky: for some reason the JsonReader/GroupingStore process data 
                    // differently when it's supplied in the Store's constructor vs. add it manually here.
                    // The constructor version requires the object keys to match the 'mapping' references, 
                    // whereas in this context it must match GridPanel column dataIndex(s).  Not sure if this 
                    // is a bug or what.
                    var rec = { key:toAdd[r].Key, value:toAdd[r].Value, category:toAdd[r].Category, sort:toAdd[r].SortOrder };
                    // add the Record instance to an Array
                    recs.push(new _parcelInfoRecord(rec));
                }
                
                // Add the array of records to the GridPanel's store
                grid.store.add(recs);
                grid.store.sort('sort', 'ASC');
            } 
        }
        
        // if any AdditionalInfo function were provided, process them
        if(parcelData.Display.AdditionalInfo) {
            // loop over each key in the AdditionalInfo object
            for(var f in parcelData.Display.AdditionalInfo) {
                // check to see if this key in the object represents a function
                if(typeof parcelData.Display.AdditionalInfo[f] === 'function') {
                    // if we have a function, call it with the Display as its context, and pass in the callback function
                    parcelData.Display.AdditionalInfo[f].call(parcelData.Display, additionalInfoCallback);
                }
            }
        }
        
        return grid;
    }

    return _self;
};

/********************   End clientsrc\viewer\mapviewer.component.identify.js ********************/


/******************** Begin clientsrc\viewer\mapviewer.component.legend.js ********************/
mapviewer.component = mapviewer.component || {};

mapviewer.component.legend = function(options) {
    var _appManager, _mapManager, _layoutManager;
    var _defaults = {
        id: 'mv-legend',
        target: 'body',
        url: 'handlers/viewer/legend.ashx'
    };
    var _base = mapviewer.component($.extend(_defaults, options));
    var _self = $.extend({}, _base);

    _self.baseClassName = _base.className;
    _self.className = 'mapviewer.component.legend';

    var $container;
    
    _self.initState = function(callback) {
        ///<summary>Setup inital state</summary>
        
        function dataToState(data) {
            var node, i, j;
            var retVal = {};
            for(i=0; i<data.length; i++) {
                if (data[i].children) {
                    for(j=0; j<data[i].children.length; j++) {
                        node = data[i].children[j];
                        retVal[node.mapLayer] = node.checked;
                    }
                }
            }
            
            return retVal;
        }
        
        function stateToData(data, state) {
            var i, j;
            var retVal = $.extend([], data);
            for(i=0; i<data.length; i++) {
                if (data[i].children) {
                    for(j=0; j<data[i].children.length; j++) {
                        retVal[i].children[j].checked = state[retVal[i].children[j].mapLayer] || false;
                    }
                }
            }
            
            return retVal;
        }
        
        $.ajax({
            url: _self.options.url,
            dataType: 'json',
            success: function(data) {
                var legendData = data;
                
                var curState = _self.getState();
                var ajaxState = dataToState(data);

                //Is state already set on the legend (ie. it's in the hash)
                if (curState) {
                    //Is the current state different than the initial (ajax) state?
                    if (_self.getStateString() !== _self.parseState(ajaxState)) {
                        //We need to update the UI to match the initial hash state
                        
                        //Set state, no need to trigger events
                        legendData = stateToData(data, curState);
                        _self.setState(curState, false);
                        
                        //Update the map state
                        $.each(curState, function(layerId, visible) {
                            _mapManager.setLayerVisiblity(visible, _self.options.layerName, layerId, true);
                        });
                        _mapManager.refreshLayerVisiblity(_self.options.layerName);
                        
                    } else {
                        //There's a hash value but it's the same as the current state.
                    }
                } else {
                    //Probably nothing in the hash. Put the state there.
                    _self.setState(ajaxState);
                }
                
                if (callback) {
                    //Used to render the legend with correct data
                    callback(legendData);
                }
            },
            error: function(resp) {
                alert(resp.responseText);
            }
        });
    };
    
    function _render(legendData) {
        $container = $('<div id="' + _self.options.id + '" class="mv-legend"></div>').appendTo(_self.options.target);
        
        function renderNode(node) {
            var id = node.id || 'anon';
            var cssclass = '';
            if(node.leaf) {
                cssclass += 'mv-legend-leaf';
            }
            if(node.checked) {
                if(cssclass) { cssclass += ' '; }
                cssclass += 'mv-legend-checked';
            }
            var html = '<li class="mv-legend-' + id + '">';
            html += '<span></span>';
            html += '<strong class="' + cssclass + '" rel="' + node.mapLayer + '">' + node.text + '</strong>';
            if(node.children && node.children.length) {
                html += '<ul>';
                $.each(node.children, function(j, child) {
                    html += renderNode(child);
                });
                html += '</ul>';
            }
            html += '</li>';
            return html;
        }
        
        var html = '<ul class="mv-legend-groups">';
        $.each(legendData, function(i, node) {
            html += renderNode(node);
        });
        html += '</ul>';
        $container.append(html);
        $('.mv-legend-leaf', $container).click(function() {
            var state = _self.getState();
            var mapLayer = $(this).attr('rel');
            
            if (!state) {
                state = {};
            }
            
            if($(this).hasClass('mv-legend-checked')) {
                $(this).removeClass('mv-legend-checked');
                _mapManager.setLayerVisiblity(false, _self.options.layerName, mapLayer);
                state[mapLayer] = false;
            } else {
                $(this).addClass('mv-legend-checked');
                _mapManager.setLayerVisiblity(true, _self.options.layerName, mapLayer);
                state[mapLayer] = true;
            }
            _self.setState(state);
        });
    }
    
    _self.init = function(am, id) {
        /// <summary>Initializes the legend component by creating a layout to manage
        ///          the visible layers.  This will be done by manipulating the layer manually
        ///          and not via the OpenLayers layer switcher control.</summary>
        _appManager = am;
        _base.init(_appManager, id);
        _mapManager = _appManager.getMapManager();
        _layoutManager = _appManager.getLayoutManager();

        return _self;
    };
    
    _self.render = function(){
        /// <summary>Renders the component to the DOM.</summary>
        
        _self.initState(function(){
            $container = $('#' + _self.options.id);

            //If it's not there, I'll go ahead and make it.
            if(!$container.length) {
                _render();
            }
        });
    };
    
    _self.syncUIState = function() {
        /// <summary>Syncs the component UI to match the current state.</summary>

        var state = _self.getState();
        if (state) {
        
        }

        //TODO: Implement later. No simple way to test this in PWD.
    };

   
    return _self;
};
/********************   End clientsrc\viewer\mapviewer.component.legend.js ********************/


/******************** Begin clientsrc\viewer\mapviewer.component.measure.js ********************/
mapviewer.component = mapviewer.component || {};

mapviewer.component.measure = function(options) {
    /// <summary>Implements the measure component</summary>
    var _defaults = {};
    var _appManager, _mapManager, _layoutManager;
    var _base = mapviewer.component($.extend(_defaults, options));
    var _self = $.extend({}, _base);
    
    _self.baseClassName = _base.className;
    _self.className = 'mapviewer.component.measure';

    var _endPointBufferFactor = 160;
    var _measureDrawLayer;
    var _panel;
    var _measurement = {
        points: [],
        sides: [],
        totalLength: function() {
            var retVal = 0;
            var i;
            for (i = 0; i < this.sides.length; i++) {
                retVal += parseFloat(this.sides[i]);
            }
            return retVal;
        }
    };
    
    if (_self.options.control) {
        _self.setOpenLayersControl(_self.options.control);
    } else {
        var control = new OpenLayers.Control(
        {
            title: 'Click the map to begin measuring.',
            displayClass: 'olControlMeasureLine',
            handler: new OpenLayers.Handler.Click(
                    this,
                    {
                        'click': function(evt) {
                            _self.measurementClick(evt.xy.x, evt.xy.y);
                        }
                    },
                    {
                        'single': true,
                        'double': false,
                        'pixelTolerance': 0,
                        'stopSingle': false,
                        'stopDouble': false
                    }
                )
        });
        _self.setOpenLayersControl(control);
    }

    _self.init = function(am, id) {
        /// <summary>Initializes the annotation component by adding an OpenLayers.Layer.Annotations
        ///          layer to the map of the component manager and adds a new accordion panel
        ///          to the tools tab panel (also accessed via the component manager).</summary>
        _appManager = am;
        _base.init(_appManager, id);
        _mapManager = _appManager.getMapManager();
        _layoutManager = _appManager.getLayoutManager();

        return _self;
    };
    
    _self.render = function(){
        /// <summary>Renders the component to the DOM.</summary>

        // Create and add an Annotations layer
        _measureDrawLayer = new OpenLayers.Layer.Vector('MeasurementLayer');
        _mapManager.getMap().addLayer(_measureDrawLayer);

        // Create and add the accordion panel to the layout
        _panel = new Ext.Panel({
            id: _layoutManager.getOption('PREFIX') + 'MeasurementPanel',
            title: 'Measure',
            layout: 'fit',
            border: false,
            html: _self.options.defaultMsg || '<img alt="Measure Tool Icon" src="client/viewer/css/images/tool_measure.gif" style="float: left;"/><span>Select the Measure tool and click the map to begin measuring.</span>'
        });

        //Require that a id panel be provided
        if (_self.options.target) {
            Ext.getCmp(_self.options.target).add(_panel);
            Ext.getCmp(_self.options.target).doLayout();
        }
    };
        
    _self.syncUIState = function() {
        /// <summary>Syncs the component UI to match the current state.</summary>

        var state = _self.getState();

        try {
            var sides, area, points = [];
            if (state) {
                if ($.isArray(state.pts)) {
                    $.each(state.pts, function(i, ptStr) {
                        var ptArray = ptStr.split(',');
                        points.push(new OpenLayers.Geometry.Point(ptArray[0], ptArray[1]));
                    });
                }
                sides = state.segs || [];
                area = state.area || null;
            }
            _measurement.points = points;
            _measurement.sides = sides;
            _measurement.area = area;
            
            _self.refreshMeasurements();
        }
        catch(e) {
            Avencia.log('An error occurred while refreshing the UI state of ' + _self.className + '.');
            Avencia.log(e);
        }
    };
        
    function addMeasurePoint(point) {
        /// <summary>Adds a new click point at the given x/y, adds a length to the measurement list
        ///          if there are enough points, and updates the area if the line is now closed.</summary>
        /// <param name="point" type="OpenLayers.Geometry.Point">The location of the next measurement point.</param>
        try {
            _measurement.points.push(point);
            if (_measurement.points.length > 1) {
                var side = _measurement.points[_measurement.points.length - 1].distanceTo(
                           _measurement.points[_measurement.points.length - 2]).toFixed(1);
                _measurement.sides.push(side);
            }
            if (_self.isComplete()) {
                _measurement.area = new OpenLayers.Geometry.Polygon(
                        [new OpenLayers.Geometry.LinearRing(_measurement.points)]).getArea();
            }
            _self.refreshMeasurements();
        } catch (ex) {
            logError(ex, "adding a measurement point: " + point);
        }
    }
    function logError(ex, action) {
        // TODO: Decide whether this is worth keeping or not.
    }
    
    _self.getWkt = function() {
        /// <summary>Returns the WKT LINESTRING representation of the measurement.</summary>
        var i, wkt = 'LINESTRING(';
        
        for(i=0; i<_measurement.points.length; i++){
            if (i>0) {
                wkt += ',';
            }
            wkt += _measurement.points[i].x + ' ' + _measurement.points[i].y;
        }
        
        wkt += ')';
        
        return wkt;
    };

    _self.measurementClick = function(x, y) {
        /// <summary>Adds another point to the measurement series.</summary>
        /// <param name="x" type="number">The x pixel value </param>
        /// <param name="y" type="number">The y pixel value</param>

        // Only process if we haven't completed an area.
        try {
            if (!_self.isComplete()) {
                // First convert to geo coordinates.
                var lonlat = _mapManager.getMap().getLonLatFromPixel(new OpenLayers.Pixel(x, y));
                // Convert to a Point.
                var clickPoint = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
                // Now determine was this close enough to complete the area?
                if (_self.canComplete()) {
                    if (_measurement.points[0].distanceTo(clickPoint) <
                            (_mapManager.getMap().getScale() / _endPointBufferFactor)) {
                        // Close enough to the beginning, so close it instead of adding the
                        // point they actually clicked on.
                        clickPoint = _measurement.points[0];
                    }
                }
                addMeasurePoint(clickPoint);
            }
        } catch (ex) {
            logError(ex, "responding to measure click event.");
        }
    };

    _self.undoLastClick = function() {
        /// <summary>Remove the last click and update the measurement list and map.</summary>
        try {
            _measurement.points.pop();
            _measurement.sides.pop();
            _measurement.area = null;
            _self.refreshMeasurements();
        } catch (ex) {
            logError(ex, "undoing last measurement click.");
        }
    };

    _self.clear = function() {
        /// <summary>Delete the all measurement points, and update the map.</summary>
        try {
            _measurement.points = [];
            _measurement.sides = [];
            _measurement.area = null;

            _self.refreshMeasurements();
        } catch (ex) {
            logError(ex, "clearing list of measurement points.");
        }
    };

    _self.canComplete = function() {
        /// <summary>Returns whether or not there are enough points to auto-complete the measurement area.</summary>
        /// <returns>boolean</returns>
        return (_measurement.points.length > 2);
    };

    _self.isComplete = function() {
        /// <summary>Returns whether or not the measurement line ends on its start point, if there
        ///          are enough points to determine area.</summary>
        /// <returns>boolean</returns>
        if (_self.canComplete()) {
            return ((_measurement.points[0].x == _measurement.points[_measurement.points.length - 1].x) &&
                    (_measurement.points[0].y == _measurement.points[_measurement.points.length - 1].y));
        }
        return false;
    };

    _self.complete = function() {
        /// <summary>Make the last measure point the same as the first (closing the polygon)
        ///          and update the map.  Only works with more than 2 points.</summary>
        try {
            if (_self.canComplete()) {
                addMeasurePoint(_measurement.points[0]);
                _self.refreshMeasurements();
            }
        } catch (ex) {
            logError(ex, "completing measurement area.");
        }
    };

    function getMeasurementStore() {
        /// <summary>Converts the _measurement object into a Ext.data.JsonStore object.</summary>

        var i, data = [];

        for (i = 0; i < _measurement.sides.length; i++) {
            data.push({
                name: 'Segment ' + (i + 1),
                length: _measurement.sides[i]
            });
        }

        data.push({
            name: 'Total Length',
            length: _measurement.totalLength()
        });

        if (_measurement.area) {
            data.push({
                name: 'Area',
                length: _measurement.area
            });
        }

        return new Ext.data.JsonStore({
            data: data,
            fields: ['name', 'length']
        });
    }

    function initMeasurementGrid() {
        try {
            /// <summary>Create the Ext.grid.GridPanel populated with measurement data</summary>
            /// <returns>Ext.grid.GridPanel</returns>

            return new Ext.grid.GridPanel({
                ds: getMeasurementStore(),
                columns: [{
                    dataIndex: 'name',
                    renderer: function(value, meta, record, rowIndex, colIndex, store) {
                        return '<em>' + value + '</em>';
                    },
                    width: 80
                },
                    {
                        dataIndex: 'length',
                        renderer: function(value, meta, record, rowIndex, colIndex, store) {
                            var retVal = parseFloat(value).toFixed(1);
                            if (record.json.name === 'Area') {
                                retVal += ' ft<sup>2</sup>';
                            } else {
                                retVal += ' ft';
                            }
                            return retVal;
                        },
                        width: 195
                    }
                ],
                autoHeight: true,
                width: _layoutManager.getOption('WEST_CONTENT_WIDTH'),
                border: false,
                disableSelection: true,
                enableHdMenu: false,
                buttons: [
                    {
                        text: 'Complete',
                        disabled: !_self.canComplete() || _self.isComplete(),
                        handler: function(btn, ev) {
                            _self.complete();
                        }
                    },
                    {
                        text: 'Undo Last Point',
                        disabled: (_measurement.points.length === 0),
                        handler: function(btn, ev) {
                            _self.undoLastClick();
                        }
                    },
                    {
                        text: 'Clear',
                        disabled: (_measurement.points.length === 0),
                        handler: function(btn, ev) {
                            _self.clear();
                        }
                    }
                ]
            });
        } catch (ex) {
            logError(ex, "refreshing measurement grid.");
        }
    }

    _self.refreshMeasurements = function() {
        /// <summary>Refreshes the displayed measurements, both on the map and the panel</summary>
        var i, measureContentPanel;
        var newFeatures;
        var feature;
        try {

            if (_measurement.points.length > 0) {
                newFeatures = [];
                // First one is a point.
                feature = new OpenLayers.Feature.Vector(_measurement.points[0], {},
                    {
                        fillColor: 'red',
                        fillOpacity: 1,
                        pointRadius: 3
                    });
                newFeatures.push(feature);

                // Create a line with all the points.
                if (_measurement.points.length > 1) {
                    feature = new OpenLayers.Feature.Vector(
                    new OpenLayers.Geometry.LineString(_measurement.points), {},
                    {
                        strokeColor: 'red',
                        strokeOpacity: 1,
                        strokeWidth: 1
                    });
                    newFeatures.push(feature);
                }
            }

            //Refresh annotation panel 
            _panel.removeItems();

            if (_measurement.points.length > 0) {
                // Create new stuff.
                measureContentPanel = new Ext.Panel({
                    id: _layoutManager.getOption('PREFIX') + 'MeasureContentPanel',
                    // Our layout is a table with only one column.
                    layout: 'table',
                    layoutConfig: { columns: 1 },
                    // Make sure everything in our table is WEST_CONTENT_WIDTH pixels wide.
                    defaults: { width: _layoutManager.getOption('WEST_CONTENT_WIDTH') },
                    // Our panel needs to be wide enough to have a scroll bar.
                    width: _layoutManager.getOption('WEST_WIDTH'),
                    // Allow the outer container to control this panel's height.
                    autoHeight: false,
                    // We want scroll bars if the contents are longer than this panel.
                    autoScroll: true,
                    border: false
                });

                measureContentPanel.add(initMeasurementGrid());
                _panel.add(measureContentPanel);
                
                var pts = [];
                $.each(_measurement.points, function(i, pt) {
                    pts.push(pt.x + ',' + pt.y);
                });
                
                var area;
                if (_measurement.area) {
                    area = parseFloat(_measurement.area).toFixed(1);
                }
                
                _self.setState({
                    pts: pts,
                    segs: _measurement.sides,
                    len: parseFloat(_measurement.totalLength()).toFixed(1),
                    area: area
                });
            } else {
                _self.setState(null);
            }

            _layoutManager.toolFocus(_layoutManager.getOption('PREFIX') + 'MeasurementPanel');
            _panel.doLayout();

            //Refresh layer
            _measureDrawLayer.destroyFeatures();
            if (newFeatures) {
                _measureDrawLayer.addFeatures(newFeatures);
            }
            
            
        } catch (ex) {
            logError(ex, "refreshing measurement map layer.");
        }
    };

    return _self;
};
/********************   End clientsrc\viewer\mapviewer.component.measure.js ********************/


/******************** Begin clientsrc\viewer\mapviewer.component.overviewMap.js ********************/
mapviewer.component = mapviewer.component || {};

mapviewer.component.overviewMap = function(options) {
    var _appManager, _mapManager, _layoutManager;
    var _defaults = {
        id: 'mv-ovmap',
        target: 'body',
        visible: true,
        olControlOptions: {}
    };
    var _base = mapviewer.component($.extend(_defaults, options));
    var _self = $.extend({}, _base);

    _self.baseClassName = _base.className;
    _self.className = 'mapviewer.component.overviewMap';
    
    var $container,
        $toggle;

    _self.testOptions = {
        id: 'mv-ovmap'
    };
    
    function _render() {
        /// <summary>Renders raw html for overview map component.</summary>
        $container = $('<div id="' + _self.options.id + '" class="mv-ovmap"></div>').appendTo(_self.options.target);
    }

    _self.init = function(am, id) {
        /// <summary>Initializes the overview map component.</summary>
        _appManager = am;
        _base.init(_appManager, id);
        _mapManager = _appManager.getMapManager();
        _layoutManager = _appManager.getLayoutManager();

        return _self;
    };
    
    _self.render = function(){
        /// <summary>Renders the component to the DOM.</summary>
        
        $container = $('#' + _self.options.id);
        if(!$container.length) {
            _render();
        }
        
        _self.options.olControlOptions.div = $container[0];
        
        var olControl = new OpenLayers.Control.OverviewMap(_self.options.olControlOptions);
        _self.setOpenLayersControl(olControl);
        _mapManager.getMap().addControl(olControl);
    };
        
    _self.syncUIState = function() {
        /// <summary>Syncs the component UI to match the current state.</summary>

        //Do nothing. This UI state will be updated when the map extent updates.
    };
    
    _self.setVisibility = function(visible) {
        /// <summary>Sets component visiblity.</summary>
        _self.options.visible = visible;
    };
    
    _self.isVisible = function() {
        /// <summary>Tells whether the component is visible.</summary>
        return _self.options.visible;
    };
    
    return _self;
};
/********************   End clientsrc\viewer\mapviewer.component.overviewMap.js ********************/


/******************** Begin clientsrc\rtt\rtt.layoutManager.js ********************/
/// <reference path="mapviewer.applicationManager.js" />
rtt.layoutManager = function(options) {
    ///<summary>Location for all application layout code and 
    ///         central broker for all layout interaction.</summary>
    ///<param name="options">Initialization options.</param>
    ///<returns>rtt.layoutManager</returns>
    var _self = {},
        _options,
        _viewport,
        _appManager;

    _options = options || { printSel: '#print' };

    _self.getOption = function(key) {
        ///<summary>Access an layout option.</summary>
        ///<param name="key">Key to extract option from the object.</param>
        return _options[key];
    };

    _self.init = function(appManager) {
        ///<summary>Initialize the manager.</summary>
        ///<param name="appManager">The application manager.</param>
        ///<returns>rtt.layoutManager</returns>
        
        _appManager = appManager;
        _self.hideBusyIndicator();
        _viewport = initViewport();
        
        $(_options.printSel).click(function(){
            _self.openPrintPage();
        });
        
        return _self;
    };
    
    _self.showBusyIndicator = function() {
        /// <summary>Shows the busy indicator as defined in options.layout.busyIndicator.contentEl</summary>
        if (_options && _options.busyIndicator) {
            Ext.get(_options.busyIndicator.contentEl).show();
        }
    };

    _self.hideBusyIndicator = function() {
        /// <summary>Hides the busy indicator as defined in options.layout.busyIndicator.contentEl</summary>
        if (_options && _options.busyIndicator) {
            Ext.get(_options.busyIndicator.contentEl).hide();
        }
    };
    
    _self.openPrintPage = function() {
        /// <summary>Gets the url to the print page.</summary>
        var mapManager = _appManager.getMapManager();
        var docSearchState = _appManager.getComponent('documentSearch').getState();
        var histMapState = _appManager.getComponent('historicMaps').getState();
        var measureState = _appManager.getComponent('measure').getState();
        var annotateState = _appManager.getComponent('annotate').getState();
        var idState = _appManager.getComponent('identify').getState();
        var i, annoStr;
        
        var form = '<form action="Print.aspx" method="post" target="_blank">' + 
            '<input type="hidden" name="layerids" value="' + mapManager.getMap().getLayersBy('isBaseLayer', true)[0].params.LAYERS + '"></input>' +
            '<input type="hidden" name="mapbbox" value="' + mapManager.getMap().getExtent().toBBOX() + '"></input>' +
            '<input type="hidden" name="ovmapbbox" value="' + mapManager.getMap().getControlsByClass('OpenLayers.Control.OverviewMap')[0].ovmap.getExtent().toBBOX() + '"></input>';

        if (idState) {
            if (idState.x && idState.y) {
                form += '<input type="hidden" name="x" value="' + idState.x + '"></input>' +
                    '<input type="hidden" name="y" value="' + idState.y + '"></input>';
            }
        
            if (idState.search) {
                form += '<input type="hidden" name="search" value="' + idState.search + '"></input>';
            }
        }
            
        if (docSearchState) {
            form += '<input type="hidden" name="docto" value="' + docSearchState.toDate + '"></input>' +
                '<input type="hidden" name="docfrom" value="' + docSearchState.fromDate + '"></input>' +
                '<input type="hidden" name="doctype" value="' + docSearchState.docType + '"></input>' + 
                '<input type="hidden" name="doclayerid" value="' + docSearchState.layerId + '"></input>' + 
                '<input type="hidden" name="docfilter" value="' + docSearchState.filter + '"></input>';
        }
        
        if (histMapState) {
            form += '<input type="hidden" name="histmapid" value="' + histMapState.mapId + '"></input>' +
                '<input type="hidden" name="histmaplayerid" value="' + histMapState.layerId + '"></input>' + 
                '<input type="hidden" name="histmapfilter" value="' + histMapState.filter + '"></input>';
        }
        
        var segs = [], wkt;
        if (measureState && measureState.pts.length > 1) {
            for(i=0; i<measureState.segs.length; i++) {
                segs.push(measureState.segs[i]);
            }
        
            form += '<input type="hidden" name="measurewkt" value="' + _appManager.getComponent('measure').getWkt() + '"></input>' +
            '<input type="hidden" name="measuresegs" value="' + segs.join(',') + '"></input>' +
            '<input type="hidden" name="measurelen" value="' + measureState.len + '"></input>';
            if (measureState.area) {
                form += '<input type="hidden" name="measurearea" value="' + measureState.area + '"></input>';
            }
        }
        
        if (annotateState && annotateState.annotations.length > 0) {
            annoStr = '[';
            for(i=0; i<annotateState.annotations.length; i++) {
                annoStr += '{x:' + annotateState.annotations[i].lonlat.lon + 
                    ',y:' + annotateState.annotations[i].lonlat.lat + 
                    ',orientation:\'UpperRight\'' + 
                    ',html:\'' + escape(annotateState.annotations[i].text) + '\'}';
            }
            annoStr += ']';
            
            form += '<input type="hidden" name="annos" value="' + annoStr + '"></input>';
        }
        form += '</form>';
        
        var $form = $(form);
        $form.appendTo('body').get(0).submit();
        $form.remove();
    };

    function initViewport() {
        /// <summary>Initializes the Ext Viewport.  It assumes that an options variable
        ///          is in scope and is valid.</summary>
        /// <returns>Ext.Viewport</returns>

        return new Ext.Viewport({
            id: _options.PREFIX + 'Viewport',
            layout: 'border',
            items: [
                initHeaderPanel(_options.header.contentEl, _options.header.height),
                initWestPanel(),
                initCenterPanel()
            ]
        });
    }

    function initHeaderPanel(contentEl, height) {
        /// <summary>Initializes the header panel.</summary>
        /// <returns>Ext.BoxComponent</returns>

        return new Ext.BoxComponent({
            region: 'north',
            el: contentEl,
            margins: '0 0 5 0',
            border: false,
            height: height
        });
    }

    //Start Center Panel init functions
    function initCenterPanel() {
        /// <summary>Initializes the center panel of the app.  It assumes that an 
        ///          options variable is in scope and is valid.</summary>
        /// <returns>Ext.Panel</returns>

        return new Ext.Panel({
            id: _options.PREFIX + 'CenterPanel',
            region: 'center',
            margins: '0 5 5 0',
            border: false,
            layout: 'border',
            items: [
                initToolbarPanel(_options.toolbar.contentEl, _options.toolbar.height),
                initMapPanel(_options.map.contentEl)
            ]
        });
    }

    function initToolbarPanel(contentEl, height) {
        /// <summary>Initializes the toolbar panel that contains the map controls.  
        ///          It assumes that an options variable is in scope and is valid.</summary>
        /// <param name="contentEl" type="string">The element id of the toolbar content</param>
        /// <param name="height" type="string">The fixed height of the toolbar content</param>
        /// <returns>Ext.Panel</returns>

        return new Ext.Panel({
            id: _options.PREFIX + 'ToolbarPanel',
            region: 'north',
            border: true,
            height: height,
            bodyStyle: 'padding:0px',
            margins: '0 0 5 0',
            contentEl: contentEl
        });
    }

    function initMapPanel(contentEl) {
        /// <summary>Initializes the map panel that contains the map.  
        ///          It assumes that an options variable is in scope and is valid.</summary>
        /// <param name="contentEl" type="string">The element id of map content</param>
        /// <returns>Ext.Panel</returns>

        return new Ext.Panel({
            id: _options.PREFIX + 'MapPanel',
            region: 'center',
            border: true,
            contentEl: contentEl
        });
    }
    //End Center Panel init functions

    //Start West Panel init functions
    function initWestPanel() {
        /// <summary>Initializes the west panel that contains the non-map components.  
        ///          It assumes that an options variable is in scope and is valid.</summary>
        /// <returns>Ext.Panel</returns>

        return new Ext.Panel({
            id: _options.PREFIX + 'WestPanel',
            region: 'west',
            /* Until we can determine how to capture this resize event */
            collapsible: false,
            width: 300,
            minSize: 175,
            maxSize: 400,
            layout: 'fit',
            margins: '0 5 5 5',
            /* Split is evil for Ext JS v2.0beta for IE 
            * http://support.microsoft.com/kb/927917/en-us
            * split:true, 
            */
            items: new Ext.Panel({
                id: _options.PREFIX + 'ToolsPanel',
                border: false,
                layout: 'border',
                items: [
                    initOverviewMapPanel(_options.overviewMap.contentEl),
                    initComponentContainerPanel()
                ]
            })
        });
    }

    function initOverviewMapPanel(contentEl) {
        /// <summary>Creates the panel where the overview map lives.</summary>
        /// <returns>Ext.Panel</returns>

        return new Ext.Panel({
            id: _options.PREFIX + 'MapOverviewPanel',
            region: 'north',
            title: 'Overview Map',
            collapsible: true,
            collapsedCls: 'x-panel',
            border: false,
            margins: '0 0 5 0',
            height: 200,
            contentEl: contentEl
        });
    }

    function initComponentContainerPanel() {
        /// <summary>Creates the component container panel (where the tab panels live).</summary>
        /// <returns>Ext.TabPanel</returns>

        return new Ext.TabPanel({
            id: _options.PREFIX + 'ComponentContainerPanel',
            border: false,
            activeTab: 0,
            region: 'center',
            items: [
	            initOptionsTabPanel(),
                initToolsTabPanel()
            ]
        });
    }

    function initOptionsTabPanel() {
        /// <summary>Creates the Map Options tab panel</summary>
        /// <returns>Ext.Panel</returns>

        return new Ext.Panel({
            id: _options.PREFIX + 'OptionsTabPanel',
            title: 'Map Options',
            split: true,
            collapsible: true,
            layout: 'accordion',
            layoutConfig: {
                animate: true
            }
            /*
            items: [
            createLegendPanel(),
            createDocumentDisplayPanel(),
            createMapConfigHistoricMaps()
            ]
            */
        });
    }

    function initToolsTabPanel() {
        /// <summary>Creates the Tools tab panel</summary>
        /// <returns>Ext.Panel</returns>

        return new Ext.Panel({
            id: _options.PREFIX + 'ToolsTabPanel',
            title: 'Tools',
            split: true,
            collapsible: true,
            layout: 'accordion',
            layoutConfig: {
                animate: true
            }
            /*
            items: [
            createParcelDataPanel(),
            createMeasureToolPanel(),
            createAnnotateToolPanel()
            ]
            */
        });
    }
    //End West Panel init functions

    _self.toolFocus = function(panelId) {
        /// <summary>Focuses on accordion panel with the provided ID.</summary>
        /// <param name="panelId" type="string">The accordion panel ID</param>

        var toolPanel = Ext.getCmp(panelId);
        var tabPanel = toolPanel.ownerCt;
        var componentContainer = tabPanel.ownerCt;

        componentContainer.activate(tabPanel);
        toolPanel.expand();
    };
    
    return _self;
};
/********************   End clientsrc\rtt\rtt.layoutManager.js ********************/


/******************** Begin clientsrc\rtt\rtt.identify.js  ********************/
// this is the function as part of the mapviewer.identify api that allows us to process data results 
// before passing them into the results datagrid
rtt.identify = {
    updateResultFeature: function(attributes, renderers) {
        // this is just an enum-like lookup because these values get used a lot.
        var categories = {
            parcel: '1. Parcel Info',
            additional: '2. Additional Info',
            parents: '3. Parents',
            children: '3. Children', // this is correct. parents and children should be mutually exclusive, so there would not be more than one "3"
            nofamily: '3. Parcel Relationships',
            links: '4. External Links',
            condos: '5. Condo Units',
            docs: '6. Documents'
        };
        
        // the entire feature is passed in as the function's context, which we need in this case to get the envelope center.
        var featureObj = this;
        
        // regMap is cached so it can be used in other searches
        var regMap = mapviewer.identify.config.renderResult(attributes, renderers, 'regMapId', 'BaseRegistryMapID', 'Map', 'ParcelNumber');
        // address is cached here because it is used in several places (parcel info, documents)
        var address = mapviewer.identify.config.renderResult(attributes, renderers, 'address', 'StreetNumber', 'StreetExtension', 'Unit', 'StreetPrefix', 'StreetName', 'StreetType', 'StreetSuffix');
        // Active, Inactive, Remainder, etc
        var parcelStatus = attributes.Status;
        // process the basic parcel info into rendered value objects
        var parcelInfoFields = mapviewer.identify.config.fieldsToRecords(attributes, renderers, rtt.identify.config.parcelInfoFields, categories.parcel);
        
        // find center of envelope to use as an X,Y to pass into other applications (external links)
        var featureCenter = { X:0, Y:0 };
        if(featureObj.BufferedEnvelope) {
            var xLen = featureObj.BufferedEnvelope.XMax - featureObj.BufferedEnvelope.XMin,
                yLen = featureObj.BufferedEnvelope.YMax - featureObj.BufferedEnvelope.YMin;
            featureCenter.X = featureObj.BufferedEnvelope.XMax - (xLen / 2);
            featureCenter.Y = featureObj.BufferedEnvelope.YMax - (yLen / 2);
        }
        
        // the "additionaInfos". these are functions that will get fired after the GridPanel is constructed, 
        // so we can add more data asynchronously.
        var additionalInfos = {
            // this does a handler call and gets "extra" parcel data from the ULRS
            extraInfo: function(callback) {
                if (parcelStatus === 1 || parcelStatus === 3) {
                    Ext.Ajax.request({
                        method:'GET',
                        url: 'handlers/parcel-extrainfo.ashx',
                        params: { regMap:attributes.BaseRegistryMapID },
                        success: function(res, req) {
                            var extraInfo = Ext.decode(res.responseText);
                            
                            var recs = mapviewer.identify.config.fieldsToRecords(extraInfo, renderers, rtt.identify.config.extraInfoFields, categories.additional, 100);
                            callback(recs);
                        },
                        failure: function(res, req) {
                            var rec = rtt.identify.config.makeErrorRecord(categories.additional);
                            callback([ rec ]);
                        }
                    });
                } else {
                    var noInfo = { Key:'Additional info', Value:'None', Category:categories.additional, SortOrder:100 };
                    callback([ noInfo ]);
                }
            },
            parcelFamily: function(callback) {
                Ext.Ajax.request({
                    method: 'GET',
                    url: 'handlers/parcel-family.ashx',
                    params: { regMap:attributes.BaseRegistryMapID },
                    success: function(res, req) {
                        var family = Ext.decode(res.responseText);
                        
                        var orderer = new mapviewer.orderer(200);
                        var recs = [];
                        if(family && family.Parents && family.Parents.length > 0) {
                            for(var i = 0; i < family.Parents.length; i++) {
                                var parent = family.Parents[i];
                                var parentVal = mapviewer.identify.config.renderResult(parent, renderers, 'parcelFamilyLink', 'ParentReg');
                                recs.push({ Key:'Parent MapReg', Value:parentVal, Category:categories.parents, SortOrder:orderer.next() });
                            }
                        }
                        if(family && family.Children && family.Children.length > 0) {
                            for(var j = 0; j < family.Children.length; j++) {
                                var child = family.Children[j];
                                var childVal = mapviewer.identify.config.renderResult(child, renderers, 'parcelFamilyLink', 'BaseReg');
                                recs.push({ Key:'Child MapReg', Value:childVal, Category:categories.children, SortOrder:orderer.next() });
                            }
                        }
                        
                        if(recs.length === 0) {
                            recs.push({ Key:'Parcel relationships', Value:'None', Category:categories.nofamily, SortOrder:orderer.next() });
                        }
                        
                        callback(recs);
                    },
                    failure: function(res, req) {
                        var errorRec = rtt.identify.config.makeErrorRecord(categories.nofamily);
                        callback([ errorRec ]);
                    }
                });
            },
            // this builds the external links
            externalLinks: function(callback) {
                // this is the object stub we'll pass into the renderer  
                var linksObj = {
                    address: address.trim(),
                    xCoord: featureCenter.X,
                    yCoord: featureCenter.Y
                };
                
                var orderer = new mapviewer.orderer(300);
                var recs = [];
                for(var i = 0; i < rtt.identify.config.externalLinkFields.length; i++) {
                    var field = rtt.identify.config.externalLinkFields[i];
                    linksObj.href = field.keys[0];
                    linksObj.title = field.keys[1];
	                var linkVal = mapviewer.identify.config.renderResult(linksObj, renderers, 'externalLink', 'href', 'title', 'address', 'xCoord', 'yCoord');
                    recs.push({ Key:field.label, Value:linkVal, Category:categories.links, SortOrder:orderer.next() });
                }
                
                callback(recs);
            },
            condos: function(callback) {
                Ext.Ajax.request({
                    method: 'GET',
                    url: 'handlers/parcel-condos.ashx',
                    params: { regMap:attributes.BaseRegistryMapID },
                    success: function(res, req) {
                        var condos = Ext.decode(res.responseText);
                        
                        var recs = [], 
                            orderer = new mapviewer.orderer(400);
                        if(condos && condos.constructor === Array && condos.length > 0) {
                            
                            for(var c = 0; c < condos.length; c++) {
                                var condo = condos[c];
                                var unit = mapviewer.identify.config.renderResult(condo, renderers, 'condoUnit', 'RecMap', 'CondoParcel');
                                var condoValue = mapviewer.identify.config.renderResult(condo, renderers, 'condoValue', 'CondoUnit', 'Status', 'CondoName');
                                recs.push({ Key:unit, Value:condoValue, Category:categories.condos, SortOrder:orderer.next() });
                            }
                        } else {
                            recs.push({ Key:'Condos', Value:'None', Category:categories.condos, SortOrder:orderer.next()});
                        }
                        callback(recs);
                    },
                    failure: function(res, req) {
                        var errorRec = rtt.identify.config.makeErrorRecord(categories.condos);
                        callback([ errorRec ]);
                    }
                });
            },
            documents: function(callback) {
                Ext.Ajax.request({
                    method:'GET',
                    url: 'handlers/parcel-docs.ashx',
                    params: { regMap:attributes.BaseRegistryMapID },
                    success: function(res, req) {
                        var docs = Ext.decode(res.responseText);
                        
                        var recs = [];
                        var orderer = new mapviewer.orderer(500);
                        if(docs && docs.constructor === Array && docs.length > 0) {
                            for(var d = 0; d < docs.length; d++) {
                                var doc = docs[d];
                                var summary = mapviewer.identify.config.renderResult(doc, renderers, 'document', 'DocType', 'Owners', 'RecordingDate');
                                recs.push({ Key:doc.ReceptionNum, Value:summary, Category:categories.docs, SortOrder:orderer.next() });
                            }
                        } else {
                            recs.push({ Key:'Documents', Value:'No documents available.', Category:categories.docs, SortOrder:orderer.next()});
                        }
                        callback(recs);
                    },
                    failure: function(res, req) {
                        var rec = rtt.identify.config.makeErrorRecord(categories.docs);
                        callback([ rec ]);
                    }
                });
            }
        };
        
        var title = regMap;
        if (address != '[No Value]') {
            if (title) {
                title = title + ' - ' + address;
            } else {
                title = address;
            }
        }
        if (!title) {
            title = '[Unidentified Parcel]';
        }
        
        if (parcelStatus) {
            switch (parcelStatus) {
                case 1: break; // no additional label necessary.
                case 2:
                    title += ' (Inactive)';
                    break;
                case 3:
                    title += ' (Remainder)';
                    break;
                case 9:
                    title += ' (Merged)';
                    break;
                default:
                    title += ' (' + parcelStatus + ')';
            }
        }

        // the final object to return to the identify code.
        var display = {
            Title: title,
            Data: parcelInfoFields,
            AdditionalInfo: additionalInfos
        };

        return display;
    }
};

/********************   End clientsrc\rtt\rtt.identify.js  ********************/


/******************** Begin clientsrc\rtt\rtt.identify.config.js ********************/
if (!mapviewer || !mapviewer.identify.config) {
    var message = 'rtt.identify.config requires that mapviewer.identify.config be loaded.';
    Avencia.log(message);
    throw ({name: 'FileNotFound', message: message});
}

rtt.identify.config = {
    makeErrorRecord: function(category, msg, sortorder) {
        var message = msg || 'An error occurred.', 
            sort = sortorder || 'A';
        
        var record = { Key:'Error', Value:message, Category:category, SortOrder:sort };
    },
    resultRenderers: {
        regMapId: function(baseReg, recMap, parcel) {
            var regMapId;
            if (recMap && parcel) {
                regMapId = recMap + '-' + parcel;
            }
            else if (baseReg) {
                try {
                    regMapId = baseReg.substr(0, 6) + '-' + baseReg.substr(6, 4);
                }
                catch(e) {
                    regMapId = baseReg;
                }
            }
            
            return regMapId;
        },
        propType: function(propTypeVal) {
            var pType = '';
            switch(propTypeVal) {
                case '1': pType = 'Residential'; break;
                case '2': pType = 'Hotels and Apartments'; break;
                case '3': pType = 'Store with Dwelling'; break;
                case '4': pType = 'Commercial'; break;
                case '5': pType = 'Industrial'; break;
                case '6': pType = 'Vacant Land'; break;
                case 'MIXED': pType = 'Mixed'; break;
                default: break;
            }
            return pType;
        },
        cityOwned: function(cityOwnedVal) {
            var cityOwned = '';
            switch(cityOwnedVal) {
                case '0': cityOwned = 'No'; break;
                case '1': cityOwned = 'Yes'; break;
                case '2': cityOwned = 'Mixed'; break;
                default: cityOwned = 'Unknown'; break;
            }
            return cityOwned;
        },
        parcelFamilyLink: function(mapReg) {
            var renderedMapReg = rtt.identify.config.resultRenderers.regMapId(mapReg);
            var onclick = 'onclick="manager.getComponent(\'identify\').identifyParcel(\'' + mapReg + '\', true)"';
            var link = '<a href="javascript:void(0)" ' + onclick + '>' + renderedMapReg + '</a>';
            return link;
        },
        condoUnit: function(recMap, condoParcel) {
            var unit = '';
            if(recMap) { unit = recMap; }
            if(condoParcel) {
                if(unit) { unit += '-'; }
                unit += condoParcel;
            }
            return unit;
        },
        condoValue: function(unitVal, statusVal, nameVal) {
            var unit = '', name = nameVal.trim();
            if(unitVal) {
                unit = 'Unit #' + unitVal;
            }
            if(statusVal != 1) {
                var status = '';
                unit += ' (' + status + ')';
            }
            if(nameVal) {
                unit += '<br />' + name;
            }
            return unit;
        },
        document: function(docType, owners, recDate) {
            var summary = '';
            if(docType) {
                summary = docType.trim() + '<br />';
            }
            if(recDate && recDate.getDate) {
                summary += mapviewer.identify.config.resultRenderers.date(recDate) + '<br />';
            }
            if(owners && owners.length > 0) {
                for(var i = 0; i < owners.length; i++) {
                    var owner = owners[i];
                    if(owner.Grantee && owner.Grantor) {
                        summary += '<em>Grantor(s):</em>';
                        summary += '<p class="docOwner"><strong>-</strong>' + owner.Grantor + '</p>';
                        summary += '<em>Grantee(s):</em>';
                        summary += '<p class="docOwner"><strong>-</strong>' + owner.Grantee + '</p>';
                    }
                }
            }
            return summary;
        }
    },
    parcelInfoFields: [
        { label:'Status', keys:['Status'], r:'parcelStatus' },
        { label:'Address', keys:['StreetNumber', 'StreetExtension', 'Unit', 'StreetPrefix', 'StreetName', 'StreetType', 'StreetSuffix'], r:'address' },
        { label:'Unit', keys:['Unit'] },
        { label:'Street Code', keys:['StreetCode'] },
        { label:'Air Rights', keys:['BottomElevation', 'TopElevation', 'ElevationFlag'], r:'elevation' },
        { label:'BaseReg', keys:['BaseRegistryMapID', 'Map', 'ParcelNumber'], r:'regMapId' },
        //{ label:'RecSub', keys:['SubmapLetter'] }, // DOR doesn't know exactly what this is and suspects it's not needed.
        { label:'Parcel', keys:['ParcelNumber'] },
        { label:'RecMap', keys:['Map'] },
        { label:'Condo', keys:['CondoFlag'], r:'yesNo' },
        { label:'Perimeter', keys:['Length'], r:'length' },
        { label:'Area', keys:['Area'], r:'area' }
    ],
    extraInfoFields: [
        { label:'Property Type', keys:['BrtPropertyType'], r:'propType' },
        { label:'City Owned', keys:['CityOwned'], r:'cityOwned' },
        { label:'Council District', keys:['CouncilDistrict'] },
        { label:'Ward', keys:['Ward'] },
        { label:'Census Tract', keys:['CensusTract'] },
        { label:'Census Block Group', keys:['CensusBlockGroup'] },
        { label:'Zipcode', keys:['Zipcode'] }
    ],
    externalLinkFields: [
        { label: 'City Maps', keys:[rtt.config.links.cityMaps, 'CityMaps Portal'], r:'externalLink' },
        { label: 'Philly History', keys:[rtt.config.links.phillyHistory, 'Nearby Historic Photos'], r:'externalLink' },
        { label: 'Zoning', keys:[rtt.config.links.zoning, 'Zoning Information'], r:'externalLink' }
    ]
    // document fields aren't listed here because documents operate more like a traditional datagrid, 
    // where the number of results created the length
};
/********************   End clientsrc\rtt\rtt.identify.config.js ********************/


/******************** Begin clientsrc\rtt\rtt.component.legend.js ********************/
rtt.component = rtt.component || {};

rtt.component.legend = function(options) {
    /// <summary>Override mapviewer legend component and adds ExtJS. *Shudder*</summary>
    var _defaults = {};
    var _appManager, _mapManager, _layoutManager;
    var _base = mapviewer.component.legend($.extend(_defaults, options));
    var _self = $.extend({}, _base);

    _self.baseClassName = _base.className;
    _self.className = 'rtt.component.legend';
    
    var _root;
    
    function _render(legendData) {
        _root = new Ext.tree.AsyncTreeNode({
            id: _layoutManager.getOption('PREFIX') + 'LegendRootNode',
            text: 'Root',
            children: legendData
        });
                
        // Define tree panel
        var panel = new Ext.tree.TreePanel({
            id: _layoutManager.getOption('PREFIX') + 'legend',
            title: 'Legend',
            autoScroll: true,
            border: false,
            rootVisible: false,
            collapsible: true,
            bodyStyle: 'padding:8px 8px 8px 8px',
            renderTo: _self.options.target,
            loader: new Ext.tree.TreeLoader(),
            root: _root,
            listeners: {
                'checkchange': function(node, isChecked) {
                    var layer, state;
                    if (_self.options && _self.options.layerName) {
                        _mapManager.setLayerVisiblity(isChecked, _self.options.layerName, node.attributes.mapLayer);
                        
                        state = _self.getState();
                        if (!state) {
                            state = {};
                        }
                        
                        state[node.attributes.mapLayer] = isChecked;
                        _self.setState(state);
                    }
                }
            }
        });
        
        Ext.getCmp(_self.options.target).insert(0, panel);
        Ext.getCmp(_self.options.target).doLayout();
        
        panel.expandAll();
    }
    
    _self.init = function(am, id) {
        /// <summary>Initializes the pwd overview map component.</summary>
        _appManager = am;
        _base.init(_appManager, id);
        _mapManager = _appManager.getMapManager();
        _layoutManager = _appManager.getLayoutManager();

        return _self;
    };
    
    _self.render = function(){
        /// <summary>Renders the component to the DOM.</summary>

        _self.initState(function(data){
            _render(data);
        });
    };

    _self.syncUIState = function() {
        /// <summary>Syncs the component UI to match the current state.</summary>

        var state = _self.getState();
        
        if (_root) {
            _root.eachChild(function(parent){
                parent.eachChild(function(child){
                    var stateChecked;
                    
                    if (state) {
                        stateChecked = state[child.attributes.mapLayer];
                        if (child.attributes.checked !== stateChecked) {
                            child.ui.checkbox.checked = stateChecked;
                            child.attributes.checked = stateChecked;
                            _mapManager.setLayerVisiblity(stateChecked, _self.options.layerName, child.attributes.mapLayer, true);
                        }
                    }
                });
            });
            _mapManager.refreshLayerVisiblity(_self.options.layerName);
        }
    };

    return _self;
};
/********************   End clientsrc\rtt\rtt.component.legend.js ********************/


/******************** Begin clientsrc\rtt\rtt.component.annotate.js ********************/
/// <reference path="components.js" />
var rtt = rtt || {};
rtt.component = rtt.component || {};

rtt.component.annotate = function(options) {
    /// <summary>Implements the annotation component</summary>
    var _defaults = {};
    var _appManager, _mapManager, _layoutManager;
    var _base = mapviewer.component($.extend(_defaults, options));
    var _self = $.extend({}, _base);

    _self.baseClassName = _base.className;
    _self.className = 'rtt.component.annotate';

    var _annotationLayer;
    var _panel;
    var _state = {
        annotations: []
    };

    if (_self.options.control) {
        _self.setOpenLayersControl(_self.options.control);
    } else {
        var control = new OpenLayers.Control(
        {
            title: 'Select and click the map to add text.',
            displayClass: 'olControlAnnotate',
            handler: new OpenLayers.Handler.Click(
                this, 
                {
                    'click': function(evt) {
                        _self.setAnnotation(evt.xy.x, evt.xy.y, null);
                    }
                }, 
                {
                    'single': true,
                    'double': false,
                    'pixelTolerance': 0,
                    'stopSingle': false,
                    'stopDouble': false
                }
            )
        });
        _self.setOpenLayersControl(control);
    }
    
    _self.init = function(am, id) {
        /// <summary>Initializes the annotation component by adding an OpenLayers.Layer.Annotations
        ///          layer to the map of the component manager and adds a new accordion panel
        ///          to the tools tab panel (also accessed via the component manager).</summary>
        _appManager = am;
        _base.init(_appManager, id);
        _mapManager = _appManager.getMapManager();
        _layoutManager = _appManager.getLayoutManager();

        // Create and add an Annotations layer
        _annotationLayer = new OpenLayers.Layer.Annotations('AnnotationsLayer');
        _mapManager.getMap().addLayer(_annotationLayer);

        return _self;        
    };
    
    _self.render = function(){
        /// <summary>Renders the component to the DOM.</summary>

        // Create and add the accordion panel to the layout
        _panel = new Ext.Panel({
            id: _layoutManager.getOption('PREFIX') + 'AnnotationPanel',
            title: 'Annotate',
            layout: 'fit',
            border: false,
            html: '<img alt="Annotate Tool Icon" src="client/rtt/css/images/tool_annotate.gif" style="float: left;"/><span>Select the Annotate tool and click the map to add text.</span>'
        });
        
        //Require that a id panel be provided
        if (_self.options.id) {
            Ext.getCmp(_self.options.id).add(_panel);
            Ext.getCmp(_self.options.id).doLayout();
        }
    };
    
    _self.syncUIState = function() {
        /// <summary>Does nothing until we can rewrite this component.</summary>
    
    };
    
    _self.getStateString = function() {
        /// <summary>Override so nothing appears in the state string until we can
        ///          rewrite this component.</summary>
        return null;
    };
    
    _self.getPanel = function() {
        /// <summary>Give public access to the accordion panel where this component lives.</summary>
        /// <returns>Ext.Panel</returns>
        return _panel;
    };
    
    _self.setAnnotation = function(x, y, html) {
        /// <summary>Creates and displays an annotation both on the map using OpenLayers.Marker.Annotation 
        ///          and tracks each annotation in the panel created during initialization.  Annotations
        ///          can be edited and deleted via the controls on the panel.</summary>
        /// <param name="x" type="number">The x pixel value </param>
        /// <param name="y" type="number">The y pixel value</param>
        /// <param name="html" type="string">(optional) The default html value.  Only required when
        ///                                   editing an existing annotation.</param>
        
        //Reference to the background color button element
        var backColorEl;
        
        //Html editor for creating rich annotations
        var annotationEditor = new Ext.form.HtmlEditor({
            id: _layoutManager.getOption('PREFIX') + 'AnnotationEditor',
            value: html || '',
            width: 250,
            height: 110,
            enableAlignments: false,
            enableLists: false,
            enableLinks: false,
            enableSourceEdit: false,
            listeners: {
                activate: function() {
                    this.syncValue();
                }
            }
        });
        
        //Dialog window for entering annotations
        var dialog = new Ext.Window({
            layout:'fit',
            title:'Annotate Map',
            width:300,
            height:160,
            closeAction:'close',
            plain: true,
            modal: true,
            items: annotationEditor,
            buttons: [{
                id: _layoutManager.getOption('PREFIX') + 'AnnotationSubmit',
                text:'Submit',
                handler: function(btn, e){
                    //Do this when the Submit button is clicked
                    var html = annotationEditor.getValue();
                    
                    if(html && html.length > 0) {
                        //Create the annotation
                        var lonlat, anno;
                        var i = getAnnotationIndex(x, y);
                        
                        if (i > -1) {
                            //Edit an existing marker
                            anno = _state.annotations[i];
                            anno.text = html;
                            anno.div.innerHTML = html;
                        } else {
                            //Add a new marker
                            lonlat = _mapManager.getMap().getLonLatFromPixel(new OpenLayers.Pixel(x, y));
                            anno = new OpenLayers.Marker.Annotation(html, lonlat, 
                                function(div) {
                                    return new OpenLayers.Pixel(0, -OpenLayers.Element.getHeight(div));
                                }
                            );
                            _state.annotations.push(anno);
                        }
                        
                        dialog.close();
                        _self.refreshAnnotations();
                    }
                }
            },{
                id: _layoutManager.getOption('PREFIX') + 'AnnotationCancel',
                text: 'Cancel',
                handler: function(btn, e) {
                    dialog.close();
                }
            }]
        });
        
        dialog.show();
        //This is very, very important if you want a cursor for IE.
        annotationEditor.focus();
        
        //Hack to remove background color button
        backColorEl = Ext.DomQuery.selectNode('.x-edit-backcolor');
        if (backColorEl) {
            Ext.get(backColorEl).hide();
        }
    };
    
    _self.deleteAnnotation = function(x, y) {
        /// <summary>Delete the (first) annotation at pixel (x,y)</summary>
        var i, anno;
        _state.annotations.splice(getAnnotationIndex(x, y), 1);

        _self.refreshAnnotations();
    };
    
    _self.deleteAllAnnotations = function() {
        /// <summary>Delete the all annotations</summary>
        _state.annotations = [];
        _self.refreshAnnotations();
    };
    
    function getAnnotationIndex(x, y) {
        /// <summary>Get the array index of the (first) annotation at pixel (x,y)</summary>
        var i, anno;
        for(i=0; i<_state.annotations.length; i++) {
            anno = _state.annotations[i];
            if(anno.px.x === x && anno.px.y === y) {
                return i;
            }
        }
        return -1;
    }
    
    function initAnnotationGrid() {
        /// <summary>Create the Ext.grid.GridPanel populated with annotation data</summary>
        /// <returns>Ext.grid.GridPanel</returns>
        
        //Create a JsonStore using the array of annotation objects as data
        var jsonStore = new Ext.data.JsonStore({
            data: _state.annotations,
            sortInfo:{field: 'text', direction: "ASC"},
            fields: ['text', 'px']
        });

        return new Ext.grid.GridPanel({
            ds: jsonStore,
            columns: [{
                    //Column 1, Annotation Text
                    width: 192,
                    renderer: function(value, meta, record, rowIndex, colIndex, store) {
                        var anno = record.json;
                        var displayText = getDisplaySubStr(anno.text, 25);
                        
                        //Provide a way to see the entire text with the title attr
                        return '<span title="' + getDisplayStr(anno.text) + '">' + displayText + '</span>';
                    }
                },
                {
                    //Column 2, Edit action
                    width: 42, 
                    dataIndex: 'text',
                    renderer: function(value, meta, record, rowIndex, colIndex, store) {
                        var anno = record.json;
                        var html = '<a id="setAnno' + anno.px.x + anno.px.y + '"href="#">Edit</a>';
                        return html;
                    }
                },
                {
                    //Column 3, Delete action
                    width: 42, 
                    dataIndex: 'text',
                    renderer: function(value, meta, record, rowIndex, colIndex, store) {
                        var anno = record.json;
                        var html = '<a id="deleteAnno' + anno.px.x + anno.px.y + '"href="#">Delete</a>';
                        return html;
                    }
                }
            ],
            autoHeight:true,
            width: _layoutManager.getOption('WEST_CONTENT_WIDTH'),
            border: false,
            disableSelection: true,
            enableHdMenu: false,
            enableColumnMove: false,
            enableColumnHide: false,
            buttons: [{
                id: _layoutManager.getOption('PREFIX') + 'AnnotationDeleteAllBtn',
                text:'Delete All',
                handler: function(btn, e){
                    // Do this when Delete All is clicked
                    Ext.MessageBox.confirm('Delete All', 
                        'Are you sure you want to delete all annotations?', 
                        function(btn) {
                            if (btn == 'yes') {
                                _self.deleteAllAnnotations();
                            }
                        }
                    );
                }
            }],
            listeners: {
                'cellclick': function(grid, rowIndex, columnIndex, e) {
                    //Do this when the user clicks a cell.  Only do something
                    //on edit or delete
                    var anno = grid.getStore().getAt(rowIndex).json;
                    var displayText;
                    
                    if (columnIndex === 1) {
                        //Edit
                        _self.setAnnotation(anno.px.x, anno.px.y, anno.text);
                    }
                    else if (columnIndex === 2) {
                        //Delete
                        displayText = getDisplaySubStr(anno.text, 15);

                        Ext.MessageBox.confirm('Delete Annotation', 
                            'Are you sure you want to delete "' + displayText + '"?',
                            function(btn) {
                                if (btn == 'yes') {
                                    _self.deleteAnnotation(anno.px.x, anno.px.y, anno.text);
                                }
                            }
                        );
                    }
                }
            }
        });
    }

    _self.refreshAnnotations = function() {
        /// <summary>Refreshes the displayed annotations, both on the map and the panel</summary>

        var i, annotationContentPanel;
        
        //Refresh annotation markers 
        _annotationLayer.clearMarkers();
        for(i=0; i<_state.annotations.length; i++) {
            _annotationLayer.addMarker(_state.annotations[i]);
        }
        
        //Refresh annotation panel 
        _panel.removeItems();
        
        if (_state.annotations.length > 0) {
            // Create new stuff.
            annotationContentPanel = new Ext.Panel({
                id: _layoutManager.getOption('PREFIX') + 'AnnotationContentPanel',
                // Our layout is a table with only one column.
                layout: 'table',
                layoutConfig: { columns: 1 },
                // Make sure everything in our table is WEST_CONTENT_WIDTH pixels wide.
                defaults: { width: _layoutManager.getOption('WEST_CONTENT_WIDTH') },
                // Our panel needs to be wide enough to have a scroll bar.
                width: _layoutManager.getOption('WEST_WIDTH'),
                // Allow the outer container to control this panel's height.
                autoHeight:false,
                // We want scroll bars if the contents are longer than this panel.
                autoScroll:true,
                border:false
            });
        
            annotationContentPanel.add(initAnnotationGrid());
            _panel.add(annotationContentPanel);
            _self.setState(_state);
        } else {
            _self.setState(null);
        }
        
        _layoutManager.toolFocus(_layoutManager.getOption('PREFIX') + 'AnnotationPanel');
        _panel.doLayout();
    };
    
    function getDisplayStr(htmlEncodedStr) {
        /// <summary>Un-html-encodes a string</summary>
        /// <param name="htmlEncodedStr" type="string">A potentially html-encoded string to 
        ///                                            truncated and formatted.</param>
        /// <returns>string formatted for display</returns>

        //Strip out html for displaying
        var noHtmlElementText = htmlEncodedStr.replace(/(<([^>]+)>)/ig,"");

        // Convert nbsp's to regular spaces.
        var display = Ext.util.Format.htmlDecode(noHtmlElementText.replace(/&nbsp;/g, " "));

        return display;
    }
    
    function getDisplaySubStr(htmlEncodedStr, len) {
        /// <summary>Truncates a potentially html-encoded string to the specified length</summary>
        /// <param name="htmlEncodedStr" type="string">A potentially html-encoded string to 
        ///                                            truncated and formatted.</param>
        /// <param name="len" type="number">The max length of the string</param>
        /// <returns>string formatted for display</returns>

        // Strip out html for displaying.
        var display = getDisplayStr(htmlEncodedStr);

        if (display.length > len) {
            display = Ext.util.Format.htmlEncode((display.substr(0, len) + '...').replace(/" "/g, "&nbsp;"));
        }

        return display;
    }
    
    return _self;
};

/********************   End clientsrc\rtt\rtt.component.annotate.js ********************/


/******************** Begin clientsrc\rtt\rtt.component.historic-maps.js ********************/
var rtt = rtt || {};

rtt.component.historicMaps = function(options) {
    var _defaults = {};
    var _appManager, _mapManager, _layoutManager;
    var _base = mapviewer.component($.extend(_defaults, options));
    var _self = $.extend({}, _base);

    _self.baseClassName = _base.className;
    _self.className = 'rtt.component.historic-maps';

    var _defaultMessage = '<span>Zoom in to view historic maps.</span>';
    var _loadingMessage = '<div class="loading-indicator">Loading...</div>';
    var _errorMessage = '<span class="error">An error occurred while retrieving the historic maps.</span>';
    var _panel;
    var _currentMaps;
    var _expandedMapSets = {};

    _self.init = function(am, id) {
        _appManager = am;
        _base.init(_appManager, id);
        _mapManager = _appManager.getMapManager();
        _layoutManager = _appManager.getLayoutManager();

        //Default the scale to refresh if not explicitly set
        if(!_self.options || !_self.options.maxRefreshScale) {
            _self.options.maxRefreshScale = 4000;
        }
        
        //Refresh the panel when the map is panned or zoomed
        _mapManager.getMap().events.on({
            'moveend': function(evt) {
                _self.refresh();
            }
        });

        return _self;
    };
    
    _self.render = function(){
        /// <summary>Renders the component to the DOM.</summary>
        
        //Default panel
        _panel = new Ext.Panel({
            id: _layoutManager.getOption('PREFIX') + 'HistoricMapsPanel',
            title: 'Historic Maps',
            layout: 'fit',
            border: false,
            collapsed: true,
            html: '<div id="historicMapMsg">' + _defaultMessage + '</div>',
            //Button to clear all historic maps, always visible
            tbar: [
                '->', {
                    text: 'Clear Historic Map',
                    handler: function() {
                        hideSelectedMap();
                        //Clear the state
                        _self.setState(null);
                        _self.refresh();
                    }
                }
            ],
            listeners: {
                //Refresh the panel when expanded
                expand: function(panel) {
                    _self.refresh();
                }
            }
        });

        //Require that a id panel be provided
        if (_self.options.id) {
            Ext.getCmp(_self.options.id).add(_panel);
            Ext.getCmp(_self.options.id).doLayout();
        }
    };
    
    function makeFilter(layerId, rasterId) {
        /// <summary>Dynamically generates a filter for the AGS layer so we 
        ///          don't have to store it in the state object.</summary>
        
        var retVal;
        layerId = parseInt(layerId, 10);
        
        $.each(_self.options.rasterData, function(i, data) {
            var regex, id = _mapManager.getLayerId(data.layerName);
            var patterns = data.patterns.split(',');
            var values = data.values.split(',');
            
            if (id && (layerId === id)) {
                $.each(patterns, function(j, pattern) {
                    regex = new RegExp(pattern, 'gi');
                    
                    if (regex.test(rasterId)) {
                        retVal = data.field + '=\'' + rasterId.replace(regex, values[j]) + '\'';
                        return false;
                    }
                });
            }
            
            if (retVal) {
                return false;
            }
        });
        
        return retVal;
    }

    function hideAllMaps() {
        /// <summary>Iterates through all of the configured map layers, clears
        ///          the filters, and hides the layers.</summary>

        $.each(_self.options.rasterData, function(i, data) {
            var layerId = _mapManager.getLayerId(data.layerName);
            _mapManager.setLayerFilter(_self.options.layerName, layerId, null);
            _mapManager.hideLayer(_self.options.layerName, layerId);
        });
    }
    
    function hideSelectedMap() {
        /// <summary>Hides the currently selected historic map, if any, on the OpenLayers 
        ///          map ONLY.  This does not reset the panel.</summary>
        var state = _self.getState();
        
        if (state && _self.options && _self.options.layerName) {
            _mapManager.setLayerFilter(_self.options.layerName, state.layerId, null);
            _mapManager.hideLayer(_self.options.layerName, state.layerId);
        }
    }
    
    function showMap(layerId, mapId) {
        /// <summary>Shows the currently selected historic map on the OpenLayers 
        ///          map ONLY.  This is not reset the panel.</summary>
        if (_self.options && _self.options.layerName) {
            var state = {
                mapId: mapId,
                layerId: layerId
            };
            
            hideAllMaps();
            _self.setState(state);

            _mapManager.setLayerFilter(_self.options.layerName, layerId, makeFilter(layerId, mapId));
            _mapManager.showLayer(_self.options.layerName, layerId);
        }
    }
    
    _self.syncUIState = function() {
        /// <summary>Syncs the component UI to match the current state.</summary>

        var state = _self.getState();
        if (state) {
            _mapManager.setLayerFilter(_self.options.layerName, state.layerId, makeFilter(state.layerId, state.mapId));
            _mapManager.showLayer(_self.options.layerName, state.layerId);
        } else {
            hideAllMaps();
        }
        _self.refresh();
    };

   
    _self.refresh = function() {
        /// <summary>Refreshes the panel according to the current extent if
        ///          the scale is low enough and the panel is expaned.</summary>

        var extent;

        //Update default text
        _panel.getEl().child('#historicMapMsg').update(_defaultMessage);

        //Remove existing panels, if any
        _currentMaps = {};
        _panel.removeItems();
        _panel.doLayout();
    
        //If zoomed in and historic maps accordion panel is open
        if (_mapManager.getMap().getScale() < _self.options.maxRefreshScale && _panel.collapsed === false) {
            //Update to loading text
            _panel.getEl().child('#historicMapMsg').update(_loadingMessage);
            
            extent = _mapManager.getMap().getExtent() || _mapManager.getMap().maxExtent;
            Ext.Ajax.request({
                method: 'GET',
                url: 'handlers/historic-maps.ashx',
                autoAbort: true,
                params: {
                    xmin: extent.left,
                    ymin: extent.bottom,
                    xmax: extent.right,
                    ymax: extent.top,
                    width: 125,
                    height: 100
                },
                success: function(res, req) {
                    //Got a response, now refresh the panel
                    _panel.add(
                        initHistoricMapsPanel(Ext.decode(res.responseText))
                    );
                    _panel.doLayout();
                },
                failure: function(res, req) {
                    //Failed, show the error message
                    _panel.getEl().child('#historicMapMsg').update(_errorMessage);
                }
            });
        }
        else {
            //Nothing to display, zoomed out too much or the panel is closed
            _panel.getEl().child('#historicMapMsg').update(_defaultMessage);
        }
    };
    
    function initHistoricMapsPanel(historicMapsList) {
        /// <summary>Creates the form panel with all of the historic map content.</summary>
        /// <returns>Ext.form.FormPanel</returns>

        return new Ext.form.FormPanel ({
            id: _layoutManager.getOption('PREFIX') + 'HistoricMapsContentPanel',
            border: false,
            bodyStyle:'padding:8px;margin-right:18px',
            labelAlign: 'right',
            labelWidth: 55,
            autoScroll: true,
            items: initHistoricMapsContentPanel(historicMapsList)
        });
    }
    
    function initHistoricMapsContentPanel(historicMapsList) {
        /// <summary>Iterates over the historic map object it got from AGS 
        ///          and builds the several Ext.Fieldsets for each group.</summary>
        /// <returns>Array of Ext.form.FieldSet</returns>
        
        var i, j, mapType, layerId, maps, fieldSets = [], collapse, fieldSet;
        
        function expandMapSet(panel) {
            _expandedMapSets[panel.title] = true;
        }
        function collapseMapSet(panel) {
            _expandedMapSets[panel.title] = false;
        }
        
        function showOnMap_Click(btn, ev) {
            //Show the historic map image on the OpenLayers map
            var formPanel = Ext.getCmp(_layoutManager.getOption('PREFIX') + 'HistoricMapsContentPanel');
            if (formPanel) {
                var items = formPanel.getForm().items.items;
                var i, len = items.length;
                for(i = 0; i<len; i++) {
                    if (items[i].inputType == 'radio' && items[i].checked) {
                        var selected = _currentMaps[items[i].getId()];
                        showMap(selected.layerId, selected.id);
                        break;
                    }
                }
            }
        }
        
        // For each map type (Registry Map, Hexamer Locher, etc)
        for (i=0; i<historicMapsList.length; i++) {
            mapType = historicMapsList[i].type;
            layerId = historicMapsList[i].layerId;
            maps = historicMapsList[i].maps;
            
            //Do we have any maps of this type to display?
            if (maps.length > 0) {
                //If a set was opened last time, make sure it's still open
                collapse = true;
                if (_expandedMapSets && _expandedMapSets[mapType]) {
                    collapse = false;
                }
                
                fieldSet = new Ext.form.FieldSet({
                    id: layerId,
                    title: mapType,
                    collapsible: true,
                    autoHeight:true,
                    collapsed: collapse,
                    items: new Ext.Panel({
                        layout:'table',
                        layoutConfig: {columns:2},
                        border: false,
                        items: initHistoricMapSets(maps, mapType, layerId)
                    }),
                    buttons: [{
                        text: 'Show on Map',
                        scope: fieldSet,
                        handler: showOnMap_Click
                    }],
                    listeners: {
                        expand: expandMapSet,
                        collapse: collapseMapSet
                    }
                });
                
                fieldSets.push(fieldSet);
            }
        }
        return fieldSets;
    }
    
    //This should return an array of Ext.Panel objects of map sets
    function initHistoricMapSets(maps, mapType, layerId) {
        /// <summary>Groups the historic map image with the radio button.</summary>
        /// <returns>Array of Ext.Panel</returns>
        
        var mapSets = [];
        var visible = false;
        var mapId, mapUrl, mapFilter;
        var state = _self.getState();

        //For each map of a type
        for (var j=0; j<maps.length; j++) {
            visible = false;
            _currentMaps[maps[j].id] = maps[j];
            _currentMaps[maps[j].id].layerId = layerId;
            
            mapId = maps[j].id;
            mapUrl = maps[j].url;
            mapFilter = maps[j].filter;
            
            //Was the map selected before?
            
            if (state && state.mapId === mapId) {
                visible = true;
            }

            //Group the map image and the radio button
            mapSets.push(new Ext.Panel({
                bodyStyle: j===0 ? 'margin:0px 4px 4px 0px;padding:3px;borderColor:#ff0000;' : 'margin:0px 4px 4px 0px;padding:3px;',
                items: [
                    new Ext.form.Radio({
                        id: mapId,
                        boxLabel: mapId,
                        hideLabel: true,
                        labelSeparator: '',
                        inputValue: mapType,
                        name: _layoutManager.getOption('PREFIX') + 'MapTypes',
                        checked: visible
                    }),
                    new Ext.Panel({
                        bodyStyle: "margin-top: 3px",
                        width: 100,
                        border: false,
                        html: '<img src="' + mapUrl + '" />'
                    })
                ]
            }));
        }

        return mapSets;
    }
    
    return _self;
};

/********************   End clientsrc\rtt\rtt.component.historic-maps.js ********************/


/******************** Begin clientsrc\rtt\rtt.component.doc-search.js ********************/
var rtt = rtt || {};

rtt.component.documentSearch = function(options) {
    var MAX_DOC_SEARCH_YEAR_SPAN = 5;
    
    var _defaults = {};
    var _appManager, _mapManager, _layoutManager;
    var _base = mapviewer.component($.extend(_defaults, options));
    var _self = $.extend({}, _base);

    _self.baseClassName = _base.className;
    _self.className = 'rtt.component.doc-search';
    
    var _panel, _layerId, _fromDateField, _toDateField, _typeCombo;

    _self.init = function(am, id) {
        /// <summary>Initializes the document search component by creating an Ext FormPanel to 
        ///          do set the layer definitions on the AGS layer.</summary>
        _appManager = am;
        _base.init(_appManager, id);
        _mapManager = _appManager.getMapManager();
        _layoutManager = _appManager.getLayoutManager();
        
        _layerId = _mapManager.getLayerId(rtt.config.documentSearch.layerName);

        return _self;
    };
    
    _self.render = function(){
        /// <summary>Renders the component to the DOM.</summary>

        // Define tree panel
        _panel = initDocumentSearchPanel();
                
        //Require that a id panel be provided
        if (_self.options.id) {
            Ext.getCmp(_self.options.id).add(_panel);
            Ext.getCmp(_self.options.id).doLayout();
        }
    };

    _self.syncUIState = function() {
        /// <summary>Syncs the component UI to match the current state.</summary>

        var state = _self.getState();
        if (state) {
            _toDateField.setValue(new Date(state.to));
            _fromDateField.setValue(new Date(state.from));
            _typeCombo.setValue(state.type);
            
            showDocumentSearch(new Date(state.from), new Date(state.to), state.type);
        } else {
            hideDocumentSearch();
        }
    };
    
    function hideDocumentSearch() {
        /// <summary>Clears the document filter and refreshes the layer.</summary>
        if (_self.options && _self.options.layerName) {
            //Clear the state
            _self.setState(null);

            _mapManager.setLayerFilter(_self.options.layerName, _layerId, null);
            _mapManager.hideLayer(_self.options.layerName, _layerId);
        }
    }
    
    function showDocumentSearch(fromDate, toDate, type) {
        /// <summary>Sets the document filter and refreshes the layer.</summary>
        /// <param name="fromDate" type="string">The from date string M/D/YYYY</param>
        /// <param name="toDate" type="string">The to date string M/D/YYYY</param>
        /// <param name="type" type="string">The id of the the document type (ie. DEED, etc)</param>
        
        _layoutManager.showBusyIndicator();
        
        Ext.Ajax.request({
            url: 'handlers/doc-filter.ashx',
            method: 'GET',
            autoAbort: true,
            params: {
                from: (fromDate.getMonth() + '/' + fromDate.getDate() + '/' + fromDate.getFullYear()),
                to: (toDate.getMonth() + '/' + toDate.getDate() + '/' + toDate.getFullYear()),
                type: type
            },
            success: function(res, req) {
                var state = {
                    from: fromDate.getTime(),
                    to: toDate.getTime(),
                    type: type
                };
                
                _self.setState(state);
                
                _mapManager.setLayerFilter(_self.options.layerName, _layerId, res.responseText);
                _mapManager.showLayer(_self.options.layerName, _layerId);
                
                _layoutManager.hideBusyIndicator();
            },
            failure: function(res, req) {
                Ext.MessageBox.alert('Error', 'We were unable to get documents from the database.  Please try again.');
                
                _layoutManager.hideBusyIndicator();
            }
        });
        
    }
    
    function initDocumentSearchPanel() {
        /// <summary>Creates the form panel with all of the document search panel.</summary>
        /// <returns>Ext.form.FormPanel</returns>

        _fromDateField = initFromDateField();
        _toDateField = initToDateField();
        _typeCombo = initDocTypeCombo();


        return new Ext.form.FormPanel ({
            id: _layoutManager.getOption('PREFIX') + 'DocumentSearchPanel',
            title: 'Document Search',
            labelWidth: 80,
            border: false,
            bodyStyle:'padding:8px 8px 8px 8px',
            collapsed: true,
            items: [
                _fromDateField,
                _toDateField,
                _typeCombo,
                initDocDescPanel()
            ],
            buttons: [{
                //Clear the search filter
                id: _layoutManager.getOption('PREFIX') + 'DocClearBtn',
                text: 'Clear Map Results',
                disabled: false,
                tooltip: 'Click to clear your document search results from the map.',
                handler: function() {
                    hideDocumentSearch();
                }
            },{
                //Open a new window with the tabular document results
                id: _layoutManager.getOption('PREFIX') + 'DocReportBtn',
                text: 'View Report',
                disabled: true,
                tooltip: 'Click to show a summary report of your document search results.',
                handler: function() {
                    window.open('DocumentSummary.aspx?from=' + _fromDateField.value + '&to=' + _toDateField.value  + '&doc=' + _typeCombo.getValue());
                }
            },{
                //Show the document parcels on the map
                id: _layoutManager.getOption('PREFIX') + 'DocSubmitBtn',
                text: 'Show on Map',
                disabled: true,
                tooltip: 'Click to highlight all parcels corresponding to your document search results.  If no parcels are highlighted, then no documents were found for your map view.',
                handler: function() {
                    var record = _typeCombo.store.getAt(_typeCombo.store.find('Id', _typeCombo.getValue())).json;
                    
                    showDocumentSearch(_fromDateField.getValue(), _toDateField.getValue(), record.Id);
                }
            }]
        });
    }
    
    function initFromDateField() {
        /// <summary>Creates a date field for the from date</summary>
        /// <returns>Ext.form.DateField</returns>

        var defaultFromDate = (new Date()).add(Date.YEAR, -1);
        
        return new Ext.form.DateField({
            id: _layoutManager.getOption('PREFIX') + 'FromDateField',
            fieldLabel: 'From date',
            name: 'from',
            format: 'n/j/Y',
            allowBlank: false,
            width: 100,
            value: defaultFromDate,
            validateOnBlur: true,
            invalidText: 'Date format must be M/D/YYYY',
            validator: function() {
                return isDateSpanValid(
                    _fromDateField.getValue(), 
                    _toDateField.getValue(), 
                    MAX_DOC_SEARCH_YEAR_SPAN
                );
            },
            listeners: {
                'valid': function(field, msg) {
                    if (isFormValid()) {
                        _toDateField.suspendEvents();
                        _toDateField.clearInvalid();
                        _toDateField.resumeEvents();
                        enableButtons();
                    }
                },
                'invalid': function(field, msg) {
                    disableButtons();
                }
            }
        });
    }
    
    function initToDateField() {
        /// <summary>Creates a date field for the to date</summary>
        /// <returns>Ext.form.DateField</returns>

        return new Ext.form.DateField({
            id: _layoutManager.getOption('PREFIX') + 'ToDateField',
            fieldLabel: 'To date',
            name: 'to',
            format: 'n/j/Y',
            allowBlank: false,
            width: 100,
            value: new Date(),
            validateOnBlur: true,
            invalidText: 'Date format must be M/D/YYYY',
            validator: function() {
                //On blur
                var toDate = new Date(this.value);
                var fromDate = new Date(_fromDateField.value);
                return isDateSpanValid(fromDate, toDate, MAX_DOC_SEARCH_YEAR_SPAN);
            },
            listeners: {
                'valid': function(field, msg) {
                    var fromDate = _fromDateField;
                    if (isFormValid()) {
                        fromDate.suspendEvents();
                        fromDate.clearInvalid();
                        fromDate.resumeEvents();
                        enableButtons();
                    }
                },
                'invalid': function(field, msg) {
                    disableButtons();
                }
            }
        });
    }
    
    function initDocTypeCombo() {
        /// <summary>Creates a combo box for the document types</summary>
        /// <returns>Ext.form.ComboBox</returns>

        var jsonStore = new Ext.data.JsonStore({
            url: 'handlers/doc-types.ashx',
            autoLoad: true,
            method: 'GET',
            fields: ['Id', 'Name', 'Description']
        });
    
        return new Ext.form.ComboBox({
            id: _layoutManager.getOption('PREFIX') + 'DocTypeCombo',
            store: jsonStore,
            displayField: 'Name',
            valueField: 'Id',
            typeAhead: true,
            mode: 'remote',
            fieldLabel: 'Documents',
            triggerAction: 'all',
            width: 160,
            listWidth: 180,
            emptyText:'Select a Document...',
            editable: false,
            allowBlank: false,
            listeners: {
                'select': function(combo, record, index) {
                    var docDesc = Ext.getCmp(_layoutManager.getOption('PREFIX') + 'DocDescPanel');
                    
                    docDesc.removeItems();

                    if (record.data.Description) {
                        docDesc.add( new Ext.Panel({
                            html: record.data.Description 
                        }));

                        docDesc.expand(true);
                    } else {
                        docDesc.collapse(true);
                    }
                    docDesc.doLayout();
                },
                'valid': function(combo, msg) {
                    if (isFormValid()) {
                        enableButtons();
                    }
                },
                'invalid': function(combo, msg) {
                    disableButtons();
                }
            }
        });
    }
    
    function initDocDescPanel() {
        /// <summary>Creates a container panel for the document descriptions</summary>
        /// <returns>Ext.Panel</returns>

        return new Ext.Panel({
            id: _layoutManager.getOption('PREFIX') + 'DocDescPanel',
            bodyStyle:'margin-top: 8px',
            defaults: {
                bodyStyle:'padding:8px'
            },
            collapsible: true,
            collapsed: true,
            border: false
        });
    }
    
    function isDateSpanValid(fromDate, toDate, yearSpan) {
        /// <summary>Validate the date span for the document search</summary>
        /// <param name="fromDate" type="Ext.Date">The from date</param>
        /// <param name="toDate" type="Ext.Date">The to date</param>
        /// <param name="yearSpan" type="number">The max number of years in span</param>
        /// <returns>boolean</returns>

        var elapsedMillis = fromDate.getElapsed(toDate);
        if (toDate < fromDate) {
            return 'To date must be later than From date.';
        }
        
        var fromDatePlusSpan = fromDate.add(Date.YEAR, yearSpan);
        if (fromDatePlusSpan < toDate) {
            return 'To date and From date can not be more than ' + yearSpan + ' years apart.';
        }
        
        return true;
    }

    function isFormValid() {
        /// <summary>Validates the form</summary>
        /// <returns>boolean</returns>
        var fromDate = _fromDateField.getValue();
        var toDate = _toDateField.getValue();
        
        var valid = true;
        
        if (!_fromDateField.isValid(false) || !_toDateField.isValid(false) || !isDateSpanValid(fromDate, toDate, MAX_DOC_SEARCH_YEAR_SPAN)) {
            valid = false;
        }
        
        if (!_typeCombo.isValid(false)) {
            valid = false;
        }

        return valid;
    }

    
    function enableButtons() {
        /// <summary>Enables the Show On Map and View Report buttons</summary>

        Ext.getCmp(_layoutManager.getOption('PREFIX') + 'DocSubmitBtn').enable();
        Ext.getCmp(_layoutManager.getOption('PREFIX') + 'DocReportBtn').enable();
    }

    function disableButtons() {
        /// <summary>Disables the Show On Map and View Report buttons</summary>
        
        Ext.getCmp(_layoutManager.getOption('PREFIX') + 'DocSubmitBtn').disable();
        Ext.getCmp(_layoutManager.getOption('PREFIX') + 'DocReportBtn').disable();
    }

    return _self;
};
/********************   End clientsrc\rtt\rtt.component.doc-search.js ********************/


/******************** Begin clientsrc\rtt\rtt.init.js      ********************/
//Note: This file lives in trunk\config\templates and is 
//merged and copied to clientsrc\rtt

//Debugging access to application manager
rtt.manager = {};

(function(){
    $(document).ready(function() {
        // Without this, quicktips don't show up!
        Ext.QuickTips.init();

        function constructComponents() {
            var components = {
                identify: mapviewer.component.identify({
                    search: {
                        //defaultSearchTerm: '<%= QS_SEARCH_TERM %>',
                        fieldId: 'searchText',
                        buttonId: 'searchBtn',
                        serviceUrl: 'handlers/viewer/identify.ashx'
                    },
                    resultRenderers: rtt.identify.config.resultRenderers,
                    updateResultFeature: rtt.identify.updateResultFeature,
                    defaultMsg: '<img alt="Identify Tool Icon" src="client/viewer/css/images/tool_identify.gif" style="float: left;"/><span>Use the Identify tool or search field to select a parcel on the map.</span>',
                    target: rtt.config.idPrefix + 'ToolsTabPanel'
                }),
                annotate: rtt.component.annotate({
                    id: rtt.config.idPrefix + 'ToolsTabPanel'
                }),
                measure: mapviewer.component.measure({
                    target: rtt.config.idPrefix + 'ToolsTabPanel'
                }),
                legend: rtt.component.legend({
                    target: rtt.config.idPrefix + 'OptionsTabPanel',
                    layerName: 'Parcel Explorer Basemap',
                    serviceUrl: 'handlers/viewer/legend.ashx'
                }),
                documentSearch: rtt.component.documentSearch({
                    layerName: 'Parcel Explorer Basemap',
                    id: rtt.config.idPrefix + 'OptionsTabPanel'
                }),
                historicMaps: rtt.component.historicMaps({
                    layerName: 'Parcel Explorer Basemap',
                    maxRefreshScale: 4000,
                    id: rtt.config.idPrefix + 'OptionsTabPanel',
                    rasterData: rtt.config.historicMaps.rasterData
                }),
                overviewMap: mapviewer.component.overviewMap({
                    id: 'overviewMapPanel',
                    olControlOptions: {
                        size: new OpenLayers.Size(298, 175),
                        minRatio: 8,
                        maxRatio: 32,
                        mapOptions: {
                            theme: null,
                            numZoomLevels: 12,
                            maxResolution: 600
                        }
                    }
                })
            };
            
            return components;
        }
        
        function constructControls(components) {
            var panControl = new OpenLayers.Control.MouseDefaults({ title: 'Drag and pan' });
            var toolsToolbar = new OpenLayers.Control.Panel({
                defaultControl: panControl,
                div: document.getElementById('tools')
            });
            toolsToolbar.addControls([
                new OpenLayers.Control.ZoomBox({ title: 'Zoom in by clicking or dragging' }),
                new OpenLayers.Control.ZoomBox({ title: 'Zoom out by clicking or dragging', out: true, displayClass: 'olControlZoomBoxOut' }),
                panControl,
                new OpenLayers.Control.ZoomToMaxExtent({ title: 'Zoom to the max extent' }),
                components.identify.getOpenLayersControl(),
                components.annotate.getOpenLayersControl(),
                components.measure.getOpenLayersControl()
            ]);
        
            return toolsToolbar;
        }
     
       $.ajax({
            url: 'handlers/viewer/map.ashx',
            dataType: 'json',
            success: function(data) {
                var serviceInfo = data;
                var serviceUrl = serviceInfo.url;
                
                var components = constructComponents();
                var toolsToolbar = constructControls(components);
                
                var layerDef, layerIdsObj = {}, i;
                
                // Iterate over all the layers, finding the ones that are visible by
                // default and using that for our initial list of layers to display.
                for (i=0; i<serviceInfo.layers.length; i++) {
                    layerDef = serviceInfo.layers[i];
                    
                    // Make sure to save the visibility (either true or false) in
                    // our collection.
                    layerIdsObj[layerDef.id] = layerDef.defaultVisibility;
                }
                
                var managerOptions = {
                    components: components,
                    map: {
                        serviceInfo: serviceInfo,
                        contentEl: 'mapPanel',
                        mapOptions: {
                            theme: null,
                            numZoomLevels: 12,
                            maxResolution: 350,
                            units: 'ft',
                            format: 'PNG8',
                            tileSize: new OpenLayers.Size(512, 512)
                        },
                        baseLayer: {
                            layerObj: new OpenLayers.Layer.ArcGIS93Rest('Parcel Explorer Basemap',
                                serviceUrl + 'export', 
                                {
                                    // We have to start this with a value for OpenLayers' sake,
                                    // after this the value will be calculated by the manager
                                    // based on layers turned on and off by the legend or whatever.
                                    layers: mapviewer.mapManager.makeLayersString(layerIdsObj)
                                },
                                {
                                    buffer: 0
                                }
                            ),
                            layerIds: layerIdsObj
                        },
                        overlayLayers : [],
                        controls: [
                            toolsToolbar,
                            new OpenLayers.Control.MousePosition(),
                            new OpenLayers.Control.ScaleLine()
                        ]
                    },
                    layout: {
                        manager: rtt.layoutManager,
                        printSel: '#linkPrint',
                        PREFIX: rtt.config.idPrefix,
                        WEST_WIDTH: 300,
                        SCROLL_WIDTH: 19,
                        WEST_CONTENT_WIDTH: 281,
                        header: {
                            contentEl: 'headerPanel',
                            height: 73
                        },
                        map: {
                            contentEl: 'mapPanel'
                        },
                        toolbar: {
                            contentEl: 'toolbarPanel',
                            height: 37
                        },
                        overviewMap: {
                            contentEl: 'overviewMapPanel'
                        },
                        busyIndicator: {
                            contentEl: 'busyIndicator'
                        }
                    }
                };

                rtt.manager = mapviewer.applicationManager(managerOptions);
                rtt.manager.init();
            },
            error: function(res, staus, err) {
                Ext.MessageBox.alert('Error', 'We were unable to contact the map service.  Please try again.');
            }
        });
    });
})();

/********************   End clientsrc\rtt\rtt.init.js      ********************/

