/*!
 * Ext JS Library 3.2.0
 * Copyright(c) 2006-2010 Ext JS, Inc.
 * licensing@extjs.com
 * http://www.extjs.com/license
 */
/**
 * List compiled by mystix on the extjs.com forums.
 * Thank you Mystix!
 *
 * English (UK) Translations
 * updated to 2.2 by Condor (8 Aug 2008)
 */

Ext.UpdateManager.defaults.indicatorText = '<div class="loading-indicator">Loading...</div>';

if(Ext.DataView){
  Ext.DataView.prototype.emptyText = "";
}

if(Ext.grid.GridPanel){
  Ext.grid.GridPanel.prototype.ddText = "{0} selected row{1}";
}

if(Ext.LoadMask){
  Ext.LoadMask.prototype.msg = "Loading...";
}

Date.monthNames = [
  "January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July",
  "August",
  "September",
  "October",
  "November",
  "December"
];

Date.getShortMonthName = function(month) {
  return Date.monthNames[month].substring(0, 3);
};

Date.monthNumbers = {
  Jan : 0,
  Feb : 1,
  Mar : 2,
  Apr : 3,
  May : 4,
  Jun : 5,
  Jul : 6,
  Aug : 7,
  Sep : 8,
  Oct : 9,
  Nov : 10,
  Dec : 11
};

Date.getMonthNumber = function(name) {
  return Date.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
};

Date.dayNames = [
  "Sunday",
  "Monday",
  "Tuesday",
  "Wednesday",
  "Thursday",
  "Friday",
  "Saturday"
];

Date.getShortDayName = function(day) {
  return Date.dayNames[day].substring(0, 3);
};

Date.parseCodes.S.s = "(?:st|nd|rd|th)";

if(Ext.MessageBox){
  Ext.MessageBox.buttonText = {
    ok     : "OK",
    cancel : "Cancel",
    yes    : "Yes",
    no     : "No"
  };
}

if(Ext.util.Format){
  Ext.util.Format.date = function(v, format){
    if(!v) return "";
    if(!(v instanceof Date)) v = new Date(Date.parse(v));
    return v.dateFormat(format || "d/m/Y");
  };
}

if(Ext.DatePicker){
  Ext.apply(Ext.DatePicker.prototype, {
    todayText         : "Today",
    minText           : "This date is before the minimum date",
    maxText           : "This date is after the maximum date",
    disabledDaysText  : "",
    disabledDatesText : "",
    monthNames        : Date.monthNames,
    dayNames          : Date.dayNames,
    nextText          : 'Next Month (Control+Right)',
    prevText          : 'Previous Month (Control+Left)',
    monthYearText     : 'Choose a month (Control+Up/Down to move years)',
    todayTip          : "{0} (Spacebar)",
    format            : "d/m/Y",
    okText            : "&#160;OK&#160;",
    cancelText        : "Cancel",
    startDay          : 0
  });
}

if(Ext.PagingToolbar){
  Ext.apply(Ext.PagingToolbar.prototype, {
    beforePageText : "Page",
    afterPageText  : "of {0}",
    firstText      : "First Page",
    prevText       : "Previous Page",
    nextText       : "Next Page",
    lastText       : "Last Page",
    refreshText    : "Refresh",
    displayMsg     : "Displaying {0} - {1} of {2}",
    emptyMsg       : 'No data to display'
  });
}

if(Ext.form.BasicForm){
    Ext.form.BasicForm.prototype.waitTitle = "Please Wait..."
}

if(Ext.form.Field){
  Ext.form.Field.prototype.invalidText = "The value in this field is invalid";
}

if(Ext.form.TextField){
  Ext.apply(Ext.form.TextField.prototype, {
    minLengthText : "The minimum length for this field is {0}",
    maxLengthText : "The maximum length for this field is {0}",
    blankText     : "This field is required",
    regexText     : "",
    emptyText     : null
  });
}

if(Ext.form.NumberField){
  Ext.apply(Ext.form.NumberField.prototype, {
    decimalSeparator : ".",
    decimalPrecision : 2,
    minText : "The minimum value for this field is {0}",
    maxText : "The maximum value for this field is {0}",
    nanText : "{0} is not a valid number"
  });
}

if(Ext.form.DateField){
  Ext.apply(Ext.form.DateField.prototype, {
    disabledDaysText  : "Disabled",
    disabledDatesText : "Disabled",
    minText           : "The date in this field must be after {0}",
    maxText           : "The date in this field must be before {0}",
    invalidText       : "{0} is not a valid date - it must be in the format {1}",
    format            : "d/m/y",
    altFormats        : "d/m/Y|d/m/y|d-m-y|d-m-Y|d/m|d-m|dm|dmy|dmY|d|Y-m-d"
  });
}

if(Ext.form.ComboBox){
  Ext.apply(Ext.form.ComboBox.prototype, {
    loadingText       : "Loading...",
    valueNotFoundText : undefined
  });
}

if(Ext.form.VTypes){
  Ext.apply(Ext.form.VTypes, {
    emailText    : 'This field should be an e-mail address in the format "user@example.com"',
    urlText      : 'This field should be a URL in the format "http:/'+'/www.example.com"',
    alphaText    : 'This field should only contain letters and _',
    alphanumText : 'This field should only contain letters, numbers and _'
  });
}

if(Ext.form.HtmlEditor){
  Ext.apply(Ext.form.HtmlEditor.prototype, {
    createLinkText : 'Please enter the URL for the link:',
    buttonTips : {
      bold : {
        title: 'Bold (Ctrl+B)',
        text: 'Make the selected text bold.',
        cls: 'x-html-editor-tip'
      },
      italic : {
        title: 'Italic (Ctrl+I)',
        text: 'Make the selected text italic.',
        cls: 'x-html-editor-tip'
      },
      underline : {
        title: 'Underline (Ctrl+U)',
        text: 'Underline the selected text.',
        cls: 'x-html-editor-tip'
      },
      increasefontsize : {
        title: 'Grow Text',
        text: 'Increase the font size.',
        cls: 'x-html-editor-tip'
      },
      decreasefontsize : {
        title: 'Shrink Text',
        text: 'Decrease the font size.',
        cls: 'x-html-editor-tip'
      },
      backcolor : {
        title: 'Text Highlight Color',
        text: 'Change the background color of the selected text.',
        cls: 'x-html-editor-tip'
      },
      forecolor : {
        title: 'Font Color',
        text: 'Change the color of the selected text.',
        cls: 'x-html-editor-tip'
      },
      justifyleft : {
        title: 'Align Text Left',
        text: 'Align text to the left.',
        cls: 'x-html-editor-tip'
      },
      justifycenter : {
        title: 'Center Text',
        text: 'Center text in the editor.',
        cls: 'x-html-editor-tip'
      },
      justifyright : {
        title: 'Align Text Right',
        text: 'Align text to the right.',
        cls: 'x-html-editor-tip'
      },
      insertunorderedlist : {
        title: 'Bullet List',
        text: 'Start a bulleted list.',
        cls: 'x-html-editor-tip'
      },
      insertorderedlist : {
        title: 'Numbered List',
        text: 'Start a numbered list.',
        cls: 'x-html-editor-tip'
      },
      createlink : {
        title: 'Hyperlink',
        text: 'Make the selected text a hyperlink.',
        cls: 'x-html-editor-tip'
      },
      sourceedit : {
        title: 'Source Edit',
        text: 'Switch to source editing mode.',
        cls: 'x-html-editor-tip'
      }
    }
  });
}

if(Ext.grid.GridView){
  Ext.apply(Ext.grid.GridView.prototype, {
    sortAscText  : "Sort Ascending",
    sortDescText : "Sort Descending",
    columnsText  : "Columns"
  });
}

if(Ext.grid.GroupingView){
  Ext.apply(Ext.grid.GroupingView.prototype, {
    emptyGroupText : '(None)',
    groupByText    : 'Group By This Field',
    showGroupsText : 'Show in Groups'
  });
}

if(Ext.grid.PropertyColumnModel){
  Ext.apply(Ext.grid.PropertyColumnModel.prototype, {
    nameText   : "Name",
    valueText  : "Value",
    dateFormat : "j/m/Y",
    trueText: "true",
    falseText: "false"
  });
}

if(Ext.layout.BorderLayout && Ext.layout.BorderLayout.SplitRegion){
  Ext.apply(Ext.layout.BorderLayout.SplitRegion.prototype, {
    splitTip            : "Drag to resize.",
    collapsibleSplitTip : "Drag to resize. Double click to hide."
  });
}

if(Ext.form.TimeField){
  Ext.apply(Ext.form.TimeField.prototype, {
    minText : "The time in this field must be equal to or after {0}",
    maxText : "The time in this field must be equal to or before {0}",
    invalidText : "{0} is not a valid time",
    format : "g:i A",
    altFormats : "g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H"
  });
}

if(Ext.form.CheckboxGroup){
  Ext.apply(Ext.form.CheckboxGroup.prototype, {
    blankText : "You must select at least one item in this group"
  });
}

if(Ext.form.RadioGroup){
  Ext.apply(Ext.form.RadioGroup.prototype, {
    blankText : "You must select one item in this group"
  });
}
try {
    var $$$ = Ext.getDom;
  }
catch (e) {
  function $$$() {
    var elements = new Array();
  
    for (var i=0, tmpIdx=arguments.length; i<tmpIdx ; i++) {
      var element = arguments[i];
      if (typeof element == 'string')
        element = document.getElementById(element);
      if (arguments.length == 1)
        return element;
      elements.push(element);
    }
    return elements;
  }
}

function trim(stringToTrim) {
	return stringToTrim.replace(/^\s+|\s+$/g,"");
}

function ltrim(stringToTrim) {
	return stringToTrim.replace(/^\s+/,"");
}

function rtrim(stringToTrim) {
	return stringToTrim.replace(/\s+$/,"");
}

function PadDigits(n, totalDigits) { 
  n = n.toString(); 
  var pd = ''; 
  if (totalDigits > n.length) { 
    for (i=0; i<(totalDigits-n.length); i++) { 
      pd += '0'; 
    } 
  } 
  return pd + n.toString(); 
} 

function printf(S, L) {
	var nS = "";
	var tS = S.split("%s");
	if (tS.length != L.length+1) throw "Input error";
 
	for(var i=0; i<L.length; i++)
	nS += tS[i] + L[i];
	return nS + tS[tS.length-1];
}

function autoTab(input,len, e) {
	var isNN = false;
	var keyCode = (isNN) ? e.which : e.keyCode;
	var filter = (isNN) ? [0,8,9] : [0,8,9,16,17,18,37,38,39,40,46];
	if(input.value.length >= len && !containsElement(filter,keyCode)) {
		input.value = input.value.slice(0, len);
		input.form[(getIndex(input)+1) % input.form.length].focus();
	}
}

function ValidateInt(Value)	{
	var regExpInt = new RegExp("^[0-9]+$");
	var arr = regExpInt.exec(Value);
	try {
		var idx = arr.index;
		return(true);
	} catch(e) {
		return(false);
	}
}

function containsElement(arr, ele) {
	var found = false, index = 0;
	while (!found && index < arr.length) {
		if (arr[index] == ele) {
			found = true;
		} else {
			index++;
		}
	}
	return found;
}


function getIndex(input) {
	var index = -1, i = 0, found = false;
	while (i < input.form.length && index == -1) {
		if (input.form[i] == input) {
			index = i;
		} else {
			i++;
		}
	}
	return index;
}


function getNodeText(inNode) {
	if (inNode) {
		return inNode.nodeValue
	} else {
		return "";
	}
}


function replaceHtml(el, html) {
	var oldEl = (typeof el === "string" ? document.getElementById(el) : el);
	/*@cc_on // Pure innerHTML is slightly faster in IE
		oldEl.innerHTML = html;
		return oldEl;
	@*/
	var newEl = oldEl.cloneNode(false);
	newEl.innerHTML = html;
	oldEl.parentNode.replaceChild(newEl, oldEl);
	/* Since we just removed the old element from the DOM, return a reference
	to the new element, which can be used to restore variable references. */
	return newEl;
};

function pausecomp(millis) {
  var date = new Date();
  var curDate = null;

  do { curDate = new Date(); }
    while(curDate-date < millis);
}

function $alert(inMsg, inTitle, inType) {
  if (inType == undefined) {
    msgIcon = Ext.MessageBox.WARNING;
  } else {
    switch (inType) {
      case 'info':
        msgIcon = Ext.MessageBox.INFO;
        break;
      case 'question':
        msgIcon = Ext.MessageBox.QUESTION;
        break;
      case 'error':
        msgIcon = Ext.MessageBox.ERROR;
        break;
      default:
        msgIcon = Ext.MessageBox.WARNING;
        break;
    }
  }
  if (inTitle == undefined) { inTitle = 'Alert'; } 
  Ext.MessageBox.show ({
    title: inTitle,
    msg: inMsg,
    buttons: Ext.MessageBox.OK,
    icon: msgIcon
  });
}


function str_repeat ( input, multiplier ) {
  // http://kevin.vanzonneveld.net
  // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // *     example 1: str_repeat('-=', 10);
  // *     returns 1: '-=-=-=-=-=-=-=-=-=-='

  var buf = '';

  for (i=0; i < multiplier; i++){
      buf += input;
  }

  return buf;
}

function iif (inCondition, inTrue, inFalse) {
  if (inCondition) {
    return inTrue;
  } else {
    return inFalse;
  }
}


function checkForFirebug() {
  if (window.console && window.console.firebug) {
    alert ('You have enabled Firebug for this site.\n Firebug is a debug tool. We dont\'t think you want to debug our service!\n\n Please disable firebug and try again.');
  };
}


function openPage(inID) {
  var width  = 600;
  var height = 450;
  var left   = (screen.width  - width)/2;
  var top    = (screen.height - height)/2;
  var params = 'width='+width+',height='+height;
  //params += ', top='+top+', left='+left;
  params += ',directories=no';
  params += ',location=no';
  params += ',menubar=no';
  params += ',resizable=yes';
  params += ',scrollbars=yes';
  params += ',status=no';
  params += ',toolbar=no';
  var url = 'pages.php?id=' + inID;
  newwin = window.open(url,'roomview',params);
  if (window.focus) {newwin.focus()}
  return false;
}


function sleep(milliseconds) {
  var start = new Date().getTime();
  for (var i = 0; i < 1e7; i++) {
    if ((new Date().getTime() - start) > milliseconds){
      break;
    }
  }
}


function RGB2HTML(red, green, blue) {
  //var decColor = red + 256 * green + 65536 * blue;
  (red   == 0) ? redC    = '00' : redC   = red.toString(16);
  (green == 0) ? greenC  = '00' : greenC = green.toString(16);
  (blue  == 0) ? blueC   = '00' : blueC  = blue.toString(16); 
  return  redC + greenC + blueC;
}/**
 * An adapter for the Shadowbox media viewer and the Yahoo! User Interface (YUI)
 * JavaScript library.
 *
 * This file is part of Shadowbox.
 *
 * Shadowbox is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * Shadowbox is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Shadowbox.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 * @copyright   2007 Michael J. I. Jackson
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
 * @version     SVN: $Id: shadowbox-ext.js 48 2008-01-26 09:58:25Z mjijackson $
 */

if(typeof Ext == 'undefined'){
    throw 'Unable to load Shadowbox, Ext framework not found.';
}

// create the Shadowbox object first
var Shadowbox = {};

Shadowbox.lib = function(){

    var E = Ext.lib.Event;

    return {

        /**
         * Gets the value of the style on the given element.
         *
         * @param   {HTMLElement}   el      The DOM element
         * @param   {String}        style   The name of the style (e.g. margin-top)
         * @return  {mixed}                 The value of the given style
         * @public
         */
        getStyle: function(el, style){
            return Ext.get(el).getStyle(style);
        },

        /**
         * Sets the style on the given element to the given value. May be an
         * object to specify multiple values.
         *
         * @param   {HTMLElement}   el      The DOM element
         * @param   {String/Object} style   The name of the style to set if a
         *                                  string, or an object of name =>
         *                                  value pairs
         * @param   {String}        value   The value to set the given style to
         * @return  void
         * @public
         */
        setStyle: function(el, style, value){
            Ext.get(el).setStyle(style, value);
        },

        /**
         * Gets a reference to the given element.
         *
         * @param   {String/HTMLElement}    el      The element to fetch
         * @return  {HTMLElement}                   A reference to the element
         * @public
         */
        get: function(el){
            return Ext.getDom(el);
        },

        /**
         * Removes an element from the DOM.
         *
         * @param   {HTMLElement}           el      The element to remove
         * @return  void
         * @public
         */
        remove: function(el){
            Ext.get(el).remove();
        },

        /**
         * Gets the target of the given event. The event object passed will be
         * the same object that is passed to listeners registered with
         * addEvent().
         *
         * @param   {mixed}                 e       The event object
         * @return  {HTMLElement}                   The event's target element
         * @public
         */
        getTarget: function(e){
            return E.getTarget(e);
        },

        /**
         * Prevents the event's default behavior. The event object passed will
         * be the same object that is passed to listeners registered with
         * addEvent().
         *
         * @param   {mixed}                 e       The event object
         * @return  void
         * @public
         */
        preventDefault: function(e){
            E.preventDefault(e);
        },

        /**
         * Adds an event listener to the given element. It is expected that this
         * function will be passed the event as its first argument.
         *
         * @param   {HTMLElement}   el          The DOM element to listen to
         * @param   {String}        name        The name of the event to register
         *                                      (i.e. 'click', 'scroll', etc.)
         * @param   {Function}      handler     The event handler function
         * @return  void
         * @public
         */
        addEvent: function(el, name, handler){
            E.addListener(el, name, handler);
        },

        /**
         * Removes an event listener from the given element.
         *
         * @param   {HTMLElement}   el          The DOM element to stop listening to
         * @param   {String}        name        The name of the event to stop
         *                                      listening for (i.e. 'click')
         * @param   {Function}      handler     The event handler function
         * @return  void
         * @public
         */
        removeEvent: function(el, name, handler){
            E.removeListener(el, name, handler);
        },

        /**
         * Animates numerous styles of the given element. The second parameter
         * of this function will be an object of the type that is expected by
         * YAHOO.util.Anim. See http://developer.yahoo.com/yui/docs/YAHOO.util.Anim.html
         * for more information.
         *
         * @param   {HTMLElement}   el          The DOM element to animate
         * @param   {Object}        obj         The animation attributes/parameters
         * @param   {Number}        duration    The duration of the animation
         *                                      (in seconds)
         * @param   {Function}      callback    A callback function to call when
         *                                      the animation completes
         * @return  void
         * @public
         */
        animate: function(el, obj, duration, callback){
            var anim = new Ext.lib.AnimBase(el, obj, duration, Ext.lib.Easing.easeOut);
            anim.animateX(callback);
        }

    };

}();
/**
 * A media-viewer script for web pages that allows content to be viewed without
 * navigating away from the original linking page.
 *
 * This file is part of Shadowbox.
 *
 * Shadowbox is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * Shadowbox is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Shadowbox. If not, see <http://www.gnu.org/licenses/>.
 *
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 * @copyright   2007 Michael J. I. Jackson
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
 * @version     SVN: $Id: shadowbox.js 75 2008-02-21 16:51:29Z mjijackson $
 */

if(typeof Shadowbox == 'undefined'){
    throw 'Unable to load Shadowbox, no base library adapter found.';
}

/**
 * The Shadowbox class. Used to display different media on a web page using a
 * Lightbox-like effect.
 *
 * Useful resources:
 * - http://www.alistapart.com/articles/byebyeembed
 * - http://www.w3.org/TR/html401/struct/objects.html
 * - http://www.dyn-web.com/dhtml/iframes/
 * - http://support.microsoft.com/kb/316992
 * - http://www.apple.com/quicktime/player/specs.html
 * - http://www.howtocreate.co.uk/wrongWithIE/?chapter=navigator.plugins
 *
 * @class       Shadowbox
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 * @singleton
 */
(function(){

    /**
     * The current version of Shadowbox.
     *
     * @property    {String}    version
     * @private
     */
    var version = '1.0';

    /**
     * Contains the default options for Shadowbox. This object is almost
     * entirely customizable.
     *
     * @property    {Object}    options
     * @private
     */
    var options = {

        /**
         * A base URL that will be prepended to the loadingImage, flvPlayer, and
         * overlayBgImage options to save on typing.
         *
         * @var     {String}    assetURL
         */
        assetURL:           '',

        /**
         * The path to the image to display while loading.
         *
         * @var     {String}    loadingImage
         */
        loadingImage:       'images/loading.gif',

        /**
         * Enable animations.
         *
         * @var     {Boolean}   animate
         */
        animate:            true,

        /**
         * Specifies the sequence of the height and width animations. May be
         * 'wh' (width then height), 'hw' (height then width), or 'sync' (both
         * at the same time). Of course this will only work if animate is true.
         *
         * @var     {String}    animSequence
         */
        animSequence:       'wh',

        /**
         * The path to flvplayer.swf.
         *
         * @var     {String}    flvPlayer
         */
        flvPlayer:          'flvplayer.swf',

        /**
         * The background color and opacity of the overlay. Note: When viewing
         * movie files on FF Mac, the default background image will be used
         * because that browser has problems displaying movies above layers
         * that aren't 100% opaque.
         *
         * @var     {String}    overlayColor
         */
        overlayColor:       '#000',

        /**
         * The background opacity to use for the overlay.
         *
         * @var     {Number}    overlayOpacity
         */
        overlayOpacity:     0.85,

        /**
         * A background image to use for browsers such as FF Mac that don't
         * support displaying movie content over backgrounds that aren't 100%
         * opaque.
         *
         * @var     {String}    overlayBgImage
         */
        overlayBgImage:     'images/overlay-85.png',

        /**
         * Listen to the overlay for clicks. If the user clicks the overlay,
         * it will trigger Shadowbox.close().
         *
         * @var     {Boolean}   listenOverlay
         */
        listenOverlay:      true,

        /**
         * Automatically play movies.
         *
         * @var     {Boolean}   autoplayMovies
         */
        autoplayMovies:     true,

        /**
         * Enable movie controllers on movie players.
         *
         * @var     {Boolean}   showMovieControls
         */
        showMovieControls:  true,

        /**
         * The duration of the resizing animations (in seconds).
         *
         * @var     {Number}    resizeDuration
         */
        resizeDuration:     0.35,

        /**
         * The duration of the overlay fade animation (in seconds).
         *
         * @var     {Number}    fadeDuration
         */
        fadeDuration:       0.35,

        /**
         * Show the navigation controls.
         *
         * @var     {Boolean}   displayNav
         */
        displayNav:         true,

        /**
         * Enable continuous galleries. When this is true, users will be able
         * to skip to the first gallery image from the last using next and vice
         * versa.
         *
         * @var     {Boolean}   continuous
         */
        continuous:         false,

        /**
         * Display the gallery counter.
         *
         * @var     {Boolean}   displayCounter
         */
        displayCounter:     true,

        /**
         * This option may be either 'default' or 'skip'. The default counter is
         * a simple '1 of 5' message. The skip counter displays a link for each
         * piece in the gallery that enables a user to skip directly to any
         * piece.
         *
         * @var     {String}    counterType
         */
        counterType:        'default',

        /**
         * The amount of padding to maintain around the viewport edge (in
         * pixels). This only applies when the image is very large and takes up
         * the entire viewport.
         *
         * @var     {Number}    viewportPadding
         */
        viewportPadding:    20,

        /**
         * How to handle images that are too large for the viewport. 'resize'
         * will resize the image while preserving aspect ratio and display it at
         * the smaller resolution. 'drag' will display the image at its native
         * resolution but it will be draggable within the Shadowbox. 'none' will
         * display the image at its native resolution but it may be cropped.
         *
         * @var     {String}    handleLgImages
         */
        handleLgImages:     'resize',

        /**
         * The initial height of Shadowbox (in pixels).
         *
         * @var     {Number}    initialHeight
         */
        initialHeight:      160,

        /**
         * The initial width of Shadowbox (in pixels).
         *
         * @var     {Number}    initialWidth
         */
        initialWidth:       320,

        /**
         * Enable keyboard control. Note: If you disable the keys, you may want
         * to change the visual styles for the navigation elements that suggest
         * keyboard shortcuts.
         *
         * @var     {Boolean}   enableKeys
         */
        enableKeys:         true,

        /**
         * The keys used to control Shadowbox. Note: In order to use these,
         * enableKeys must be true. Key values or key codes may be used.
         *
         * @var     {Array}
         */
        keysClose:          ['c', 'q', 27], // c, q, or esc
        keysNext:           ['n', 39],      // n or right arrow
        keysPrev:           ['p', 37],      // p or left arrow

        /**
         * A hook function to be fired when Shadowbox opens. The single argument
         * will be the current gallery element.
         *
         * @var     {Function}
         */
        onOpen:             null,

        /**
         * A hook function to be fired when Shadowbox finishes loading its
         * content. The single argument will be the current gallery element on
         * display.
         *
         * @var     {Function}
         */
        onFinish:           null,

        /**
         * A hook function to be fired when Shadowbox changes from one gallery
         * element to the next. The single argument will be the current gallery
         * element that is about to be displayed.
         *
         * @var     {Function}
         */
        onChange:           null,

        /**
         * A hook function that will be fired when Shadowbox closes. The single
         * argument will be the gallery element most recently displayed.
         *
         * @var     {Function}
         */
        onClose:            null,

        /**
         * The mode to use when handling unsupported media. May be either
         * 'remove' or 'link'. If it is 'remove', the unsupported gallery item
         * will merely be removed from the gallery. If it is the only item in
         * the gallery, the link will simply be followed. If it is 'link', a
         * link will be provided to the appropriate plugin page in place of the
         * gallery element.
         *
         * @var     {String}    handleUnsupported
         */
        handleUnsupported:  'link',

        /**
         * Skips calling Shadowbox.setup() in init(). This means that it must
         * be called later manually.
         *
         * @var     {Boolean}   skipSetup
         */
        skipSetup:          false,

        /**
         * Text messages to use for Shadowbox. These are provided so they may be
         * translated into different languages.
         *
         * @var     {Object}    text
         */
        text:           {

            cancel:     'Cancel',

            loading:    'loading',

            close:      '<span class="shortcut">C</span>lose',

            next:       '<span class="shortcut">N</span>ext',

            prev:       '<span class="shortcut">P</span>revious',

            errors:     {
                single: 'You must install the <a href="{0}">{1}</a> browser plugin to view this content.',
                shared: 'You must install both the <a href="{0}">{1}</a> and <a href="{2}">{3}</a> browser plugins to view this content.',
                either: 'You must install either the <a href="{0}">{1}</a> or the <a href="{2}">{3}</a> browser plugin to view this content.'
            }

        },

        /**
         * An object containing names of plugins and links to their respective
         * download pages.
         *
         * @var     {Object}    errors
         */
        errors:         {

            fla:        {
                name:   'Flash',
                url:    'http://www.adobe.com/products/flashplayer/'
            },

            qt:         {
                name:   'QuickTime',
                url:    'http://www.apple.com/quicktime/download/'
            },

            wmp:        {
                name:   'Windows Media Player',
                url:    'http://www.microsoft.com/windows/windowsmedia/'
            },

            f4m:        {
                name:   'Flip4Mac',
                url:    'http://www.flip4mac.com/wmv_download.htm'
            }

        },

        /**
         * The HTML markup to use for Shadowbox. Note: The script depends on
         * most of these elements being present, so don't modify this variable
         * unless you know what you're doing.
         *
         * @var     {Object}    skin
         */
        skin:           {

            main:       '<div id="shadowbox_overlay"></div>' +
                        '<div id="shadowbox_container">' +
                            '<div id="shadowbox">' +
                                '<div id="shadowbox_title">' +
                                    '<div id="shadowbox_title_inner"></div>' +
                                '</div>' +
                                '<div id="shadowbox_body">' +
                                    '<div id="shadowbox_body_inner"></div>' +
                                    '<div id="shadowbox_loading"></div>' +
                                '</div>' +
                                '<div id="shadowbox_toolbar">' +
                                    '<div id="shadowbox_toolbar_inner"></div>' +
                                '</div>' +
                            '</div>' +
                        '</div>',

            loading:    '<img src="{0}" alt="{1}" />' +
                        '<span><a href="javascript:Shadowbox.close();">{2}</a></span>',

            counter:    '<div id="shadowbox_counter">{0}</div>',

            close:      '<div id="shadowbox_nav_close">' +
                            '<a href="javascript:Shadowbox.close();">{0}</a>' +
                        '</div>',

            next:       '<div id="shadowbox_nav_next">' +
                            '<a href="javascript:Shadowbox.next();">{0}</a>' +
                        '</div>',

            prev:       '<div id="shadowbox_nav_previous">' +
                            '<a href="javascript:Shadowbox.previous();">{0}</a>' +
                        '</div>'

        },

        /**
         * An object containing arrays of all supported file extensions. Each
         * property of this object contains an array. If this object is to be
         * modified, it must be done before calling init().
         *
         * - img: Supported image file extensions
         * - qt: Movie file extensions supported by QuickTime
         * - wmp: Movie file extensions supported by Windows Media Player
         * - qtwmp: Movie file extensions supported by both QuickTime and Windows Media Player
         * - iframe: File extensions that will be display in an iframe
         *
         * @var     {Object}    ext
         */
        ext:     {
            img:        ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'phpg'],
            qt:         ['dv', 'mov', 'moov', 'movie', 'mp4'],
            wmp:        ['asf', 'wm', 'wmv'],
            qtwmp:      ['avi', 'mpg', 'mpeg'],
            iframe:     ['asp', 'aspx', 'cgi', 'cfm', 'htm', 'html', 'pl', 'php',
                        'php3', 'php4', 'php5', 'phtml', 'rb', 'rhtml', 'shtml',
                        'txt', 'vbs']
        }

    };

    /**
     * Stores the default set of options in case a custom set of options is used
     * on a link-by-link basis so we can restore them later.
     *
     * @property    {Object}    default_options
     * @private
     */
    var default_options = null;

    /**
     * Shorthand for Shadowbox.lib.
     *
     * @property    {Object}        SL
     * @private
     */
    var SL = Shadowbox.lib;

    /**
     * An object containing some regular expressions we'll need later. Compiled
     * up front for speed.
     *
     * @property    {Object}        RE
     * @private
     */
    var RE = {
        resize:         /(img|swf|flv)/, // file types to resize
        overlay:        /(img|iframe|html|inline)/, // content types to not use an overlay image for on FF Mac
        swf:            /\.swf\s*$/i, // swf file extension
        flv:            /\.flv\s*$/i, // flv file extension
        domain:         /:\/\/(.*?)[:\/]/, // domain prefix
        inline:         /#(.+)$/, // inline element id
        rel:            /^(light|shadow)box/i, // rel attribute format
        gallery:        /^(light|shadow)box\[(.*?)\]/i, // rel attribute format for gallery link
        unsupported:    /^unsupported-(\w+)/, // unsupported media type
        param:          /\s*([a-z_]*?)\s*=\s*(.+)\s*/, // rel string parameter
        empty:          /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i // elements that don't have children
    };

    /**
     * A cache of options for links that have been set up for use with
     * Shadowbox.
     *
     * @property    {Array}         cache
     * @private
     */
    var cache = [];

    /**
     * An array of pieces currently being viewed. In the case of non-gallery
     * pieces, this will only hold one object.
     *
     * @property    {Array}         current_gallery
     * @private
     */
    var current_gallery;

    /**
     * The array index of the current_gallery that is currently being viewed.
     *
     * @property    {Number}        current
     * @private
     */
    var current;

    /**
     * Keeps track of the current optimal height of the box. We use this so that
     * if the user resizes the browser window to get a better view, and we're
     * currently at a size smaller than the optimal, we can resize easily.
     *
     * @see         resizeContent()
     * @property    {Number}        optimal_height
     * @private
     */
    var optimal_height = options.initialHeight;

    /**
     * Keeps track of the current optimal width of the box. See optimal_height
     * explanation (above).
     *
     * @property    {Number}        optimal_width
     * @private
     */
    var optimal_width = options.initialWidth;

    /**
     * Keeps track of the current height of the box. This is useful in drag
     * calculations.
     *
     * @property    {Number}        current_height
     * @private
     */
    var current_height = 0;

    /**
     * Keeps track of the current width of the box. Useful in drag calculations.
     *
     * @property    {Number}        current_width
     * @private
     */
    var current_width = 0;

    /**
     * Resource used to preload images. It's class-level so that when a new
     * image is requested, the same resource can be reassigned, cancelling
     * the original's callback.
     *
     * @property    {HTMLElement}   preloader
     * @private
     */
    var preloader;

    /**
     * Keeps track of whether or not Shadowbox has been initialized. We never
     * want to initialize twice.
     *
     * @property    {Boolean}       initialized
     * @private
     */
    var initialized = false;

    /**
     * Keeps track of whether or not Shadowbox is activated.
     *
     * @property    {Boolean}       activated
     * @private
     */
    var activated = false;

    /**
     * Keeps track of 4 floating values (x, y, start_x, & start_y) that are used
     * in the drag calculations.
     *
     * @property    {Object}        drag
     * @private
     */
    var drag;

    /**
     * Holds the draggable element so we don't have to fetch it every time
     * the mouse moves.
     *
     * @property    {HTMLElement}   draggable
     * @private
     */
    var draggable;

    /**
     * Keeps track of whether or not we're currently using the overlay
     * background image to display the current gallery. We do this because we
     * use different methods for fading the overlay in and out. The color fill
     * overlay fades in and out nicely, but the image overlay stutters. By
     * keeping track of the type of overlay in use, we don't have to check again
     * what type of overlay we're using when it's time to get rid of it later.
     *
     * @property    {Boolean}       overlay_img_needed
     * @private
     */
    var overlay_img_needed;

    /**
     * These parameters for simple browser detection. Used in Ext.js.
     *
     * @ignore
     */
    var ua = navigator.userAgent.toLowerCase();
    var isStrict = document.compatMode == 'CSS1Compat',
        isOpera = ua.indexOf("opera") > -1,
        isIE = ua.indexOf('msie') > -1,
        isIE7 = ua.indexOf('msie 7') > -1,
        isBorderBox = isIE && !isStrict,
        isSafari = (/webkit|khtml/).test(ua),
        isSafari3 = isSafari && !!(document.evaluate),
        isGecko = !isSafari && ua.indexOf('gecko') > -1,
        isWindows = (ua.indexOf('windows') != -1 || ua.indexOf('win32') != -1),
        isMac = (ua.indexOf('macintosh') != -1 || ua.indexOf('mac os x') != -1),
        isLinux = (ua.indexOf('linux') != -1);

    /**
     * Do we need to hack the position to make Shadowbox appear fixed? We could
     * hack this using CSS, but let's just get over all the hacks and let IE6
     * users get what they deserve! Down with hacks! Hmm...now that I think
     * about it, I should just flash all kinds of alerts and annoying popups on
     * their screens, and then redirect them to some foreign spyware site that
     * will upload a nasty virus...
     *
     * @property    {Boolean}   absolute_pos
     * @private
     */
    var absolute_pos = isIE && !isIE7;

    /**
     * Contains plugin support information. Each property of this object is a
     * boolean indicating whether that plugin is supported.
     *
     * - fla: Flash player
     * - qt: QuickTime player
     * - wmp: Windows Media player
     * - f4m: Flip4Mac plugin
     *
     * @property    {Object}    plugins
     * @private
     */
    var plugins = null;

    // detect plugin support
    if(navigator.plugins && navigator.plugins.length){
        var detectPlugin = function(plugin_name){
            var detected = false;
            for (var i = 0, len = navigator.plugins.length; i < len; ++i){
                if(navigator.plugins[i].name.indexOf(plugin_name) > -1){
                    detected = true;
                    break;
                }
            }
            return detected;
        };
        var f4m = detectPlugin('Flip4Mac');
        var plugins = {
            fla:    detectPlugin('Shockwave Flash'),
            qt:     detectPlugin('QuickTime'),
            wmp:    !f4m && detectPlugin('Windows Media'), // if it's Flip4Mac, it's not really WMP
            f4m:    f4m
        };
    }else{
        var detectPlugin = function(plugin_name){
            var detected = false;
            try {
                var axo = new ActiveXObject(plugin_name);
                if(axo){
                    detected = true;
                }
            } catch (e) {}
            return detected;
        };
        var plugins = {
            fla:    detectPlugin('ShockwaveFlash.ShockwaveFlash'),
            qt:     detectPlugin('QuickTime.QuickTime'),
            wmp:    detectPlugin('wmplayer.ocx'),
            f4m:    false
        };
    }

    /**
     * Applies all properties of e to o. This function is recursive so that if
     * any properties of e are themselves objects, those objects will be applied
     * to objects with the same key that may exist in o.
     *
     * @param   {Object}    o       The original object
     * @param   {Object}    e       The extension object
     * @return  {Object}            The original object with all properties
     *                              of the extension object applied (deep)
     * @private
     */
    var apply = function(o, e){
        for(var p in e) o[p] = e[p];
        return o;
    };

    /**
     * Determines if the given object is an anchor/area element.
     *
     * @param   {mixed}     el      The object to check
     * @return  {Boolean}           True if the object is a link element
     * @private
     */
    var isLink = function(el){
        return typeof el.tagName == 'string' && (el.tagName.toUpperCase() == 'A' || el.tagName.toUpperCase() == 'AREA');
    };

    /**
     * Gets the height of the viewport in pixels. Note: This function includes
     * scrollbars in Safari 3.
     *
     * @return  {Number}        The height of the viewport
     * @public
     * @static
     */
    SL.getViewportHeight = function(){
        var height = window.innerHeight; // Safari
        var mode = document.compatMode;
        if((mode || isIE) && !isOpera){
            height = isStrict ? document.documentElement.clientHeight : document.body.clientHeight;
        }
        return height;
    };

    /**
     * Gets the width of the viewport in pixels. Note: This function includes
     * scrollbars in Safari 3.
     *
     * @return  {Number}        The width of the viewport
     * @public
     * @static
     */
    SL.getViewportWidth = function(){
        var width = window.innerWidth; // Safari
        var mode = document.compatMode;
        if(mode || isIE){
            width = isStrict ? document.documentElement.clientWidth : document.body.clientWidth;
        }
        return width;
    };

    /**
     * Gets the height of the document (body and its margins) in pixels.
     *
     * @return  {Number}        The height of the document
     * @public
     * @static
     */
    SL.getDocumentHeight = function(){
        var scrollHeight = isStrict ? document.documentElement.scrollHeight : document.body.scrollHeight;
        return Math.max(scrollHeight, SL.getViewportHeight());
    };

    /**
     * Gets the width of the document (body and its margins) in pixels.
     *
     * @return  {Number}        The width of the document
     * @public
     * @static
     */
    SL.getDocumentWidth = function(){
        var scrollWidth = isStrict ? document.documentElement.scrollWidth : document.body.scrollWidth;
        return Math.max(scrollWidth, SL.getViewportWidth());
    };

    /**
     * A utility function used by the fade functions to clear the opacity
     * style setting of the given element. Required in some cases for IE.
     * Based on Ext.Element's clearOpacity.
     *
     * @param   {HTMLElement}   el      The DOM element
     * @return  void
     * @private
     */
    var clearOpacity = function(el){
        if(isIE){
            if(typeof el.style.filter == 'string' && (/alpha/i).test(el.style.filter)){
                el.style.filter = '';
            }
        }else{
            el.style.opacity = '';
            el.style['-moz-opacity'] = '';
            el.style['-khtml-opacity'] = '';
        }
    };

    /**
     * Fades the given element from 0 to the specified opacity.
     *
     * @param   {HTMLElement}   el              The DOM element to fade
     * @param   {Number}        endingOpacity   The final opacity to animate to
     * @param   {Number}        duration        The duration of the animation
     *                                          (in seconds)
     * @param   {Function}      callback        A callback function to call
     *                                          when the animation completes
     * @return  void
     * @private
     */
    var fadeIn = function(el, endingOpacity, duration, callback){
        if(options.animate){
            SL.setStyle(el, 'opacity', 0);
            el.style.visibility = 'visible';
            SL.animate(el, {
                opacity: { to: endingOpacity }
            }, duration, function(){
                if(endingOpacity == 1) clearOpacity(el);
                if(typeof callback == 'function') callback();
            });
        }else{
            if(endingOpacity == 1){
                clearOpacity(el);
            }else{
                SL.setStyle(el, 'opacity', endingOpacity);
            }
            el.style.visibility = 'visible';
            if(typeof callback == 'function') callback();
        }
    };

    /**
     * Fades the given element from its current opacity to 0.
     *
     * @param   {HTMLElement}   el          The DOM element to fade
     * @param   {Number}        duration    The duration of the fade animation
     * @param   {Function}      callback    A callback function to call when
     *                                      the animation completes
     * @return  void
     * @private
     */
    var fadeOut = function(el, duration, callback){
        var cb = function(){
            el.style.visibility = 'hidden';
            clearOpacity(el);
            if(typeof callback == 'function') callback();
        };
        if(options.animate){
            SL.animate(el, {
                opacity: { to: 0 }
            }, duration, cb);
        }else{
            cb();
        }
    };

    /**
     * Appends an HTML fragment to the given element.
     *
     * @param   {String/HTMLElement}    el      The element to append to
     * @param   {String}                html    The HTML fragment to use
     * @return  {HTMLElement}                   The newly appended element
     * @private
     */
    var appendHTML = function(el, html){
        el = SL.get(el);
        if(el.insertAdjacentHTML){
            el.insertAdjacentHTML('BeforeEnd', html);
            return el.lastChild;
        }
        if(el.lastChild){
            var range = el.ownerDocument.createRange();
            range.setStartAfter(el.lastChild);
            var frag = range.createContextualFragment(html);
            el.appendChild(frag);
            return el.lastChild;
        }else{
            el.innerHTML = html;
            return el.lastChild;
        }
    };

    /**
     * Overwrites the HTML of the given element.
     *
     * @param   {String/HTMLElement}    el      The element to overwrite
     * @param   {String}                html    The new HTML to use
     * @return  {HTMLElement}                   The new firstChild element
     * @private
     */
    var overwriteHTML = function(el, html){
        el = SL.get(el);
        el.innerHTML = html;
        return el.firstChild;
    };

    /**
     * Gets either the offsetHeight or the height of the given element plus
     * padding and borders (when offsetHeight is not available). Based on
     * Ext.Element's getComputedHeight.
     *
     * @return  {Number}            The computed height of the element
     * @private
     */
    var getComputedHeight = function(el){
        var h = Math.max(el.offsetHeight, el.clientHeight);
        if(!h){
            h = parseInt(SL.getStyle(el, 'height'), 10) || 0;
            if(!isBorderBox){
                h += parseInt(SL.getStyle(el, 'padding-top'), 10)
                    + parseInt(SL.getStyle(el, 'padding-bottom'), 10)
                    + parseInt(SL.getStyle(el, 'border-top-width'), 10)
                    + parseInt(SL.getStyle(el, 'border-bottom-width'), 10);
            }
        }
        return h;
    };

    /**
     * Gets either the offsetWidth or the width of the given element plus
     * padding and borders (when offsetWidth is not available). Based on
     * Ext.Element's getComputedWidth.
     *
     * @return  {Number}            The computed width of the element
     * @private
     */
    var getComputedWidth = function(el){
        var w = Math.max(el.offsetWidth, el.clientWidth);
        if(!w){
            w = parseInt(SL.getStyle(el, 'width'), 10) || 0;
            if(!isBorderBox){
                w += parseInt(SL.getStyle(el, 'padding-left'), 10)
                    + parseInt(SL.getStyle(el, 'padding-right'), 10)
                    + parseInt(SL.getStyle(el, 'border-left-width'), 10)
                    + parseInt(SL.getStyle(el, 'border-right-width'), 10);
            }
        }
        return w;
    };

    /**
     * Determines the player needed to display the file at the given URL. If
     * the file type is not supported, the return value will be 'unsupported'.
     * If the file type is not supported but the correct player can be
     * determined, the return value will be 'unsupported-*' where * will be the
     * player abbreviation (e.g. 'qt' = QuickTime).
     *
     * @param   {String}        url     The url of the file
     * @return  {String}                The name of the player to use
     * @private
     */
    var getPlayerType = function(url){
        if(RE.img.test(url)) return 'img';
        var match = url.match(RE.domain);
        var this_domain = match ? document.domain == match[1] : false;
        if(url.indexOf('#') > -1 && this_domain) return 'inline';
        var q_index = url.indexOf('?');
        if(q_index > -1) url = url.substring(0, q_index); // strip query string for player detection purposes
        if(RE.swf.test(url)) return plugins.fla ? 'swf' : 'unsupported-swf';
        if(RE.flv.test(url)) return plugins.fla ? 'flv' : 'unsupported-flv';
        if(RE.qt.test(url)) return plugins.qt ? 'qt' : 'unsupported-qt';
        if(RE.wmp.test(url)){
            if(plugins.wmp){
                return 'wmp';
            }else if(plugins.f4m){
                return 'qt';
            }else{
                return isMac ? (plugins.qt ? 'unsupported-f4m' : 'unsupported-qtf4m') : 'unsupported-wmp';
            }
        }else if(RE.qtwmp.test(url)){
            if(plugins.qt){
                return 'qt';
            }else if(plugins.wmp){
                return 'wmp';
            }else{
                return isMac ? 'unsupported-qt' : 'unsupported-qtwmp';
            }
        }else if(!this_domain || RE.iframe.test(url)){
            return 'iframe';
        }
        return 'unsupported';
    };

    /**
     * Handles all clicks on links that have been set up to work with Shadowbox
     * and cancels the default event behavior when appropriate.
     *
     * @param   {Event}         ev          The click event object
     * @return  void
     * @private
     */
    var handleClick = function(ev){
        // get anchor/area element
        var link;
        if(isLink(this)){
            link = this; // jQuery, Prototype, YUI
        }else{
            link = SL.getTarget(ev); // Ext
            while(!isLink(link) && link.parentNode){
                link = link.parentNode;
            }
        }

        Shadowbox.open(link);
        if(current_gallery.length) SL.preventDefault(ev);
    };

    /**
     * Sets up the current gallery for the given object. Modifies the current
     * and current_gallery variables to contain the appropriate information.
     * Also, checks to see if there are any gallery pieces that are not
     * supported by the client's browser/plugins. If there are, they will be
     * handled according to the handleUnsupported option.
     *
     * @param   {Object}    obj         The content to get the gallery for
     * @return  void
     * @private
     */
    var setupGallery = function(obj){
        // create a copy so it doesn't get modified later
        var copy = apply({}, obj);

        // is it part of a gallery?
        if(!obj.gallery){ // single item, no gallery
            current_gallery = [copy];
            current = 0;
        }else{
            current_gallery = []; // clear the current gallery
            var index, ci;
            for(var i = 0, len = cache.length; i < len; ++i){
                ci = cache[i];
                if(ci.gallery){
                    if(ci.content == obj.content
                        && ci.gallery == obj.gallery
                        && ci.title == obj.title){ // compare content, gallery, & title
                            index = current_gallery.length; // key element found
                    }
                    if(ci.gallery == obj.gallery){
                        current_gallery.push(apply({}, ci));
                    }
                }
            }
            // if not found in cache, prepend to front of gallery
            if(index == null){
                current_gallery.unshift(copy);
                index = 0;
            }
            current = index;
        }

        // are any media in the current gallery supported?
        var match, r;
        for(var i = 0, len = current_gallery.length; i < len; ++i){
            r = false;
            if(current_gallery[i].type == 'unsupported'){ // don't support this at all
                r = true;
            }else if(match = RE.unsupported.exec(current_gallery[i].type)){ // handle unsupported elements
                if(options.handleUnsupported == 'link'){
                    current_gallery[i].type = 'html';
                    // generate a link to the appropriate plugin download page(s)
                    var m;
                    switch(match[1]){
                        case 'qtwmp':
                            m = String.format(options.text.errors.either,
                                options.errors.qt.url, options.errors.qt.name,
                                options.errors.wmp.url, options.errors.wmp.name);
                        break;
                        case 'qtf4m':
                            m = String.format(options.text.errors.shared,
                                options.errors.qt.url, options.errors.qt.name,
                                options.errors.f4m.url, options.errors.f4m.name);
                        break;
                        default:
                            if(match[1] == 'swf' || match[1] == 'flv') match[1] = 'fla';
                            m = String.format(options.text.errors.single,
                                options.errors[match[1]].url, options.errors[match[1]].name);
                    }
                    current_gallery[i] = apply(current_gallery[i], {
                        height:     160, // error messages are short so they
                        width:      320, // only need a small box to display properly
                        content:    '<div class="shadowbox_message">' + m + '</div>'
                    });
                }else{
                    r = true;
                }
            }else if(current_gallery[i].type == 'inline'){ // handle inline elements
                // retrieve the innerHTML of the inline element
                var match = RE.inline.exec(current_gallery[i].content);
                if(match){
                    var el;
                    if(el = SL.get(match[1])){
                        current_gallery[i].content = el.innerHTML;
                    }else{
                        throw 'No element found with id ' + match[1];
                    }
                }else{
                    throw 'No element id found for inline content';
                }
            }
            if(r){
                // remove the element from the gallery
                current_gallery.splice(i, 1);
                if(i < current) --current;
                --i;
            }
        }
    };

    /**
     * Hides the title bar and toolbar and populates them with the proper
     * content.
     *
     * @return  void
     * @private
     */
    var buildBars = function(){
        var link = current_gallery[current];
        if(!link) return; // nothing to build

        // build the title
        var title_i = SL.get('shadowbox_title_inner');
        title_i.innerHTML = (link.title) ? link.title : '';
        // empty the toolbar
        var tool_i = SL.get('shadowbox_toolbar_inner');
        tool_i.innerHTML = '';

        // build the nav
        if(options.displayNav){
            tool_i.innerHTML = String.format(options.skin.close, options.text.close);
            if(current_gallery.length > 1){
                if(options.continuous){
                    // show both
                    appendHTML(tool_i, String.format(options.skin.next, options.text.next));
                    appendHTML(tool_i, String.format(options.skin.prev, options.text.prev));
                }else{
                    // not last in the gallery, show the next link
                    if((current_gallery.length - 1) > current){
                        appendHTML(tool_i, String.format(options.skin.next, options.text.next));
                    }
                    // not first in the gallery, show the previous link
                    if(current > 0){
                        appendHTML(tool_i, String.format(options.skin.prev, options.text.prev));
                    }
                }
            }
        }

        // build the counter
        if(current_gallery.length > 1 && options.displayCounter){
            // append the counter div
            var counter = '';
            if(options.counterType == 'skip'){
                for(var i = 0, len = current_gallery.length; i < len; ++i){
                    counter += '<a href="javascript:Shadowbox.change(' + i + ');"';
                    if(i == current){
                        counter += ' class="shadowbox_counter_current"';
                    }
                    counter += '>' + (i + 1) + '</a>';
                }
            }else{
                counter = (current + 1) + ' of ' + current_gallery.length;
            }
            appendHTML(tool_i, String.format(options.skin.counter, counter));
        }
    };

    /**
     * Hides the title and tool bars.
     *
     * @param   {Function}  callback        A function to call on finish
     * @return  void
     * @private
     */
    var hideBars = function(callback){
        var title_m = getComputedHeight(SL.get('shadowbox_title'));
        var tool_m = 0 - getComputedHeight(SL.get('shadowbox_toolbar'));
        var title_i = SL.get('shadowbox_title_inner');
        var tool_i = SL.get('shadowbox_toolbar_inner');

        if(options.animate && callback){
            // animate the transition
            SL.animate(title_i, {
                marginTop: { to: title_m }
            }, 0.2);
            SL.animate(tool_i, {
                marginTop: { to: tool_m }
            }, 0.2, callback);
        }else{
            SL.setStyle(title_i, 'marginTop', title_m + 'px');
            SL.setStyle(tool_i, 'marginTop', tool_m + 'px');
        }
    };

    /**
     * Shows the title and tool bars.
     *
     * @param   {Function}  callback        A callback function to execute after
     *                                      the animation completes
     * @return  void
     * @private
     */
    var showBars = function(callback){
        var title_i = SL.get('shadowbox_title_inner');
        if(options.animate){
            if(title_i.innerHTML != ''){
                SL.animate(title_i, { marginTop: { to: 0 } }, 0.35);
            }
            SL.animate(SL.get('shadowbox_toolbar_inner'), {
                marginTop: { to: 0 }
            }, 0.35, callback);
        }else{
            if(title_i.innerHTML != ''){
                SL.setStyle(title_i, 'margin-top', '0px');
            }
            SL.setStyle(SL.get('shadowbox_toolbar_inner'), 'margin-top', '0px');
            callback();
        }
    };

    /**
     * Resets the class drag variable.
     *
     * @return  void
     * @private
     */
    var resetDrag = function(){
        drag = {
            x:          0,
            y:          0,
            start_x:    null,
            start_y:    null
        };
    };

    /**
     * Toggles the drag function on and off.
     *
     * @param   {Boolean}   on      True to toggle on, false to toggle off
     * @return  void
     * @private
     */
    var toggleDrag = function(on){
        if(on){
            resetDrag();
            // add drag layer to prevent browser dragging of actual image
            var styles = [
                'position:absolute',
                'cursor:' + (isGecko ? '-moz-grab' : 'move')
            ];
            // make drag layer transparent
            styles.push(isIE ? 'background-color:#fff;filter:alpha(opacity=0)' : 'background-color:transparent');
            appendHTML('shadowbox_body_inner', '<div id="shadowbox_drag_layer" style="' + styles.join(';') + '"></div>');
            SL.addEvent(SL.get('shadowbox_drag_layer'), 'mousedown', listenDrag);
        }else{
            var d = SL.get('shadowbox_drag_layer');
            if(d){
                SL.removeEvent(d, 'mousedown', listenDrag);
                SL.remove(d);
            }
        }
    };

    /**
     * Sets up a drag listener on the document. Called when the mouse button is
     * pressed (mousedown).
     *
     * @param   {mixed}     ev      The mousedown event
     * @return  void
     * @private
     */
    var listenDrag = function(ev){
        drag.start_x = ev.clientX;
        drag.start_y = ev.clientY;
        draggable = SL.get('shadowbox_content');
        SL.addEvent(document, 'mousemove', positionDrag);
        SL.addEvent(document, 'mouseup', unlistenDrag);
        if(isGecko) SL.setStyle(SL.get('shadowbox_drag_layer'), 'cursor', '-moz-grabbing');
    };

    /**
     * Removes the drag listener. Called when the mouse button is released
     * (mouseup).
     *
     * @return  void
     * @private
     */
    var unlistenDrag = function(){
        SL.removeEvent(document, 'mousemove', positionDrag);
        SL.removeEvent(document, 'mouseup', unlistenDrag); // clean up
        if(isGecko) SL.setStyle(SL.get('shadowbox_drag_layer'), 'cursor', '-moz-grab');
    };

    /**
     * Positions an oversized image on drag.
     *
     * @param   {mixed}     ev      The drag event
     * @return  void
     * @private
     */
    var positionDrag = function(ev){
        var move_y = ev.clientY - drag.start_y;
        drag.start_y = drag.start_y + move_y;
        drag.y = Math.max(Math.min(0, drag.y + move_y), current_height - optimal_height); // y boundaries
        SL.setStyle(draggable, 'top', drag.y + 'px');
        var move_x = ev.clientX - drag.start_x;
        drag.start_x = drag.start_x + move_x;
        drag.x = Math.max(Math.min(0, drag.x + move_x), current_width - optimal_width); // x boundaries
        SL.setStyle(draggable, 'left', drag.x + 'px');
    };

    /**
     * Loads the Shadowbox with the current piece.
     *
     * @return  void
     * @private
     */
    var loadContent = function(){
        var obj = current_gallery[current];
        if(!obj) return; // invalid

        buildBars();

        switch(obj.type){
            case 'img':
                // preload the image
                preloader = new Image();
                preloader.onload = function(){
                    // images default to image height and width
                    var h = obj.height ? parseInt(obj.height, 10) : preloader.height;
                    var w = obj.width ? parseInt(obj.width, 10) : preloader.width;
                    resizeContent(h, w, function(dims){
                        showBars(function(){
                            setContent({
                                tag:    'img',
                                height: dims.i_height,
                                width:  dims.i_width,
                                src:    obj.content,
                                style:  'position:absolute'
                            });
                            if(dims.enableDrag && options.handleLgImages == 'drag'){
                                // listen for drag
                                toggleDrag(true);
                                SL.setStyle(SL.get('shadowbox_drag_layer'), {
                                    height:     dims.i_height + 'px',
                                    width:      dims.i_width + 'px'
                                });
                            }
                            finishContent();
                        });
                    });

                    preloader.onload = function(){}; // clear onload for IE
                };
                preloader.src = obj.content;
            break;

            case 'swf':
            case 'flv':
            case 'qt':
            case 'wmp':
                var markup = Shadowbox.movieMarkup(obj);
                resizeContent(markup.height, markup.width, function(){
                    showBars(function(){
                        setContent(markup);
                        finishContent();
                    });
                });
            break;

            case 'iframe':
                // iframes default to full viewport height and width
                var h = obj.height ? parseInt(obj.height, 10) : SL.getViewportHeight();
                var w = obj.width ? parseInt(obj.width, 10) : SL.getViewportWidth();
                var content = {
                    tag:            'iframe',
                    name:           'shadowbox_content',
                    height:         '100%',
                    width:          '100%',
                    frameborder:    '0',
                    marginwidth:    '0',
                    marginheight:   '0',
                    scrolling:      'auto'
                };

                resizeContent(h, w, function(dims){
                    showBars(function(){
                        setContent(content);
                        var win = (isIE)
                            ? SL.get('shadowbox_content').contentWindow
                            : window.frames['shadowbox_content'];
                        win.location = obj.content;
                        finishContent();
                    });
                });
            break;

            case 'html':
            case 'inline':
                // HTML content defaults to full viewport height and width
                var h = obj.height ? parseInt(obj.height, 10) : SL.getViewportHeight();
                var w = obj.width ? parseInt(obj.width, 10) : SL.getViewportWidth();
                var content = {
                    tag:    'div',
                    cls:    'html', /* give special class to make scrollable */
                    html:   obj.content
                };
                resizeContent(h, w, function(){
                    showBars(function(){
                        setContent(content);
                        finishContent();
                    });
                });
            break;

            default:
                // should never happen
                throw 'Shadowbox cannot open content of type ' + obj.type;
        }

        // preload neighboring images
        if(current_gallery.length > 0){
            var next = current_gallery[current + 1];
            if(!next){
                next = current_gallery[0];
            }
            if(next.type == 'img'){
                var preload_next = new Image();
                preload_next.src = next.href;
            }

            var prev = current_gallery[current - 1];
            if(!prev){
                prev = current_gallery[current_gallery.length - 1];
            }
            if(prev.type == 'img'){
                var preload_prev = new Image();
                preload_prev.src = prev.href;
            }
        }
    };

    /**
     * Removes old content and sets the new content of the Shadowbox.
     *
     * @param   {Object}        obj     The content to set (appropriate to pass
     *                                  directly to Shadowbox.createHTML())
     * @return  {HTMLElement}           The newly appended element (or null if
     *                                  none is provided)
     * @private
     */
    var setContent = function(obj){
        var id = 'shadowbox_content';
        var content = SL.get(id);
        if(content){
            // remove old content first
            switch(content.tagName.toUpperCase()){
                case 'OBJECT':
                    // if we're in a gallery (i.e. changing and there's a new
                    // object) we want the LAST link object
                    var link = current_gallery[(obj ? current - 1 : current)];
                    if(link.type == 'wmp' && isIE){
                        try{
                            shadowbox_content.controls.stop(); // stop the movie
                            shadowbox_content.URL = 'non-existent.wmv'; // force player refresh
                            window.shadowbox_content = function(){}; // remove from window
                        }catch(e){}
                    }else if(link.type == 'qt' && isSafari){
                        try{
                            document.shadowbox_content.Stop(); // stop QT movie
                        }catch(e){}
                        // stop QT audio stream for movies that have not yet loaded
                        content.innerHTML = '';
                        // console.log(document.shadowbox_content);
                    }
                    setTimeout(function(){ // using setTimeout prevents browser crashes with WMP
                        SL.remove(content);
                    }, 10);
                break;
                case 'IFRAME':
                    SL.remove(content);
                    if(isGecko) delete window.frames[id]; // needed for Firefox
                break;
                default:
                    SL.remove(content);
            }
        }
        if(obj){
            if(!obj.id) obj.id = id;
            return appendHTML('shadowbox_body_inner', Shadowbox.createHTML(obj));
        }
        return null;
    };

    /**
     * This function is used as the callback after the Shadowbox has been
     * positioned, resized, and loaded with content.
     *
     * @return  void
     * @private
     */
    var finishContent = function(){
        var obj = current_gallery[current];
        if(!obj) return; // invalid
        hideLoading(function(){
            listenKeyboard(true);
            // fire onFinish handler
            if(options.onFinish && typeof options.onFinish == 'function'){
                options.onFinish(obj);
            }
        });
    };

    /**
     * Resizes and positions the content box using the given height and width.
     * If the callback parameter is missing, the transition will not be
     * animated. If the callback parameter is present, it will be passed the
     * new calculated dimensions object as its first parameter. Note: the height
     * and width here should represent the optimal height and width of the box.
     *
     * @param   {Function}  callback    A callback function to use when the
     *                                  resize completes
     * @return  void
     * @private
     */
    var resizeContent = function(height, width, callback){
        // update optimal height and width
        optimal_height = height;
        optimal_width = width;
        var resizable = RE.resize.test(current_gallery[current].type);
        var dims = getDimensions(optimal_height, optimal_width, resizable);
        if(callback){
            var cb = function(){ callback(dims); };
            switch(options.animSequence){
                case 'hw':
                    adjustHeight(dims.height, dims.top, true, function(){
                        adjustWidth(dims.width, true, cb);
                    });
                break;
                case 'wh':
                    adjustWidth(dims.width, true, function(){
                        adjustHeight(dims.height, dims.top, true, cb);
                    });
                break;
                default: // sync
                    adjustWidth(dims.width, true);
                    adjustHeight(dims.height, dims.top, true, cb);
            }
        }else{ // window resize
            adjustWidth(dims.width, false);
            adjustHeight(dims.height, dims.top, false);
            // resize content images & flash in 'resize' mode
            if(options.handleLgImages == 'resize' && resizable){
                var content = SL.get('shadowbox_content');
                if(content){ // may be animating, not present
                    content.height = dims.i_height;
                    content.width = dims.i_width;
                }
            }
        }
    };

    /**
     * Calculates the dimensions for Shadowbox, taking into account the borders,
     * margins, and surrounding elements of the shadowbox_body. If the image
     * is still to large for Shadowbox, and options.handleLgImages is 'resize',
     * the resized dimensions will be returned (preserving the original aspect
     * ratio). Otherwise, the originally calculated dimensions will be returned.
     * The returned object will have the following properties:
     *
     * - height: The height to use for shadowbox_body_inner
     * - width: The width to use for shadowbox
     * - i_height: The height to use for resizable content
     * - i_width: The width to use for resizable content
     * - top: The top to use for shadowbox
     * - enableDrag: True if dragging should be enabled (image is oversized)
     *
     * @param   {Number}    o_height    The optimal height
     * @param   {Number}    o_width     The optimal width
     * @param   {Boolean}   resizable   True if the content is able to be
     *                                  resized. Defaults to false.
     * @return  {Object}                The resize dimensions (see above)
     * @private
     */
    var getDimensions = function(o_height, o_width, resizable){
        if(typeof resizable == 'undefined') resizable = false;

        var height = o_height = parseInt(o_height);
        var width = o_width = parseInt(o_width);
        var shadowbox_b = SL.get('shadowbox_body');

        // calculate the max height
        var view_height = SL.getViewportHeight();
        var extra_height = parseInt(SL.getStyle(shadowbox_b, 'border-top-width'), 10)
            + parseInt(SL.getStyle(shadowbox_b, 'border-bottom-width'), 10)
            + parseInt(SL.getStyle(shadowbox_b, 'margin-top'), 10)
            + parseInt(SL.getStyle(shadowbox_b, 'margin-bottom'), 10)
            + getComputedHeight(SL.get('shadowbox_title'))
            + getComputedHeight(SL.get('shadowbox_toolbar'))
            + (2 * options.viewportPadding);
        if((height + extra_height) >= view_height){
            height = view_height - extra_height;
        }

        // calculate the max width
        var view_width = SL.getViewportWidth();
        var extra_body_width = parseInt(SL.getStyle(shadowbox_b, 'border-left-width'), 10)
            + parseInt(SL.getStyle(shadowbox_b, 'border-right-width'), 10)
            + parseInt(SL.getStyle(shadowbox_b, 'margin-left'), 10)
            + parseInt(SL.getStyle(shadowbox_b, 'margin-right'), 10);
        var extra_width = extra_body_width + (2 * options.viewportPadding);
        if((width + extra_width) >= view_width){
            width = view_width - extra_width;
        }

        // handle oversized images & flash
        var enableDrag = false;
        var i_height = o_height;
        var i_width = o_width;
        var handle = options.handleLgImages;
        if(resizable && (handle == 'resize' || handle == 'drag')){
            var change_h = (o_height - height) / o_height;
            var change_w = (o_width - width) / o_width;
            if(handle == 'resize'){
                if(change_h > change_w){
                    width = Math.round((o_width / o_height) * height);
                }else if(change_w > change_h){
                    height = Math.round((o_height / o_width) * width);
                }
                // adjust image height or width accordingly
                i_width = width;
                i_height = height;
            }else{
                // drag on oversized images only
                var link = current_gallery[current];
                if(link) enableDrag = link.type == 'img' && (change_h > 0 || change_w > 0);
            }
        }

        return {
            height: height,
            width: width + extra_body_width,
            i_height: i_height,
            i_width: i_width,
            top: ((view_height - (height + extra_height)) / 2) + options.viewportPadding,
            enableDrag: enableDrag
        };
    };

    /**
     * Centers Shadowbox vertically in the viewport. Needs to be called on
     * scroll in IE6 because it does not support fixed positioning.
     *
     * @return  void
     * @private
     */
    var centerVertically = function(){
        var shadowbox = SL.get('shadowbox');
        var scroll = document.documentElement.scrollTop;
        var s_top = scroll + Math.round((SL.getViewportHeight() - (shadowbox.offsetHeight || 0)) / 2);
        SL.setStyle(shadowbox, 'top', s_top + 'px');
    };

    /**
     * Adjusts the height of shadowbox_body_inner and centers Shadowbox
     * vertically in the viewport.
     *
     * @param   {Number}    height      The height of shadowbox_body_inner
     * @param   {Number}    top         The top of the Shadowbox
     * @param   {Boolean}   animate     True to animate the transition
     * @param   {Function}  callback    A callback to use when the animation completes
     * @return  void
     * @private
     */
    var adjustHeight = function(height, top, animate, callback){
        height = parseInt(height);

        // update current_height
        current_height = height;

        // adjust the height
        var sbi = SL.get('shadowbox_body_inner');
        if(animate && options.animate){
            SL.animate(sbi, {
                height: { to: height }
            }, options.resizeDuration, callback);
        }else{
            SL.setStyle(sbi, 'height', height + 'px');
            if(typeof callback == 'function') callback();
        }

        // manually adjust the top because we're using fixed positioning in IE6
        if(absolute_pos){
            // listen for scroll so we can adjust
            centerVertically();
            SL.addEvent(window, 'scroll', centerVertically);

            // add scroll to top
            top += document.documentElement.scrollTop;
        }

        // adjust the top
        var shadowbox = SL.get('shadowbox');
        if(animate && options.animate){
            SL.animate(shadowbox, {
                top: { to: top }
            }, options.resizeDuration);
        }else{
            SL.setStyle(shadowbox, 'top', top + 'px');
        }
    };

    /**
     * Adjusts the width of shadowbox.
     *
     * @param   {Number}    width       The width to use
     * @param   {Boolean}   animate     True to animate the transition
     * @param   {Function}  callback    A callback to use when the animation completes
     * @return  void
     * @private
     */
    var adjustWidth = function(width, animate, callback){
        width = parseInt(width);

        // update current_width
        current_width = width;

        var shadowbox = SL.get('shadowbox');
        if(animate && options.animate){
            SL.animate(shadowbox, {
                width: { to: width }
            }, options.resizeDuration, callback);
        }else{
            SL.setStyle(shadowbox, 'width', width + 'px');
            if(typeof callback == 'function') callback();
        }
    };

    /**
     * Sets up a listener on the document for keystrokes.
     *
     * @param   {Boolean}   on      True to enable the listner, false to turn
     *                              it off
     * @return  void
     * @private
     */
    var listenKeyboard = function(on){
        if(!options.enableKeys) return;
        if(on){
            document.onkeydown = handleKey;
        }else{
            document.onkeydown = '';
        }
    };

    /**
     * Asserts the given key or code is present in the array of valid keys.
     *
     * @param   {Array}     valid       An array of valid keys and codes
     * @param   {String}    key         The character that was pressed
     * @param   {Number}    code        The key code that was pressed
     * @return  {Boolean}               True if the key is valid
     * @private
     */
    var assertKey = function(valid, key, code){
        return (valid.indexOf(key) != -1 || valid.indexOf(code) != -1);
    };

    /**
     * A listener function that will act on a key pressed.
     *
     * @param   {Event}     e       The event object
     * @return  void
     * @private
     */
    var handleKey = function(e){
        var code = e ? e.which : event.keyCode;
        var key = String.fromCharCode(code).toLowerCase();
        if(assertKey(options.keysClose, key, code)){
            Shadowbox.close();
        }else if(assertKey(options.keysPrev, key, code)){
            Shadowbox.previous();
        }else if(assertKey(options.keysNext, key, code)){
            Shadowbox.next();
        }
    };

    /**
     * Shows and hides elements that are troublesome for modal overlays.
     *
     * @param   {Boolean}   on      True to show the elements, false otherwise
     * @return  void
     * @private
     */
    var toggleTroubleElements = function(on){
        var vis = (on ? 'visible' : 'hidden');
        var selects = document.getElementsByTagName('select');
        for(i = 0, len = selects.length; i < len; ++i){
            selects[i].style.visibility = vis;
        }
        var objects = document.getElementsByTagName('object');
        for(i = 0, len = objects.length; i < len; ++i){
            objects[i].style.visibility = vis;
        }
        var embeds = document.getElementsByTagName('embed');
        for(i = 0, len = embeds.length; i < len; ++i){
            embeds[i].style.visibility = vis;
        }
    };

    /**
     * Fills the Shadowbox with the loading skin.
     *
     * @return  void
     * @private
     */
    var showLoading = function(){
        var loading = SL.get('shadowbox_loading');
        overwriteHTML(loading, String.format(options.skin.loading,
            options.assetURL + options.loadingImage,
            options.text.loading,
            options.text.cancel));
        loading.style.visibility = 'visible';
    };

    /**
     * Hides the Shadowbox loading skin.
     *
     * @param   {Function}  callback        The callback function to call after
     *                                      hiding the loading skin
     * @return  void
     * @private
     */
    var hideLoading = function(callback){
        var t = current_gallery[current].type;
        var anim = (t == 'img' || t == 'html'); // fade on images & html
        var loading = SL.get('shadowbox_loading');
        if(anim){
            fadeOut(loading, 0.35, callback);
        }else{
            loading.style.visibility = 'hidden';
            callback();
        }
    };

    /**
     * Sets the size of the overlay to the size of the document.
     *
     * @return  void
     * @private
     */
    var resizeOverlay = function(){
        var overlay = SL.get('shadowbox_overlay');
        SL.setStyle(overlay, {
            height: '100%',
            width: '100%'
        });
        SL.setStyle(overlay, 'height', SL.getDocumentHeight() + 'px');
        if(!isSafari3){
            // Safari3 includes vertical scrollbar in SL.getDocumentWidth()!
            // Leave overlay width at 100% for now...
            SL.setStyle(overlay, 'width', SL.getDocumentWidth() + 'px');
        }
    };

    /**
     * Used to determine if the pre-made overlay background image is needed
     * instead of using the trasparent background overlay. A pre-made background
     * image is used for all but image pieces in FF Mac because it has problems
     * displaying correctly if the background layer is not 100% opaque. When
     * displaying a gallery, if any piece in the gallery meets these criteria,
     * the pre-made background image will be used.
     *
     * @return  {Boolean}       Whether or not an overlay image is needed
     * @private
     */
    var checkOverlayImgNeeded = function(){
        if(!(isGecko && isMac)) return false;
        for(var i = 0, len = current_gallery.length; i < len; ++i){
            if(!RE.overlay.exec(current_gallery[i].type)) return true;
        }
        return false;
    };

    /**
     * Activates (or deactivates) the Shadowbox overlay. If a callback function
     * is provided, we know we're activating. Otherwise, deactivate the overlay.
     *
     * @param   {Function}  callback    A callback to call after activation
     * @return  void
     * @private
     */
    var toggleOverlay = function(callback){
        var overlay = SL.get('shadowbox_overlay');
        if(overlay_img_needed == null){
            overlay_img_needed = checkOverlayImgNeeded();
        }

        if(callback){
            resizeOverlay(); // size the overlay before showing
            if(overlay_img_needed){
                SL.setStyle(overlay, {
                    visibility:         'visible',
                    backgroundColor:    'transparent',
                    backgroundImage:    'url(' + options.assetURL + options.overlayBgImage + ')',
                    backgroundRepeat:   'repeat',
                    opacity:            1
                });
                callback();
            }else{
                SL.setStyle(overlay, {
                    visibility:         'visible',
                    backgroundColor:    options.overlayColor,
                    backgroundImage:    'none'
                });
                fadeIn(overlay, options.overlayOpacity, options.fadeDuration,
                    callback);
            }
        }else{
            if(overlay_img_needed){
                SL.setStyle(overlay, 'visibility', 'hidden');
            }else{
                fadeOut(overlay, options.fadeDuration);
            }

            // reset for next time
            overlay_img_needed = null;
        }
    };

    /**
     * Initializes the Shadowbox environment. Appends Shadowbox' HTML to the
     * document and sets up listeners on the window and overlay element.
     *
     * @param   {Object}    opts    The default options to use
     * @return  void
     * @public
     * @static
     */
    Shadowbox.init = function(opts){
        if(initialized) return; // don't initialize twice
        options = apply(options, opts || {});

        // add markup
        appendHTML(document.body, options.skin.main);

        // compile file type regular expressions here for speed
        //RE.img = new RegExp('\.(' + options.ext.img.join('|') + ')\s*$', 'i');
        // *** by Stelios -> RegExp change to support the phpg images that do not match at the end (have get params!) 
        RE.img = new RegExp('\.(' + options.ext.img.join('|') + ')\s*', 'i');
        RE.qt = new RegExp('\.(' + options.ext.qt.join('|') + ')\s*$', 'i');
        RE.wmp = new RegExp('\.(' + options.ext.wmp.join('|') + ')\s*$', 'i');
        RE.qtwmp = new RegExp('\.(' + options.ext.qtwmp.join('|') + ')\s*$', 'i');
        RE.iframe = new RegExp('\.(' + options.ext.iframe.join('|') + ')\s*$', 'i');

        // handle window resize events
        var id = null;
        var resize = function(){
            clearInterval(id);
            id = null;
            resizeOverlay();
            resizeContent(optimal_height, optimal_width);
        };
        SL.addEvent(window, 'resize', function(){
            if(activated){
                // use event buffering to prevent jerky window resizing
                if(id){
                    clearInterval(id);
                    id = null;
                }
                if(!id) id = setInterval(resize, 50);
            }
        });

        if(options.listenOverlay){
            // add a listener to the overlay
            SL.addEvent(SL.get('shadowbox_overlay'), 'click', Shadowbox.close);
        }

        // adjust some positioning if needed
        if(absolute_pos){
            // give the container absolute positioning
            SL.setStyle(SL.get('shadowbox_container'), 'position', 'absolute');
            // give shadowbox_body "layout"...whatever that is
            SL.setStyle('shadowbox_body', 'zoom', 1);
            // need to listen to the container element because it covers the top
            // half of the page
            SL.addEvent(SL.get('shadowbox_container'), 'click', function(e){
                var target = SL.getTarget(e);
                if(target.id && target.id == 'shadowbox_container') Shadowbox.close();
            });
        }

        // skip setup, will need to be done manually later
        if(!options.skipSetup) Shadowbox.setup();
        initialized = true;
    };

    /**
     * Sets up listeners on the given links that will trigger Shadowbox. If no
     * links are given, this method will set up every anchor element on the page
     * with the appropriate rel attribute. Note: Because AREA elements do not
     * support the rel attribute, they must be explicitly passed to this method.
     *
     * @param   {Array}     links       An array (or array-like) list of anchor
     *                                  and/or area elements to set up
     * @param   {Object}    opts        Some options to use for the given links
     * @return  void
     * @public
     * @static
     */
    Shadowbox.setup = function(links, opts){
        // get links if none specified
        if(!links){
            var links = [];
            var a = document.getElementsByTagName('a'), rel;
            for(var i = 0, len = a.length; i < len; ++i){
                rel = a[i].getAttribute('rel');
                if(rel && RE.rel.test(rel)) links[links.length] = a[i];
            }
        }else if(!links.length){
            links = [links]; // one link
        }

        var link;
        for(var i = 0, len = links.length; i < len; ++i){
            link = links[i];
            if(typeof link.shadowboxCacheKey == 'undefined'){
                // assign cache key expando
                // use integer primitive to avoid memory leak in IE
                link.shadowboxCacheKey = cache.length;
                SL.addEvent(link, 'click', handleClick); // add listener
            }
            cache[link.shadowboxCacheKey] = this.buildCacheObj(link, opts);
        }
    };

    /**
     * Builds an object from the original link element data to store in cache.
     * These objects contain (most of) the following keys:
     *
     * - el: the link element
     * - title: the linked file title
     * - type: the linked file type
     * - content: the linked file's URL
     * - gallery: the gallery the file belongs to (optional)
     * - height: the height of the linked file (only necessary for movies)
     * - width: the width of the linked file (only necessary for movies)
     * - options: custom options to use (optional)
     *
     * @param   {HTMLElement}   link    The link element to process
     * @return  {Object}                An object representing the link
     * @public
     * @static
     */
    Shadowbox.buildCacheObj = function(link, opts){
        var href = link.href; // don't use getAttribute() here
        var o = {
            el:         link,
            title:      link.getAttribute('title'),
            type:       getPlayerType(href),
            options:    apply({}, opts || {}), // break the reference
            content:    href
        };

        // remove link-level options from top-level options
        var opt, l_opts = ['title', 'type', 'height', 'width', 'gallery'];
        for(var i = 0, len = l_opts.length; i < len; ++i){
            opt = l_opts[i];
            if(typeof o.options[opt] != 'undefined'){
                o[opt] = o.options[opt];
                delete o.options[opt];
            }
        }

        // HTML options always trump JavaScript options, so do these last
        var rel = link.getAttribute('rel');
        if(rel){
            // extract gallery name from shadowbox[name] format
            var match = rel.match(RE.gallery);
            if(match) o.gallery = escape(match[2]);

            // other parameters
            var params = rel.split(';');
            for(var i = 0, len = params.length; i < len; ++i){
                match = params[i].match(RE.param);
                if(match){
                    if(match[1] == 'options'){
                        eval('o.options = apply(o.options, ' + match[2] + ')');
                    }else{
                        o[match[1]] = match[2];
                    }
                }
            }
        }

        return o;
    };

    /**
     * Applies the given set of options to those currently in use. Note: Options
     * will be reset on Shadowbox.open() so this function is only useful after
     * it has already been called (while Shadowbox is open).
     *
     * @param   {Object}    opts        The options to apply
     * @return  void
     * @public
     * @static
     */
    Shadowbox.applyOptions = function(opts){
        if(opts){
            // use apply here to break references
            default_options = apply({}, options); // store default options
            options = apply(options, opts); // apply options
        }
    };

    /**
     * Reverts Shadowbox' options to the last default set in use before
     * Shadowbox.applyOptions() was called.
     *
     * @return  void
     * @public
     * @static
     */
    Shadowbox.revertOptions = function(){
        if(default_options){
            options = default_options; // revert to default options
            default_options = null; // erase for next time
        }
    };

    /**
     * Opens the given object in Shadowbox. This object may be either an
     * anchor/area element, or an object similar to the one created by
     * Shadowbox.buildCacheObj().
     *
     * @param   {mixed}     obj         The object or link element that defines
     *                                  what to display
     * @return  void
     * @public
     * @static
     */
    Shadowbox.open = function(obj, opts){
        if(activated) return; // already open
        activated = true;

        // is it a link?
        if(isLink(obj)){
            if(typeof obj.shadowboxCacheKey == 'undefined' || typeof cache[obj.shadowboxCacheKey] == 'undefined'){
                // link element that hasn't been set up before
                // create an object on-the-fly
                obj = this.buildCacheObj(obj, opts);
            }else{
                // link element that has been set up before, get from cache
                obj = cache[obj.shadowboxCacheKey];
            }
        }

        this.revertOptions();
        if(obj.options || opts){
            // use apply here to break references
            this.applyOptions(apply(apply({}, obj.options || {}), opts || {}));
        }

        // update current & current_gallery
        setupGallery(obj);

        // anything to display?
        if(current_gallery.length){
            // fire onOpen hook
            if(options.onOpen && typeof options.onOpen == 'function'){
                options.onOpen(obj);
            }

            // display:block here helps with correct dimension calculations
            SL.setStyle(SL.get('shadowbox'), 'display', 'block');

            toggleTroubleElements(false);
            var dims = getDimensions(options.initialHeight, options.initialWidth);
            adjustHeight(dims.height, dims.top);
            adjustWidth(dims.width);
            hideBars(false);

            // show the overlay and load the content
            toggleOverlay(function(){
                SL.setStyle(SL.get('shadowbox'), 'visibility', 'visible');
                showLoading();
                loadContent();
            });
        }
    };

    /**
     * Jumps to the piece in the current gallery with index num.
     *
     * @param   {Number}    num     The gallery index to view
     * @return  void
     * @public
     * @static
     */
    Shadowbox.change = function(num){
        if(!current_gallery) return; // no current gallery
        if(!current_gallery[num]){ // index does not exist
            if(!options.continuous){
                return;
            }else{
                num = (num < 0) ? (current_gallery.length - 1) : 0; // loop
            }
        }

        // update current
        current = num;

        // stop listening for drag
        toggleDrag(false);
        // empty the content
        setContent(null);
        // turn this back on when done
        listenKeyboard(false);

        // fire onChange handler
        if(options.onChange && typeof options.onChange == 'function'){
            options.onChange(current_gallery[current]);
        }

        showLoading();
        hideBars(loadContent);
    };

    /**
     * Jumps to the next piece in the gallery.
     *
     * @return  {Boolean}       True if the gallery changed to next item, false
     *                          otherwise
     * @public
     * @static
     */
    Shadowbox.next = function(){
        return this.change(current + 1);
    };

    /**
     * Jumps to the previous piece in the gallery.
     *
     * @return  {Boolean}       True if the gallery changed to previous item,
     *                          false otherwise
     * @public
     * @static
     */
    Shadowbox.previous = function(){
        return this.change(current - 1);
    };

    /**
     * Deactivates Shadowbox.
     *
     * @return  void
     * @public
     * @static
     */
    Shadowbox.close = function(){
        if(!activated) return; // already closed

        // stop listening for keys
        listenKeyboard(false);
        // hide
        SL.setStyle(SL.get('shadowbox'), {
            display: 'none',
            visibility: 'hidden'
        });
        // stop listening for scroll on IE
        if(absolute_pos) SL.removeEvent(window, 'scroll', centerVertically);
        // stop listening for drag
        toggleDrag(false);
        // empty the content
        setContent(null);
        // prevent old image requests from loading
        if(preloader){
            preloader.onload = function(){};
            preloader = null;
        }
        // hide the overlay
        toggleOverlay(false);
        // turn on trouble elements
        toggleTroubleElements(true);

        // fire onClose handler
        if(options.onClose && typeof options.onClose == 'function'){
            options.onClose(current_gallery[current]);
        }

        activated = false;
    };

    /**
     * Clears Shadowbox' cache and removes listeners and expandos from all
     * cached link elements. May be used to completely reset Shadowbox in case
     * links on a page change.
     *
     * @return  void
     * @public
     * @static
     */
    Shadowbox.clearCache = function(){
        for(var i = 0, len = cache.length; i < len; ++i){
            if(cache[i].el){
                SL.removeEvent(cache[i].el, 'click', handleClick);
                delete cache[i].shadowboxCacheKey;
            }
        }
        cache = [];
    };

    /**
     * Generates the markup necessary to embed the movie file with the given
     * link element. This markup will be browser-specific. Useful for generating
     * the media test suite.
     *
     * @param   {HTMLElement}   link        The link to the media file
     * @return  {Object}                    The proper markup to use (see above)
     * @public
     * @static
     */
    Shadowbox.movieMarkup = function(obj){
        // movies default to 300x300 pixels
        var h = obj.height ? parseInt(obj.height, 10) : 300;
        var w = obj.width ? parseInt(obj.width, 10) : 300;

        var autoplay = options.autoplayMovies;
        var controls = options.showMovieControls;
        if(obj.options){
            if(obj.options.autoplayMovies != null){
                autoplay = obj.options.autoplayMovies;
            }
            if(obj.options.showMovieControls != null){
                controls = obj.options.showMovieControls;
            }
        }

        var markup = {
            tag:    'object',
            name:   'shadowbox_content'
        };

        switch(obj.type){
            case 'swf':
                var dims = getDimensions(h, w, true);
                h = dims.height;
                w = dims.width;
                markup.type = 'application/x-shockwave-flash';
                markup.data = obj.content;
                markup.children = [
                    { tag: 'param', name: 'movie', value: obj.content }
                ];
            break;
            case 'flv':
                autoplay = autoplay ? 'true' : 'false';
                var showicons = 'false';
                var a = h/w; // aspect ratio
                if(controls){
                    showicons = 'true';
                    h += 20; // height of JW FLV player controller
                }
                var dims = getDimensions(h, h/a, true); // resize
                h = dims.height;
                w = (h-(controls?20:0))/a; // maintain aspect ratio
                var flashvars = [
                    'file=' + obj.content,
                    'height=' + h,
                    'width=' + w,
                    'autostart=' + autoplay,
                    'displayheight=' + (h - (controls?20:0)),
                    'showicons=' + showicons,
                    'backcolor=0x000000&amp;frontcolor=0xCCCCCC&amp;lightcolor=0x557722'
                ];
                markup.type = 'application/x-shockwave-flash';
                markup.data = options.assetURL + options.flvPlayer;
                markup.children = [
                    { tag: 'param', name: 'movie', value: options.assetURL + options.flvPlayer },
                    { tag: 'param', name: 'flashvars', value: flashvars.join('&amp;') },
                    { tag: 'param', name: 'allowfullscreen', value: 'true' }
                ];
            break;
            case 'qt':
                autoplay = autoplay ? 'true' : 'false';
                if(controls){
                    controls = 'true';
                    h += 16; // height of QuickTime controller
                }else{
                    controls = 'false';
                }
                markup.children = [
                    { tag: 'param', name: 'src', value: obj.content },
                    { tag: 'param', name: 'scale', value: 'aspect' },
                    { tag: 'param', name: 'controller', value: controls },
                    { tag: 'param', name: 'autoplay', value: autoplay }
                ];
                if(isIE){
                    markup.classid = 'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B';
                    markup.codebase = 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0';
                }else{
                    markup.type = 'video/quicktime';
                    markup.data = obj.content;
                }
            break;
            case 'wmp':
                autoplay = autoplay ? 1 : 0;
                markup.children = [
                    { tag: 'param', name: 'autostart', value: autoplay }
                ];
                if(isIE){
                    if(controls){
                        controls = 'full';
                        h += 70; // height of WMP controller in IE
                    }else{
                        controls = 'none';
                    }
                    // markup.type = 'application/x-oleobject';
                    markup.classid = 'clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6';
                    markup.children[markup.children.length] = { tag: 'param', name: 'url', value: obj.content };
                    markup.children[markup.children.length] = { tag: 'param', name: 'uimode', value: controls };
                }else{
                    if(controls){
                        controls = 1;
                        h += 45; // height of WMP controller in non-IE
                    }else{
                        controls = 0;
                    }
                    markup.type = 'video/x-ms-wmv';
                    markup.data = obj.content;
                    markup.children[markup.children.length] = { tag: 'param', name: 'showcontrols', value: controls };
                }
            break;
        }

        markup.height = h; // new height includes controller
        markup.width = w;

        return markup;
    };

    /**
     * Creates an HTML string from an object representing HTML elements. Based
     * on Ext.DomHelper's createHtml.
     *
     * @param   {Object}    obj     The HTML definition object
     * @return  {String}            An HTML string
     * @public
     * @static
     */
    Shadowbox.createHTML = function(obj){
        var html = '<' + obj.tag;
        for(var attr in obj){
            if(attr == 'tag' || attr == 'html' || attr == 'children') continue;
            if(attr == 'cls'){
                html += ' class="' + obj['cls'] + '"';
            }else{
                html += ' ' + attr + '="' + obj[attr] + '"';
            }
        }
        if(RE.empty.test(obj.tag)){
            html += '/>\n';
        }else{
            html += '>\n';
            var cn = obj.children;
            if(cn){
                for(var i = 0, len = cn.length; i < len; ++i){
                    html += this.createHTML(cn[i]);
                }
            }
            if(obj.html) html += obj.html;
            html += '</' + obj.tag + '>\n';
        }
        return html;
    };

    /**
     * Gets an object that lists which plugins are supported by the client. The
     * keys of this object will be:
     *
     * - fla: Adobe Flash Player
     * - qt: QuickTime Player
     * - wmp: Windows Media Player
     * - f4m: Flip4Mac QuickTime Player
     *
     * @return  {Object}        The plugins object
     * @public
     * @static
     */
    Shadowbox.getPlugins = function(){
        return plugins;
    };

    /**
     * Gets the current options object in use.
     *
     * @return  {Object}        The options object
     * @public
     * @static
     */
    Shadowbox.getOptions = function(){
        return options;
    };

    /**
     * Gets the current gallery object.
     *
     * @return  {Object}        The current gallery item
     * @public
     * @static
     */
    Shadowbox.getCurrent = function(){
        return current_gallery[current];
    };

    /**
     * Gets the current version number of Shadowbox.
     *
     * @return  {String}        The current version
     * @public
     * @static
     */
    Shadowbox.getVersion = function(){
        return version;
    };

})();

/**
 * Finds the index of the given object in this array.
 *
 * @param   {mixed}     o   The object to search for
 * @return  {Number}        The index of the given object
 * @public
 */
Array.prototype.indexOf = Array.prototype.indexOf || function(o){
    for(var i = 0, len = this.length; i < len; ++i){
        if(this[i] == o) return i;
    }
    return -1;
};

/**
 * Formats a string with the given parameters. The string for format must have
 * placeholders that correspond to the numerical index of the arguments passed
 * in surrounded by curly braces (e.g. 'Some {0} string {1}').
 *
 * @param   {String}    format      The string to format
 * @param   ...                     The parameters to put inside the string
 * @return  {String}                The string with the specified parameters
 *                                  replaced
 * @public
 * @static
 */
String.format = String.format || function(format){
    var args = Array.prototype.slice.call(arguments, 1);
    return format.replace(/\{(\d+)\}/g, function(m, i){
        return args[i];
    });
};
                     

Ext.ux.TabCloseMenu = function(){
    var tabs, menu, ctxItem;
    this.init = function(tp){
        tabs = tp;
        tabs.on('contextmenu', onContextMenu);
    }

    function onContextMenu(ts, item, e){
        if(!menu){ // create context menu on first right click
            menu = new Ext.menu.Menu([{
                id: tabs.id + '-close',
                text: 'Close Hotel Tab',
                iconCls: 'closeMenuIcon',
                handler : function(){
                    tabs.remove(ctxItem);
                }
            },{
                id: tabs.id + '-close-others',
                text: 'Close Other Hotels Tabs',
                iconCls: 'closeOtherMenuIcon',
                handler : function(){
                    tabs.items.each(function(item){
                        if(item.closable && item != ctxItem){
                            tabs.remove(item);
                        }
                    });
                }
            }, {
                id: tabs.id + '-close-all',
                text: 'Close All Hotels Tabs',
                iconCls: 'closeAllMenuIcon',
                handler : function(){
                    tabs.items.each(function(item){
                        if(item.closable){
                            tabs.remove(item);
                        }
                    });
                }
            }
            ]);
        }
        ctxItem = item;
        var items = menu.items;
        items.get(tabs.id + '-close').setDisabled(!item.closable);
        var disableOthers = true;
        tabs.items.each(function(){
            if(this != item && this.closable){
                disableOthers = false;
                return false;
            }
        });
        items.get(tabs.id + '-close-others').setDisabled(disableOthers);
        items.get(tabs.id + '-close-all').setDisabled(disableOthers);
        menu.showAt(e.getPoint());
    }
};
/* global Ext */
/**
 * @version 4.0
 * ***********************************************************************************
 *
 * Ext.lib.Ajax enhancements:
 * - adds EventManager Support to Ext.lib.Ajax (if Ext.util.Observable is present in the stack)
 * - adds Synchronous Ajax Support ( options.async =false )
 * - Permits IE to Access Local File Systems using IE's older ActiveX interface via the forceActiveX property
 * - Pluggable Form encoder (encodeURIComponent is still the default encoder)
 * - Corrects the Content-Type Headers for posting JSON (application/json) and XML (text/xml)
 *   data payloads and sets only one value (per RFC)
 * - Adds fullStatus:{ isLocal, proxied, isOK, isError, isTimeout, isAbort, error, status, statusText} object
 *   to the existing Response Object.
 * - Adds standard HTTP Auth cache support to every request (XHR userId, password config options)
 * - options.method prevails over any method derived by the lib.Ajax stack (DELETE, PUT, HEAD etc).
 * - Adds named-Priority-Queuing for Ajax Requests
 * - adds Script=Tag support for foreign-domains (proxied:true) with configurable callbacks.
 * - Adds final features for $JIT support.
 *
 * - Adds Browser capabilities object reporting on presence of:
 *      Ext.capabilities.isEventSupported('resize'[, forElement]) to determine if the browser supports a specific event.
 *      SVG, Canvas, Flash, Cookies, XPath, Audio(HTML5), ChromeFrame (IE)
 *      if(Ext.capabilities.hasFlash){ ... }
 * - Adds Ext.overload supported for parameter-based overloading of Function and class methods.
 * - Adds Ext.clone functions for any datatype.
 * - Adds Array prototype features: first, last, clone, forEach, atRandom, include, flatten, compact, unique, filter, map
 * - Connection/response object members : getAllResponseHeaders, getResponseHeader are now functions.
 * - Adds Array.slice support for other browsers (Gecko already supports it)
 *    @example:  Array.slice( someArray, 2 )
 * - Adds Ext[isFunction, isObject, isDocument, isElement, isEvent]  methods.
 * - Adds multiPart Response handling (via onpart callbacks and/or parts Array of response Object)
 * - Adds parsed contentType to response objects
 * - Adds Xdomain request support for modern browsers
 *
 * ***********************************************************************************
 * @author Doug Hendricks. doug[always-At]theactivegroup.com 
 * @copyright 2007-2009, Active Group, Inc. All rights reserved.
 * ***********************************************************************************
 *
 * @license <a href="http://www.gnu.org/licenses/gpl.html">GPL 3.0</a>
 *
 * Commercial use is prohibited without a Developer License, see:
 * http://licensing.theactivegroup.com.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see < http://www.gnu.org/licenses/gpl.html>.
 *
 * Donations are welcomed: http://donate.theactivegroup.com
 *
 */
 
(function() {
    var A = Ext.lib.Ajax,
        defined = function(test){return typeof test != 'undefined';},
        emptyFn = Ext.emptyFn || function(){},
        OP = Object.prototype;
        
        
    Ext.lib.Ajax.Queue = function(config) {

        config = config ? (config.name ? config : { name : config }) : {};
        Ext.apply(this, config, {
            name : 'q-default',
            priority : 5,
            FIFO : true, // false implies LIFO
            callback : null, // optional callback when queue is emptied
            scope : null, // scope of callback
            suspended : false,
            progressive : false // if true, one queue item is dispatched per poll interval

            });
        this.requests = new Array();
        this.pending = false;
        // assert/resolve to 0-9
        this.priority = this.priority > 9 ? 9 : (this.priority < 0 ? 0 : this.priority);
    };

    Ext.extend(Ext.lib.Ajax.Queue, Object, {
       /**
        * Adds Ext.lib.Ajax.request arguments to queue
        * @param {Array} request An array of request method arguments.
        *
        */
        add : function(req) {

            var permit = A.events ? A.fireEvent('beforequeue', this, req) : true;
            if (permit !== false) {
                this.requests.push(req);
                this.pending = true;
                A.pendingRequests++;
                this.manager && this.manager.start();
            }
        },

       /**
        * @property {Boolean} suspended Indicate the suspense state of the queue.
        */
        suspended : false,
        /**
         * @property {Object} activeRequest A reference to current/last active request.
         */
        activeRequest : null,

       /**
        * Selects the next item on the queue stack
        * @param {Boolean} peek If true, the queue item is returned but not removed from the stack.
        * @ default false
        */
        next : function(peek) {
            var req = peek ?
                this.requests[this.FIFO ? 'first' : 'last']()
               :this.requests[this.FIFO ? 'shift' : 'pop']();

            if (this.requests.length == 0) {
                // queue emptied callback
                this.pending = false;
                Ext.isFunction(this.callback) && this.callback.call(this.scope || null, this);
                A.events && A.fireEvent('queueempty', this);
            }
            return req || null;
        },

        /**
        * clear the queue of any remaining (pending) requests
        */
        clear : function() {
            this.suspend();
            A.pendingRequests -= this.requests.length;
            this.requests.length = 0;
            this.pending = false;
            this.resume();
            this.next(); //force the empty callback/event

        },

        /**
        * Suspend queue further queue dispatches of any remaining (pending) requests until the {@link #Ext.ux.ModuleManager-resume} method is called.
        */
        suspend : function() {
            this.suspended = true;
        },

        /** Resume from a suspended state */
        resume : function() {
            this.suspended = false;
        },

        /**
        * Dispatches the next queue item and initiates a Ext.lib.Ajax request on the result.
        * @param {Boolean} peek If true, the queue item is returned but not removed from the stack.
        * @return activeRequest
        */
        requestNext : function(peek) {
            var req;
            this.activeRequest = null;
            if (!this.suspended && (req = this.next(peek))) {
                if(req.active){  //was it aborted
                    this.activeRequest = A.request.apply(A,req);
                    A.pendingRequests--;
                } else {
                    return this.requestNext(peek);
                }
            }
            return this.activeRequest;
        }
    });

    Ext.lib.Ajax.QueueManager = function(config) {

        Ext.apply(this, config || {}, {
                    quantas : 10, // adjustable milliseconds deferred dispatch
                                    // value
                    priorityQueues : new Array(new Array(), new Array(),
                            new Array(), new Array(), new Array(), new Array(),
                            new Array(), new Array(), new Array(), new Array()), // iterable
                                                                                    // array
                                                                                    // (0-9)
                                                                                    // of
                                                                                    // prioritized
                                                                                    // queues:
                    queues : {}
                });
    };

    Ext.extend(Ext.lib.Ajax.QueueManager, Object, {
       /**
        * @cfg {Integer} quantas Adjustable milliseconds deferred dispatch timer interval
        */
        quantas : 10,

       /** Return a named queue reference
        * param {String} name The name of the desired queue.
        * @return Ext.lib.Ajax.Queue
        */
        getQueue : function(name) {
            return this.queues[name];
        },
        
        createQueue : function(config) {
            if (!config) {return null;}

            var q = new A.Queue(config);
            q.manager = this;
            this.queues[q.name] = q;

            var pqa = this.priorityQueues[q.priority];
            pqa && pqa.indexOf(q.name) == -1 && pqa.push(q.name);
            return q;
        },
       /** Remove a Queue by passed name or Queue Object reference
        * @param {String/Ext.lib.Ajax.Queue} queue
        */
        removeQueue : function(q) {
            if (q && (q = this.getQueue(q.name || q))) {
                q.clear(); // purge any pending requests
                this.priorityQueues[q.priority].remove(q.name);
                delete this.queues[q.name];
            }
        },

        /** @private */
        start : function() {
            if (!this.started) {
                this.started = true;
                this.dispatch();
            }
            return this;
        },

        /** Suspends all defined queues */
        suspendAll : function() {
            forEach(this.queues, function(Q) { Q.suspend(); });
        },

        /** Resumes all suspended queues */
        resumeAll : function() {
            forEach(this.queues, function(Q) { Q.resume();  });
            this.start();
        },

        /**
         * @cfg (Boolean) progressive Default Dispatch mode for all defined queues<p>
         * a false value will exhaust a priority queue until empty during dispatch (sequential) <p>
         * true to dispatch a single request from each priority queue until all queues exhausted.<p>This
         * option may be set on the Queue itself as well.
         * @default false
         */
        progressive : false,

        stop : function() {
            this.started = false;
            return this;
        },

        /** private
         * main Request dispatch loop. This keeps the maximum allowed number of
         * requests going at any one time (based on defined queue priority and
         * dispatch mode (see progressive).
         */

        dispatch   : function(){
            var qm = this, qmq = qm.queues;
            var quit=(A.activeRequests > A.maxConcurrentRequests);
            while(A.pendingRequests && !quit){

               var disp = function(qName) {
                    var q = qmq[qName], AR;

                    while (q && !q.suspended && q.pending && q.requestNext()) {

                        quit || (quit = A.activeRequests > A.maxConcurrentRequests);
                        if(quit)break;

                        // progressive, take the first one off each queue only
                        if (q.progressive || qm.progressive) { break;}

                     }
                     // keep going?
                     if(quit)return false;
                };

                forEach(this.priorityQueues, function(pqueue) {
                    // pqueue == array of queue names
                    !!pqueue.length && forEach(pqueue , disp, this);
                    quit || (quit = A.activeRequests > A.maxConcurrentRequests);
                    if(quit)return false;
                }, this);

            }

            if(A.pendingRequests || quit){
                this.dispatch.defer(this.quantas, this);
            } else{
                this.stop();
            }
        }
    });

    Ext.apply(A, {

        headers           : A.headers || {},
        defaultPostHeader : A.defaultPostHeader || 'application/x-www-form-urlencoded; charset=UTF-8',
        defaultHeaders    : A.defaultHeaders || {},
        useDefaultXhrHeader  : !!A.useDefaultXhrHeader,
        defaultXhrHeader  : 'Ext.basex',
        
        //Reusable script tag pool for IE.
        SCRIPTTAG_POOL    : [],
        _domRefs          : [],        
        onUnload          : function(){
           Ext.Element.uncache.apply(Ext.Element, A.SCRIPTTAG_POOL.concat(A._domRefs));
           delete A._domRefs;
           delete A.SCRIPTTAG_POOL;
        },
        
        /**
	     * private -- <script and link> tag support
	     */
	    monitoredNode : function(tag, attributes, callback, context, deferred) {
	        
	        var node = null, doc = (context || window).document,
	            head = doc ? doc.getElementsByTagName("head")[0] : null;
	        
	        if (tag && doc && head) {
	            node = tag.toUpperCase() == 'SCRIPT' && !!A.SCRIPTTAG_POOL.length ? Ext.get(A.SCRIPTTAG_POOL.pop()) : null;
	            if(node){
	                node.removeAllListeners();
	            }else{
	                node = Ext.get(doc.createElement(tag));
	            }
	            var ndom = Ext.getDom(node);
	            
	            ndom && forEach(attributes || {}, function(value, attrib) {
	                
	                value && (attrib in ndom) && ndom.setAttribute(attrib, value);
	            });
	
	            if (callback) {
	                var cb = (callback.success || callback).createDelegate(callback.scope || null, [callback||{}], 0);
	                
	                Ext.isIE ? node.on('readystatechange', function(){
	                    this.dom.readyState == 'loaded' && cb();    
	                }) : node.on("load", cb);
	            }
	            deferred || ndom.parentNode || head.appendChild(ndom);
	        }
	        A._domRefs.push(node);
	        return node;
	    },
        
        poll              : {},

        pollInterval      : A.pollInterval || 50,

        queueManager      : new A.QueueManager(),

        // If true (or queue config object) ALL requests are queued
        queueAll : false,

        // the Current number of active Ajax requests.
        activeRequests : 0,

        // the Current number of pending Queued requests.
        pendingRequests : 0,

        /**
         * @property maxConcurrentRequests
         * Specify the maximum allowed during concurrent Queued browser (XHR) requests
         * Note:   IE8 increases this limit to 6
         */
        maxConcurrentRequests : Ext.isIE ? Ext.value(window.maxConnectionsPerServer, 2) : 4,

        /** set True as needed, to coerce IE to use older ActiveX interface
         */
        forceActiveX : false,

        /**
         *  Global default may be toggled at any time
         */
        async : true,

        /** private */
        createXhrObject : function(transactionId, options) {
            var obj = {
                status : {
                    isError : false
                },
                
                tId   : transactionId
            }, 
            ecode = null;
            
            options || (options = {});
            try {
                options.xdomain && window.XDomainRequest && (obj.conn =  new XDomainRequest());
                
                if (!defined(obj.conn) && 
                   Ext.capabilities.hasActiveX && 
                     !!Ext.value(options.forceActiveX, this.forceActiveX)) {
                    throw ("IE7forceActiveX");
                }
                obj.conn || (obj.conn = new XMLHttpRequest());
                
            } catch (eo) {
                var actX = Ext.capabilities.hasActiveX ?
                    ( options.multiPart ? this.activeXMultipart : this.activeX ) : null ;
                    
                if(actX){
	                for (var i = 0, l = actX.length; i < l; ++i) {
	                    try {
	                        obj.conn = new ActiveXObject(actX[i]);
	                        break;
	                    } catch (e) {ecode = (eo == "IE7forceActiveX"? e: eo);}
	                }
                }
            } finally {
                obj.status.isError = !defined(obj.conn);
                obj.status.error=  ecode;
            }
            return obj;

        },
                
        createExceptionObject: function (tId, callbackArg, isAbort, isTimeout, errObj) {          
            return {
                tId        : tId,
                status     : isAbort ? -1 : 0,
                statusText : isAbort ? 'transaction aborted' : 'communication failure',
                    isAbort: isAbort,
                  isTimeout: isTimeout,
                  argument : callbackArg
            };
        },  

        /* Replaceable Form encoder */

        encoder : encodeURIComponent,

        serializeForm : function(){ 
            var reSelect = /select-(one|multiple)/i,
                reInput = /file|undefined|reset|button/i,
                reChecks = /radio|checkbox/i;
        
	        return function(form) {
	            var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
	                        hasSubmit = false,
	                        encoder = this.encoder,
	                        element,
	                        options,
	                        name,
	                        val,
	                        data = '',
	                        type;
	            forEach(fElements, function(element) {
	                name = element.name;
	                type = element.type;
	                if (!element.disabled && name){
	                    if(reSelect.test(type)){
	                        forEach(element.options, function(opt) {
	                            if (opt.selected) {
	                                data += String.format("{0}={1}&",
	                                     encoder(name),
	                                     encoder(
                                           opt.hasAttribute && opt.hasAttribute('value') &&
                                              opt.getAttribute('value') !== null ? opt.value : opt.text
                                             )
                                       );
	                            }
	                        });
	                    } else if(!reInput.test(type)) {
	                        if(!(reChecks.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)){
	                            data += encoder(name) + '=' + encoder(element.value) + '&';
	                            hasSubmit = /submit/i.test(type);
	                        }
	                    }
	                }
	            });
	            return data.substr(0, data.length - 1);
            };
        }(),

        /** private */
        getHttpStatus : function(reqObj, isAbort, isTimeout) {

            var statObj = {
                status : 0,
                statusText : '',
                isError : false,
                isLocal : false,
                isOK : true,
                error : null,
                isAbort : !!isAbort,
                isTimeout : !!isTimeout
            };

            try {
                if (!reqObj || !('status' in reqObj)) { throw ('noobj'); }
                statObj.status = reqObj.status;
                statObj.readyState = reqObj.readyState;
                statObj.isLocal = (!reqObj.status && location.protocol == "file:")
                        || (Ext.isSafari && !defined(reqObj.status));

                statObj.isOK = (statObj.isLocal || (statObj.status == 304
                        || statObj.status == 1223 || (statObj.status > 199 && statObj.status < 300)));

                statObj.statusText = reqObj.statusText || '';
            } catch (e) {
            } // status may not avail/valid yet, called too early, or status not support by the transport

            return statObj;

        },
        /**
         * @private
         */
        handleTransactionResponse : function(o, callback, isAbort, isTimeout) {

            callback = callback || {};
            var responseObject = null;
            o.isPart || A.activeRequests--;
            
            if (!o.status.isError) {
                o.status = this.getHttpStatus(o.conn, isAbort, isTimeout);
                /*
                 * create and enhance the response with proper status and XMLDOM
                 * if necessary
                 */
                responseObject = this.createResponseObject(o, callback.argument, isAbort, isTimeout);
            }
            o.isPart || this.releaseObject(o);

            /*
             * checked again in case exception was raised - ActiveX was
             * disabled during XML-DOM creation? And mixin everything the
             * XHR object had to offer as well
             */
            o.status.isError && (responseObject = Ext.apply({}, responseObject || {},
                this.createExceptionObject(o.tId, callback.argument, !!isAbort, !!isTimeout, o.status.error)));

            responseObject.options = o.options;
            responseObject.fullStatus = o.status;

            if (!this.events
                    || this.fireEvent('status:' + o.status.status,
                            o.status.status, o, responseObject, callback,
                            isAbort) !== false) {

                if (o.status.isOK && !o.status.isError) {
                    if (!this.events
                            || this.fireEvent('response', o, responseObject,
                                    callback, isAbort, isTimeout) !== false) {
                        
                        var cb = o.isPart? 'onpart':'success';
                        
                        Ext.isFunction(callback[cb]) && 
                            callback[cb].call(callback.scope || null,responseObject);
                        
                    }
                } else {
                    if (!this.events
                            || this.fireEvent('exception', o, responseObject,
                                    callback, isAbort, isTimeout, responseObject.fullStatus.error) !== false) {
                        Ext.isFunction(callback.failure) &&
                            callback.failure.call(callback.scope || null, responseObject, responseObject.fullStatus.error);
                        
                    }
                }
            }

            return responseObject; 

        },
        /**
         * @private
         * Release the allocated XHR object and reset any timers
         */
        releaseObject:function(o){
            
            o && (o.conn = null);
            if(o && Ext.value(o.tId,-1)+1){
	            if(this.poll[o.tId]){
	                window.clearInterval(this.poll[o.tId]);
	                delete this.poll[o.tId];
	            }
	            if(this.timeout[o.tId]){
	                window.clearInterval(this.timeout[o.tId]);
		            delete this.timeout[o.tId];
	            }
            }
        },

        /**
         *  replace with a custom JSON decoder/validator if required
         */
        decodeJSON : Ext.decode,

        /**
         * @cfg reCtypeJSON
         * regexp test pattern applied to incoming response Content-Type header
         * to identify a potential JSON response. The default pattern handles
         * either text/json or application/json
         */
        reCtypeJSON : /(application|text)\/json/i,
        
        /**
         * @cfg reCtypeXML
         * regexp test pattern applied to incoming response Content-Type header
         * to identify a potential JSON response. The default pattern handles
         * either text/json or application/json
         */
        reCtypeXML : /(application|text)\/xml/i,
        
         /** private */
        createResponseObject : function(o, callbackArg, isAbort, isTimeout) {
            var CTYPE = 'content-type', 
                obj = {
	                responseXML : null,
	                responseText : '',
	                responseStream : null,
	                responseJSON : null,
	                contentType : null,
	                getResponseHeader : emptyFn,
	                getAllResponseHeaders : emptyFn
	            };

            var headerObj = {}, headerStr = '';

            if (isAbort !== true) {
                try { // to catch bad encoding problems here
                    obj.responseJSON = o.conn.responseJSON || null;
                    obj.responseStream = o.conn.responseStream || null;
                    obj.contentType = o.conn.contentType || null;
                    obj.responseText = o.conn.responseText;
                } catch (e) {
                    o.status.isError = true;
                    o.status.error = e;
                }

                try {
                    obj.responseXML = o.conn.responseXML || null;
                } catch (ex) {
                }

                try {
                    headerStr = ('getAllResponseHeaders' in o.conn ? o.conn.getAllResponseHeaders() : null ) || '';
                    var s;
                    headerStr.split('\n').forEach( function(sHeader){
                        (s = sHeader.split(':')) && s.first() && 
	                        (headerObj[s.first().trim().toLowerCase()] = (s.last()||'').trim());
                    });
	                
                } catch (ex1) {
                    o.status.isError = true; // trigger future exception callback
                    o.status.error = ex1;
                }
                finally{ obj.contentType = obj.contentType || headerObj[CTYPE] || ''; }

                if ((o.status.isLocal || o.proxied)
                        && typeof obj.responseText == 'string') {

                    o.status.isOK = !o.status.isError
                            && ((o.status.status = (!!obj.responseText.length)
                                    ? 200 : 404) == 200);

                    if (o.status.isOK
                            && 
                             ( (!obj.responseXML && this.reCtypeXML.test(obj.contentType ))
                             || (obj.responseXML && obj.responseXML.childNodes.length === 0) )
                        ) {

                        var xdoc = null;
                        try { // ActiveX may be disabled
                            if (Ext.capabilities.hasActiveX) {
                                xdoc = new ActiveXObject("MSXML2.DOMDocument.3.0");
                                xdoc.async = false;
                                xdoc.loadXML(obj.responseText);
                            } else {
                                var domParser = null;
                                try { // Opera 9 will fail parsing non-XML content, so trap here.
                                    domParser = new DOMParser();
                                    xdoc = domParser.parseFromString(obj.responseText,'application\/xml');
                                } catch (exP) {
                                } finally {
                                    domParser = null;
                                }
                            }
                        } catch (exd) {
                            o.status.isError = true;
                            o.status.error = exd;
                        }
                        obj.responseXML = xdoc;
                    }
                    if (obj.responseXML) {
                        var parseBad = (obj.responseXML.documentElement && obj.responseXML.documentElement.nodeName == 'parsererror')
                                || (obj.responseXML.parseError || 0) !== 0
                                || obj.responseXML.childNodes.length === 0;
                        parseBad || 
                            (obj.contentType = headerObj[CTYPE] = obj.responseXML.contentType || 'text\/xml');
                    }
                }

                if (o.options.isJSON || (this.reCtypeJSON && this.reCtypeJSON.test(headerObj[CTYPE] || ""))) {
                    try {
                        Ext.isObject(obj.responseJSON) || 
                            (obj.responseJSON = Ext.isFunction( this.decodeJSON ) && 
                               Ext.isString(obj.responseText)
                                ? this.decodeJSON(obj.responseText)
                                : null);
                    } catch (exJSON) {
                        o.status.isError = true; // trigger future exception callback
                        o.status.error = exJSON;
                    }
                }

            } // isAbort?
            o.status.proxied = !!o.proxied;

            Ext.apply(obj, {
                        tId     : o.tId,
                        status  : o.status.status,
                        statusText : o.status.statusText,
                        contentType : obj.contentType || headerObj[CTYPE],
                        getResponseHeader : function(header){return headerObj[(header||'').trim().toLowerCase()];},
                        getAllResponseHeaders : function(){return headerStr;},
                        fullStatus : o.status,
                        isPart : o.isPart || false
                    });
               
            o.parts && !o.isPart && (obj.parts = o.parts);
            defined(callbackArg) && (obj.argument = callbackArg);
            return obj;
        },


        setDefaultPostHeader : function(contentType) {
            this.defaultPostHeader = contentType||'';
        },

        /**
         * Toggle use of the DefaultXhrHeader ('Ext.basex')
         */
        setDefaultXhrHeader : function(bool) {
            this.useDefaultXhrHeader = bool || false;
        },

        request : function(method, uri, cb, data, options) {

            var O = options = Ext.apply({
                        async : this.async || false,
                        headers : false,
                        userId : null,
                        password : null,
                        xmlData : null,
                        jsonData : null,
                        queue : null,
                        proxied : false,
                        multiPart : false,
                        xdomain  : false
                    }, options || {});
                    
                    //Seek out nested config options
                    var _to;
                    if( cb.argument && 
                         cb.argument.options &&
                          cb.argument.options.request &&
                          (_to = cb.argument.options.request.arg) ){
                            
                         Ext.apply(O,{
                           async : O.async || _to.async,
                           proxied : O.proxied || _to.proxied,
                           multiPart : O.multiPart || _to.multiPart,
                           xdomain : O.xdomain ||_to.xdomain,
                           queue   : O.queue ||_to.queue,
                           onPart  : O.onPart ||_to.onPart
                         }); 
                     }
            
            if (!this.events
                    || this.fireEvent('request', method, uri, cb, data, O) !== false) {

                // Named priority queues
                if (!O.queued && (O.queue || (O.queue = this.queueAll || null)) ) {

                    O.queue === true && (O.queue = {name:'q-default'});
                    var oq = O.queue;
                    var qname = oq.name || oq , qm = this.queueManager;

                    var q = qm.getQueue(qname) || qm.createQueue(oq);
                    O.queue = q;
                    O.queued = true;

                    var req = [method, uri, cb, data, O];
                    req.active = true;
                    q.add(req);

                    return {
                        tId : this.transactionId++,
                        queued : true,
                        request : req,
                        options : O
                    };
                }
                
                options.onpart && (cb.onpart || 
                 (cb.onpart = Ext.isFunction(options.onpart) ? 
                    options.onpart.createDelegate(options.scope): null));
                    
                O.headers && forEach(O.headers, 
                    function(value, key) { this.initHeader(key, value, false); },this);

                var cType;
                // The Content-Type specified on options.headers always has priority over 
                // a calculated value.
                if (cType = (this.headers ? this.headers['Content-Type'] || null : null)) {
                    // remove to ensure only ONE is passed later.(per RFC)
                    delete this.headers['Content-Type'];
                }
                if (O.xmlData) {
                    cType || (cType = 'text/xml');
                    method = 'POST';
                    data = O.xmlData;
                } else if (O.jsonData) {
                    cType || (cType = 'application/json; charset=utf-8');
                    method = 'POST';
                    data = Ext.isObject(O.jsonData) ? Ext.encode(O.jsonData) : O.jsonData;
                }
                if (data) {
                    cType || (cType = this.useDefaultHeader
                                    ? this.defaultPostHeader
                                    : null);
                    cType && this.initHeader('Content-Type', cType, false);
                }

                // options.method prevails over any derived method.
                return this.makeRequest(O.method || method, uri, cb, data, O);
            }
            return null;

        },


        /** private */
        getConnectionObject : function(uri, options, data) {
            var o, f;
            var tId = this.transactionId;
            options || (options = {});
            try {
                if (f = options.proxied) { /* JSONP scriptTag Support */
                    
                    o = {
                        tId : tId,
                        status : {isError : false},
                        proxied : true,
                        // synthesize an XHR object
                        conn : {
                            el : null,
                            send : function(data) {
                                var doc = (f.target || window).document,
                                head = doc.getElementsByTagName("head")[0];
                                if (head && this.el) {
                                    head.appendChild(this.el.dom);
                                    this.readyState = 2;
                                }
                            },
                            abort : function() {
                                this.readyState = 0;
                                window[o.cbName] = undefined;
		                        //IE dislikes this
		                        Ext.isIE || delete window[o.cbName];
                                
		                        var d = Ext.getDom(this.el);
		                        
                                if(this.el){
                                  this.el.removeAllListeners();  
                                  if(!o.debug){
                                    if(Ext.isIE){
                                        //Script Tags are re-usable in IE
                                        A.SCRIPTTAG_POOL.push(this.el);
                                    }else{
	                                    this.el.remove();
	                                    //Other Browsers will not GBG-collect these tags, so help them along
	                                    if(d ){
	                                        for(var prop in d) {delete d[prop];}
	                                    }
                                    }
                                  }
                                }
		                        this.el = d = null;
                            },
                            _headers : {},
                            getAllResponseHeaders : function(){
                                var out=[];
                                forEach(this._headers,function(value, name){
                                   value && out.push(name + ': '+value);
                                });
                                return out.join('\n');
                                
                                },
                            getResponseHeader : function(header){ 
                                return this._headers[String(header).toLowerCase()] || ''; 
                               },
                            onreadystatechange : null,
                            onload : null,
                            readyState : 0,
                            status : 0,
                            responseText : null,
                            responseXML : null,
                            responseJSON : null
                        },
                        debug : f.debug,
                        params : Ext.isString(options.params) ?  Ext.urlDecode(options.params) : options.params || {},
                        cbName : f.callbackName || 'basexCallback' + tId,
                        cbParam : f.callbackParam || null
                    };

                    window[o.cbName] = o.cb = function(content) {

                        content && typeof(content)=='object' && (this.responseJSON = content);
                        this.responseText = content || null;
                        this.status = !!content ? 200 : 404;
                        this.abort();
                        this.readyState = 4;
                        Ext.isFunction(this.onreadystatechange) && this.onreadystatechange();
                        Ext.isFunction(this.onload) && this.onload();
                        
                    }.createDelegate(o.conn);

                    o.conn.open = function() {

                        if (o.cbParam) {
                            o.params[o.cbParam] = o.cbName;
                        }
                        
                        //apply any new params to any already supplied by the uri and postData
                        var params = Ext.urlEncode(
                            Ext.apply(
                                Ext.urlDecode(data) || {},  //decode any postData
                                o.params,
                                uri.indexOf("?") > -1 ? Ext.urlDecode(uri.split('?').last()): false
                            )) ;
                        
                        o.uri = params ? uri.split('?').first() + '?' + params : uri;

                        this.el = A.monitoredNode(
                                f.tag || 'script', 
                                {
                                    type : f.contentType || "text/javascript",
                                    src : o.uri,
                                    charset : f.charset || options.charset || null
                                },
                                null,
                                f.target, 
                                true); //defer head insertion until send method
                        
                        this._headers['content-type'] = this.el.dom.type;        
                        this.readyState = 1; // show CallInProgress
                        Ext.isFunction(this.onreadystatechange) && this.onreadystatechange();

                    };
                    options.async = true; // force timeout support
                    
                } else {
                    o = this.createXhrObject(tId, options);
                }
                if (o) {
                    this.transactionId++;
                }
            } catch (ex3) { 
                o && (o.status.isError = !!(o.status.error = ex3));
            } finally {
                return o;
            }
        },
        
        /** private */
        makeRequest : function(method, uri, callback, postData, options) {

            var o;
            if (o = this.getConnectionObject(uri, options, postData)) {
                o.options = options;
                var r = o.conn;
                
                try {
                    if(o.status.isError){ throw o.status.error };
                    
                    A.activeRequests++;
                    r.open(method.toUpperCase(), uri, options.async, options.userId, options.password);
                   
                    ('onreadystatechange' in r) && 
                        (r.onreadystatechange = this.onStateChange.createDelegate(this, [o, callback, 'readystate'], 0));
                    
                    ('onload' in r) &&
                        (r.onload = this.onStateChange.createDelegate(this, [o, callback, 'load', 4], 0));
                        
                    ('onprogress' in r) &&
                        (r.onprogress = this.onStateChange.createDelegate(this, [o, callback, 'progress'], 0));
                        
                    //IE8/other? evolving timeout callback support
	                if(callback && callback.timeout){
                        ('timeout' in r) && (r.timeout = callback.timeout);
                        ('ontimeout' in r) && 
                           (r.ontimeout = this.abort.createDelegate(this, [o, callback, true], 0));
                        ('ontimeout' in r) ||
                           // Timers for syncro calls won't work here, as it's a blocking call
                           (options.async && (this.timeout[o.tId] = window.setInterval(
                                function() {A.abort(o, callback, true);
                            }, callback.timeout)));
                    }
                    
                    if (this.useDefaultXhrHeader && !options.xdomain) {
	                    this.defaultHeaders['X-Requested-With'] ||
	                        this.initHeader('X-Requested-With', this.defaultXhrHeader, true);
	                }
	                this.setHeaders(o);
	                
	                if (!this.events
                            || this.fireEvent('beforesend', o, method, uri,
                                    callback, postData, options) !== false) {
                        r.send(postData || null);
                    }
                } catch (exr) {
                    o.status.isError = true;
                    o.status.error = exr;
                }
                if(o.status.isError ) {
                    return Ext.apply(o, this.handleTransactionResponse(o, callback));
                }
                options.async || this.onStateChange(o, callback, 'load'); 
                return o;
            }
        },


        abort : function(o, callback, isTimeout) {

            o && Ext.apply(o.status,{
                isAbort : !!!isTimeout,
                isTimeout : !!isTimeout,
                isError  : !!isTimeout || !!o.status.isError
              }); 
            if (o && o.queued && o.request) {
                o.request.active = o.queued = false;
                this.events && this.fireEvent('abort', o, callback);
                return true;
            } else if (o && this.isCallInProgress(o)) {
                
                if (!this.events || this.fireEvent(isTimeout ? 'timeout' : 'abort', o, callback)!== false){
                    ('abort' in o.conn) && o.conn.abort();
                    this.handleTransactionResponse(o, callback, o.status.isAbort, o.status.isTimeout);
                }
                return true;
            } 
            return false;
        },
        
        isCallInProgress : function(o) {
            // if there is a connection and readyState is supported, and not 0 or 4
            if( o && o.conn ){
                if('readyState' in o.conn && {0:true,4:true}[o.conn.readyState]){
                    return false;
                }
                return true;
            }
            return false;
        },

        /**
         * Clears the Browser authentication Cache
         * @param {String} url {optional) reset url for non-IE browsers
         * @return void
         */
        clearAuthenticationCache : function(url) {

            try {

                if (Ext.isIE) {
                    // IE clear HTTP Authentication, (but ALL realms though)
                    document.execCommand("ClearAuthenticationCache");
                } else {
                    // create an xmlhttp object
                    var xmlhttp;
                    if (xmlhttp = new XMLHttpRequest()) {
                        // prepare invalid credentials
                        xmlhttp.open("GET", url || '/@@', true, "logout", "logout");
                        // send the request to the server
                        xmlhttp.send("");
                        // abort the request
                        xmlhttp.abort.defer(100, xmlhttp);
                    }
                }
            } catch (e) {} // There was an error
           
        },

        // private
        initHeader : function(label, value) {
            (this.headers = this.headers || {})[label] = value;
        },
          
        /** @private 
         * General readyStateChange multiPart handler 
         */
        onStateChange : function(o, callback, mode) {
            
            if(!o.conn || o.status.isTimeout || o.status.isError){ return; }
            
            var C = o.conn, readyState = ('readyState' in C ? C.readyState : 0);
            if(mode === 'load' || readyState > 2){
                var ct;
                try{ct = C.contentType || C.getResponseHeader('Content-Type') || '';}
                catch(exRs){ }
                
                if(ct && /multipart\//i.test(ct)){
                    var r = null, boundary = ct.split('"')[1], kb = '--' + boundary;
                    o.multiPart = true;
                    try{r = C.responseText;}catch(ers){}
                     
                    var p = r ? r.split(kb) : null;
                        
                    if(p){
                         o.parts || (o.parts = []);
	                     p.shift();
	                     p.pop();
	                    
	                     forEach( 
                           Array.slice(p, o.parts.length), //skip parts already parsed 
		                      function(newPart){
		                        var content = newPart.split('\n\n');
		                        var H = (content[0] ? content[0] : '') + '\n';
		                        o.parts.push(this.handleTransactionResponse(
		                          Ext.apply(
                                    Ext.clone(o),{
		                            boundary : boundary,
		                                conn : {  //synthetic conn structure for each part
		                                    status : 200,
		                                    responseText : (content[1]||'').trim(),
		                           getAllResponseHeaders : function(){
		                                        return H.split('\n').filter(
		                                            function(value){return !!value;}).join('\n');
		                                    }
		                                },
		                                isPart : true
		                          }), callback));
		                  },this);
                    }
                    
                }
            }
            (readyState === 4 || mode === 'load') && A.handleTransactionResponse(o, callback);
            this.events && this.fireEvent.apply(this, ['readystatechange'].concat(Array.slice(arguments, 0)));
        },
        
        setHeaders:function(o){

            //Some XDomain implementations (IE8) do not support setting headers
            if(o.conn && 'setRequestHeader' in o.conn){
	            this.defaultHeaders &&
		            forEach(this.defaultHeaders, function(value, key){ o.conn.setRequestHeader(key, value);});
	
	            this.headers &&
		            forEach(this.headers, function(value, key){o.conn.setRequestHeader(key, value);});
            }
            this.headers = {};
            this.hasHeaders = false;

        },

        resetDefaultHeaders:function() {
            delete this.defaultHeaders;
            this.defaultHeaders = {};
            this.hasDefaultHeaders = false;
        },
        
        //These are only current versions of ActiveX XHR that support multipart responses
        activeXMultipart : [
        'MSXML2.XMLHTTP.6.0',
        'MSXML3.XMLHTTP' 
        ],
        
        activeX:[
        'MSXML2.XMLHTTP.3.0',
        'MSXML2.XMLHTTP',
        'Microsoft.XMLHTTP'
        ]

    });
    
    if (Ext.util.Observable) {

        Ext.apply(A, {

            events : {
                request : true,
                beforesend : true,
                response : true,
                exception : true,
                abort : true,
                timeout : true,
                readystatechange : true,
                beforequeue : true,
                queue : true,
                queueempty : true
            },

            /**
             * onStatus define eventListeners for a single (or array) of
             * HTTP status codes.
             */

            onStatus : function(status, fn, scope, options) {
                var args = Array.slice(arguments, 1);
                status = new Array().concat(status || new Array());
                forEach(status, function(statusCode) {
                            statusCode = parseInt(statusCode, 10);
                            if (!isNaN(statusCode)) {
                                var ev = 'status:' + statusCode;
                                this.events[ev] || (this.events[ev] = true);
                                this.on.apply(this, [ev].concat(args));
                            }
                        }, this);
            },
            
            /**
             * unStatus unSet eventListeners for a single (or array) of
             * HTTP status codes.
             */

            unStatus : function(status, fn, scope, options) {
                var args = Array.slice(arguments, 1);
                status = new Array().concat(status || new Array());
                forEach(status, function(statusCode) {
                            statusCode = parseInt(statusCode, 10);
                            if (!isNaN(statusCode)) {
                                var ev = 'status:' + statusCode;
                                this.un.apply(this, [ev].concat(args));
                            }
                        }, this);
            }

        }, new Ext.util.Observable());

        Ext.hasBasex = true;
    }
        
    // Array, object iteration and clone support
    Ext.stopIteration = { stopIter : true };

    Ext.applyIf(Array.prototype, {

        /*
         * Fix for IE, Opera < 9.5, which does not seem to include the map
         * function on Array's
         */
        map : function(fun, scope) {
            var len = this.length;
            if (typeof fun != "function") {
                throw new TypeError();
            }
            var res = new Array(len);

            for (var i = 0; i < len; ++i) {
                i in this &&
                    (res[i] = fun.call(scope || this, this[i], i, this));
            }
            return res;
        },
        
        /**
         * Return true of the passed Function test true of ANY array elememt.
         * (added for IE)
         */
        some  : function(fn){
          var f= Ext.isFunction(fn) ? fn : function(){};
          var i=0, l=this.length, test=false;
          while(i<l && !(test=!!f(this[i++]))){}
          return test;
        },
        
        /**
         * Return true of the passed Function test true of ALL array elememts.
         * (added for IE)
         */
        every  : function(fn){
          var f= Ext.isFunction(fn) ? fn : function(){};
          var i=0, l=this.length, test=true;
          while(i<l && (test=!!f(this[i++]))){}
          return test;
        },

        include : function(value, deep) { // Boolean: is value present
                                            // in Array
            // use native indexOf if available
            if (!deep && typeof this.indexOf == 'function') {
                return this.indexOf(value) != -1;
            }
            var found = false;
            try {
                this.forEach(function(item, index) {
                    if (found = (deep
                            ? (item.include
                                    ? item.include(value, deep)
                                    : (item === value))
                            : item === value)) {
                        throw Ext.stopIteration;
                    }
                });
            } catch (exc) {
                if (exc != Ext.stopIteration) {
                    throw exc;
                }
            }
            return found;
        },
        // Using iterFn, traverse the array, push the current element
        // value onto the
        // result if the iterFn returns true
        filter : function(iterFn, scope) {
            var a = new Array();
            iterFn || (iterFn = function(value) {
                return value;
            });
            this.forEach(function(value, index) {
                iterFn.call(scope, value, index) && a.push(value);
            });
            return a;
        },

        compact : function(deep) { // Remove null, undefined array
                                    // elements
            var a = new Array();
            this.forEach(function(v) {
                (v === null || v === undefined) || a.push(deep && Ext.isArray(v) ? v.compact() : v);
            }, this);
            return a;
        },

        flatten : function() { // flatten: [1,2,3,[4,5,6]] ->
                                // [1,2,3,4,5,6]
            var a = new Array();
            this.forEach(function(v) {
                Ext.isArray(v) ? (a = a.concat(v)) : a.push(v);
            }, this);
            return a;
        },
        
        indexOf : function(o){
	       for (var i = 0, len = this.length; i < len; ++i){
	           if(this[i] == o) return i;
	       }
	       return -1;
	    },

        
        lastIndexOf : function(val){
            var i= this.length-1;
            while(i>-1 && this[i] != val){i--;}
            return i;
        },

        unique : function(sorted /* sort optimization */, exact) { // unique:
                                                                    // [1,3,3,4,4,5]
                                                                    // ->
                                                                    // [1,3,4,5]
            var a = new Array();
            this.forEach(function(value, index) {
                if (0 == index
                        || (sorted ? a.last() != value : !a.include(value, exact))) {
                    a.push(value);
                }
            }, this);
            return a;
        },
        // search array values based on regExpression pattern returning
        // test (and optionally execute function(value,index) on test
        // before returned)
        grep : function(rePattern, iterFn, scope) {
            var a = new Array();
            iterFn || (iterFn = function(value) {
                return value;
            });
            var fn = scope ? iterFn.createDelegate(scope) : iterFn;

            if (typeof rePattern == 'string') {
                rePattern = new RegExp(rePattern);
            }
            rePattern instanceof RegExp && 
             this.forEach(function(value, index) {
                rePattern.test(value) && a.push(fn(value, index));
            });
            return a;
        },
        
        first : function() {
            return this[0];
        },

        last : function() {
            return this[this.length - 1];
        },

        clear : function() {
            this.length = 0;
        },

        // return an array element selected at random
        atRandom : function(defValue) {
            var r = Math.floor(Math.random() * this.length);
            return this[r] || defValue;
        },

        clone : function(deep) {
            if (!deep) {return this.concat();}

            var length = this.length || 0, t = new Array(length);
            while (length--) {
                t[length] = Ext.clone(this[length], true);
            }
            return t;

        },
        
         /*
         * Array forEach Iteration based on previous work by: Dean Edwards
         * (http://dean.edwards.name/weblog/2006/07/enum/) Gecko already
         * supports forEach for Arrays : see
         * http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach
         */
        forEach : function( block, scope) {
            Array.forEach(this, block, scope);
        },
        
        reversed : function(){
	        var length = this.length || 0, t = [];
	        while (length--) {
	            t.push(this[length]);
	        }
	        return t;   
	    }   

    });


    // globally resolve forEach enumeration
    window.forEach = function(object, block, context, deep) {
        context = context || object;
        if (object) {
            if (typeof block != "function") {
                throw new TypeError();
            }
            var resolve = Object;
            if (object instanceof Function) {
                // functions have a "length" property
                resolve = Function;
            
            } else if (object.forEach instanceof Function) {
                // the object implements a custom forEach method so use that
                return object.forEach(block, context);
               
            } else if (typeof object == "string") {
                // the object is a string
                resolve = String;
                
            } else if (Ext.isNumber(object.length)) {
                // the object is array-like
                resolve = Array;
            } 
            return resolve.forEach(object, block, context, deep);
        }
    }; 

    /**
     * 
     * Primary clone Function
     */
    Ext.clone = function(obj, deep) {
        if (obj === null || obj === undefined) {return obj;}
        
        if (Ext.isFunction(obj.clone)) { 
            return obj.clone(deep);
        }
        else if(Ext.isFunction(obj.cloneNode)){
            return obj.cloneNode(deep);
        }
        var o={};
        forEach(obj, function(value, name, objAll){
            o[name] = (value === objAll ? // reference to itself?
                o : deep ? Ext.clone(value, true) : value); 
        }, obj, deep);
        return o;
    };
   
    var slice = Array.prototype.slice;
    var filter = Array.prototype.filter;
    Ext.applyIf(Array,{
        // Permits: Array.slice(arguments, 1); // mozilla already supports this
        slice: function(obj) {
            return slice.apply(obj, slice.call(arguments, 1));
            },
        //String filter iteration
        filter: function(obj, fn){
            var t = obj && typeof obj == 'string' ? obj.split('') : [];
            return filter.call(t, fn);
        },
         /*
         * Array forEach Iteration based on previous work by: Dean Edwards
         * (http://dean.edwards.name/weblog/2006/07/enum/) Gecko already
         * supports forEach for Arrays : see
         * https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach
         */
        forEach : function( collection, block, scope) {

            if (typeof block != "function") {
                throw new TypeError();
            }
            for (var i = 0, l = collection.length >>> 0; i < l; ++i) {
               (i in collection) && block.call(scope || null, collection[i], i, collection);
            }
          }
    });
    
    //Add clone function to primitive prototypes
    
    Ext.applyIf(RegExp.prototype,{
        clone : function() {
            return new RegExp(this);
        }        
    });

    Ext.applyIf(Date.prototype, {
        clone  : function(deep){
            return deep? new Date(this.getTime()) : this ;
        }
    });

    Ext.applyIf(Boolean.prototype, {
        clone : function(){
           return this === true; 
        }
    }); 
    
    Ext.applyIf(Number.prototype, {
        times  : function(block, context){
            var total = parseInt(this,10) || 0;
            for (var i=1; i <= total; ){
               block.call(context, i++);
            }
        },
        forEach : function(){
           this.times.apply(this, arguments);
        },
        
        clone : function(){
           return (this)+ 0; 
        }
    });

    // character enumeration
    Ext.applyIf(String.prototype, {
        
        trim : function() {
            var re = /^\s+|\s+$/g;
            return function() {
                return this.replace(re, "");
            };
        }(),
        
        trimRight : function() {
            var re = /^|\s+$/g;
            return function() {
                return this.replace(re, "");
            };
        }(),
        
        trimLeft : function() {
            var re = /^\s+|$/g;
            return function() {
                return this.replace(re, "");
            };
        }(),

        clone : function() { return String(this)+''; },
        
        forEach : function(block, context){
            String.forEach(this, block,context);
        }

    });

    
    var overload = function(pfn, fn ){

           var f = typeof pfn == 'function' ? pfn : function(){};

           var ov = f._ovl; //call signature hash
           if(!ov){
               ov = { base: f};
               ov[f.length|| 0] = f;

               f= function(){  //the proxy stub
                  var o = arguments.callee._ovl;
                  var fn = o[arguments.length] || o.base;
                  //recursion safety
                  return fn && fn != arguments.callee ? fn.apply(this,arguments): undefined;
               };
           }
           var fnA = [].concat(fn);
           for(var i=0,l=fnA.length; i<l; ++i){
             //ensures no duplicate call signatures, but last in rules!
             ov[fnA[i].length] = fnA[i];
           }
           f._ovl= ov;
           return f;

       };

    
    Ext.applyIf(Ext,{
        overload : overload( overload,
           [
             function(fn){ return overload(null, fn);},
             function(obj, mname, fn){
                 return obj[mname] = overload(obj[mname],fn);}
          ]),
          
        isIterable : function(obj){
            //check for array or arguments
            if( obj === null || obj === undefined )return false; 
            if(Ext.isArray(obj) || !!obj.callee || Ext.isNumber(obj.length) ) return true;
            
            return !!((/NodeList|HTMLCollection/i).test(OP.toString.call(obj)) || //check for node list type
              //NodeList has an item and length property
              //IXMLDOMNodeList has nextNode method, needs to be checked first.
             obj.nextNode || obj.item || false); 
        },

        isArray : function(obj){
           return OP.toString.apply(obj) == '[object Array]';
        },

        isObject:function(obj){
            return (obj !== null) && typeof obj == 'object';
        },
        
        isNumber: function(obj){
            return typeof obj == 'number' && isFinite(obj);
        },
        
        isBoolean: function(obj){
            return typeof obj == 'boolean';
        },

        isDocument : function(obj){
            return OP.toString.apply(obj) == '[object HTMLDocument]' || (obj && obj.nodeType === 9);
        },

        isElement : function(obj){
            return obj && Ext.type(obj)== 'element';
        },

        isEvent : function(obj){
            return OP.toString.apply(obj) == '[object Event]' || (Ext.isObject(obj) && !Ext.type(obj.constructor) && (window.event && obj.clientX && obj.clientX === window.event.clientX));
        },

        isFunction: function(obj){
            return OP.toString.apply(obj) == '[object Function]';
        },

        isString : function(obj){
            return typeof obj == 'string';
        },
        
        isDefined: defined
        
    });
     /**
      * @class Ext
      * @singleton
      * @constructor
      * @description Ext Adapter extensions
      */
          
    /**
     * @class Ext.capabilities
     * @singleton
     * @version 4.0
     * @donate <a target="tag_donate" href="http://donate.theactivegroup.com"><img border="0" src="http://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif" border="0" alt="Make a donation to support ongoing development"></a>
     * @license <a href="http://www.gnu.org/licenses/gpl.html">GPL 3.0</a> 
     * @author Doug Hendricks. Forum ID: <a href="http://extjs.com/forum/member.php?u=8730">hendricd</a> 
     * @copyright 2007-2009, Active Group, Inc. All rights reserved.
     * @desc Describes Detected Browser capabilities.
     */
    Ext.capabilities = {
            /**
             * @property {Boolean} hasActiveX True if the Browser support (and is enabled) ActiveX.
             */
            hasActiveX : defined(window.ActiveXObject),
            
            /**
             * @property {Boolean} hasXDR True if the Browser has native Cross-Domain Ajax request support.
             */
            hasXDR  : function(){
                return defined(window.XDomainRequest) || (defined(window.XMLHttpRequest) && 'withCredentials' in new XMLHttpRequest());
            }(),
            
            /**
             * @property {Boolean} hasChromeFrame true, if the Google ChromeFrame plugin is install on IE
             */
            hasChromeFrame : function(){
                try{
                  if(defined(window.ActiveXObject) && !!(new ActiveXObject("ChromeTab.ChromeFrame")))return true;
                }catch(ef){}
                var a = navigator.userAgent.toLowerCase();
                return !!(a.indexOf("chromeframe")>=0 || a.indexOf("x-clock")>=0 );  
                
            }(),
            
            /**
             * @property {Boolean} hasFlash True if the Flash Browser plugin is installed.
             */
            hasFlash : (function(){
                //Check for ActiveX first because some versions of IE support navigator.plugins, just not the same as other browsers
                if(defined(window.ActiveXObject)){
                    try{
                        //try to create a flash instance
                        new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
                        return true;
                    }catch(e){};
                    //If the try-catch fails, return false
                    return false;
                }else if(navigator.plugins){
                    //Loop through all the plugins
                    for(var i=0, P=navigator.plugins,length = P.length; i < length; ++i){
                        //test to see if any plugin names contain the word flash, if so it must support it - return true
                        if((/flash/i).test(P[i].name)){
                            return true;
                        }
                    }
                    //return false if no plugins match
                    return false;
                }
                //Return false if ActiveX and nagivator.plugins are not supported
                return false;
                })(),
            
            /**
             * @property {Boolean} hasCookies True if the browser cookies are enabled/supported.
             * On IE, ModalDialog windows will issue a security risk warning to the user during this check, so assert 
             * to false. (Cookie implementations on IE's [Modeless|Modal]Dialogs are not supported as 
             * they run in a seperate ActiveX browser context)
             */
            hasCookies : Ext.isIE && ('dialogArguments' in window) ? false : !!navigator.cookieEnabled ,
                        
            /**
             * @property {Boolean} hasCanvas True if the browser has canvas Element support.
             */
            hasCanvas  : !!document.createElement("canvas").getContext,
            
            /**
             * @property {Boolean} hasCanvasText True if the browser has canvas Element Text support.
             */
            hasCanvasText : function(){
                return !!(this.hasCanvas && typeof document.createElement('canvas').getContext('2d').fillText == 'function');
            }(),
            
            /**
             * @property {Boolean} hasSVG True if the browser has SVG support.
             */
            hasSVG     : !!(document.createElementNS && document.createElementNS('http://www.w3.org/2000/svg', 'svg').width),
            
            /**
             * @property {Boolean} hasXpath True if the browser has Xpath query support.
             */
            hasXpath   : !!document.evaluate,
            
            /**
             * @property {Boolean} hasWorkers True if the browser has support for threaded Workers.
             */
            hasWorkers  : defined(window.Worker),
            
            /**
             * @property {Boolean} hasOffline True if the browser has offline support. 
             */
            hasOffline : defined(window.applicationCache),
            
            /**
             * @property {Boolean} hasLocalStorage True if the browser has Local Storage support. 
             */
            hasLocalStorage : defined(window.localStorage),
            
            /**
             * Basic HTML5 geolocation services support test 
             * @property {Boolean} hasGeoLocation
             */
            hasGeoLocation : defined(navigator.geolocation),
            
            hasBasex   : true,
            
            /**
             * 
             * @property {Boolean/Object} hasAudio
             * @desc Basic HTML5 Element support for the &lt;audio> tag and/or Audio object.
             * @example
 If the browser has &lt;audio> tag or Audio object support,<br />the property contains a mime-type map of standard audio formats.
       {
        mp3   : false,  //mp3
        ogg   : false,  //Ogg Vorbis
        wav   : true,   //wav 
        basic : false,  //au, snd
        aif   : false,  //aif, aifc, aiff
        tag  : true,    //is audio HTML element supported?
        object : true,  //is the window.Audio Object supported
        <b>testMime</b> : function()
        }
        
The included <b>testMime</b> function permits selective mime-type testing as well for custom audio formats:
        if(Ext.capabilities.hasAudio &&
             Ext.capabilities.hasAudio.testMime('audio/ogg') ){
                alert ('Vorbis playback is supported');
          }
         */
            hasAudio   : function(){
                
                var aTag = !!document.createElement('audio').canPlayType,
                    aAudio = ('Audio' in window) ? new Audio('') : {},
	                caps = aTag || ('canPlayType' in aAudio) ? 
                        { tag   : aTag, 
                         object : ('play' in aAudio),
                         
                         /*
                          * Test for a specific audio mime-type
                          */
                         testMime : function(mime){
                             var M; return (M = aAudio.canPlayType ? aAudio.canPlayType(mime): 'no') !== 'no' && M !== '';
                           }
                         } : false,
                    mime,
                    chk,
                    mimes = {
                            mp3   : 'audio/mpeg', //mp3
                            ogg   : 'audio/ogg',  //Ogg Vorbis
                            wav   : 'audio/x-wav', //wav 
                            basic : 'audio/basic', //au, snd
                            aif   : 'audio/x-aiff' //aif, aifc, aiff
                        };
                    
                    if(caps && caps.testMime){
                       for (chk in mimes){ 
	                        caps[chk] = caps.testMime(mimes[chk]);
	                    }
                    }                     
                    return caps;
            }(),
            
            /**
             *  
             * @property {Boolean/Object} hasVideo
             * @desc Basic HTML5 Element support for the &lt;video> tag.
             * @example
 If the browser has &lt;video> tag support, the property contains a codec map of supported video formats.
       {
        mp4  : false,
        ogg  : true,
        testCodec : function()
        }
The testCodec function permits selective codec support testing:
        if(Ext.capabilities.hasVideo &&
             Ext.capabilities.hasVideo.testCodec("avc1.42E01E, mp4a.40.2") ){
                alert ('Apple Video decoder is supported');
          }
           */
            hasVideo  : function(){
                   var vTag = !!document.createElement('video').canPlayType, 
                    vVideo = vTag ? document.createElement('video') : {},
                    caps = ('canPlayType' in vVideo) ? 
                      {     tag : vTag,
                      /*
                       * Test for a specific video and codec (eg: 'video/ogg; codecs="theora, vorbis"' ) 
                       */
                      testCodec : function(codec){
                         var C; return (C = vVideo.canPlayType ? vVideo.canPlayType(codec): 'no') !== 'no' && C !== '';   
                         }
                      } : false,
                    codec,
                    chk,
                    codecs = {
                            mp4 : 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"', //mp4 (Apple: patented licensed codec)
                            ogg : 'video/ogg; codecs="theora, vorbis"'          //ogg Vorbis codec
                        };
                    
                   if(caps && caps.testCodec){
                       for (chk in codecs){ 
                            caps[chk] = caps.testCodec(codecs[chk]);
                        }
                    }                     
                   return caps;
            }(),
            
             /**
             * @desc Basic HTML5 Input Element support for autofocus. 
             * @return {Boolean} 
             */
            hasInputAutoFocus : function(){
                return ('autofocus' in (document.createElement('input')));
            }(),
            
            /**
             * @desc Basic HTML5 Input Element support for placeholder 
             * @return {Boolean}
             */
            hasInputPlaceHolder : function(){
                return ('placeholder' in (document.createElement('input')));
            }(),
            
            /**
             * 
             * @param {String) type The input type to test for.
             * @return {Boolean} 
             * @desc Does the HTML5 enabled browser support extended input types
             * @example Typical input type tests:
             * search, number, range, color, tel, url, email, date, month, week, tine, datetime, datetime-local
             */
            hasInputType : function(type){
              var el = document.createElement("input");
              if(el){
                 try{ el.setAttribute("type", type)}catch(e){};
                 return el.type !== 'text';
              }
              return false;
            },
            
            /**
	         * 
	         * @param {String} type The eventName (without the 'on' prefix)
	         * @param {HTMLElement/Object/String} testEl (optional) A specific HTMLElement/Object to test against, otherwise a tagName to test against.
	         * based on the passed eventName is used, or DIV as default. (window and document objects are supported)
	         * @return {Boolean} True if the passed object supports the named event.
             * @desc Determines whether a specified DOMEvent is supported by a given HTMLElement or Object.
             * @example Does the &lt;script> tag support the load event?
   Ext.capabilities.isEventSupported('load', document.createElement('script')); 
	         */  
	        isEventSupported : function(){
	            var TAGNAMES = {
	              'select':'input',
                  'change':'input',
	              'submit':'form',
                  'reset':'form',
                  'load':'img',
	              'error':'img',
                  'abort':'img'
	            }
	            //Cached results
	            var cache = {},
                    onPrefix = /^on/i,
	                //Get a tokenized string of the form nodeName:type
	                getKey = function(type, el){
	                    var tEl = Ext.getDom(el);
		                return (tEl ?
	                           (Ext.isElement(tEl) || Ext.isDocument(tEl) ?
	                                tEl.nodeName.toLowerCase() :
	                                    el.self ? '#window' : el || '#object')
	                       : el || 'div') + ':' + type;
	                };
	
	            return function (evName, testEl) {
                  evName = (evName || '').replace(onPrefix,'');
                  var el, isSupported = false;
                  var eventName = 'on' + evName;
                  var tag = (testEl ? testEl : TAGNAMES[evName]) || 'div';
	              var key = getKey(evName, tag);
                  
	              if(key in cache){
	                //Use a previously cached result if available
	                return cache[key];
	              }
	              
	              el = Ext.isString(tag) ? document.createElement(tag): testEl;
	              isSupported = (!!el && (eventName in el));
	              
	              isSupported || (isSupported = window.Event && !!(String(evName).toUpperCase() in window.Event));
                  
	              if (!isSupported && el) {
	                el.setAttribute && el.setAttribute(eventName, 'return;');
	                isSupported = Ext.isFunction(el[eventName]);
	              }
	              //save the cached result for future tests
	              cache[key] = isSupported;
	              el = null;
	              return isSupported;
	            };
	
	        }()
        };
        Ext.EventManager.on(window,   "beforeunload",  A.onUnload ,A,{single:true});
})();

 // enumerate custom class properties (not prototypes unless protos==true)
 // usually only called by the global forEach function
 Ext.applyIf(Function.prototype, {
   forEach : function( object, block, context, protos) {
       if(object){
        var key;
         for (key in object) {
            (!!protos || object.hasOwnProperty(key)) &&
               block.call(context||object, object[key], key, object);
        }
      }
    },
    
    // Credit: @Animal -- the_bagbournes@btinternet.com
    createBuffered: function(buffer, scope){
        var method = this, task = new Ext.util.DelayedTask();
        return function(){
            task.delay(buffer, method, scope, Array.slice(arguments,0));
        };
    },
    
    /**
     * Credit: @Animal -- the_bagbournes@btinternet.com
     * Creates a delegate (callback) which, when called, executes after a specific delay.
     * Optionally, a replacement (or additional) argument list may be specified.
     * @param {Number} delay The number of milliseconds to defer execution by whenever called.
     * @param {Object} scope (optional) The scope (<code>this</code> reference) used by the function at execution time.
     * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
     * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
     * if a number the args are inserted at the specified position.
     * @return {Function} A function which, when called, executes the original function after the specified delay.
     */
    createDelayed: function(delay, scope, args, appendArgs){
        var method = (scope || args) ? this.createDelegate(scope, args, appendArgs) : this;
        return delay ? function() {
            setTimeout(method, delay);
        } : method;
    },


    clone : function(deep){ return this;}
  }); Ext.form.TriggerField.override({
    afterRender : function(){
        Ext.form.TriggerField.superclass.afterRender.call(this);
        var y;
        if(Ext.isIE && !this.hideTrigger && this.el.getY() != (y = this.trigger.getY())){
            this.el.position();
            this.el.setY(y);
        }
    }
});
/*
	Author       : Jay Garcia
	Site         : http://tdg-i.com
	blog post    : http://tdg-i.com/59/how-to-add-a-tab-scroller-menu
	Contact Info : jgarcia@tdg-i.com
	Purpose      : Tab panel scroller menu
	Warranty     : none
	Price        : free
	Version      : b1
	Date         : 01/16/2009
	
*/


Ext.ns('Ext.plugins', 'Ext.plugins.TDGi');

Ext.plugins.TDGi.tabScrollerMenu =  Ext.extend(Object, {
	init : function(tabPanel) {
		Ext.apply(tabPanel, this.tabPanelMethods);
		var thisRef = this;
		
		tabPanel.on({
			render : {
				scope  : tabPanel,
				single : true,
				fn     : function() { 
					var newFn = tabPanel.createScrollers.createSequence(thisRef.createPanelsMenu, this);
					tabPanel.createScrollers = newFn;
				}
			}
		});
	},
	// private && sequeneced
	createPanelsMenu : function() {
		var h = this.stripWrap.dom.offsetHeight;
		
		//move the right menu item to the left 18px
		var rtScrBtn = this.header.dom.firstChild;
		Ext.fly(rtScrBtn).applyStyles({
			right : '18px'
		});
		
		// Add the new righthand menu
		var rightMenu = this.header.insertFirst({
			cls:'x-tab-tabmenu-right'
		});
		rightMenu.setHeight(h);
		rightMenu.addClassOnOver('x-tab-tabmenu-over');
		rightMenu.on('click', this.showTabsMenu, this);	
	
	},
	// private && applied to the tab panel itself.
	tabPanelMethods : {
	
		// private	
		showTabsMenu : function(e) {		
			if (! this.tabsMenu) {
				this.tabsMenu =  new Ext.menu.Menu();
				this.on('beforedestroy', this.tabsMenu.destroy, this.tabsMenu);
				
				this.tabsMenu.on('hide',this.tabsMenu.removeAll, this.tabsMenu);
			}
			this.tabsMenu.removeAll();
			this.generateTabMenuItems();
			var target = Ext.get(e.getTarget());
			
			
			var xy = target.getXY();
			//Y param + 10
			xy[1] += 24;
			
			this.tabsMenu.showAt(xy);
		},
		// private	
		generateTabMenuItems : function() {
			var curActive = this.getActiveTab();
			var totalItems = this.items.getCount();
			if (totalItems > 10)  {
				var numSubMenus = Math.floor(totalItems / 10);
				var remainder   = totalItems % 10;
				
				// Loop through all of the items and create submenus in chunks of 10
				for (var i = 0 ; i < numSubMenus; i++) {
					var curPage = (i + 1) * 10;
					var menuItems = [];
					
					
					for (var x = 0; x < 10; x++) {				
						index = x + curPage - 10;
						var item = this.items.get(index);
						menuItems.push(this.autoGenMenuItem(item));
					}
					
					this.tabsMenu.add({
						text : 'Tabs '  + (curPage - 9) + ' - ' + curPage,
						menu : menuItems
					});
				
				}
				// remaining items
				if (remainder > 0) {
					var start = numSubMenus * 10;
					menuItems = [];
					for (var i = start ; i < totalItems; i ++ ) {					
						var item = this.items.get(i);
						menuItems.push(this.autoGenMenuItem(item));
					
					}
					this.tabsMenu.add({
						text : 'Tabs ' + start + ' - rest',
						menu : menuItems
					});			
				}
			}
			else {
				this.items.each(function(item) {
          //Changed by Stelios
					//if (item.id != curActive.id && ! item.hidden) {
						this.tabsMenu.add({
							text      : item.title,
							handler   : this.showTabFromMenu,
							scope     : this,
							disabled  : item.disabled,
							tabToShow : item
						});
					//Changed by Stelios
          //}
				}, this);
			}	
		},
		// private
		autoGenMenuItem : function(item) {
			return {
				text      : item.title,
				handler   : this.showTabFromMenu,
				scope     : this,
				disabled  : item.disabled,
				tabToShow : item
			}
		
		},
		// private
		showTabFromMenu : function(menuItem) {
			this.setActiveTab(menuItem.tabToShow);
		}	
	
	
	}	
});
﻿/*
  * Ext.ux.DatePickerPlus  Addon
  * Ext.ux.form.DateFieldPlus  Addon  
  *
  * @author    Marco Wienkoop (wm003/lubber)
  * @copyright (c) 2008, Marco Wienkoop (marco.wienkoop@lubber.de) http://www.lubber.de
  *
  * @class Ext.ux.DatePickerPlus
  * @extends Ext.DatePicker
  *
  * v.1.4 Beta
  *
  * @class Ext.ux.form.DateFieldPlus
  * @extends Ext.form.DateField
  *
  
  You need at least ExtJS 2.0.2 or higher 
  
Also adds Ext.util.EasterDate
	Calculates the Date-Object of easter-sunday of a given year

-----------------------------------------------------------------------------------------------------
-- DatePickerPlus Extension based on 4 contributed extensions from the ext-forum
-- and of course the original Datepicker from the awesome ExtJS Javascript Library
-----------------------------------------------------------------------------------------------------
-- (1) Multimonth calendar extension (enhanced integration) 
-- (2) Datepicker extension for multiple day/week/month selection (basic idea adopted)
-- (3) Weeknumber display (enhanced integration)
-- (4) XDateField with configurable submitFormat (full integration)
-- using my own getFirstDateOfWeek routine as it is more flexible for choosing which day is the first day of a week (in some countries its sunday, not monday!)
-----------------------------------------------------------------------------------------------------
-- (2) (multimonth calendar)
-- Author: aungii
-- Source: http://extjs.com/forum/showthread.php?t=20597
--
-- (2)  (multiple day/week/month selection)
-- Author: cocorossello / stevenvegt
-- Source: http://extjs.com/forum/showthread.php?t=22473
--
-- (3) (weeknumber display)
-- Author: eni.kao
-- Source: http://extjs.com/forum/showthread.php?t=15635
--
-- (4) (XDateField with configurable submitFormat)
-- Author: jsakalos
-- Source: http://extjs.com/forum/showthread.php?t=25900



 * @license  licensing of Ext.ux.DatePickerPlus and Ext.ux.form.DateFieldPlus depends of the underlying ExtJS Framework Version
 *
 * If you use ExtJS <= 2.0.2 Ext.ux.DatePickerPlus and Ext.ux.form.DateFieldPlus are licensed under the terms of the
 * LGPL v3
 * License details: http://www.gnu.org/licenses/lgpl.html
 * 
 * If you use ExtJS >= 2.1 Ext.ux.DatePickerPlus and Ext.ux.form.DateFieldPlus are licensed under the terms of the
 * GPL v3
 * License details: http://www.gnu.org/licenses/gpl.html

 Commercial License available! See http://www.lubber.de/extjs/datepickerplus for more info

* Donations are always welcome :)
* Any amount is greatly appreciated and will help to continue on developing ExtJS Widgets
*
* You can donate via PayPal to donate@lubber.de
*
	This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>
	
 * This Addon requires the ExtJS Library, which is distributed under the terms of the GPL v3 (from V2.1)
 * See http://extjs.com/license for more info
 



  
Revision History:
v.1.4 Beta [2009/07/03]
- checked to work with ExtJS 3.0-RC3
- checked to work with ExtJS 2.2.1
- support of jsondates  (e.g. "2008-08-04T12:22:00") in setEventDates, setSelectedDates, setAllowedDates,setMindate and setMaxdate
- some Code optimizations
- corrected holidays in german locale
- added events beforedateclick, beforeweekclick and beforemonthclick
- added dutch locale (thanks to walldorff)
- added french locale (thanks to dubdub)
- added Norwegian bokmål locale (thanks to Alex Brasetvik)
- added spanish locale (thanks to erzsebet)
- added version config
- added config prevNextDaysView ("mark","nomark",false) to disable automatic selection/view of selected days of current months in previous and next month, so only the current months days are selectable (suggested by sirtimbly)
- BUGFIX: select-event on datefieldplus was added again each time the trigger has been clicked
- BUGFIX: Fix for updatehidden in case of multiselection (thanks to Hunyi)
- BUGFIX: do not handle dateselection when disabled-property is set

v.1.3 [2008/08/05]
- Support of ExtJS 2.2
- Adopted new config items from 1.2 to DateFieldPlus also

v.1.2 [2008/08/04]
- support "allowOtherMenus" Config for DateFieldPlus
- datefieldplus can be hidden by clicking the triggerbutton again in cases hiding by clicking outside isn't possible
- added config "styleDisabledDates" to be able to set custom style dates (eventdates/weekends..) on disabled dates also (suggested by descheret)
- added config "eventDatesSelectable" to disble event-dates selection if desired (even if the dates are not disabled at all) (thanks to descheret)
- added config "disableSingleDateSelection" to force user to use week- or monthselection only (suggested by chemist458)
- added config "stayInAllowedRange" when setting minDate/maxDate, this will prevent to change months outside the allowed daterange  (suggested by descheret)
- added config "summarizeHeader" to add an optional global header when using multimonth display containing the month range (e.g. january 2008-october 2008)
- added italian locale (thanks to andreabat)
- BUGFIX: setMinDate/MaxDate/DateLimits did not update the viewport properly

V1.1 Final [2008/06/12]
- added config "allowMouseWheel" to generally turn on/off Mousewheelsupport (suggested by boraldo)
- added event "beforemousewheel" to be able to temporary disable the mousewheel if desired
- added event "beforemaxdays" to be able to cancel the default MessageBox but do something on your own instead
- Implemented fix for xdatefield code to support applyTo Config (thanks to mystix)
- updated russian locale (thanks to WhiteRussian)
- BUGFIX: updating eventclasses (and others) could result in wrong class-definition per cell (reported by aacraig)


V1.1 RC4 [2008/05/20]
- DateFieldPlus now also supports multiselection (thanks to Nohcs777)
- extended xdatefield to support multiselection
- "value" config for datefieldplus now also supports arrays in multiselection mode instead of just one date
- range selection is now also possible for a wider period than only the visible months (suggested by jo2008)
- updated xdatefield code integration to disable/enable the hidden submitfield from datefieldplus also, if the mainformfield gets disabled/enabled
- improved xdatefield code to fill the hiddenField with a given value at config time
- Improved some code-sections (mainly for respecting summertime changings when handling with Date.gettime())
- Corrected eventhandling on Datemenu and DateFieldPlus
- support for minDate and maxDate for Datefieldplus (as an alias for datepickers minValue and maxValue) to be more compatible to usual datepicker/datemenu config options
- added "multiSelectionDelimiter" config (datefieldplus and multiselection only)
- added "renderPrevNextButtons" config (if you want the user not to be able to change the month or force him to use the monthpicker)
- added "renderPrevNextYearButtons" config to display 2 small double-arrow buttons for changing next/previous year 
- added "nextYearText" config which will be displayed as tooltip on NextYear Button (updated locale!)
- added "prevYearText" config which will be displayed as tooltip on PrevYear Button (updated locale!)
- added "showActiveDate" will display the active Date to use keynavigation
- added "shiftSpaceSelect" if set to true (default) and showactivedate is set to true you can select dates on keynavigation by using shift+Space (because the space-key alone will select the today-date)
	if this is set to false , this behaviour reverses (shift+space selects today, space alone select the date under the shown activedate from keynavigation)
- added "disableMonthPicker" config
- added "disabledLetter" config to display e.g. a "X" instead of the daynumber if a date is disabled. (default false)
- added event "beforeyearchange"
- added event "afteryearchange"
- added russian locale (thanks to WhiteRussian)
- UP/DOWN/LEFT/RIGHT Keynavigation is now only available if showActiveDate is set to true and works much faster
- CTRL+UP/DOWN for year changing is now only available if either disableMonthPicker is false or renderPrevNextYearButtons is true
- CTRL+LEFT/RIGHT for month changing is now only available if either disableMonthPicker is false or renderPrevNextButtons is true
- BUGFIX: setEventDates did not update the viewport (reported by aacraig)
- BUGFIX: Array-Cloning was done in a wrong way (reported by lpfLoveExt)
- BUGFIX: weekselection was wrong when a different startDay was given (reported by WhiteRussian)
- Minor Upgrade Version because of much added features instead of a bugfix-only release


V1.0 RC3 [2008/04/21]
- checked to work with ExtJS 2.1
- added config strictRangeSelect (suggested by sigaref)
- added config displayMask and displayMaskText to support update masking 
- added config defaultEventDatesText and defaultEventDatesCls. used if no text/cls-object is given in eventdates
- added Events "aftermonthchange" and "beforemonthchange" (fires everytime the first month changes (by monthpicker or prev/next-month buttons)
- added method setEventDates, to automatically transform given arrays/or objects to working functions, if not already specified
- BUGFIX: range selection over specific months was incorrect

V1.0 RC2 [2008/04/10]
- BUGFIX: typo in DateFieldPlus corrected (reported by sigaref)

V1.0 RC1 [2008/04/10]
- BUGFIX: Undo-Function works again
- BUGFIX: Config items allowedDates and allowedDatesText had no effect on DateFieldPlus

V0.9 Beta 9 [2008/04/09]
- Added config items allowedDates and allowedDatesText
- Added method setAllowedDates()

V0.9 Beta 8 [2008/04/09]
- BUGFIX: setSelectedDates had another BUG...(thanks to wehtam for reporting!)

V0.9 Beta 7 [2008/04/08]
- added the state of the afterdateclick to examine, if the date was selected or unselected, same with week/month
- added option to cleanSelectedDates to not update the picker (e.g. to immediatly add dates manually by setSelectedDates(that would call update twice)
- added option to setSelectedDates to clean the selectedDates before setting the new once and to not update the picker
- BUGFIX: setSelectedDates did not work properly

V0.9 Beta 6 [2008/04/08]
- Added method clearSelectedDates() (suggested by wehtam)
- Added method setSelectedDates() (suggested by wehtam)
- Changes eventtriggering for afterdateclick. It now will always be fired when a date is clicked . Regardless, whether multiSelection is enabled or not.
- BUGFIX: Given listeners to DateFieldPlus have been ignored (reported by Richie1985)

V0.9 Beta 5 [2008/04/07]
- Added method setDateLimits() to change minDate and maxDate at once at runtime
- BUGFIX: Range selection by using the SHIFT-Key for a range more than one month, did not select some remaining days at the end of the range (reported by Spirit)

V0.9 Beta 4 [2008/04/06]
- Added method setMinDate() to change the minDate at runtime and immediatly update the datepicker
- Added method setMaxDate() to change the maxDate at runtime and immediatly update the datepicker
- BUGFIX: hidden submitformat Field had same name as original field, thus confuses IE with duplicate id/name. if name has not been specified in the config or is same as id datefieldplus will add a suffix to the hiddenfield (default "-format"). this field holds the custom submitFormat value

V0.9 Beta 3 [2008/04/06]
- Added xtype "datefieldplus"
- BUGFIX: DateFieldPlus accidently had renderTodayButton set to false by default...

V0.9 Beta 2 [2008/04/06]
- BUGFIX: Width on DateMenu and DateFieldPlus was broken in Firefox 3 (tested on latest Beta 5) (reported by ludoo)
- BUGFIX: Some old testpath in generating transparent GIF images was left in the code and has been deleted now (reported by sanjshah)
- Added new config options
"disablePartialUnselect" : Boolean/String (default true) (suggested by DVSDevise)
When multiselecting whole months or weeks, already selected days within this week/month will _not_ get unselected anymore. Set this to false, if you want them to get unselected.
Note: When the _whole set_ of the month/week are already selected, they get _all_ unselected anyway.

"renderOkUndoButtons" : Boolean (default true) (suggested by jsakalos)
If set to false, the OK- and Undo-Buttons will not be rendered on Multiselection Calendars
This way any selected Date will be immediatly available in the "selectedDates" Array. If used together with DateMenu or DateFieldPlus you need to click outside the Calendar to make it disappear or press Return (if calendar got focus...)
Works only if multiSelection is set to true

"renderTodayButton" : Boolean (default true) (suggested by jsakalos)
Whether the Today-Button should be rendered


V0.9 Beta [2008/04/05]
Initial Release:
Joined the extensions together nicely and added even more features:
- fixed some bugs/improved the original extensions a bit
- works on Original DateMenu and DateField (Ext.ux.form.DateFieldPlus) also
- Definable Multimonths (rows,amount,fill..)
- Custom CSS for definable days
- Weeknumber Display
- Weekend CSS Styling
- National Holidays CSS Styling
- Quicktip support
- Function based custom displayed days
- Multiselection support by CTRL-Key to add single days (when clicked on a date)
- Multiselection support by CTRL-Key to add single weeks (when clicked on a weeknumber)
- Multiselection support by CTRL-Key to add single months (when clicked on the weeknumber header)
- Multiselection support by SHIFT-Key to add a range of days depending on the lastclicked day  (when clicked on a single date)
- returned the prev/next monthbuttons to the monthlabelrow
- implemented mousewheel-event again for comfortable increasing/decreasing months
- implemented monthpicker again to comfortably select the starting month. the monthpicker is rendered on the very first monthname so with only 1 month given, it acts just like the original datepicker
- added quick dayselection routine without calling update() every time. MUCH faster selection, especially when using huge multimonth calendars!
- added "OK"- and "Undo"-Buttons when multiSelection is set to true
- unneccessary renderupdate trigger eliminated (performance-leak on huge multimonthcalendars!) (setvalue-function changed, much more faster now)
- prevented opening a new browsertab in IE7/IE8 when CTRL multiselecting (occured in original multimonth calendar extension and datepicker also if clicked on an empty area within the cell) (default behaviour for a-tags, prevented this by CSS)
- extend keynavigation (RETURN=ok-Button, CTRL as usual)
- added Tooltip functionality to DateFieldPlus just like Buttons (tooltip show on triggerbutton only, this way invalidtext tooltips stay intact)


- Tested in FF2/3/3.5,IE6/7/8,Op9/10b,Saf3/4(Win),Chrome2/3

- Default Language is (of course) english (including US Holidays!)
- Current available localization files (including Holidays):
	german
	russian
	italian
	dutch
	french
	norwegian
	spanish
	english (for your own translations)

Create a copy of ext.ux.datepickerplus-lang-en.js and change it to your language settings to get this widget easily translated
Be sure to include it AFTER the datepickerwidget!

--- See Release-Notes for Full API Documentation --- 

ROAD MAP:

v1.5 (~ Summer/Autumn 2009)
- add an additional event when the Ok-button is clicked
- support shiftclick without deleting all previous selected dates
- separate method to add/remove an eventdate or an array of eventdates without the need to supply the full set of eventdates
- same for disableddates/alloweddates/selecteddates (creating something like addAllowedDates/removeAllowedDates)
- support of multiple events at the same date
- support of month-gaps (e.g. display every 3rd Month only)
- support of descending month display on multimonths and navigation
- give eventdates tooltips more priority or merge them with the today/holidays tooltips (suggested by RuiDC)
- turned today button into cyclebutton to be able to also use "Begin of year" "next decade"...(idea from Peter seliger -> http://extjs.com/forum/showthread.php?t=61645)
- added "resizable" to support resizing of datepicker when displayed as datemenu or datefield and automatically create more/less months and adjust noOfMonth, noOfMonthPerRow
- support selection of all weekdays per month by clicking on the apropriate weekday shortcut column header
- support hovering a full week/month/days when moving the mouse over weekday/weeknumber/weeknumberheader
- support dateranges for eventdates

v1.6/2.0 (~ Winter 2009/Spring 2010)
- change monthselection to combobox selection of month and year as an option
- implement time selection also like http://extjs.com/forum/showthread.php?p=170472#post170472
- use the spinner plugin for above selections if available (or integrate it) or combobox instead (?)
- optional combobox as an alternative to the monthpicker with a given range of previous/next months to select from
- context menu to select predefined dates (12 months ago, next 3 thursdays, etc...thinking of integrating datejs for this ?)
- usage of window.createPopup for IE only to render more quickly (? based on http://extjs.com/forum/showthread.php?t=33331)
- create a new form.datelist item (select-box with multiselect and no dropdown) component to be able to display multiselected dates like datefield after selection
- add config to define the sorting of prevnext(year) buttons (currently the prevnextyear buttons are rendered inside as the usual prevnextmonth buttons are outside anytime)
- support drag selection of days/weeks/months (like in dataview example)
- extend property grid/create plugin to use datepickerplus aswell for date-fields in there
- show monthpicker only (requested in http://extjs.com/forum/showthread.php?t=13911)
- full support of editor grids
- try to speed up rendering-performance, when clicking on next/previous month (update()) and on startup (onRender()) (IE and FF are much slower than Opera(which is equal slow, but renders immediatly every part of the calendar while IE/FF are rendering the complete calender at the end). Safari3.2(Win) and Chrome render very fast by now!
																																																																										

* ? BROWSER BUGS ? *
- FF2: CTRL-multiselect clicking leaves an odd blue frame on the cell when clicking in empty areas of the cell (the CSS-Trick for preventing new TABs in IE does not work here...yet :)


*/


Date.prototype.getFirstDateOfWeek = function(startDay) {
//set startDay to Sunday by default
	if (typeof startDay === "undefined") {
		startDay=(Ext.DatePicker?Ext.DatePicker.prototype.startDay:0);
	}
	var dayDiff = this.getDay()-startDay;
	if (dayDiff<0) {
		dayDiff+=7;
	}
	return this.add(Date.DAY,-dayDiff);
};

Array.prototype.sortDates = function() {
	return this.sort(function(a,b){
		return a.getTime() - b.getTime();		
	});
};


if (!Ext.util.EasterDate) {
	Ext.util.EasterDate = function(year, plusDays) {
		if (typeof year === "undefined") {
			year = new Date().getFullYear();
		}
		year = parseInt(year,10);
	
		if (typeof plusDays === "undefined") {
			plusDays = 0;
		}
		plusDays = parseInt(plusDays,10);
		
	//difference to first sunday after first fullmoon after beginning of spring
		var a = year % 19;
		var d = (19 * a + 24) % 30;
		var diffDay = d + (2 * (year % 4) + 4 * (year % 7) + 6 * d + 5) % 7;
		if ((diffDay == 35) || ((diffDay == 34) && (d == 28) && (a > 10))) {
			diffDay -= 7;
		}
	
		var EasterDate = new Date(year, 2, 22);	//beginning of spring
		EasterDate.setTime(EasterDate.getTime() + 86400000 * diffDay + 86400000 * plusDays);
		return EasterDate;
	};
}


Ext.namespace('Ext.ux','Ext.ux.form');

/**
 * @class Ext.ux.DatePickerPlus
 * @extends Ext.DatePicker
 * @constructor
  * @param {Object} config The config object
 */
Ext.ux.DatePickerPlus = Ext.extend(Ext.DatePicker, {
								   
	version: "1.4",
    /**
    * @cfg {Number} noOfMonth
    * No of Month to be displayed
	* Default to 1 so it will displayed as original Datepicker 
    */
    noOfMonth : 1,
	/**
    * @cfg {Array} noOfMonthPerRow
    * No. Of Month to be displayed in a row
    */    
    noOfMonthPerRow : 3,
    /**
    * @cfg {Array} fillupRows
    * eventually extends the number of months to view to fit the given row/column matrix and avoid odd white gaps (especially when using as datemenu fill will lookup ugly when set to false
    */    
	fillupRows : true,
    /**
    * @cfg {Function returns Array} eventDates
    * a Function which returns an Object List of Dates which have an event (show in separate given css-class)
	* This function is called everytime a year has changed when rendering the calendar
	* attributes are date, text(optional) and cls(optional)
	* Its implemented as a function to be able to create cycling days for year
	* example
	* eventDates: function(year) {
		var myDates = 
		[{
			date: new Date(2008,0,1), //fixed date marked only on 2008/01/01
			text: "New Year 2008",
			cls: "x-datepickerplus-eventdates"			
		},
		{
			date: new Date(year,4,11), //will be marked every year on 05/11
			text: "May 11th, Authors Birthday (Age:"+(year-1973)+")",
			cls: "x-datepickerplus-eventdates"
		}];
		return myDates;
	*
	*
    */    
    eventDates : function(year) {
		return [];
	},
	
	styleDisabledDates: false,
	eventDatesSelectable : true,

	defaultEventDatesText : '',
	defaultEventDatesCls : 'x-datepickerplus-eventdates',
	
	setEventDates : function(edArray,update) {
		if (typeof update === "undefined") {
			update=true;
		}
		this.edArray = [];
		for (var i=0,il=edArray.length;i<il;++i) {
			if (Ext.isDate(edArray[i])) {
				this.edArray.push({
					date:edArray[i],
					text:this.defaultEventDatesText,
					cls:this.defaultEventDatesCls
				});
			}
			else if (edArray[i].date) {
				edArray[i].date = this.jsonDate(edArray[i].date);
				this.edArray.push(edArray[i]);				
			}
		}
		this.eventDates = function(year) {
			return this.edArray;
		};
		if (this.rendered && update) {
			this.eventDatesNumbered = this.convertCSSDatesToNumbers(this.eventDates(this.activeDate.getFullYear()));
			this.update(this.activeDate);
		}
	},
	/**
	 * @cfg {Boolean} eventDatesRE
	 * To selected specific Days over a regular expression
	 */
	eventDatesRE : false,
	
	/**
	 * @cfg {String} eventDatesRECls
	 * Specifies what CSS Class will be applied to the days found by "eventDatesRE"
	 */
	eventDatesRECls : '',
	
	/**
	 * @cfg {String} eventDatesRECls
	 * Specifies what Quicktip will be displayed to the days found by "eventDatesRE"
	 */
	eventDatesREText : '',	
	
	/**
	 * @cfg {Boolean} showWeekNumber
	 * Whether the week number should be shown
	 */
	showWeekNumber : true,
	/**
	 * @cfg {String} weekName
	 * The short name of the week number column
	 */
	weekName : "Wk.",
	/**
	 * @cfg {String} selectWeekText
	 * Text to display when hovering over the weekNumber and multiSelection is enabled
	 */
	selectWeekText : "Click to select all days of this week",
	/**
	 * @cfg {String} selectMonthText
	 * Text to display when hovering over the MonthNumber and multiSelection is enabled
	 * Whole Month selection is disabled when displaying only 1 Month (think twice..)	 
	 */
	selectMonthText : "Click to select all weeks of this month",

	/**
	 * @cfg {String} multiSelection
	 * whether multiselection of dates is allowed. selection of weeks depends on displaying of weeknumbers
	 */
	multiSelection : false,
	/**
	 * @cfg {String} multiSelectByCTRL
	 * whether multiselection is made by pressing CTRL (default behaviour, a single click without CTRL will set the selection list to the last selected day/week) or without (ever click a day is added/removed)
	 */
	
	multiSelectByCTRL : true,

/**
    * @cfg {Array of Dateobjects} selectedDates
    * List of Dates which have been selected when multiselection is set to true (this.value only sets the startmonth then)
    */    
    selectedDates : [],


/**
    * @cfg {String/Bool} prevNextDaysView
    * "mark" selected days will be marke in prev/next months also
	* "nomark" will not be marked and are not selectable
	* false: will hide them, thus are not selectable too
    */    
	prevNextDaysView: "mark",
	
	/**
    * @cfg {Array of Dateobjects} preSelectedDates
    * contains the same at selection runtime (until "OK" is pressed)
	*/
	preSelectedDates : [], 

	/**
    * @cfg {Object} lastSelectedDate
    * contains the last selected Date or false right after initializing the object..
    */    
	lastSelectedDate : false, 

	/**
	 * @cfg {Array} markNationalHolidays
	 * trigger to add existing nationalHolidays to the eventdates list (nationalholidays can be changed in locale files, so these are independant from custom event Dates
	 */
	markNationalHolidays :true,

	/**
	 * @cfg {String} nationalHolidaysCls
	 * CSS Class displayed to national Holidays if markNationalHolidays is set to true
	 */
	nationalHolidaysCls : 'x-datepickerplus-nationalholidays',
	
	/**
    * @cfg {Function} nationalHolidays
    * returns an Array-List of national Holiday Dates which could by marked with separate given CSS. Will be shown if markNationalHolidays is set to true
	* Change this in your local file to override it with you country's own national Holiday Dates
	*
	* if markNationalHolidays is set to true, a new instance of this array (and thus recalculation of holidays) will be generated at month update, if year has been changed from last drawn month.
	*
    */  

	nationalHolidays : function(year) {
		year = (typeof year === "undefined" ? (this.lastRenderedYear ? this.lastRenderedYear : new Date().getFullYear()) : parseInt(year,10));
//per default the US national holidays are calculated (according to http://en.wikipedia.org/wiki/Public_holidays_of_the_United_States) 
//override this function in your local file to calculate holidays for your own country
//but remember to include the locale file _AFTER_ datepickerplus !
		var dayOfJan01 = new Date(year,0,1).getDay();
		var dayOfFeb01 = new Date(year,1,1).getDay();
		var dayOfMay01 = new Date(year,4,1).getDay();
		var dayOfSep01 = new Date(year,8,1).getDay();
		var dayOfOct01 = new Date(year,9,1).getDay();
		var dayOfNov01 = new Date(year,10,1).getDay();		

		var holidays = 
		[{
			text: "New Year's Day",
			date: new Date(year,0,1)
		},
		{
			text: "Martin Luther King Day", //(every third monday in january)
			date: new Date(year,0,(dayOfJan01>1?16+7-dayOfJan01:16-dayOfJan01))
		},
		{
			text: "Washington's Birthday", //(every third monday in february)
			date: new Date(year,1,(dayOfFeb01>1?16+7-dayOfFeb01:16-dayOfFeb01))
		},
		{
			text: "Memorial Day",//(last Monday in May)
			date: new Date(year,4,(dayOfMay01==6?31:30-dayOfMay01))
		},
		{
			text: "Independence Day",
			date: new Date(year,6,4)
		},
		{
			text: "Labor Day",//(every first monday in September)
			date: new Date(year,8,(dayOfSep01>1?2+7-dayOfSep01:2-dayOfSep01))
		},
		{
			text: "Columbus Day",//(every second monday in october)
			date: new Date(year,9,(dayOfOct01>1?9+7-dayOfOct01:9-dayOfOct01))
		},
		{
			text: "Veterans Day",
			date: new Date(year,10,11)
		},
		{
			text: "Thanksgiving Day",//(Fourth Thursday in November)
			date: new Date(year,10,(dayOfNov01>4?26+7-dayOfNov01:26-dayOfNov01))
		},
		{
			text: "Christmas Day",
			date: new Date(year,11,25)
		}];
		
		return holidays;
	},
	
	/**
	 * @cfg {Boolean} markWeekends
	 * whether weekends should be displayed differently
	 */
	markWeekends :true,
	/**
	 * @cfg {String} weekendCls
	 * CSS class to use for styling Weekends
	 */
	weekendCls : 'x-datepickerplus-weekends',
	/**
	 * @cfg {String} weekendText
	 * Quicktip for Weekends
	 */
	weekendText :'',
	/**
	 * @cfg {Array} weekendDays
	 * Array of Days (according to Days from dateobject thus Sunday=0,Monday=1,...Saturday=6)
	 * Additionally to weekends, you could use this to display e.g. every Tuesday and Thursday with a separate CSS class
	 */
	weekendDays: [6,0],
	
	/**
	 * @cfg {Boolean} useQuickTips
	 * Wheter TIps should be displayed as Ext.quicktips or browsercontrolled title-attributes
	 */
	useQuickTips : true,
	
	/**
	 * @cfg {Number} pageKeyWarp
	 * Amount of Months the picker will move forward/backward when pressing the pageUp/pageDown Keys
	 */
	pageKeyWarp : 1,

	/**
	 * @cfg {Number} maxSelectionDays
	 * Amount of Days that are selectable, set to false for unlimited selection
	 */
	maxSelectionDays : false,
	
	maxSelectionDaysTitle : 'Datepicker',
	maxSelectionDaysText : 'You can only select a maximum amount of %0 days',
	undoText : "Undo",
	
	
	/**
	 * @cfg {Boolean} stayInAllowedRange
	 * used then mindate/maxdate is set to prevent changing to a month that does not contain allowed dates
	 */
	stayInAllowedRange: true,

	/**
	 * @cfg {Boolean} summarizeHeader
	 * displays the from/to daterange on top of the datepicker
	 */
	summarizeHeader:false,
	
	/**
	 * @cfg {Boolean} resizable
	 * Whether the calendar can be extended with more/less months by simply resizing it like window
	 */
	resizable: false,
	
	/**
	 * @cfg {Boolean} renderOkUndoButtons
	 * If set to true, the OK- and Undo-Buttons will not be rendered on Multiselection Calendars
	 */
	renderOkUndoButtons : true,

	/**
	 * @cfg {Boolean} renderTodayButton
	 * Whether the Today Button should be rendered
	 */
	renderTodayButton : true,
	/**
	 * @cfg {Boolean} disablePartialUnselect
	 * When multiselecting whole months or weeks, already selected days within this week/month will _not_ get unselected anymore. Set this to false, if you want them to get unselected.
	 * Note: When the _whole set_ of the month/week are already selected, they get _all_ unselected anyway.
	 */
	disablePartialUnselect: true,
	
	allowedDates : false,
	allowedDatesText : '',

	strictRangeSelect : false,

	/**
	 * @cfg {Boolean/Number} displayMask
	 * As huge multimonth calendars can take some updating time this will display a mask when the noOfMonth property is higher than the given value in displayMask.
	 * Set to false to never display the mask
	 * default is 3
	 */
	displayMask:3,
	
	displayMaskText: 'Please wait...',
	
	renderPrevNextButtons: true,
	renderPrevNextYearButtons: false,
	disableMonthPicker:false,
	
	nextYearText: "Next Year (Control+Up)",
	prevYearText: "Previous Year (Control+Down)",
	
	showActiveDate: false,
	shiftSpaceSelect: true,
	disabledLetter: false,
	
	allowMouseWheel: true,
	
//this is accidently called too often in the original (when hovering over monthlabel or bottombar..there is no need to update the cells again and just leaks performance)
	focus: Ext.emptyFn,
	
	initComponent : function(){
		Ext.ux.DatePickerPlus.superclass.initComponent.call(this);
		this.noOfMonthPerRow = this.noOfMonthPerRow > this.noOfMonth ?this.noOfMonth : this.noOfMonthPerRow;
        this.addEvents(
            /**
             * @event beforeyearchange
             * Fires before a new year is selected (or prevYear/nextYear buttons)
             * @param {DatePicker} this
             * @param {oldyearnumber} dates The previous selected year
             * @param {newyearnumber} dates The new selected year
             */
            'beforeyearchange',
            /**
             * @event afteryearchange
             * Fires before a new year is selected (by prevYear/nextYear buttons)
             * @param {DatePicker} this
             * @param {oldyearnumber} dates The previous selected year		 
             * @param {newyearnumber} dates The new selected year
             */
            'afteryearchange',
            /**
             * @event beforemonthchange
             * Fires before a new startmonth is selected (by monthpicker or prev/next buttons)
             * @param {DatePicker} this
             * @param {oldmonthnumber} dates The previous selected month	 
             * @param {newmonthnumber} dates The new selected month
             */
            'beforemonthchange',
            /**
             * @event aftermonthchange
             * Fires before a new startmonth is selected (by monthpicker or prev/next buttons)
             * @param {DatePicker} this
             * @param {oldmonthnumber} dates The previous selected month			 
             * @param {newmonthnumber} dates The new selected month
             */
            'aftermonthchange',
            /**
             * @event beforemonthclick
             * Fires before a full month is (un)selected
             * @param {DatePicker} this
             * @param {monthnumber} dates The selected month
             */
            'beforemonthclick',
            /**
             * @event beforeweekclick
             * Fires before a week is (un)selected
             * @param {DatePicker} this
             * @param {dateobject} dates The first date of selected week
             */
            'beforeweekclick',
            /**
             * @event beforeweekclick
             * Fires before a single day is (un)selected
             * @param {DatePicker} this
             * @param {dateobject} dates The selected date
             */
            'beforedateclick',
            /**
             * @event aftermonthclick
             * Fires after a full month is (un)selected
             * @param {DatePicker} this
             * @param {monthnumber} dates The selected month
             */
            'aftermonthclick',
            /**
             * @event afterweekclick
             * Fires after a week is (un)selected
             * @param {DatePicker} this
             * @param {dateobject} dates The first date of selected week
             */
            'afterweekclick',
            /**
             * @event afterweekclick
             * Fires after a single day is (un)selected
             * @param {DatePicker} this
             * @param {dateobject} dates The selected date
             */
            'afterdateclick',
            /**
             * @event undo
             * Fires when Undo Button is clicked on multiselection right before deleting the preselected dates
             * @param {DatePicker} this
             * @param {Array} dates The preselected Dates
             */
            'undo',
            /**
             * @event beforemousewheel
             * Fires before a mousewheel event should be triggered return false in your function to disable the month change
             * @param {DatePicker} this
             * @param {object} event object
             */
			'beforemousewheel',
            /**
             * @event beforemousewheel
             * Fires before the default message box appears when max days have been reached
			 * return false to cancel the messagebox (to do something on your own)
             * @param {DatePicker} this
             * @param {object} event object
             */
			'beforemaxdays');
	},  
	
	activeDateKeyNav: function(direction) {
		if (this.showActiveDate) {
			this.activeDate = this.activeDate.add("d", direction);
			var adCell = this.activeDateCell.split("#");
			var tmpMonthCell = parseInt(adCell[0],10);
			var tmpDayCell = parseInt(adCell[1],10);
			var currentGetCell = Ext.get(this.cellsArray[tmpMonthCell].elements[tmpDayCell]);
//cursor gets out of visible range?
			if (	(tmpDayCell+direction>41 && tmpMonthCell+1>=this.cellsArray.length)	||
					(tmpDayCell+direction<0 && tmpMonthCell-1<0)	){
				this.update(this.activeDate);
			}
			else {
				currentGetCell.removeClass("x-datepickerplus-activedate");
				tmpDayCell+=direction;
				if (tmpDayCell>41) {
					tmpDayCell-=42;
					tmpMonthCell++;
				}
				else if (tmpDayCell<0) {
					tmpDayCell+=42;
					tmpMonthCell--;
				}
				currentGetCell = Ext.get(this.cellsArray[tmpMonthCell].elements[tmpDayCell]);
				currentGetCell.addClass("x-datepickerplus-activedate");
				this.activeDateCell = tmpMonthCell+"#"+tmpDayCell;
			}
		}
	},

    handleMouseWheel : function(e){
        if(this.fireEvent("beforemousewheel", this,e) !== false){
			var oldStartMonth = (this.activeDate ? this.activeDate.getMonth() : 99);
			var oldStartYear = (this.activeDate ? this.activeDate.getFullYear() : 0);			
			Ext.ux.DatePickerPlus.superclass.handleMouseWheel.call(this,e);
			var newStartMonth = (this.activeDate ? this.activeDate.getMonth() : 999);
			var newStartYear = (this.activeDate ? this.activeDate.getFullYear() : 9999);
			if (oldStartMonth!=newStartMonth) {
				this.fireEvent("aftermonthchange", this, oldStartMonth, newStartMonth);
			}
			if (oldStartYear!=newStartYear) {
				this.fireEvent("afteryearchange", this, oldStartYear, newStartYear);
			}
		}
	},
	
    // private
	onRender : function(container, position){
		if (this.noOfMonthPerRow===0) {
			this.noOfMonthPerRow = 1;
		}
		if (this.fillupRows && this.noOfMonthPerRow > 1 && this.noOfMonth % this.noOfMonthPerRow!==0) {
			this.noOfMonth+= (this.noOfMonthPerRow - (this.noOfMonth % this.noOfMonthPerRow));
		}
		var addIEClass = (Ext.isIE?' x-datepickerplus-ie':'');
		var m = ['<table cellspacing="0"',(this.multiSelection?' class="x-date-multiselect'+addIEClass+'" ':(addIEClass!==''?'class="'+addIEClass+'" ':'')),'>'];

		m.push("<tr>");

		var widfaker = (Ext.isIE?'<img src="'+Ext.BLANK_IMAGE_URL+'" />':'');
		var weekNumberQuickTip = (this.multiSelection ? (this.useQuickTips? ' ext:qtip="'+this.selectWeekText+'" ' :' title="'+this.selectWeekText+'" ') : '');
//as weekends (or defined weekly cycles) are displayed on every month at the same place, we can render the quicktips here to save time in update process
		var weekEndQuickTip = (this.markWeekends && this.weekendText!==''? (this.useQuickTips? ' ext:qtip="'+this.weekendText+'" ' :' title="'+this.weekendText+'" '):'');


//calculate the HTML of one month at first to gain some speed when rendering many calendars
		var mpre = ['<thead><tr>'];
		if (this.showWeekNumber) {
			mpre.push('<th class="x-date-weeknumber-header"><a href="#" hidefocus="on" class="x-date-weeknumber" tabIndex="1"><em><span ',(this.multiSelection ? (this.useQuickTips? ' ext:qtip="'+this.selectMonthText+'" ' :' title="'+this.selectMonthText+'" ') : ''),'>' + this.weekName + '</span></em></a></th>');
		}
		
		var dn = this.dayNames;
		for(var i = 0; i < 7; ++i){
		   var d = this.startDay+i;
		   if(d > 6){
			   d = d-7;
		   }
			mpre.push('<th><span>', dn[d].substr(0,1), '</span></th>');
		}
		mpre.push('</tr></thead><tbody><tr>');

		if (this.showWeekNumber) {
			mpre.push('<td class="x-date-weeknumber-cell"><a href="#" hidefocus="on" class="x-date-weeknumber" tabIndex="1"><em><span ',weekNumberQuickTip,'></span></em></a></td>');
		}
		
		for(var k = 0; k < 42; ++k) {
			if(k % 7 === 0 && k > 0){
				if (this.showWeekNumber) {
					mpre.push('</tr><tr><td class="x-date-weeknumber-cell"><a href="#" hidefocus="on" class="x-date-weeknumber" tabIndex="1"><em><span ',weekNumberQuickTip,'></span></em></a></td>');
				} else {
					mpre.push('</tr><tr>');
				}
			}
			mpre.push('<td class="x-date-date-cell"><a href="#" hidefocus="on" class="x-date-date" tabIndex="1"><em><span ',(this.weekendDays.indexOf((k+this.startDay)%7)!=-1?weekEndQuickTip:''),'></span></em></a></td>');
		}
		mpre.push('</tr></tbody></table></td></tr></table></td>');
		var prerenderedMonth = mpre.join("");

		if (this.summarizeHeader && this.noOfMonth > 1) {
			m.push('<td align="center" id="',this.id,'-summarize" colspan="',this.noOfMonthPerRow,'" class="x-date-middle x-date-pickerplus-middle"></td></tr>');
			m.push("<tr>");
		}

		for(var x=0,xk=this.noOfMonth; x<xk; ++x) {            
            m.push('<td><table class="x-date-pickerplus',(x%this.noOfMonthPerRow===0?'':' x-date-monthtable'),(!this.prevNextDaysView?" x-date-pickerplus-prevnexthide":""),'" cellspacing="0"><tr>');
			if (x===0) {
				m.push('<td class="x-date-left">');
				if (this.renderPrevNextButtons) {
					m.push('<a class="npm" href="#" ',(this.useQuickTips? ' ext:qtip="'+this.prevText+'" ' :' title="'+this.prevText+'" '),'></a>');
				}
				if (this.renderPrevNextYearButtons) {
					m.push('<a class="npy" href="#" ',(this.useQuickTips? ' ext:qtip="'+this.prevYearText+'" ' :' title="'+this.prevYearText+'" '),'></a>');
				}
				m.push('</td>');
			}			
			else if (x==this.noOfMonthPerRow-1) {
				if (this.renderPrevNextButtons) {				
					m.push('<td class="x-date-dummy x-date-middle">',widfaker,'</td>');
				}
			}			
            m.push("<td class='x-date-middle x-date-pickerplus-middle",(x===0 && !this.disableMonthPicker ?" x-date-firstMonth":""),"' align='center'>");
			if (x>0 || this.disableMonthPicker) {
				m.push('<span id="',this.id,'-monthLabel', x , '"></span>');
			}
			m.push('</td>');
			if (x==this.noOfMonthPerRow-1)	{
				m.push('<td class="x-date-right">');
				if (this.renderPrevNextButtons) {				
					m.push('<a class="npm" href="#" ', (this.useQuickTips? ' ext:qtip="'+this.nextText+'" ' :' title="'+this.nextText+'" ') ,'></a>');
				}
				if (this.renderPrevNextYearButtons) {
					m.push('<a class="npy" href="#" ',(this.useQuickTips? ' ext:qtip="'+this.nextYearText+'" ' :' title="'+this.nextYearText+'" '),'></a>');
				}
				m.push('</td>');				
			}
			else if (x===0) {
				if (this.renderPrevNextButtons) {				
					m.push('<td class="x-date-dummy x-date-middle">',widfaker,'</td>');
				}
			}			
			
            m.push('</tr><tr><td',(x===0 || x==this.noOfMonthPerRow-1?' colspan="3" ':''),'><table class="x-date-inner" id="',this.id,'-inner-date', x ,'" cellspacing="0">');

			m.push(prerenderedMonth);
	
            if( (x+1) % this.noOfMonthPerRow === 0) {
                m.push("</tr><tr>");
            }            
        }
        m.push('</tr>');
		
		m.push('<tr><td',(this.noOfMonthPerRow>1?' colspan="'+this.noOfMonthPerRow+'"':''),' class="x-date-bottom" align="center"><div><table width="100%" cellpadding="0" cellspacing="0"><tr><td align="right" class="x-date-multiokbtn">',widfaker,'</td><td align="center" class="x-date-todaybtn">',widfaker,'</td><td align="left" class="x-date-multiundobtn">',widfaker,'</td></tr></table></div></td></tr>');
		
		m.push('</table><div class="x-date-mp"></div>');
        var el = document.createElement("div");
        el.className = "x-date-picker";
        el.innerHTML = m.join("");  

        container.dom.insertBefore(el, position);

        this.el = Ext.get(el);        
        this.eventEl = Ext.get(el.firstChild);

		if (this.renderPrevNextButtons) {
			var crl = new Ext.util.ClickRepeater(this.el.child("td.x-date-left a.npm"), {
				handler: this.showPrevMonth,
				scope: this,
				preventDefault:true,
				stopDefault:true
			});
	
			var crr = new Ext.util.ClickRepeater(this.el.child("td.x-date-right a.npm"), {
				handler: this.showNextMonth,
				scope: this,
				preventDefault:true,
				stopDefault:true
			});
		}
		
		if (this.renderPrevNextYearButtons) {
			var cryl = new Ext.util.ClickRepeater(this.el.child("td.x-date-left a.npy"), {
				handler: this.showPrevYear,
				scope: this,
				preventDefault:true,
				stopDefault:true
			});
	
			var cryr = new Ext.util.ClickRepeater(this.el.child("td.x-date-right a.npy"), {
				handler: this.showNextYear,
				scope: this,
				preventDefault:true,
				stopDefault:true
			});
		}
		if (this.allowMouseWheel) {
			this.eventEl.on("mousewheel", this.handleMouseWheel,  this);
		}

		if (!this.disableMonthPicker) {
	        this.monthPicker = this.el.down('div.x-date-mp');
	        this.monthPicker.enableDisplayMode('block');
		}


        var kn = new Ext.KeyNav(this.eventEl, {
            "left" : function(e){
                (!this.disabled && e.ctrlKey && (!this.disableMonthPicker || this.renderPrevNextButtons) ?
                    this.showPrevMonth() :
					this.activeDateKeyNav(-1));
            },

            "right" : function(e){
                (!this.disabled && e.ctrlKey && (!this.disableMonthPicker || this.renderPrevNextButtons) ?
                    this.showNextMonth() :
					this.activeDateKeyNav(1));
            },

            "up" : function(e){
                (!this.disabled && e.ctrlKey && (!this.disableMonthPicker || this.renderPrevNextYearButtons) ?
                    this.showNextYear() :
					this.activeDateKeyNav(-7));
            },

            "down" : function(e){
                (!this.disabled && e.ctrlKey && (!this.disableMonthPicker || this.renderPrevNextYearButtons) ?
                    this.showPrevYear() :
					this.activeDateKeyNav(7));
            },

            "pageUp" : function(e){
				if (!this.disabled) {
			        this.update(this.activeDate.add("mo", this.pageKeyWarp*(-1)));
				}
            },

            "pageDown" : function(e){
				if (!this.disabled) {				
			        this.update(this.activeDate.add("mo", this.pageKeyWarp));
				}
            },

            "enter" : function(e){
                e.stopPropagation();
				if (!this.disabled) {				
					if (this.multiSelection) {
						this.okClicked();
					}
					else {
						this.finishDateSelection(this.activeDate);
					}
				}
                return true;
            }, 
            scope : this 
        });

		if (!this.disableSingleDateSelection) {
			this.eventEl.on("click", this.handleDateClick,  this, {delegate: "a.x-date-date"});
		}
		if (this.multiSelection && this.showWeekNumber) {
			this.eventEl.on("click", this.handleWeekClick,  this, {delegate: "a.x-date-weeknumber"});
		}
        this.eventEl.addKeyListener(Ext.EventObject.SPACE, this.spaceKeyPressed,  this);		
        
        this.cellsArray = [];
        this.textNodesArray = [];
        this.weekNumberCellsArray = [];
        this.weekNumberTextElsArray = [];		
		this.weekNumberHeaderCellsArray = [];
	
		var cells,textNodes,weekNumberCells,weekNumberTextEls,weekNumberHeaderCells;
        for(var xx=0,xxk=this.noOfMonth; xx< xxk; ++xx) {
            cells = Ext.get(this.id+'-inner-date'+xx).select("tbody td.x-date-date-cell");
            textNodes = Ext.get(this.id+'-inner-date'+xx).query("tbody td.x-date-date-cell span");
            this.cellsArray[xx] = cells;
            this.textNodesArray[xx] = textNodes;
			if (this.showWeekNumber) {
				weekNumberCells = Ext.get(this.id+'-inner-date'+xx).select("tbody td.x-date-weeknumber-cell");
				weekNumberTextEls = Ext.get(this.id+'-inner-date'+xx).select("tbody td.x-date-weeknumber-cell span");				
				this.weekNumberCellsArray[xx] = weekNumberCells;
				this.weekNumberTextElsArray[xx] = weekNumberTextEls;				
				weekNumberHeaderCells = Ext.get(this.id+'-inner-date'+xx).select("th.x-date-weeknumber-header");
				this.weekNumberHeaderCellsArray[xx] = weekNumberHeaderCells;
			}
        }

//set the original monthpicker again to the first month only to be able to quickly change the startmonth		
		if (!this.disableMonthPicker) {
			this.mbtn = new Ext.Button({
				text: " ",
				tooltip: this.monthYearText,
				renderTo: this.el.child("td.x-date-firstMonth", true)			
			});
	
			this.mbtn.on('click', this.showMonthPickerPlus, this);
			this.mbtn.el.child(this.mbtn.menuClassTarget).addClass("x-btn-with-menu");
		}

//showtoday from Ext 2.2
		if (this.renderTodayButton || this.showToday) {
	        var today = new Date().dateFormat(this.format);			
			this.todayBtn = new Ext.Button({
				renderTo: this.el.child("td.x-date-bottom .x-date-todaybtn", true),
                text: String.format(this.todayText, today),
				tooltip: String.format(this.todayTip, today),
				handler: this.selectToday,
				scope: this
			});
		}
		
		if (this.multiSelection && this.renderOkUndoButtons) {
			this.OKBtn = new Ext.Button({
	            renderTo: this.el.child("td.x-date-bottom .x-date-multiokbtn", true),
				text: this.okText,
				handler: this.okClicked,
				scope: this
			});

			this.undoBtn = new Ext.Button({
	            renderTo: this.el.child("td.x-date-bottom .x-date-multiundobtn", true),
				text: this.undoText,
				handler: function() {
					if (!this.disabled) {
						this.fireEvent("undo", this, this.preSelectedDates);
						this.preSelectedDates = [];
						for (var i=0,il=this.selectedDates.length;i<il;++i) {
							this.preSelectedDates.push(this.selectedDates[i].clearTime().getTime());
						}
						this.update(this.activeDate);
					}
				},
				scope: this
			});
		}
		
//In development...
/*
		if (this.resizable) {		
			var resizer = new Ext.Resizable(this.el, {
				handles: 'all',
// at least one month should be displayed				
				minWidth:200,
				minHeight:300,
				maxWidth: 1000,
				maxHeight: 800,
				heightIncrement: 250,
				widthIncrement: 200,
				adjustments: 'auto',	
				transparent:true
			});
			resizer.on("resize", function(){
	//			alert("you resized the calendar,ouch!");
			},this);
		}
*/

		if(Ext.isIE){
            this.el.repaint();
        }
//preselect dates if given
		this.preSelectedDates = [];
		for(var sdc=0, sdcl=this.selectedDates.length; sdc < sdcl; ++sdc) {
		   this.preSelectedDates.push(this.selectedDates[sdc].clearTime().getTime());
		}

        this.update(this.value);
    },
	
	showMonthPickerPlus: function() {
		if (!this.disabled) {
			this.showMonthPicker();
		}
	},

//converts all custom dates to timestamps numbers for faster calculations and splits their attributes into separate arrays
	convertCSSDatesToNumbers : function(objarr) {
//date,text,class		
		var converted =  [[],[],[]];
		for (var i=0,il=objarr.length;i<il;++i) {
			converted[0][i] = objarr[i].date.clearTime().getTime();
			converted[1][i] = (objarr[i].text ? objarr[i].text : this.defaultEventDatesText);
			converted[2][i] = (objarr[i].cls ? objarr[i].cls : this.defaultEventDatesCls);
		}
		return converted;
	},

	clearSelectedDates : function(update) {
		if (typeof update === "undefined") {
			update=true;
		}
		this.selectedDates = [];
		this.preSelectedDates = [];
		if (this.rendered && update) {
			this.update(this.activeDate);
		}
	},
	
//support json dates
	jsonDate: function(dates) {
		if (!Ext.isArray(dates)) {
			if (typeof dates === "string") {
				return Date.parseDate(dates.replace(/T/," "),'Y-m-d H:i:s');
			}
		}
		else {
			for (var i=0,il=dates.length;i<il;i++) {
				if (typeof dates[i] === "string") {
					dates[i] = Date.parseDate(dates[i].replace(/T/," "),'Y-m-d H:i:s');
				}
			}
		}
		return dates;
	},
	
	setSelectedDates : function(dates,update) {
		if (typeof update === "undefined") {
			update=true;
		}
		dates = this.jsonDate(dates);
		if (!Ext.isArray(dates)) {
			dates = [dates];
		}
		var d, dt;
		for (var i=0,il=dates.length;i<il;++i) {
			d = dates[i];
			dt = d.clearTime().getTime();
			if (this.preSelectedDates.indexOf(dt)==-1) {
				this.preSelectedDates.push(dt);
				this.selectedDates.push(d);				
			}
		}
		if (this.rendered && update) {
			this.update(this.activeDate);
		}
	},

	setAllowedDates : function(dates,update) {
		if (typeof update === "undefined") {
			update=true;
		}
		this.allowedDates = this.jsonDate(dates);
		if (this.rendered && update) {
			this.update(this.activeDate);
		}
	},

	setMinDate: function(minDate) {
		this.minDate = this.jsonDate(minDate);
        this.update(this.value, true);
	},

	setMaxDate: function(maxDate) {
		this.maxDate = this.jsonDate(maxDate);
        this.update(this.value, true);
	},

	setDateLimits: function(minDate,maxDate) {
		this.minDate = this.jsonDate(minDate);
		this.maxDate = this.jsonDate(maxDate);
        this.update(this.value, true);
	},

	
	// private
//forcerefresh option from ext 2.2 just included to be compatible	
    update : function(date, forceRefresh ,masked){
		if (typeof masked==="undefined")  {
			masked = false;
		}
		if (typeof forceRefresh==="undefined")  {
			forceRefresh = false;
		}
		
		if (forceRefresh) {
			var ad = this.activeDate;
			this.activeDate = null;
			date = ad;			
		}				
		
		var dMask = (this.displayMask && (isNaN(this.displayMask) || this.noOfMonth > this.displayMask)? true: false);
		
		if (!masked && dMask) {
			this.el.mask(this.displayMaskText);
//set forcerefresh to false because new date (from old activedate) is already calculated
			this.update.defer(10, this, [date,false,true]);
			return false;
		}
		
		if (this.stayInAllowedRange && (this.minDate||this.maxDate)) {
			if (this.minDate && (this.minDate.getFullYear() > date.getFullYear() || (this.minDate.getMonth() > date.getMonth() && this.minDate.getFullYear() == date.getFullYear()))) {
				date = new Date(this.minDate.getTime());
			}
			else if (this.maxDate && (this.maxDate.getFullYear() < date.getFullYear() || (this.maxDate.getMonth() < date.getMonth() && this.maxDate.getFullYear() == date.getFullYear()))) {
				date = new Date(this.maxDate.getTime());
			}
		}
		
		var newStartMonth = date.getMonth();
		var oldStartMonth = (this.activeDate ? this.activeDate.getMonth() : newStartMonth);
		var newStartYear = date.getFullYear();
		var oldStartYear = (this.activeDate ? this.activeDate.getFullYear() : newStartYear);
		
		if (oldStartMonth!=newStartMonth) {
            this.fireEvent("beforemonthchange", this, oldStartMonth, newStartMonth);			
		}
		if (oldStartYear!=newStartYear) {
            this.fireEvent("beforeyearchange", this, oldStartYear, newStartYear);
		}
		
        this.activeDate = date.clearTime();
		this.preSelectedCells = [];
		this.lastSelectedDateCell = '';
		this.activeDateCell = '';
		var lsd = (this.lastSelectedDate?this.lastSelectedDate:0);
		var today = new Date().clearTime().getTime();
		var min = this.minDate ? this.minDate.clearTime().getTime() : Number.NEGATIVE_INFINITY;
		var max = this.maxDate ? this.maxDate.clearTime().getTime() : Number.POSITIVE_INFINITY;
		var ddMatch = this.disabledDatesRE;
		var ddText = this.disabledDatesText;
		var ddays = this.disabledDays ? this.disabledDays.join("") : false;
		var ddaysText = this.disabledDaysText;
		
		var edMatch = this.eventDatesRE;
		var edCls = this.eventDatesRECls;
		var edText = this.eventDatesREText;		

		var adText = this.allowedDatesText;
		
		var format = this.format;
		var adt = this.activeDate.getTime();
		
		this.todayMonthCell	= false;
		this.todayDayCell = false;
		if (this.allowedDates) {
			this.allowedDatesT = [];
			for (var k=0, kl=this.allowedDates.length;k<kl;++k) {
				this.allowedDatesT.push(this.allowedDates[k].clearTime().getTime());
			}
		}
		var setCellClass = function(cal, cell,textnode,d){
	
			var foundday, eCell = Ext.get(cell), eTextNode = Ext.get(textnode), t = d.getTime(), tiptext=false, fvalue;
			cell.title = "";
			cell.firstChild.dateValue = t;

//check this per day, so holidays between years in the same week will be recognized (newyear in most cases),
//yearly eventdates are also possible then
			var dfY = d.getFullYear();
			if (cal.lastRenderedYear!==dfY) {
				cal.lastRenderedYear=dfY;
				if(cal.markNationalHolidays) {
//calculate new holiday list for current year
					cal.nationalHolidaysNumbered = cal.convertCSSDatesToNumbers(cal.nationalHolidays(dfY));
				}
				cal.eventDatesNumbered = cal.convertCSSDatesToNumbers(cal.eventDates(dfY));
			}
			
			// disabling
			if(t < min) {
				cell.className = " x-date-disabled";
				tiptext = cal.minText;				
			}
			if(t > max) {
				cell.className = " x-date-disabled";
				tiptext = cal.maxText;
			}
			if(ddays){
				if(ddays.indexOf(d.getDay()) != -1){
					tiptext = ddaysText;
					cell.className = " x-date-disabled";
				}
			}
			if(ddMatch && format){
				fvalue = d.dateFormat(format);
				if(ddMatch.test(fvalue)){
					tiptext = ddText.replace("%0", fvalue);					
					cell.className = " x-date-disabled";
				}
			}

			if (cal.allowedDates && cal.allowedDatesT.indexOf(t)==-1){
				cell.className = " x-date-disabled";
				tiptext = adText;
			}

			//mark weekends
			if(cal.markWeekends && cal.weekendDays.indexOf(d.getDay()) != -1 && !eCell.hasClass('x-date-disabled')) {
				eCell.addClass(cal.weekendCls);
			}
			

			if(!eCell.hasClass('x-date-disabled') || cal.styleDisabledDates) {
//mark dates with specific css (still selectable) (higher priority than weekends)
				if (cal.eventDatesNumbered[0].length>0) {
					foundday = cal.eventDatesNumbered[0].indexOf(t);
					if (foundday!=-1) {
						if(cal.eventDatesNumbered[2][foundday]!==""){						
							eCell.addClass(cal.eventDatesNumbered[2][foundday]+(cal.eventDatesSelectable?"":"-disabled"));
							tiptext = (cal.eventDatesNumbered[1][foundday]!=="" ? cal.eventDatesNumbered[1][foundday] : false);
						}
					}
				}

//regular Expression custom CSS Dates
				if(edMatch && format){
					fvalue = d.dateFormat(format);
					if(edMatch.test(fvalue)){
						tiptext = edText.replace("%0", fvalue);					
						cell.className = edCls;
					}
				}
			}
			
			
			if(!eCell.hasClass('x-date-disabled')) {
//mark Holidays				
				if(cal.markNationalHolidays && cal.nationalHolidaysNumbered[0].length>0) {
					foundday = cal.nationalHolidaysNumbered[0].indexOf(t);
					if (foundday!=-1) {
						eCell.addClass(cal.nationalHolidaysCls);
						tiptext = (cal.nationalHolidaysNumbered[1][foundday]!=="" ? cal.nationalHolidaysNumbered[1][foundday] : false);
					}
				}
				
				
//finally mark already selected items as selected
				if (cal.preSelectedDates.indexOf(t)!=-1) {
					eCell.addClass("x-date-selected");
					cal.preSelectedCells.push(cell.firstChild.monthCell+"#"+cell.firstChild.dayCell);
				}
				
				if (t == lsd) {
					cal.lastSelectedDateCell = cell.firstChild.monthCell+"#"+cell.firstChild.dayCell;
				}
				
			}
			else if (cal.disabledLetter){
				textnode.innerHTML = cal.disabledLetter;
			}

//mark today afterwards to ensure today CSS has higher priority
			if(t == today){
				eCell.addClass("x-date-today");
				tiptext = cal.todayText;
			}

//keynavigation?
			if(cal.showActiveDate && t == adt && cal.activeDateCell === ''){
				eCell.addClass("x-datepickerplus-activedate");
				cal.activeDateCell = cell.firstChild.monthCell+"#"+cell.firstChild.dayCell;
			}

//any quicktips necessary?
			if (tiptext) {
				if (cal.useQuickTips) {
					Ext.QuickTips.register({
						target: eTextNode,
						text: tiptext
					});
				}
				else {
					cell.title = tiptext;
				}
			}
			
			
		};

		var cells,textEls,days,firstOfMonth,startingPos,pm,prevStart,d,sel,i,intDay,weekNumbers,weekNumbersTextEls,curWeekStart,weekNumbersHeader,monthLabel,main,w;
		var summarizeHTML = [];
		for(var x=0,xk=this.noOfMonth;x<xk;++x) {
			if (this.summarizeHeader && this.noOfMonth > 1 && (x===0||x==this.noOfMonth-1)) {
				summarizeHTML.push(this.monthNames[date.getMonth()]," ",date.getFullYear());
				if (x===0) {
					summarizeHTML.push(" - ");
				}
			}
			cells = this.cellsArray[x].elements;
			textEls = this.textNodesArray[x];

			if ((this.markNationalHolidays || this.eventDates.length>0) && this.useQuickTips) {
				for (var e=0,el=textEls.length;e<el;++e) {
					Ext.QuickTips.unregister(textEls[e]);
				}
			}
			
			days = date.getDaysInMonth();
			firstOfMonth = date.getFirstDateOfMonth();
			startingPos = firstOfMonth.getDay()-this.startDay;
	
			if(startingPos <= this.startDay){
				startingPos += 7;
			}
	
			pm = date.add("mo", -1);
			prevStart = pm.getDaysInMonth()-startingPos;
	
			days += startingPos;
	
			d = new Date(pm.getFullYear(), pm.getMonth(), prevStart).clearTime();
	
			i = 0;
			if (this.showWeekNumber) {
				weekNumbers = this.weekNumberCellsArray[x].elements;
				weekNumbersTextEls = this.weekNumberTextElsArray[x].elements;				
				curWeekStart = new Date(d);
				curWeekStart.setDate(curWeekStart.getDate() + 7);
				
				weekNumbersHeader = this.weekNumberHeaderCellsArray[x].elements;
				weekNumbersHeader[0].firstChild.monthValue = date.getMonth();
				weekNumbersHeader[0].firstChild.dateValue = curWeekStart.getTime();				
				weekNumbersHeader[0].firstChild.monthCell = x;
				weekNumbersHeader[0].firstChild.dayCell = 0;
				
				while(i < weekNumbers.length) {
					weekNumbersTextEls[i].innerHTML = curWeekStart.getWeekOfYear();
					weekNumbers[i].firstChild.dateValue = curWeekStart.getTime();
					weekNumbers[i].firstChild.monthCell = x;
					weekNumbers[i].firstChild.dayCell = (i*7);
					curWeekStart.setDate(curWeekStart.getDate() + 7);
					i++;
				}
				i = 0;
			}

			for(; i < startingPos; ++i) {
				textEls[i].innerHTML = (++prevStart);
				cells[i].firstChild.monthCell = x;
				cells[i].firstChild.dayCell = i;
				
				d.setDate(d.getDate()+1);
				cells[i].className = "x-date-prevday";
				setCellClass(this, cells[i],textEls[i],d);
			}
			
			for(; i < days; ++i){
				intDay = i - startingPos + 1;
				textEls[i].innerHTML = (intDay);
				cells[i].firstChild.monthCell = x;
				cells[i].firstChild.dayCell = i;
				d.setDate(d.getDate()+1);
				cells[i].className = "x-date-active";
				setCellClass(this, cells[i],textEls[i],d);
				if(d.getTime() == today){
					this.todayMonthCell	= x;
					this.todayDayCell = i;
				}
			}
		
			var extraDays = 0;
			for(; i < 42; ++i) {
				textEls[i].innerHTML = (++extraDays);
				cells[i].firstChild.monthCell = x;
				cells[i].firstChild.dayCell = i;
				d.setDate(d.getDate()+1);
				cells[i].className = "x-date-nextday";
				setCellClass(this, cells[i],textEls[i],d);
			}

			if (x===0 && !this.disableMonthPicker) {
				this.mbtn.setText(this.monthNames[date.getMonth()] + " " + date.getFullYear());
			}
			else {
				monthLabel = Ext.get(this.id+'-monthLabel' + x);                    
				monthLabel.update(this.monthNames[date.getMonth()] + " " + date.getFullYear());
			}
			date = date.add('mo',1);

			
			if(!this.internalRender){
				main = this.el.dom.firstChild;
				w = main.offsetWidth;
				this.el.setWidth(w + this.el.getBorderWidth("lr"));
				Ext.fly(main).setWidth(w);
				this.internalRender = true;
				// opera does not respect the auto grow header center column
				// then, after it gets a width opera refuses to recalculate
				// without a second pass
//Not needed anymore (tested with opera 9)
/*
				if(Ext.isOpera && !this.secondPass){
					main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + "px";
					this.secondPass = true;
					this.update.defer(10, this, [date]);
				}
*/							
			}
		}
		if (this.summarizeHeader && this.noOfMonth > 1) {
			var topHeader = Ext.get(this.id+'-summarize');
			topHeader.update(summarizeHTML.join(""));
		}
		this.el.unmask();
		if (oldStartMonth!=newStartMonth) {
            this.fireEvent("aftermonthchange", this, oldStartMonth, newStartMonth);
		}
		if (oldStartYear!=newStartYear) {
            this.fireEvent("afteryearchange", this, oldStartYear, newStartYear);
		}
	
    },

	beforeDestroy : function() {
		if(this.rendered) {		
			if (this.mbtn) {		
				this.mbtn.destroy();
			}
			if (this.todayBtn) {
				this.todayBtn.destroy();
			}
			if (this.OKBtn){
				this.OKBtn.destroy();
			}
			if (this.undoBtn){
				this.undoBtn.destroy();			
			}
		}
	},


    handleWeekClick : function(e, t){
		if (!this.disabled) {
			e.stopEvent();
			var startweekdate = new Date(t.dateValue).getFirstDateOfWeek(this.startDay), amount=0, startmonth, curmonth,enableUnselect;
			var monthcell = t.monthCell;
			var daycell = t.dayCell;
			switch(t.parentNode.tagName.toUpperCase()) {
			case "TH":
				amount=42;
				startmonth = t.monthValue;
				break;
			case "TD":
				amount=7;
				break;
			}
			
			if ((amount==42 && this.fireEvent("beforemonthclick", this, startmonth,this.lastStateWasSelected) !== false)
			||  (amount==7 && this.fireEvent("beforeweekclick", this, startweekdate,this.lastStateWasSelected) !== false)) {
			
				if (!Ext.EventObject.ctrlKey && this.multiSelectByCTRL) {
					this.removeAllPreselectedClasses();
				}
				
				enableUnselect=true;	
				if (this.disablePartialUnselect) {
					var teststartweekdate = startweekdate;
					for (var k=0;k<amount;++k) {
		//check, if the whole set is still selected, then make unselection possible again
						curmonth = teststartweekdate.getMonth();		
						if ((amount == 7 || curmonth === startmonth) && this.preSelectedDates.indexOf(teststartweekdate.clearTime().getTime())==-1) {
							enableUnselect=false;
							break;
						}
						teststartweekdate = teststartweekdate.add(Date.DAY,1);
					}
				}
		
				var reverseAdd =  false;
				var dateAdder = 1;
				if (this.strictRangeSelect &&	(
													(this.preSelectedDates.indexOf(startweekdate.add(Date.DAY,-1).clearTime().getTime())==-1 && !enableUnselect) ||
													(this.preSelectedDates.indexOf(startweekdate.add(Date.DAY,-1).clearTime().getTime())!=-1 && enableUnselect)
												)
					) {
					reverseAdd = true;
					startweekdate = startweekdate.add(Date.DAY,amount-1);
					dateAdder = -1;
				}
				
				this.maxNotified = false;
				for (var i=0,ni;i<amount;++i) {
					curmonth = startweekdate.getMonth();
					ni = (reverseAdd ? amount-1-i : i);
					if (amount == 7 || curmonth === startmonth) {
						this.markDateAsSelected(startweekdate.clearTime().getTime(),true,monthcell,daycell+ni,enableUnselect);
					}
					startweekdate = startweekdate.add(Date.DAY,dateAdder);
				}
				if (amount==42) {
					this.fireEvent("aftermonthclick", this, startmonth,this.lastStateWasSelected);
				}
				else {
					this.fireEvent("afterweekclick", this, new Date(t.dateValue).getFirstDateOfWeek(this.startDay),this.lastStateWasSelected);
				}
			}
		}
	},

	markDateAsSelected : function(t,fakeCTRL,monthcell,daycell,enableUnselect) {
		var currentGetCell = Ext.get(this.cellsArray[monthcell].elements[daycell]);
	
		if ((currentGetCell.hasClass("x-date-prevday") || currentGetCell.hasClass("x-date-nextday") ) && this.prevNextDaysView!=="mark") {		
			return false;
		}

		if (this.multiSelection && (Ext.EventObject.ctrlKey || fakeCTRL)) {
			var beforeDate = new Date(t).add(Date.DAY,-1).clearTime().getTime();
			var afterDate = new Date(t).add(Date.DAY,1).clearTime().getTime();				
			
			if (this.preSelectedDates.indexOf(t)==-1) {
				if (this.maxSelectionDays === this.preSelectedDates.length) {
					if (!this.maxNotified)  {
				        if(this.fireEvent("beforemaxdays", this) !== false){
							Ext.Msg.alert(this.maxSelectionDaysTitle,this.maxSelectionDaysText.replace(/%0/,this.maxSelectionDays));
						}
						this.maxNotified = true;
					}
					return false;
				}
				if (currentGetCell.hasClass("x-date-disabled")) {
					return false;
				}
				
				if (this.strictRangeSelect && this.preSelectedDates.indexOf(afterDate)==-1 && this.preSelectedDates.indexOf(beforeDate)==-1 && this.preSelectedDates.length > 0) {
					return false;
				}
				
				this.preSelectedDates.push(t);
				this.markSingleDays(monthcell,daycell,false);
				this.markGhostDatesAlso(monthcell,daycell,false);
				this.lastStateWasSelected = true;
			}
			else {
				if (enableUnselect &&	(!this.strictRangeSelect ||
											(this.strictRangeSelect && 
											 	(
													(this.preSelectedDates.indexOf(afterDate)==-1 && this.preSelectedDates.indexOf(beforeDate)!=-1 ) ||
													(this.preSelectedDates.indexOf(afterDate)!=-1 && this.preSelectedDates.indexOf(beforeDate)==-1 )
												)
											)
										)
					){
					this.preSelectedDates.remove(t);
					this.markSingleDays(monthcell,daycell,true);
					this.markGhostDatesAlso(monthcell,daycell,true);
					this.lastStateWasSelected = false;
				}
			}
		}
		else {
//calling update in any case would get too slow on huge multiselect calendars, so set the class for the selected cells manually	 (MUCH faster if not calling update() every time!)
			this.removeAllPreselectedClasses();
			this.preSelectedDates = [t];			
			this.preSelectedCells = [];
			this.markSingleDays(monthcell,daycell,false);
			this.markGhostDatesAlso(monthcell,daycell,false);
			this.lastStateWasSelected = true;
		}
		this.lastSelectedDate = t;
		this.lastSelectedDateCell = monthcell+"#"+daycell;
		if (this.multiSelection && !this.renderOkUndoButtons) {
			this.copyPreToSelectedDays();
		}
		return true;
	},

	markSingleDays : function(monthcell,daycell,remove) {
		if(!remove) {
			Ext.get(this.cellsArray[monthcell].elements[daycell]).addClass("x-date-selected");
			this.preSelectedCells.push((monthcell)+"#"+(daycell));
		}
		else {
			Ext.get(this.cellsArray[monthcell].elements[daycell]).removeClass("x-date-selected");
			this.preSelectedCells.remove((monthcell)+"#"+(daycell));
		}
	},

	markGhostDatesAlso : function(monthcell,daycell,remove) {
		if (this.prevNextDaysView=="mark") {
			var currentGetCell = Ext.get(this.cellsArray[monthcell].elements[daycell]), dayCellDiff;
			if(currentGetCell.hasClass("x-date-prevday") && monthcell>0) {
				dayCellDiff = (5-Math.floor(daycell/7))*7;
				if(Ext.get(this.cellsArray[monthcell-1].elements[daycell+dayCellDiff]).hasClass("x-date-nextday")) {
					dayCellDiff-=7;
				}
				this.markSingleDays(monthcell-1,daycell+dayCellDiff,remove);
			}
			else if(currentGetCell.hasClass("x-date-nextday") && monthcell<this.cellsArray.length-1) {
				dayCellDiff = 28;
				if(this.cellsArray[monthcell].elements[daycell].firstChild.firstChild.firstChild.innerHTML != this.cellsArray[monthcell+1].elements[daycell-dayCellDiff].firstChild.firstChild.firstChild.innerHTML) {
					dayCellDiff=35;
				}
				this.markSingleDays(monthcell+1,daycell-dayCellDiff,remove);
			}
			else if(currentGetCell.hasClass("x-date-active") && ((daycell < 14 && monthcell>0) || (daycell > 27 && monthcell<this.cellsArray.length-1))){
				if (daycell<14) {
					dayCellDiff = 28;
					if(!Ext.get(this.cellsArray[monthcell-1].elements[daycell+dayCellDiff]).hasClass("x-date-nextday")) {
						dayCellDiff=35;
					}
					if(daycell+dayCellDiff < 42 && this.cellsArray[monthcell].elements[daycell].firstChild.firstChild.firstChild.innerHTML == this.cellsArray[monthcell-1].elements[daycell+dayCellDiff].firstChild.firstChild.firstChild.innerHTML) {
						this.markSingleDays(monthcell-1,daycell+dayCellDiff,remove);
					}
				}
				else {
					dayCellDiff = 28;
					if(!Ext.get(this.cellsArray[monthcell+1].elements[daycell-dayCellDiff]).hasClass("x-date-prevday")) {
						dayCellDiff=35;
					}
					if(daycell-dayCellDiff >= 0 && this.cellsArray[monthcell].elements[daycell].firstChild.firstChild.firstChild.innerHTML == this.cellsArray[monthcell+1].elements[daycell-dayCellDiff].firstChild.firstChild.firstChild.innerHTML) {
						this.markSingleDays(monthcell+1,daycell-dayCellDiff,remove);
					}
				}
			}
		}
	},
	
	
	removeAllPreselectedClasses : function() {
		for (var e=0,el=this.preSelectedCells.length;e<el;++e) {												
			var position = this.preSelectedCells[e].split("#");
			Ext.get(this.cellsArray[position[0]].elements[position[1]]).removeClass("x-date-selected");
		}
		this.preSelectedDates = [];
		this.preSelectedCells = [];
	},

    handleDateClick : function(e, t){
		
		e.stopEvent();
		var tp = Ext.fly(t.parentNode);

        if(!this.disabled && t.dateValue && !tp.hasClass("x-date-disabled") && !tp.hasClass("x-datepickerplus-eventdates-disabled") && this.fireEvent("beforedateclick", this,e) !== false){
			if (( !tp.hasClass("x-date-prevday") && !tp.hasClass("x-date-nextday") ) || this.prevNextDaysView=="mark") {
				var eO = Ext.EventObject;
				if ((!eO.ctrlKey && this.multiSelectByCTRL) || eO.shiftKey || !this.multiSelection) {
					this.removeAllPreselectedClasses();
				}
				var ctrlfaker = (((!eO.ctrlKey && !this.multiSelectByCTRL) || eO.shiftKey) && this.multiSelection ? true:false);
	
	
				if (eO.shiftKey && this.multiSelection && this.lastSelectedDate) {
					var startdate = this.lastSelectedDate;
					var targetdate = t.dateValue;
					var dayDiff = (startdate<targetdate? 1:-1);
					var lsdCell = this.lastSelectedDateCell.split("#");
					var tmpMonthCell = parseInt(lsdCell[0],10);
					var tmpDayCell = parseInt(lsdCell[1],10);
					var testCell,ghostCounter=0,ghostplus=0;
	
					this.maxNotified = false;
	
	
	
	//startdate lies in nonvisible month ?
					var firstVisibleDate = this.activeDate.getFirstDateOfMonth().clearTime().getTime();
					var lastVisibleDate = this.activeDate.add(Date.MONTH,this.noOfMonth-1).getLastDateOfMonth().clearTime().getTime();
	
					if (startdate<firstVisibleDate ||
						startdate>lastVisibleDate) {
				
	//prepare for disabledCheck
						var min = this.minDate ? this.minDate.clearTime().getTime() : Number.NEGATIVE_INFINITY;
						var max = this.maxDate ? this.maxDate.clearTime().getTime() : Number.POSITIVE_INFINITY;
						var ddays = this.disabledDays ? this.disabledDays.join("") : "";
						var ddMatch = this.disabledDatesRE;
						var format = this.format;
						var allowedDatesT =  this.allowedDates ? this.allowedDatesT : false;
						var d,ddMatchResult,fvalue;
	//check, if the days would be disabled
						while(startdate<firstVisibleDate || startdate>lastVisibleDate) {
							d=new Date(startdate);
	
							ddMatchResult = false;
							if(ddMatch){
								fvalue = d.dateFormat(format);
								ddMatchResult = ddMatch.test(fvalue);
							}
	//don't use >= and <= here for datecomparison, because the dates can differ in timezone
							if(	!(startdate < min) &&
								!(startdate > max) &&
								ddays.indexOf(d.getDay()) == -1 &&
								!ddMatchResult &&
								( !allowedDatesT || allowedDatesT.indexOf(startdate)!=-1 )
							   ) {
	//is not disabled and can be processed
	
								if (this.maxSelectionDays === this.preSelectedDates.length) {
									if(this.fireEvent("beforemaxdays", this) !== false){								
										Ext.Msg.alert(this.maxSelectionDaysTitle,this.maxSelectionDaysText.replace(/%0/,this.maxSelectionDays));
									}
									break;
								}
								this.preSelectedDates.push(startdate);
	
							}
							startdate = new Date(startdate).add(Date.DAY,dayDiff).clearTime().getTime();
						}
					
						tmpMonthCell = (dayDiff>0 ? 0 : this.cellsArray.length-1);
						tmpDayCell = (dayDiff>0 ? 0 : 41);
	
	//mark left ghostdates aswell
						testCell = Ext.get(this.cellsArray[tmpMonthCell].elements[tmpDayCell]);
						while (testCell.hasClass("x-date-prevday") || testCell.hasClass("x-date-nextday")) {
							testCell.addClass("x-date-selected");
							this.preSelectedCells.push((tmpMonthCell)+"#"+(tmpDayCell));
							tmpDayCell+=dayDiff;
							testCell = Ext.get(this.cellsArray[tmpMonthCell].elements[tmpDayCell]);
						}
					}
					
	//mark range of visible dates
					while ((targetdate-startdate)*dayDiff >0 && tmpMonthCell>=0 && tmpMonthCell<this.cellsArray.length) {									
						this.markDateAsSelected(startdate,ctrlfaker,tmpMonthCell,tmpDayCell,true);
	
	//take care of summertime changing (would return different milliseconds)
						startdate = new Date(startdate).add(Date.DAY,dayDiff).clearTime().getTime();
										
						testCell = Ext.get(this.cellsArray[tmpMonthCell].elements[tmpDayCell]);
	
						if (testCell.hasClass("x-date-active")) {
							ghostCounter=0;						
						}
						else {
							ghostCounter++;
						}
						tmpDayCell+=dayDiff;
						if (tmpDayCell==42) {
							tmpMonthCell++;
							tmpDayCell=(ghostCounter>=7?14:7);
						}
						else if (tmpDayCell<0) {
							tmpMonthCell--;
							tmpDayCell=34;
							
							testCell = Ext.get(this.cellsArray[tmpMonthCell].elements[tmpDayCell]);
							if (testCell.hasClass("x-date-nextday") || ghostCounter==7) {
								tmpDayCell=27;
							}
						}
					}
	
				}
				
	
				this.markDateAsSelected(t.dateValue,ctrlfaker,t.monthCell,t.dayCell,true);
					
				this.finishDateSelection(new Date(t.dateValue));
			}
		}
    },
	
	copyPreToSelectedDays : function() {
		this.selectedDates = [];
		for (var i=0,il=this.preSelectedDates.length;i<il;++i) {
			this.selectedDates.push(new Date(this.preSelectedDates[i]));
		}
	},
	okClicked : function() {
		this.copyPreToSelectedDays();
		this.selectedDates = this.selectedDates.sortDates();
		this.fireEvent("select", this, this.selectedDates);
	},

	spaceKeyPressed: function(e) {
		var ctrlfaker = (((!Ext.EventObject.ctrlKey && !this.multiSelectByCTRL) || Ext.EventObject.shiftKey) && this.multiSelection ? true:false);
		if (!this.disabled && this.shiftSpaceSelect == Ext.EventObject.shiftKey && this.showActiveDate) {
			var adCell = this.activeDateCell.split("#");
			var tmpMonthCell = parseInt(adCell[0],10);
			var tmpDayCell = parseInt(adCell[1],10);
			this.markDateAsSelected(this.activeDate.getTime(),ctrlfaker,tmpMonthCell,tmpDayCell,true);
			this.finishDateSelection(this.activeDate);
		}
		else {
			this.selectToday();
		}
	},

	finishDateSelection: function(date) {
        this.setValue(date);		
		if (this.multiSelection) {
			this.fireEvent("afterdateclick", this, date,this.lastStateWasSelected);
		}
		else {
			this.fireEvent("afterdateclick", this, date,this.lastStateWasSelected);				
	        this.fireEvent("select", this, this.value);
		}
	},

    selectToday : function(){
        if(!this.disabled && this.todayBtn && !this.todayBtn.disabled){
			var today = new Date().clearTime();
			var todayT = today.getTime();
		//today already visible?
			if (typeof this.todayMonthCell === "number") {
				this.markDateAsSelected(todayT,false,this.todayMonthCell,this.todayDayCell,true);
			}
			else if (this.multiSelection){
				this.update(today);
			}
			this.finishDateSelection(today);
        }		
    },
	
    setValue : function(value){
		if (Ext.isArray(value)) {
			this.selectedDates = [];
			this.preSelectedDates = [];			
			this.setSelectedDates(value,true);
			value = value[0];
		}
        this.value = value.clearTime(true);

        if(this.el && !this.multiSelection && this.noOfMonth==1){
            this.update(this.value);
        }
		
    },
	
/* this is needed to get it displayed in a panel correctly, it is called several times...*/	
	setSize: Ext.emptyFn
	
});
Ext.reg('datepickerplus', Ext.ux.DatePickerPlus);  


/*
To use DatepickerPlus in menus and datefields, DateItem and datefield needs to be rewritten. This way Ext.DateMenu stays original and by supplying new config item usePickerPlus:true will use the datepickerplus insted of the original picker. 
*/

	
if (parseInt(Ext.version.substr(0,1),10)>2) {
//ext 3.0		
	Ext.menu.DateItem = Ext.ux.DatePickerPlus;
	Ext.override(Ext.menu.DateMenu,{
		initComponent: function(){
			this.on('beforeshow', this.onBeforeShow, this);
			if(this.strict = (Ext.isIE7 && Ext.isStrict)){
				this.on('show', this.onShow, this, {single: true, delay: 20});
			}
			var PickerWidget = (this.initialConfig.usePickerPlus ? Ext.ux.DatePickerPlus : Ext.DatePicker);
			Ext.apply(this, {
				plain: true,
				showSeparator: false,
				items: this.picker = new PickerWidget(Ext.apply({
					internalRender: this.strict || !Ext.isIE,
					ctCls: 'x-menu-date-item'
				}, this.initialConfig))
			});
			Ext.menu.DateMenu.superclass.initComponent.call(this);
			this.relayEvents(this.picker, ["select"]);
			this.on('select', this.menuHide, this);
			if(this.handler){
				this.on('select', this.handler, this.scope || this);
			}
		}
	});
	
}
else {
//ext 2.x
	Ext.menu.DateItem = function(config){
		if (config && config.usePickerPlus) {
			Ext.menu.DateItem.superclass.constructor.call(this, new Ext.ux.DatePickerPlus(config), config);	//NEW LINE			
		}
		else {
			Ext.menu.DateItem.superclass.constructor.call(this, new Ext.DatePicker(config), config);
		}
		this.picker = this.component;
		this.addEvents('select');
		
		this.picker.on("render", function(picker){
			picker.getEl().swallowEvent("click");
			picker.container.addClass("x-menu-date-item");
		});

		this.picker.on("select", this.onSelect, this);
	};
//this breaks in ext 3.0 (Ext.menu.Adapter and Ext.menu.DateItem do not exist in ext 3.0 anymore)
	Ext.extend(Ext.menu.DateItem, Ext.menu.Adapter,{
		// private
		onSelect : function(picker, date){
			this.fireEvent("select", this, date, picker);
			Ext.menu.DateItem.superclass.handleClick.call(this);
		}
	});
}


if (Ext.form && Ext.form.DateField) {
	Ext.ux.form.DateFieldPlus = Ext.extend(Ext.form.DateField, {
		usePickerPlus: true,
		showWeekNumber: true,
		noOfMonth : 1,
		noOfMonthPerRow : 3,
		nationalHolidaysCls: 'x-datepickerplus-nationalholidays',
		markNationalHolidays:true,
		eventDates: function(year) {
			return [];
		},
		eventDatesRE : false,
		eventDatesRECls : '',
		eventDatesREText : '',
		multiSelection: false,
		multiSelectionDelimiter: ',',			
		multiSelectByCTRL: true,	
		fillupRows: true,
		markWeekends:true,
		weekendText:'',
		weekendCls: 'x-datepickerplus-weekends',
		weekendDays: [6,0],
		useQuickTips: true,
		pageKeyWarp: 1,
		maxSelectionDays: false,
		resizable: false,
		renderTodayButton: true,
		renderOkUndoButtons: true,
		tooltipType: 'qtip',
		allowedDates : false,
		allowedDatesText : '',
		renderPrevNextButtons: true,
		renderPrevNextYearButtons: false,
		disableMonthPicker:false,
		showActiveDate: false,
		shiftSpaceSelect: true,
		disabledLetter: false,
		allowMouseWheel:  true,
		summarizeHeader: false,
		stayInAllowedRange: true,
		disableSingleDateSelection: false,
		eventDatesSelectable: false,
		styleDisabledDates: false,

		allowOtherMenus: false,

		onBeforeYearChange : function(picker, oldStartYear, newStartYear){
			this.fireEvent("beforeyearchange", this, oldStartYear, newStartYear, picker);
		},
		
		onAfterYearChange : function(picker, oldStartYear, newStartYear){
			this.fireEvent("afteryearchange", this, oldStartYear, newStartYear, picker);
		},
		
		onBeforeMonthChange : function(picker, oldStartMonth, newStartMonth){
			this.fireEvent("beforemonthchange", this, oldStartMonth, newStartMonth, picker);
		},
		
		onAfterMonthChange : function(picker, oldStartMonth, newStartMonth){
			this.fireEvent("aftermonthchange", this, oldStartMonth, newStartMonth, picker);
		},
		
		onAfterMonthClick : function(picker, month, wasSelected){
			this.fireEvent("aftermonthclick", this, month, wasSelected, picker);
		},
		
		onAfterWeekClick : function(picker, startOfWeek, wasSelected){
			this.fireEvent("afterweekclick", this, startOfWeek, wasSelected, picker);
		},

		onAfterDateClick : function(picker, date, wasSelected){
			this.fireEvent("afterdateclick", this, date, wasSelected, picker);
		},
		
		onBeforeMouseWheel : function(picker, event){
			this.fireEvent("beforemousewheel", this, event, picker);
		},
		
		onBeforeMaxDays : function(picker){
			this.fireEvent("beforemaxdays", this, picker);
		},
		
		onUndo : function(picker, preSelectedDates){
			this.fireEvent("undo", this, preSelectedDates, picker);
		},

		onTriggerClick : function(){
			if(this.disabled){
				return;
			}
			if(!this.menu){
				this.menu = new Ext.menu.DateMenu({
					allowOtherMenus: this.allowOtherMenus,
//is needed at initialisation		
					usePickerPlus:this.usePickerPlus,
					noOfMonth:this.noOfMonth,
					noOfMonthPerRow:this.noOfMonthPerRow,
					listeners: {
						'beforeyearchange': {fn:this.onBeforeYearChange,scope:this},
						'afteryearchange': {fn:this.onAfterYearChange,scope:this},
						'beforemonthchange': {fn:this.onBeforeMonthChange,scope:this},
						'aftermonthchange': {fn:this.onAfterMonthChange,scope:this},
						'afterdateclick': {fn:this.onAfterDateClick,scope:this},
						'aftermonthclick': {fn:this.onAfterMonthClick,scope:this},
						'afterweekclick': {fn:this.onAfterWeekClick,scope:this},
						'beforemousewheel': {fn:this.onBeforeMouseWheel,scope:this},
						'beforemaxdays': {fn:this.onBeforeMaxDays,scope:this},
						'undo': {fn:this.onUndo,scope:this}
					}
				});
//do this only once!					
				this.relayEvents(this.menu, ["select"]);
			}

			if (this.menu.isVisible()) {
				this.menu.hide();
				return;
			}
			if (this.disabledDatesRE) {
				this.ddMatch = this.disabledDatesRE;
			}
			if(typeof this.minDate == "string"){
				this.minDate = this.parseDate(this.minDate);
			}
			if(typeof this.maxDate == "string"){
				this.maxDate = this.parseDate(this.maxDate);
			}
			
			Ext.apply(this.menu.picker,  {
				minDate : this.minValue || this.minDate,
				maxDate : this.maxValue || this.maxDate,
				disabledDatesRE : this.ddMatch,
				disabledDatesText : this.disabledDatesText,
				disabledDays : this.disabledDays,
				disabledDaysText : this.disabledDaysText,
				showToday : this.showToday,	//from Ext 2.2
				format : this.format,
				minText : String.format(this.minText, this.formatDate(this.minValue || this.minDate)),
				maxText : String.format(this.maxText, this.formatDate(this.maxValue || this.maxDate)),
				showWeekNumber: this.showWeekNumber,
				nationalHolidaysCls: this.nationalHolidaysCls,
				markNationalHolidays:this.markNationalHolidays,
				multiSelectByCTRL: this.multiSelectByCTRL,	
				fillupRows: this.fillupRows,
				multiSelection: this.multiSelection,
				markWeekends:this.markWeekends,
				weekendText:this.weekendText,
				weekendCls: this.weekendCls,
				weekendDays: this.weekendDays,
				useQuickTips: this.useQuickTips,
				eventDates: this.eventDates,
				eventDatesRE: this.eventDatesRE,
				eventDatesRECls: this.eventDatesRECls,
				eventDatesREText: this.eventDatesREText,
				pageKeyWarp: this.pageKeyWarp,
				maxSelectionDays: this.maxSelectionDays,
				resizable: this.resizable,
				renderTodayButton: this.renderTodayButton,
				renderOkUndoButtons: this.renderOkUndoButtons,
				allowedDates : this.allowedDates,
				allowedDatesText : this.allowedDatesText,
				renderPrevNextButtons: this.renderPrevNextButtons,
				renderPrevNextYearButtons: this.renderPrevNextYearButtons,
				disableMonthPicker:this.disableMonthPicker,
				showActiveDate: this.showActiveDate,
				shiftSpaceSelect: this.shiftSpaceSelect,
				disabledLetter: this.disabledLetter,
				allowMouseWheel: this.allowMouseWheel,
				summarizeHeader: this.summarizeHeader,
				stayInAllowedRange: this.stayInAllowedRange,
				disableSingleDateSelection: this.disableSingleDateSelection,
				eventDatesSelectable: this.eventDatesSelectable,
				styleDisabledDates: this.styleDisabledDates
			});
//Ext 3.0
			if (this.menuEvents) {
				this.menuEvents('on');
			}
			else {
//ext 2.2.x				
				this.menu.on(Ext.apply({}, this.menuListeners, {
					scope:this
				}));
			}
			this.menu.picker.setValue(this.getValue() || new Date());
			this.menu.show(this.el, "tl-bl?");
			this.menu.focus();
		},
		
		setValue : function(date){
			var field = this;     
			if (Ext.isArray(date)) {
				var formatted = [];
				for (var e=0,el=date.length;e<el;++e) {
					formatted.push(field.formatDate(date[e]));
				}
			
				var value = formatted.join(this.multiSelectionDelimiter);
			
//bypass setValue validation on Ext.DateField
				Ext.form.DateField.superclass.setValue.call(this, value);
			}
			else {
				Ext.form.DateField.superclass.setValue.call(this, this.formatDate(this.parseDate(date)));				   
			}
		},

		validateValue : function(value){
			if (this.multiSelection){
				var field = this;
				var values = value.split(this.multiSelectionDelimiter);
				var isValid = true;
				for (var e=0,el=values.length;e<el;++e) {											  
					if (!Ext.ux.form.DateFieldPlus.superclass.validateValue.call(field, values[e])) {
						isValid = false;
					}
				}
				return isValid;
			}
			else {
				return Ext.ux.form.DateFieldPlus.superclass.validateValue.call(this, value);
			}         
		},

		getValue : function() {
			if (this.multiSelection) {
				var value = Ext.form.DateField.superclass.getValue.call(this);
				var field = this;					
				var values = value.split(this.multiSelectionDelimiter);
				var dates = [];
				for (var e=0,el=values.length;e<el;++e) {											  
					var checkDate = field.parseDate(values[e]);
					if (checkDate) {
						dates.push(checkDate);
					}
				}
				return (dates.length>0?dates:"");
			}
			else {
				return Ext.ux.form.DateFieldPlus.superclass.getValue.call(this);
			}
		},			


		beforeBlur : function(){
			if (this.multiSelection) {
				this.setValue(this.getRawValue().split(this.multiSelectionDelimiter));
			}
			else {
				var v = this.parseDate(this.getRawValue());
				if(v){
					this.setValue(v);
				}
			}
		},


		
		submitFormat:'Y-m-d',
		submitFormatAddon: '-format',			
		onRender:function() {
	
			Ext.ux.form.DateFieldPlus.superclass.onRender.apply(this, arguments);
//be sure not to have duplicate formfield names (at least IE moans about it and gets confused)				
//				this.name =  (typeof this.name==="undefined"?this.id+this.submitFormatAddon:(this.name==this.id?this.name+this.submitFormatAddon:this.name));		
			var name =  this.name || this.el.dom.name || (this.id+this.submitFormatAddon);
			if (name==this.id) {
				name+= this.submitFormatAddon;
			}
			this.hiddenField = this.el.insertSibling({
				tag:'input',
				type:'hidden',
				name: name,
				value:this.formatHiddenDate(this.parseDate(this.value))
			});
			this.hiddenName = name;
			this.el.dom.removeAttribute('name');
			this.el.on({
				keyup:{scope:this, fn:this.updateHidden},
				blur:{scope:this, fn:this.updateHidden}
			});
	
			this.setValue = this.setValue.createSequence(this.updateHidden);
			
			if(this.tooltip){
				if(typeof this.tooltip == 'object'){
					Ext.QuickTips.register(Ext.apply({
						  target: this.trigger
					}, this.tooltip));
				} else {
					this.trigger.dom[this.tooltipType] = this.tooltip;
				}
			}
			
	
		},
		onDisable: function(){
			Ext.ux.form.DateFieldPlus.superclass.onDisable.apply(this, arguments);
			if(this.hiddenField) {
				this.hiddenField.dom.setAttribute('disabled','disabled');
			}
		},
		
		onEnable: function(){
			Ext.ux.form.DateFieldPlus.superclass.onEnable.apply(this, arguments);
			if(this.hiddenField) {
				this.hiddenField.dom.removeAttribute('disabled');
			}
		},
		
		formatHiddenDate : function(date){
			return Ext.isDate(date) ? Ext.util.Format.date(date, this.submitFormat) : date;
		},
		
		formatMultiHiddenDate : function(date) {
			var field = this, formatted = [],value;
			for (var e=0,el=date.length;e<el;++e) {
				formatted.push(field.formatHiddenDate(date[e]));
			}
			value = formatted.join(this.multiSelectionDelimiter);
			this.hiddenField.dom.value = value;
		},
		
		updateHidden:function(date) {
			if (Ext.isArray(date)) {
				this.formatMultiHiddenDate(date);
			}
			else {
				var value = this.getValue();
				if (Ext.isArray(value)) {
					this.formatMultiHiddenDate(value);
				} else {
					this.hiddenField.dom.value = this.formatHiddenDate(value);
				}
			}
		}

	});
	Ext.reg('datefieldplus', Ext.ux.form.DateFieldPlus);
}
/* ExtInfoWindow, v1.1: See code.google.com/p/gmaps-utility-library for license and info */
function ExtInfoWindow(a,b,c,d){this.html_=c;this.marker_=a;this.infoWindowId_=b;this.options_=d==null?{}:d;this.ajaxUrl_=this.options_.ajaxUrl==null?null:this.options_.ajaxUrl;this.callback_=this.options_.ajaxCallback==null?null:this.options_.ajaxCallback;this.borderSize_=this.options_.beakOffset==null?0:this.options_.beakOffset;this.paddingX_=this.options_.paddingX==null?0+this.borderSize_:this.options_.paddingX+this.borderSize_;this.paddingY_=this.options_.paddingY==null?0+this.borderSize_:this.options_.paddingY+this.borderSize_;this.map_=null;this.container_=document.createElement('div');this.container_.style.position='relative';this.container_.style.display='none';this.contentDiv_=document.createElement('div');this.contentDiv_.id=this.infoWindowId_+'_contents';this.contentDiv_.innerHTML=this.html_;this.contentDiv_.style.display='block';this.contentDiv_.style.visibility='hidden';this.wrapperDiv_=document.createElement('div')};
ExtInfoWindow.prototype=new GOverlay();
ExtInfoWindow.prototype.initialize=function(a){this.map_=a;this.defaultStyles={containerWidth:this.map_.getSize().width/2,borderSize:1};this.wrapperParts={tl:{t:0,l:0,w:0,h:0,domElement:null},t:{t:0,l:0,w:0,h:0,domElement:null},tr:{t:0,l:0,w:0,h:0,domElement:null},l:{t:0,l:0,w:0,h:0,domElement:null},r:{t:0,l:0,w:0,h:0,domElement:null},bl:{t:0,l:0,w:0,h:0,domElement:null},b:{t:0,l:0,w:0,h:0,domElement:null},br:{t:0,l:0,w:0,h:0,domElement:null},beak:{t:0,l:0,w:0,h:0,domElement:null},close:{t:0,l:0,w:0,h:0,domElement:null}};for(var i in this.wrapperParts){var b=document.createElement('div');b.id=this.infoWindowId_+'_'+i;b.style.visibility='hidden';document.body.appendChild(b);b=document.getElementById(this.infoWindowId_+'_'+i);var c=eval('this.wrapperParts.'+i);c.w=parseInt(this.getStyle_(b,'width'));c.h=parseInt(this.getStyle_(b,'height'));document.body.removeChild(b)}for(var i in this.wrapperParts){if(i=='close'){this.wrapperDiv_.appendChild(this.contentDiv_)}var d=null;if(this.wrapperParts[i].domElement==null){d=document.createElement('div');this.wrapperDiv_.appendChild(d)}else{d=this.wrapperParts[i].domElement}d.id=this.infoWindowId_+'_'+i;d.style.position='absolute';d.style.width=this.wrapperParts[i].w+'px';d.style.height=this.wrapperParts[i].h+'px';d.style.top=this.wrapperParts[i].t+'px';d.style.left=this.wrapperParts[i].l+'px';this.wrapperParts[i].domElement=d}this.map_.getPane(G_MAP_FLOAT_PANE).appendChild(this.container_);this.container_.id=this.infoWindowId_;var e=this.getStyle_(document.getElementById(this.infoWindowId_),'width');this.container_.style.width=(e==null?this.defaultStyles.containerWidth:e);this.map_.getContainer().appendChild(this.contentDiv_);this.contentWidth=this.getDimensions_(this.container_).width;this.contentDiv_.style.width=this.contentWidth+'px';this.contentDiv_.style.position='absolute';this.container_.appendChild(this.wrapperDiv_);GEvent.bindDom(this.container_,'mousedown',this,this.onClick_);GEvent.trigger(this.map_,'extinfowindowopen');if(this.ajaxUrl_!=null){this.ajaxRequest_(this.ajaxUrl_)}};
ExtInfoWindow.prototype.onClick_=function(e){if(navigator.userAgent.toLowerCase().indexOf('msie')!=-1&&document.all){window.event.cancelBubble=true;window.event.returnValue=false}else{e.preventDefault();e.stopPropagation()}};
ExtInfoWindow.prototype.remove=function(){if(this.map_.getExtInfoWindow()!=null){GEvent.trigger(this.map_,'extinfowindowbeforeclose');GEvent.clearInstanceListeners(this.container_);if(this.container_.outerHTML){this.container_.outerHTML=''}if(this.container_.parentNode){this.container_.parentNode.removeChild(this.container_)}this.container_=null;GEvent.trigger(this.map_,'extinfowindowclose');this.map_.setExtInfoWindow_(null)}};
ExtInfoWindow.prototype.copy=function(){return new ExtInfoWindow(this.marker_,this.infoWindowId_,this.html_,this.options_)};
ExtInfoWindow.prototype.redraw=function(a){if(!a||this.container_==null)return;var b=this.contentDiv_.offsetHeight;this.contentDiv_.style.height=b+'px';this.contentDiv_.style.left=this.wrapperParts.l.w+'px';this.contentDiv_.style.top=this.wrapperParts.tl.h+'px';this.contentDiv_.style.visibility='visible';this.wrapperParts.tl.t=0;this.wrapperParts.tl.l=0;this.wrapperParts.t.l=this.wrapperParts.tl.w;this.wrapperParts.t.w=(this.wrapperParts.l.w+this.contentWidth+this.wrapperParts.r.w)-this.wrapperParts.tl.w-this.wrapperParts.tr.w;this.wrapperParts.t.h=this.wrapperParts.tl.h;this.wrapperParts.tr.l=this.wrapperParts.t.w+this.wrapperParts.tl.w;this.wrapperParts.l.t=this.wrapperParts.tl.h;this.wrapperParts.l.h=b;this.wrapperParts.r.l=this.contentWidth+this.wrapperParts.l.w;this.wrapperParts.r.t=this.wrapperParts.tr.h;this.wrapperParts.r.h=b;this.wrapperParts.bl.t=b+this.wrapperParts.tl.h;this.wrapperParts.b.l=this.wrapperParts.bl.w;this.wrapperParts.b.t=b+this.wrapperParts.tl.h;this.wrapperParts.b.w=(this.wrapperParts.l.w+this.contentWidth+this.wrapperParts.r.w)-this.wrapperParts.bl.w-this.wrapperParts.br.w;this.wrapperParts.b.h=this.wrapperParts.bl.h;this.wrapperParts.br.l=this.wrapperParts.b.w+this.wrapperParts.bl.w;this.wrapperParts.br.t=b+this.wrapperParts.tr.h;this.wrapperParts.close.l=this.wrapperParts.tr.l+this.wrapperParts.tr.w-this.wrapperParts.close.w-this.borderSize_;this.wrapperParts.close.t=this.borderSize_;this.wrapperParts.beak.l=this.borderSize_+(this.contentWidth/2)-(this.wrapperParts.beak.w/2);this.wrapperParts.beak.t=this.wrapperParts.bl.t+this.wrapperParts.bl.h-this.borderSize_;for(var i in this.wrapperParts){if(i=='close'){this.wrapperDiv_.insertBefore(this.contentDiv_,this.wrapperParts[i].domElement)}var c=null;if(this.wrapperParts[i].domElement==null){c=document.createElement('div');this.wrapperDiv_.appendChild(c)}else{c=this.wrapperParts[i].domElement}c.id=this.infoWindowId_+'_'+i;c.style.position='absolute';c.style.width=this.wrapperParts[i].w+'px';c.style.height=this.wrapperParts[i].h+'px';c.style.top=this.wrapperParts[i].t+'px';c.style.left=this.wrapperParts[i].l+'px';this.wrapperParts[i].domElement=c}var d=this.marker_;var e=this.map_;GEvent.addDomListener(this.wrapperParts.close.domElement,'click',function(){e.closeExtInfoWindow()});var f=this.map_.fromLatLngToDivPixel(this.marker_.getPoint());this.container_.style.position='absolute';var g=this.marker_.getIcon();this.container_.style.left=(f.x-(this.contentWidth/2)-g.iconAnchor.x+g.infoWindowAnchor.x)+'px';this.container_.style.top=(f.y-this.wrapperParts.bl.h-b-this.wrapperParts.tl.h-this.wrapperParts.beak.h-g.iconAnchor.y+g.infoWindowAnchor.y+this.borderSize_)+'px';this.container_.style.display='block';if(this.map_.getExtInfoWindow()!=null){this.repositionMap_()}};
ExtInfoWindow.prototype.resize=function(){var a=this.contentDiv_.cloneNode(true);a.id=this.infoWindowId_+'_tempContents';a.style.visibility='hidden';a.style.height='auto';document.body.appendChild(a);a=document.getElementById(this.infoWindowId_+'_tempContents');var b=a.offsetHeight;document.body.removeChild(a);this.contentDiv_.style.height=b+'px';var c=this.contentDiv_.offsetWidth;var d=this.map_.fromLatLngToDivPixel(this.marker_.getPoint());var e=this.wrapperParts.t.domElement.offsetHeight+this.wrapperParts.l.domElement.offsetHeight+this.wrapperParts.b.domElement.offsetHeight;var f=this.wrapperParts.t.domElement.offsetTop;this.wrapperParts.l.domElement.style.height=b+'px';this.wrapperParts.r.domElement.style.height=b+'px';var g=this.wrapperParts.b.domElement.offsetTop-b;this.wrapperParts.l.domElement.style.top=g+'px';this.wrapperParts.r.domElement.style.top=g+'px';this.contentDiv_.style.top=g+'px';windowTHeight=parseInt(this.wrapperParts.t.domElement.style.height);g-=windowTHeight;this.wrapperParts.close.domElement.style.top=g+this.borderSize_+'px';this.wrapperParts.tl.domElement.style.top=g+'px';this.wrapperParts.t.domElement.style.top=g+'px';this.wrapperParts.tr.domElement.style.top=g+'px';this.repositionMap_()};
ExtInfoWindow.prototype.repositionMap_=function(){var a=this.map_.fromLatLngToDivPixel(this.map_.getBounds().getNorthEast());var b=this.map_.fromLatLngToDivPixel(this.map_.getBounds().getSouthWest());var c=this.map_.fromLatLngToDivPixel(this.marker_.getPoint());var d=0;var e=0;var f=this.paddingX_;var g=this.paddingY_;var h=this.marker_.getIcon().infoWindowAnchor;var i=this.marker_.getIcon().iconAnchor;var j=this.wrapperParts.t.domElement;var k=this.wrapperParts.l.domElement;var l=this.wrapperParts.b.domElement;var m=this.wrapperParts.r.domElement;var n=this.wrapperParts.beak.domElement;var o=c.y-(-h.y+i.y+this.getDimensions_(n).height+this.getDimensions_(l).height+this.getDimensions_(k).height+this.getDimensions_(j).height+this.paddingY_);if(o<a.y){e=a.y-o}else{var p=c.y+this.paddingY_;if(p>=b.y){e=-(p-b.y)}}var q=Math.round(c.x+this.getDimensions_(this.container_).width/2+this.getDimensions_(m).width+this.paddingX_+h.x-i.x);if(q>a.x){d=-(q-a.x)}else{var r=-(Math.round((this.getDimensions_(this.container_).width/2-this.marker_.getIcon().iconSize.width/2)+this.getDimensions_(k).width+this.borderSize_+this.paddingX_)-c.x-h.x+i.x);if(r<b.x){d=b.x-r}}if(d!=0||e!=0&&this.map_.getExtInfoWindow()!=null){this.map_.panBy(new GSize(d,e))}};
ExtInfoWindow.prototype.ajaxRequest_=function(d){var e=this.map_;var f=this.callback_;GDownloadUrl(d,function(a,b){var c=document.getElementById(e.getExtInfoWindow().infoWindowId_+'_contents');if(a==null||b==-1){c.innerHTML='<span class="error">ERROR: The Ajax request failed to get HTML content from "'+d+'"</span>'}else{c.innerHTML=a}if(f!=null){f()}e.getExtInfoWindow().resize();GEvent.trigger(e,'extinfowindowupdate')})};
ExtInfoWindow.prototype.getDimensions_=function(a){var b=this.getStyle_(a,'display');if(b!='none'&&b!=null){return{width:a.offsetWidth,height:a.offsetHeight}}var c=a.style;var d=c.visibility;var e=c.position;var f=c.display;c.visibility='hidden';c.position='absolute';c.display='block';var g=a.clientWidth;var h=a.clientHeight;c.display=f;c.position=e;c.visibility=d;return{width:g,height:h}};
ExtInfoWindow.prototype.getStyle_=function(a,b){var c=false;b=this.camelize_(b);var d=a.style[b];if(!d){if(document.defaultView&&document.defaultView.getComputedStyle){var e=document.defaultView.getComputedStyle(a,null);d=e?e[b]:null}else if(a.currentStyle){d=a.currentStyle[b]}}if((d=='auto')&&(b=='width'||b=='height')&&(this.getStyle_(a,'display')!='none')){if(b=='width'){d=a.offsetWidth}else{d=a.offsetHeight}}if(window.opera&&['left','top','right','bottom'].include(b)){if(this.getStyle_(a,'position')=='static')d='auto'}return(d=='auto')?null:d};
ExtInfoWindow.prototype.camelize_=function(a){var b=a.split('-'),len=b.length;if(len==1)return b[0];var c=a.charAt(0)=='-'?b[0].charAt(0).toUpperCase()+b[0].substring(1):b[0];for(var i=1;i<len;i++){c+=b[i].charAt(0).toUpperCase()+b[i].substring(1)}return c};GMap.prototype.ExtInfoWindowInstance_=null;GMap.prototype.ClickListener_=null;GMap.prototype.InfoWindowListener_=null;
GMarker.prototype.openExtInfoWindow=function(b,c,d,e){if(b==null){throw'Error in GMarker.openExtInfoWindow: map cannot be null';return false}if(c==null||c==''){throw'Error in GMarker.openExtInfoWindow: must specify a cssId';return false}b.closeInfoWindow();if(b.getExtInfoWindow()!=null){b.closeExtInfoWindow()}if(b.getExtInfoWindow()==null){b.setExtInfoWindow_(new ExtInfoWindow(this,c,d,e));if(b.ClickListener_==null){b.ClickListener_=GEvent.addListener(b,'click',function(a){if(!a&&b.getExtInfoWindow()!=null){b.closeExtInfoWindow()}})}if(b.InfoWindowListener_==null){b.InfoWindowListener_=GEvent.addListener(b,'infowindowopen',function(a){if(b.getExtInfoWindow()!=null){b.closeExtInfoWindow()}})}b.addOverlay(b.getExtInfoWindow())}};
GMarker.prototype.closeExtInfoWindow=function(a){a.closeExtInfoWindow()};
GMap2.prototype.getExtInfoWindow=function(){return this.ExtInfoWindowInstance_};
GMap2.prototype.setExtInfoWindow_=function(a){this.ExtInfoWindowInstance_=a}
GMap2.prototype.closeExtInfoWindow=function(){this.ExtInfoWindowInstance_.remove()};
/**
 * @name MarkerClusterer
 * @version 1.0
 * @author Xiaoxi Wu
 * @copyright (c) 2009 Xiaoxi Wu
 * @fileoverview
 * This javascript library creates and manages per-zoom-level 
 * clusters for large amounts of markers (hundreds or thousands).
 * This library was inspired by the <a href="http://www.maptimize.com">
 * Maptimize</a> hosted clustering solution.
 * <br /><br/>
 * <b>How it works</b>:<br/>
 * The <code>MarkerClusterer</code> will group markers into clusters according to
 * their distance from a cluster's center. When a marker is added,
 * the marker cluster will find a position in all the clusters, and 
 * if it fails to find one, it will create a new cluster with the marker.
 * The number of markers in a cluster will be displayed
 * on the cluster marker. When the map viewport changes,
 * <code>MarkerClusterer</code> will destroy the clusters in the viewport 
 * and regroup them into new clusters.
 *
 */

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/**
 * @name MarkerClustererOptions
 * @class This class represents optional arguments to the {@link MarkerClusterer}
 * constructor.
 * @property {Number} [maxZoom] The max zoom level monitored by a
 * marker cluster. If not given, the marker cluster assumes the maximum map
 * zoom level. When maxZoom is reached or exceeded all markers will be shown
 * without cluster.
 * @property {Number} [gridSize=60] The grid size of a cluster in pixel. Each
 * cluster will be a square. If you want the algorithm to run faster, you can set
 * this value larger.
 * @property {Array of MarkerStyleOptions} [styles]
 * Custom styles for the cluster markers.
 * The array should be ordered according to increasing cluster size,
 * with the style for the smallest clusters first, and the style for the
 * largest clusters last.
 */

/**
 * @name MarkerStyleOptions
 * @class An array of these is passed into the {@link MarkerClustererOptions}
 * styles option.
 * @property {String} [url] Image url.
 * @property {Number} [height] Image height.
 * @property {Number} [height] Image width.
 * @property {Array of Number} [opt_anchor] Anchor for label text, like [24, 12]. 
 *    If not set, the text will align center and middle.
 * @property {String} [opt_textColor="black"] Text color.
 */

/**
 * Creates a new MarkerClusterer to cluster markers on the map.
 *
 * @constructor
 * @param {GMap2} map The map that the markers should be added to.
 * @param {Array of GMarker} opt_markers Initial set of markers to be clustered.
 * @param {MarkerClustererOptions} opt_opts A container for optional arguments.
 */
function MarkerClusterer(map, opt_markers, opt_opts) {
  // private members
  var clusters_ = [];
  var map_ = map;
  var maxZoom_ = null;
  var me_ = this;
  var gridSize_ = 60;
  var sizes = [53, 56, 66, 78, 90];
  var styles_ = [];
  var leftMarkers_ = [];
  var mcfn_ = null;

  var i = 0;
  for (i = 1; i <= 5; ++i) {
    styles_.push({
      'url': "http://gmaps-utility-library.googlecode.com/svn/trunk/markerclusterer/images/m" + i + ".png",
      'height': sizes[i - 1],
      'width': sizes[i - 1]
    });
  }

  if (typeof opt_opts === "object" && opt_opts !== null) {
    if (typeof opt_opts.gridSize === "number" && opt_opts.gridSize > 0) {
      gridSize_ = opt_opts.gridSize;
    }
    if (typeof opt_opts.maxZoom === "number") {
      maxZoom_ = opt_opts.maxZoom;
    }
    if (typeof opt_opts.styles === "object" && opt_opts.styles !== null && opt_opts.styles.length !== 0) {
      styles_ = opt_opts.styles;
    }
  }

  /**
   * When we add a marker, the marker may not in the viewport of map, then we don't deal with it, instead
   * we add the marker into a array called leftMarkers_. When we reset MarkerClusterer we should add the
   * leftMarkers_ into MarkerClusterer.
   */
  function addLeftMarkers_() {
    if (leftMarkers_.length === 0) {
      return;
    }
    var leftMarkers = [];
    for (i = 0; i < leftMarkers_.length; ++i) {
      me_.addMarker(leftMarkers_[i], true, null, null, true);
    }
    leftMarkers_ = leftMarkers;
  }

  /**
   * Get cluster marker images of this marker cluster. Mostly used by {@link Cluster}
   * @private
   * @return {Array of String}
   */
  this.getStyles_ = function () {
    return styles_;
  };

  /**
   * Remove all markers from MarkerClusterer.
   */
  this.clearMarkers = function () {
    for (var i = 0; i < clusters_.length; ++i) {
      if (typeof clusters_[i] !== "undefined" && clusters_[i] !== null) {
        clusters_[i].clearMarkers();
      }
    }
    clusters_ = [];
    leftMarkers_ = [];
    GEvent.removeListener(mcfn_);
  };

  /**
   * Check a marker, whether it is in current map viewport.
   * @private
   * @return {Boolean} if it is in current map viewport
   */
  function isMarkerInViewport_(marker) {
    return map_.getBounds().containsLatLng(marker.getLatLng());
  }

  /**
   * When reset MarkerClusterer, there will be some markers get out of its cluster.
   * These markers should be add to new clusters.
   * @param {Array of GMarker} markers Markers to add.
   */
  function reAddMarkers_(markers) {
    var len = markers.length;
    var clusters = [];
    for (var i = len - 1; i >= 0; --i) {
      me_.addMarker(markers[i].marker, true, markers[i].isAdded, clusters, true);
    }
    addLeftMarkers_();
  }

  /**
   * Add a marker.
   * @private
   * @param {GMarker} marker Marker you want to add
   * @param {Boolean} opt_isNodraw Whether redraw the cluster contained the marker
   * @param {Boolean} opt_isAdded Whether the marker is added to map. Never use it.
   * @param {Array of Cluster} opt_clusters Provide a list of clusters, the marker
   *     cluster will only check these cluster where the marker should join.
   */
  this.addMarker = function (marker, opt_isNodraw, opt_isAdded, opt_clusters, opt_isNoCheck) {
    if (opt_isNoCheck !== true) {
      if (!isMarkerInViewport_(marker)) {
        leftMarkers_.push(marker);
        return;
      }
    }

    var isAdded = opt_isAdded;
    var clusters = opt_clusters;
    var pos = map_.fromLatLngToDivPixel(marker.getLatLng());

    if (typeof isAdded !== "boolean") {
      isAdded = false;
    }
    if (typeof clusters !== "object" || clusters === null) {
      clusters = clusters_;
    }

    var length = clusters.length;
    var cluster = null;
    for (var i = length - 1; i >= 0; i--) {
      cluster = clusters[i];
      var center = cluster.getCenter();
      if (center === null) {
        continue;
      }
      center = map_.fromLatLngToDivPixel(center);

      // Found a cluster which contains the marker.
      if (pos.x >= center.x - gridSize_ && pos.x <= center.x + gridSize_ &&
          pos.y >= center.y - gridSize_ && pos.y <= center.y + gridSize_) {
        cluster.addMarker({
          'isAdded': isAdded,
          'marker': marker
        });
        if (!opt_isNodraw) {
          cluster.redraw_();
        }
        return;
      }
    }

    // No cluster contain the marker, create a new cluster.
    cluster = new Cluster(this, map);
    cluster.addMarker({
      'isAdded': isAdded,
      'marker': marker
    });
    if (!opt_isNodraw) {
      cluster.redraw_();
    }

    // Add this cluster both in clusters provided and clusters_
    clusters.push(cluster);
    if (clusters !== clusters_) {
      clusters_.push(cluster);
    }
  };

  /**
   * Remove a marker.
   *
   * @param {GMarker} marker The marker you want to remove.
   */

  this.removeMarker = function (marker) {
    for (var i = 0; i < clusters_.length; ++i) {
      if (clusters_[i].remove(marker)) {
        clusters_[i].redraw_();
        return;
      }
    }
  };

  /**
   * Redraw all clusters in viewport.
   */
  this.redraw_ = function () {
    var clusters = this.getClustersInViewport_();
    for (var i = 0; i < clusters.length; ++i) {
      clusters[i].redraw_(true);
    }
  };

  /**
   * Get all clusters in viewport.
   * @return {Array of Cluster}
   */
  this.getClustersInViewport_ = function () {
    var clusters = [];
    var curBounds = map_.getBounds();
    for (var i = 0; i < clusters_.length; i ++) {
      if (clusters_[i].isInBounds(curBounds)) {
        clusters.push(clusters_[i]);
      }
    }
    return clusters;
  };

  /**
   * Get max zoom level.
   * @private
   * @return {Number}
   */
  this.getMaxZoom_ = function () {
    return maxZoom_;
  };

  /**
   * Get map object.
   * @private
   * @return {GMap2}
   */
  this.getMap_ = function () {
    return map_;
  };

  /**
   * Get grid size
   * @private
   * @return {Number}
   */
  this.getGridSize_ = function () {
    return gridSize_;
  };

  /**
   * Get total number of markers.
   * @return {Number}
   */
  this.getTotalMarkers = function () {
    var result = 0;
    for (var i = 0; i < clusters_.length; ++i) {
      result += clusters_[i].getTotalMarkers();
    }
    return result;
  };

  /**
   * Get total number of clusters.
   * @return {int}
   */
  this.getTotalClusters = function () {
    return clusters_.length;
  };

  /**
   * Collect all markers of clusters in viewport and regroup them.
   */
  this.resetViewport = function () {
    var clusters = this.getClustersInViewport_();
    var tmpMarkers = [];
    var removed = 0;

    for (var i = 0; i < clusters.length; ++i) {
      var cluster = clusters[i];
      var oldZoom = cluster.getCurrentZoom();
      if (oldZoom === null) {
        continue;
      }
      var curZoom = map_.getZoom();
      if (curZoom !== oldZoom) {

        // If the cluster zoom level changed then destroy the cluster
        // and collect its markers.
        var mks = cluster.getMarkers();
        for (var j = 0; j < mks.length; ++j) {
          var newMarker = {
            'isAdded': false,
            'marker': mks[j].marker
          };
          tmpMarkers.push(newMarker);
        }
        cluster.clearMarkers();
        removed++;
        for (j = 0; j < clusters_.length; ++j) {
          if (cluster === clusters_[j]) {
            clusters_.splice(j, 1);
          }
        }
      }
    }

    // Add the markers collected into marker cluster to reset
    reAddMarkers_(tmpMarkers);
    this.redraw_();
  };


  /**
   * Add a set of markers.
   *
   * @param {Array of GMarker} markers The markers you want to add.
   */
  this.addMarkers = function (markers) {
    for (var i = 0; i < markers.length; ++i) {
      this.addMarker(markers[i], true);
    }
    this.redraw_();
  };

  // initialize
  if (typeof opt_markers === "object" && opt_markers !== null) {
    this.addMarkers(opt_markers);
  }

  // when map move end, regroup.
  mcfn_ = GEvent.addListener(map_, "moveend", function () {
    me_.resetViewport();
  });
}

/**
 * Create a cluster to collect markers.
 * A cluster includes some markers which are in a block of area.
 * If there are more than one markers in cluster, the cluster
 * will create a {@link ClusterMarker_} and show the total number
 * of markers in cluster.
 *
 * @constructor
 * @private
 * @param {MarkerClusterer} markerClusterer The marker cluster object
 */
function Cluster(markerClusterer) {
  var center_ = null;
  var markers_ = [];
  var markerClusterer_ = markerClusterer;
  var map_ = markerClusterer.getMap_();
  var clusterMarker_ = null;
  var zoom_ = map_.getZoom();

  /**
   * Get markers of this cluster.
   *
   * @return {Array of GMarker}
   */
  this.getMarkers = function () {
    return markers_;
  };

  /**
   * If this cluster intersects certain bounds.
   *
   * @param {GLatLngBounds} bounds A bounds to test
   * @return {Boolean} Is this cluster intersects the bounds
   */
  this.isInBounds = function (bounds) {
    if (center_ === null) {
      return false;
    }

    if (!bounds) {
      bounds = map_.getBounds();
    }
    var sw = map_.fromLatLngToDivPixel(bounds.getSouthWest());
    var ne = map_.fromLatLngToDivPixel(bounds.getNorthEast());

    var centerxy = map_.fromLatLngToDivPixel(center_);
    var inViewport = true;
    var gridSize = markerClusterer.getGridSize_();
    if (zoom_ !== map_.getZoom()) {
      var dl = map_.getZoom() - zoom_;
      gridSize = Math.pow(2, dl) * gridSize;
    }
    if (ne.x !== sw.x && (centerxy.x + gridSize < sw.x || centerxy.x - gridSize > ne.x)) {
      inViewport = false;
    }
    if (inViewport && (centerxy.y + gridSize < ne.y || centerxy.y - gridSize > sw.y)) {
      inViewport = false;
    }
    return inViewport;
  };

  /**
   * Get cluster center.
   *
   * @return {GLatLng}
   */
  this.getCenter = function () {
    return center_;
  };

  /**
   * Add a marker.
   *
   * @param {Object} marker An object of marker you want to add:
   *   {Boolean} isAdded If the marker is added on map.
   *   {GMarker} marker The marker you want to add.
   */
  this.addMarker = function (marker) {
    if (center_ === null) {
      /*var pos = marker['marker'].getLatLng();
       pos = map.fromLatLngToContainerPixel(pos);
       pos.x = parseInt(pos.x - pos.x % (GRIDWIDTH * 2) + GRIDWIDTH);
       pos.y = parseInt(pos.y - pos.y % (GRIDWIDTH * 2) + GRIDWIDTH);
       center = map.fromContainerPixelToLatLng(pos);*/
      center_ = marker.marker.getLatLng();
    }
    markers_.push(marker);
  };

  /**
   * Remove a marker from cluster.
   *
   * @param {GMarker} marker The marker you want to remove.
   * @return {Boolean} Whether find the marker to be removed.
   */
  this.removeMarker = function (marker) {
    for (var i = 0; i < markers_.length; ++i) {
      if (marker === markers_[i].marker) {
        if (markers_[i].isAdded) {
          map_.removeOverlay(markers_[i].marker);
        }
        markers_.splice(i, 1);
        return true;
      }
    }
    return false;
  };

  /**
   * Get current zoom level of this cluster.
   * Note: the cluster zoom level and map zoom level not always the same.
   *
   * @return {Number}
   */
  this.getCurrentZoom = function () {
    return zoom_;
  };

  /**
   * Redraw a cluster.
   * @private
   * @param {Boolean} isForce If redraw by force, no matter if the cluster is
   *     in viewport.
   */
  this.redraw_ = function (isForce) {
    if (!isForce && !this.isInBounds()) {
      return;
    }

    // Set cluster zoom level.
    zoom_ = map_.getZoom();
    var i = 0;
    var mz = markerClusterer.getMaxZoom_();
    if (mz === null) {
      mz = map_.getCurrentMapType().getMaximumResolution();
    }
    if (zoom_ >= mz || this.getTotalMarkers() === 1) {

      // If current zoom level is beyond the max zoom level or the cluster
      // have only one marker, the marker(s) in cluster will be showed on map.
      for (i = 0; i < markers_.length; ++i) {
        if (markers_[i].isAdded) {
          if (markers_[i].marker.isHidden()) {
            markers_[i].marker.show();
          }
        } else {
          map_.addOverlay(markers_[i].marker);
          markers_[i].isAdded = true;
        }
      }
      if (clusterMarker_ !== null) {
        clusterMarker_.hide();
      }
    } else {
      // Else add a cluster marker on map to show the number of markers in
      // this cluster.
      for (i = 0; i < markers_.length; ++i) {
        if (markers_[i].isAdded && (!markers_[i].marker.isHidden())) {
          markers_[i].marker.hide();
        }
      }
      if (clusterMarker_ === null) {
        clusterMarker_ = new ClusterMarker_(center_, this.getTotalMarkers(), markerClusterer_.getStyles_(), markerClusterer_.getGridSize_());
        map_.addOverlay(clusterMarker_);
      } else {
        if (clusterMarker_.isHidden()) {
          clusterMarker_.show();
        }
        clusterMarker_.redraw(true);
      }
    }
  };

  /**
   * Remove all the markers from this cluster.
   */
  this.clearMarkers = function () {
    if (clusterMarker_ !== null) {
      map_.removeOverlay(clusterMarker_);
    }
    for (var i = 0; i < markers_.length; ++i) {
      if (markers_[i].isAdded) {
        map_.removeOverlay(markers_[i].marker);
      }
    }
    markers_ = [];
  };

  /**
   * Get number of markers.
   * @return {Number}
   */
  this.getTotalMarkers = function () {
    return markers_.length;
  };
}

/**
 * ClusterMarker_ creates a marker that shows the number of markers that
 * a cluster contains.
 *
 * @constructor
 * @private
 * @param {GLatLng} latlng Marker's lat and lng.
 * @param {Number} count Number to show.
 * @param {Array of Object} styles The image list to be showed:
 *   {String} url Image url.
 *   {Number} height Image height.
 *   {Number} width Image width.
 *   {Array of Number} anchor Text anchor of image left and top.
 *   {String} textColor text color.
 * @param {Number} padding Padding of marker center.
 */
function ClusterMarker_(latlng, count, styles, padding) {
  var index = 0;
  var dv = count;
  while (dv !== 0) {
    dv = parseInt(dv / 10, 10);
    index ++;
  }

  if (styles.length < index) {
    index = styles.length;
  }
  this.url_ = styles[index - 1].url;
  this.height_ = styles[index - 1].height;
  this.width_ = styles[index - 1].width;
  this.textColor_ = styles[index - 1].opt_textColor;
  this.anchor_ = styles[index - 1].opt_anchor;
  this.latlng_ = latlng;
  this.index_ = index;
  this.styles_ = styles;
  this.text_ = count;
  this.padding_ = padding;
}

ClusterMarker_.prototype = new GOverlay();

/**
 * Initialize cluster marker.
 * @private
 */
ClusterMarker_.prototype.initialize = function (map) {
  this.map_ = map;
  var div = document.createElement("div");
  var latlng = this.latlng_;
  var pos = map.fromLatLngToDivPixel(latlng);
  pos.x -= parseInt(this.width_ / 2, 10);
  pos.y -= parseInt(this.height_ / 2, 10);
  var mstyle = "";
  if (document.all) {
    mstyle = 'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale,src="' + this.url_ + '");';
  } else {
    mstyle = "background:url(" + this.url_ + ");";
  }
  if (typeof this.anchor_ === "object") {
    if (typeof this.anchor_[0] === "number" && this.anchor_[0] > 0 && this.anchor_[0] < this.height_) {
      mstyle += 'height:' + (this.height_ - this.anchor_[0]) + 'px;padding-top:' + this.anchor_[0] + 'px;';
    } else {
      mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
    }
    if (typeof this.anchor_[1] === "number" && this.anchor_[1] > 0 && this.anchor_[1] < this.width_) {
      mstyle += 'width:' + (this.width_ - this.anchor_[1]) + 'px;padding-left:' + this.anchor_[1] + 'px;';
    } else {
      mstyle += 'width:' + this.width_ + 'px;text-align:center;';
    }
  } else {
    mstyle += 'height:' + this.height_ + 'px;line-height:' + this.height_ + 'px;';
    mstyle += 'width:' + this.width_ + 'px;text-align:center;';
  }
  var txtColor = this.textColor_ ? this.textColor_ : 'black';

  div.style.cssText = mstyle + 'cursor:pointer;top:' + pos.y + "px;left:" +
      pos.x + "px;color:" + txtColor +  ";position:absolute;font-size:11px;" +
      'font-family:Arial,sans-serif;font-weight:bold';
  div.innerHTML = this.text_;
  map.getPane(G_MAP_MAP_PANE).appendChild(div);
  var padding = this.padding_;
  GEvent.addDomListener(div, "click", function () {
    var pos = map.fromLatLngToDivPixel(latlng);
    var sw = new GPoint(pos.x - padding, pos.y + padding);
    sw = map.fromDivPixelToLatLng(sw);
    var ne = new GPoint(pos.x + padding, pos.y - padding);
    ne = map.fromDivPixelToLatLng(ne);
    var zoom = map.getBoundsZoomLevel(new GLatLngBounds(sw, ne), map.getSize());
    map.setCenter(latlng, zoom);
  });
  this.div_ = div;
};

/**
 * Remove this overlay.
 * @private
 */
ClusterMarker_.prototype.remove = function () {
  this.div_.parentNode.removeChild(this.div_);
};

/**
 * Copy this overlay.
 * @private
 */
ClusterMarker_.prototype.copy = function () {
  return new ClusterMarker_(this.latlng_, this.index_, this.text_, this.styles_, this.padding_);
};

/**
 * Redraw this overlay.
 * @private
 */
ClusterMarker_.prototype.redraw = function (force) {
  if (!force) {
    return;
  }
  var pos = this.map_.fromLatLngToDivPixel(this.latlng_);
  pos.x -= parseInt(this.width_ / 2, 10);
  pos.y -= parseInt(this.height_ / 2, 10);
  this.div_.style.top =  pos.y + "px";
  this.div_.style.left = pos.x + "px";
};

/**
 * Hide this cluster marker.
 */
ClusterMarker_.prototype.hide = function () {
  this.div_.style.display = "none";
};

/**
 * Show this cluster marker.
 */
ClusterMarker_.prototype.show = function () {
  this.div_.style.display = "";
};

/**
 * Get whether the cluster marker is hidden.
 * @return {Boolean}
 */
ClusterMarker_.prototype.isHidden = function () {
  return this.div_.style.display === "none";
};
/**
 * @author Shea Frederick
 * http://www.vinylfox.com
 */

Ext.namespace('Ext.ux');
 
/**
 * This extension adds Google maps functionality to any panel or panel based component (ie: windows).
 * @class Ext.ux.GMapPanel
 * @extends Ext.Panel
 * @param {Object} config The config object
 */
Ext.ux.GMapPanel = Ext.extend(Ext.Panel, {
    respErrors: [{
            code: 400, // G_GEO_BAD_REQUEST
            msg: 'A directions request could not be successfully parsed. For example, the request may have been rejected if it contained more than the maximum number of waypoints allowed.' 
        },{
            code: G_GEO_SERVER_ERROR,
            msg: 'A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.'
        },{
            code: 601, // G_GEO_MISSING_QUERY
            msg: 'The HTTP q parameter was either missing or had no value. For geocoding requests, this means that an empty address was specified as input. For directions requests, this means that no query was specified in the input.'
        },{
            code: G_GEO_MISSING_ADDRESS,
            msg: 'Synonym for G_GEO_MISSING_QUERY.' 
        },{
            code: G_GEO_UNKNOWN_ADDRESS,
            msg: 'No corresponding geographic location could be found for the specified address. This may be due to the fact that the address is relatively new, or it may be incorrect.' 
        },{
            code: G_GEO_UNAVAILABLE_ADDRESS,
            msg: 'The geocode for the given address or the route for the given directions query cannot be returned due to legal or contractual reasons.' 
        },{
            code: 604, // G_GEO_UNKNOWN_DIRECTIONS
            msg: 'The GDirections object could not compute directions between the points mentioned in the query. This is usually because there is no route available between the two points, or because we do not have data for routing in that region.'
        },{
            code: G_GEO_BAD_KEY,
            msg: 'The given key is either invalid or does not match the domain for which it was given.' 
        },{
            code: G_GEO_TOO_MANY_QUERIES,
            msg: 'The given key has gone over the requests limit in the 24 hour period or has submitted too many requests in too short a period of time. If you\'re sending multiple requests in parallel or in a tight loop, use a timer or pause in your code to make sure you don\'t send the requests too quickly.' 
    }],
    respErrorTitle : 'Error',
    geoErrorMsgUnable : 'Unable to Locate the Address you provided',
    geoErrorTitle : 'Address Location Error',
    geoErrorMsgAccuracy : 'The address provided has a low accuracy.<br><br>Level {0} Accuracy (8 = Exact Match, 1 = Vague Match)',
    /**
    * @cfg {String} gmapType
    * The type of map to display, generic options available are: 'map', 'panorama'. 
    * More specific maps can be used by specifying the google map type:
    * 
    * G_NORMAL_MAP displays the default road map view
    * G_SATELLITE_MAP displays Google Earth satellite images
    * G_HYBRID_MAP displays a mixture of normal and satellite views
    * G_DEFAULT_MAP_TYPES contains an array of the above three types, useful for iterative processing.
    * G_PHYSICAL_MAP displays a physical map based on terrain information. 
    * G_MOON_ELEVATION_MAP displays a shaded terrain map of the surface of the Moon, color-coded by altitude.
    * G_MOON_VISIBLE_MAP displays photographic imagery taken from orbit around the moon.
    * G_MARS_ELEVATION_MAP displays a shaded terrain map of the surface of Mars, color-coded by altitude.
    * G_MARS_VISIBLE_MAP displays photographs taken from orbit around Mars.
    * G_MARS_INFRARED_MAP displays a shaded infrared map of the surface of Mars, where warmer areas appear brighter and colder areas appear darker.
    * G_SKY_VISIBLE_MAP displays a mosaic of the sky, as seen from Earth, covering the full celestial sphere.
    * 
    * These map types can be used within a configuration like this:  { gmapType: G_MOON_VISIBLE_MAP }
    */
    /**
    * @cfg {Object} setCenter
    * A center starting point for the map. The map needs to be centered before it can be used.
    * The config can contain an address to geocode, and even a marker
    * \{
    *   geoCodeAddr: '4 Yawkey Way, Boston, MA, 02215-3409, USA',
    *   marker: \{title: 'Fenway Park'\}
    * \}
    * Or it can simply be a lat/lng. Either way, a marker is not required, all we are really looking for here is a starting center point for the map.
    * \{
    *   lat: 42.339641,
    *   lng: -71.094224
    * \}
    */
    /**
     * @cfg {Number} zoomLevel
     * The zoom level to initialize the map at, generally between 1 (whole planet) and 40 (street). Also used as the zoom level for panoramas, zero specifies no zoom at all.
     */
    /**
     * @cfg {Number} yaw
     * The Yaw, or rotational direction of the users perspective in degrees. Only applies to panoramas.
     */
    /**
     * @cfg {Number} pitch
     * The pitch, or vertical direction of the users perspective in degrees. Default is 0 (zero), straight ahead. Valid values are between +90 (straight up) and -90 (straight down). 
     */
    /**
     * @cfg {Boolean} displayGeoErrors
     * True to display geocoding errors to the end user via a message box.
     */
    /**
     * @cfg {Boolean} minGeoAccuracy
     * The level (between 1 & 8) to display an accuracy error below. Defaults to seven (7).
     * see: http://code.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy
     */
    /**
     * @cfg {Array} mapConfOpts
     * Array of strings representing configuration methods to call, a full list can be found here: http://code.google.com/apis/maps/documentation/reference.html#GMap2
     */
    /**
     * @cfg {Array} mapControls
     * Array of strings representing map controls to initialize, a full list can be found here: http://code.google.com/apis/maps/documentation/reference.html#GControlImpl
     */
    // private
    initComponent : function(){
        
        var defConfig = {
            plain: true,
            zoomLevel: 0,
            yaw: 180,
            pitch: 0,
            gmapType: 'map',
            border: false,
            displayGeoErrors: false,
                        minGeoAccuracy: 7,
                        mapDefined: false,
                        mapDefinedGMap: false
        };
        
        Ext.applyIf(this,defConfig);
        
        Ext.ux.GMapPanel.superclass.initComponent.call(this);        

    },
    // private
    afterRender : function(){
        
        var wh = this.ownerCt.getSize();
        Ext.applyIf(this, wh);
        
        Ext.ux.GMapPanel.superclass.afterRender.call(this);    
        
        if (this.gmapType === 'map'){
            this.gmap = new GMap2(this.body.dom);
                        this.mapDefined = true;
                        this.mapDefinedGMap = true;
        }
        
        if (this.gmapType === 'panorama'){
            this.gmap = new GStreetviewPanorama(this.body.dom);
                        this.mapDefined = true;
        }

                if (!this.mapDefined && this.gmapType){
                        this.gmap = new GMap2(this.body.dom);
                        this.gmap.setMapType(this.gmapType);
                        this.mapDefined = true;
                        this.mapDefinedGMap = true;
                }
        
        GEvent.bind(this.getMap(), 'load', this, this.onMapReady);
        
        if (typeof this.setCenter === 'object') {
            if (typeof this.setCenter.geoCodeAddr === 'string'){
                this.geoCodeLookup(this.setCenter.geoCodeAddr, this.setCenter.marker, false, true, this.setCenter.listeners);
            }else{
                if (this.gmapType === 'map'){
                    var point = this.fixLatLng(new GLatLng(this.setCenter.lat,this.setCenter.lng));
                    this.getMap().setCenter(point, this.zoomLevel);    
                }
                if (typeof this.setCenter.marker === 'object' && typeof point === 'object') {
                    this.addMarker(point, this.setCenter.marker, this.setCenter.marker.clear);
                }
            }
            if (this.gmapType === 'panorama'){
                this.getMap().setLocationAndPOV(new GLatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoomLevel});
            }
        }

    },
    // private
    onMapReady : function(){
        
        this.addMapControls();
        this.addOptions();
        
        this.addMarkers(this.markers);
        this.addKMLOverlay(this.autoLoadKML);
        
    },
    // private
    onResize : function(w, h){
        
        // check for the existance of the google map in case the onResize fires too early
        if (typeof this.getMap() == 'object') {
            this.getMap().checkResize();
        }
        
        Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);

    },
    // private
    setSize : function(width, height, animate){
        
        // check for the existance of the google map in case setSize is called too early
        if (typeof this.getMap() == 'object') {
            this.getMap().checkResize();
        }
        
        Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);
        
    },
    /**
     * Returns the current google map
     * @return {GMap} this
     */
    getMap : function(){
        return this.gmap;
        
    },
    /**
     * Returns the maps center as a GLatLng object
     * @return {GLatLng} this
     */
    getCenter : function(){
        
        return this.fixLatLng(this.getMap().getCenter());
        
    },
    /**
     * Returns the maps center as a simple object
     * @return {Object} this has lat and lng properties only
     */
    getCenterLatLng : function(){
        
        var ll = this.getCenter();
        return {lat: ll.lat(), lng: ll.lng()};
        
    },
    /**
     * Creates markers from the array that is passed in. Each marker must consist of at least lat and lng properties.
     * @param {Array} markers an array of marker objects
     */
    addMarkers : function(markers) {
        
        if (Ext.isArray(markers)){
            for (var i = 0; i < markers.length; i++) {
                if (typeof markers[i].geoCodeAddr == 'string') {
                    this.geoCodeLookup(markers[i].geoCodeAddr, markers[i].marker, false, markers[i].setCenter, markers[i].listeners);
                }else{
                    var mkr_point = this.fixLatLng(new GLatLng(markers[i].lat, markers[i].lng));
                    this.addMarker(mkr_point, markers[i].marker, false, markers[i].setCenter, markers[i].listeners);
                }
            }
        }
        
    },
    /**
     * Creates a single marker.
     * @param {Object} point a GLatLng point
     * @param {Object} marker a marker object consisting of at least lat and lng
     * @param {Boolean} clear clear other markers before creating this marker
     * @param {Boolean} center true to center the map on this marker
     * @param {Object} listeners a listeners config
     */
    addMarker : function(point, marker, clear, center, listeners){
        
        Ext.applyIf(marker,G_DEFAULT_ICON);

        if (clear === true){
            this.getMap().clearOverlays();
        }
        if (center === true) {
            this.getMap().setCenter(point, this.zoomLevel);
        }

        var mark = new GMarker(point,marker);
        if (typeof listeners === 'object'){
            for (evt in listeners) {
                GEvent.bind(mark, evt, this, listeners[evt]);
            }
        }
        this.getMap().addOverlay(mark);

    },
    // private
    addMapControls : function(){
        
        if (this.gmapType === 'map') {
            if (Ext.isArray(this.mapControls)) {
                for(i=0;i<this.mapControls.length;i++){
                    this.addMapControl(this.mapControls[i]);
                }
            }else if(typeof this.mapControls === 'string'){
                this.addMapControl(this.mapControls);
            }else if(typeof this.mapControls === 'object'){
                this.getMap().addControl(this.mapControls);
            }
        }
        
    },
    /**
     * Adds a GMap control to the map.
     * @param {String} mc a string representation of the control to be instantiated.
     */
    addMapControl : function(mc){
        
        var mcf = window[mc];
        if (typeof mcf === 'function') {
            this.getMap().addControl(new mcf());
        }    
        
    },
    // private
    addOptions : function(){
        
        if (Ext.isArray(this.mapConfOpts)) {
            var mc;
            for(i=0;i<this.mapConfOpts.length;i++){
                this.addOption(this.mapConfOpts[i]);
            }
        }else if(typeof this.mapConfOpts === 'string'){
            this.addOption(this.mapConfOpts);
        }        
        
    },
    /**
     * Adds a GMap option to the map.
     * @param {String} mo a string representation of the option to be instantiated.
     */
    addOption : function(mo){
        
        var mof = this.getMap()[mo];
        if (typeof mof === 'function') {
            this.getMap()[mo]();
        }    
        
    },
    /**
     * Loads a KML file into the map.
     * @param {String} kmlfile a string URL to the KML file.
     */
    addKMLOverlay : function(kmlfile){
        
        if (typeof kmlfile === 'string' && kmlfile !== '') {
            var geoXml = new GGeoXml(kmlfile);
            this.getMap().addOverlay(geoXml);
        }
        
    },
    /**
     * Adds a marker to the map based on an address string (ie: "123 Fake Street, Springfield, NA, 12345, USA") or center the map on the address.
     * @param {String} addr the address to lookup.
     * @param {Object} marker the marker to add (optional).
     * @param {Boolean} clear clear other markers before creating this marker
     * @param {Boolean} center true to set this point as the center of the map.
     * @param {Object} listeners a listeners config
     */
    geoCodeLookup : function(addr, marker, clear, center, listeners) {
        
        if (!this.geocoder) {
            this.geocoder = new GClientGeocoder();
        }
        this.geocoder.getLocations(addr, this.addAddressToMap.createDelegate(this, [addr, marker, clear, center, listeners], true));
        
    },
    // private
    addAddressToMap : function(response, addr, marker, clear, center, listeners){
        if (!response || response.Status.code != 200) {
            this.respErrorMsg(response.Status.code);
        }else{
            place = response.Placemark[0];
            addressinfo = place.AddressDetails;
            accuracy = addressinfo.Accuracy;
            if (accuracy === 0) {
                this.geoErrorMsg(this.geoErrorTitle, this.geoErrorMsgUnable);
            }else{
                if (accuracy < this.minGeoAccuracy) {
                    this.geoErrorMsg(this.geoErrorTitle, String.format(this.geoErrorMsgAccuracy, accuracy));
                }else{
                    point = this.fixLatLng(new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]));
                    if (center){
                        this.getMap().setCenter(point, this.zoomLevel);
                    }
                    if (typeof marker === 'object') {
                        if (!marker.title){
                            marker.title = place.address;
                        }
                        Ext.applyIf(marker, G_DEFAULT_ICON);
                        this.addMarker(point, marker, clear, false, listeners);
                    }
                }
            }
        }
        
    },
    // private
    geoErrorMsg : function(title,msg){
        if (this.displayGeoErrors) {
            Ext.MessageBox.alert(title,msg);
        }
    },
    // private
    respErrorMsg : function(code){
        Ext.each(this.respErrors, function(obj){
            if (code == obj.code){
                Ext.MessageBox.alert(this.respErrorTitle, obj.msg);
            }
        }, this);
    },
        // private
        // used to inverse the lat/lng coordinates to correct locations on the sky map
        fixLatLng : function(llo){
        if (this.getMap().getCurrentMapType()) {
            if (this.getMap().getCurrentMapType().QO == 'visible') {
                llo.lat(180 - llo.lat());
                llo.lng(180 - llo.lng());
            }
        }
                return llo;
        }
});

Ext.reg('gmappanel',Ext.ux.GMapPanel);                     

function initDlgSearch() {
  containerSearchDlg = new Ext.Component( {
    applyTo: 'dlgSearchDIV',
    autoHeight: true
  });
  containerSearchPnl = containerSearchDlg.cloneConfig();
  
  pnlSearch = new Ext.Panel( {
    layout: 'anchor',
    applyTo: 'col3Search',
    width:580,
    autoHeight: true,
    items: containerSearchPnl,
    title: 'Search for Hotels',
    buttons: [ {
      text: 'Search for Hotels',
      icon: './cssImages/button_search.png',
      iconCls: 'x-btn-text-icon',
      tooltip: '<b>Search for Hotels</b><br/>Start Searching for Hotels. You can alter your selection, anytime, clicking the \'Change Selection\' link ', 
      handler: function() { handleSearchHotels() }      
    }],
    keys: [ {
      key: Ext.EventObject.ENTER,
      fn: function() { handleSearchHotels() }
    }]
  });

	dlgSearch = new Ext.Window( {
    layout: 'anchor',
    items: containerSearchDlg,
    title: 'Search for Hotels',
    width:580,
    autoHeight: true,
    closable: false,
    closeAction: 'hide',
    shim: true,
     
      animateTarget: 'newSearchLink',
        modal: true,
    resizable: false,
    buttons: [ {
      text: 'Search for Hotels',
      icon: './cssImages/button_search.png',
      iconCls: 'x-btn-text-icon',
      tooltip: '<b>Search for Hotels</b><br/>Start Searching for Hotels. You can alter your selection, anytime, clicking the \'Change Selection\' link ', 
      handler: function() { handleSearchHotels() }      
    }, {
      text: 'Cancel',
      icon: './cssImages/button_cancel.png',
      iconCls: 'x-btn-text-icon',
      tooltip: '<b>Cancel</b><br/>Close this window. You can see this window anytime, by clicking the \'New Search\' link (if you haven\'t perform a search previously, or \'Change Selection\' link if you have performed a search previously.', 
      handler: function() { dlgSearch.hide();}
    }],
    keys: [ {
      key: Ext.EventObject.ENTER,
      fn: function() { handleSearchHotels(); }
    }, {
      key: Ext.EventObject.ESC,
      fn: function()  { dlgSearch.hide() }
    }]
  });
  
  dlgSearch.on('show', function() {
    var f = Ext.get('acGeoSearch');
    f.focus.defer(100, f);
    $$$('acGeoSearch').value = preSelectCountryObj.geoname;
  });
   
  Ext.EventManager.onWindowResize( function() {
                                      if ( dlgSearch.container != undefined) {
                                        dlgSearch.center();
                                      }
                                    }, dlgSearch);

}



function createRecentSearchesDIVoutput() {
	var tmpOut = '';
	var maxRecentSearchesRecords = 5;
	
	if (recentSearchesObjArray.length > 0) {
		if (recentSearchesObjArray.length < maxRecentSearchesRecords) {
			maxRecentSearchesRecords = 1;
		} else {
			maxRecentSearchesRecords = recentSearchesObjArray.length - maxRecentSearchesRecords;
		}
		tmpOut += '<table width="100%" style="padding:3px; margin:3px; border: solid 1px black; background-color:#efefef;"><tr><td style="font-size:12px; font-weight:bold;">Recent Searches</td></tr>';
		for (idx=recentSearchesObjArray.length; idx>=maxRecentSearchesRecords; idx--) {
			var tmpGeoName = recentSearchesObjArray[idx-1].geoname;
			if (tmpGeoName.length > 22) {
				tmpGeoName = tmpGeoName.substring(0,22) + '...';
			}
			tmpOut += ['<tr>',
								'<td><a href="javascript:void(null)" onclick="doModifySearch(', (idx-1), ', true)" title="', recentSearchesObjArray[idx-1].geoname, '">',	tmpGeoName, '</a></td>',
								'<td>',	recentSearchesObjArray[idx-1].arrivalFormated, ' - ', recentSearchesObjArray[idx-1].departureFormated, '</td>',
								'<td>',	recentSearchesObjArray[idx-1].rooms, ' room(s) for ',
											  String.format(" <img src='./images/ico_pax{0}_{1}.gif' border='0' height='15' align='absmiddle'> ", recentSearchesObjArray[idx-1].adults, recentSearchesObjArray[idx-1].children), 
								'</td',
								'<td><a href="javascript:void(null)" onclick="doModifySearch(', (idx-1), ', false)">Modify Search</a></td>',
								'<td><a href="javascript:void(null)" onclick="doDeleteRecentSearches(', (idx-1), ')"><img src="./images/trash.gif" border="0"></a></td>',
								'</tr>'].join('');
								
		}
		tmpOut += '</table>';
		$$$('recentSearchesDIV').innerHTML = tmpOut;
		$$$('recentSearchesDIV').style.display = '';
		if (dlgSearch.rendered) {
			dlgSearch.render();
		}
	} else {
		$$$('recentSearchesDIV').style.display = 'none';
	}
}

function doDeleteRecentSearches(inIDX) {
	recentSearchesObjArray.splice(inIDX,1);
	createRecentSearchesDIVoutput();
}

function doModifySearch(inIDX, inPost) {
	$$$('acGeoSearch').value				= recentSearchesObjArray[inIDX].geoname;
	preSelectCountryObj.country			= recentSearchesObjArray[inIDX].country;
	preSelectCountryObj.city				= recentSearchesObjArray[inIDX].city;
	preSelectCountryObj.area				= recentSearchesObjArray[inIDX].area;
	preSelectCountryObj.geoname			= recentSearchesObjArray[inIDX].geoname;
	preSelectCountryObj.weatherCode = recentSearchesObjArray[inIDX].weatherCode;
	CheckIn.setValue(recentSearchesObjArray[inIDX].arrival);
	CheckOut.setValue(recentSearchesObjArray[inIDX].departure);
	arrival = recentSearchesObjArray[inIDX].arrival;
	departure = recentSearchesObjArray[inIDX].departure;
	calcNights();
	Ext.getCmp('cbRooms').setValue (recentSearchesObjArray[inIDX].rooms);
	Ext.getCmp('cbBedConfig').setValue(recentSearchesObjArray[inIDX].adults + '_' + recentSearchesObjArray[inIDX].children);
  $$$("Adults").value = recentSearchesObjArray[inIDX].adults;
  $$$("Children").value = recentSearchesObjArray[inIDX].children;
	
	if (inPost) {
		handleSearchHotels();
	}
}


function doShowHotelsFromIndex(inCountry, inCity, inArea, inArrival, inNights, inRooms, inAdults, inChildren, inHotelID, inPost, inShowLeftAccord, inShowRightAccord) {
  objPreSelectedHotel.id = inHotelID;
  
  if (inShowLeftAccord == undefined) {
    gExpandLeft = true;
  } else {
    gExpandLeft = inShowLeftAccord; 
  }
  
  if (inShowRightAccord == undefined) {
    gExpandRight = true;
  } else {
    gExpandRight = inShowRightAccord; 
  }
   
  if (inArrival == '') {
    inArrival = arrival;
  } else {
    inArrival = Date.parseDate(inArrival, 'Ymd');
    arrival = inArrival;
    CheckIn.setValue (arrival);
  }
  Ext.getCmp('cbNights').setValue(inNights);
  $$$('s_nights').value = inNights;
  calcDeparture(inNights);
  
  Ext.getCmp('cbRooms').setValue(inRooms);
  Ext.getCmp('cbBedConfig').setValue( inAdults + '_' + inChildren);
  $$$("Adults").value = inAdults;
  $$$("Children").value = inChildren;
  
  plsWaitPanel.show();
  var sURL =  'id=getCities' +
              '&locstr=' +
              '&country=' + inCountry +
              '&city=' + inCity +
              '&area=' + inArea;
  var con = new Ext.data.Connection({
      timeout: 60000
  });
  con.request ({
      url : baseURL,
      params: sURL,
      method: 'GET',
      success:  function(result, options) {
                  if ( trim(result.responseText) != '' && result.status == 200) {
                    var nXML      = result.responseXML.documentElement;
                    var nRS       = nXML.getElementsByTagName('rs');
                    preSelectCountryObj.country     = getNodeText(nRS[0].getElementsByTagName('countryid')[0].firstChild);
                    preSelectCountryObj.city        = getNodeText(nRS[0].getElementsByTagName('cityid')[0].firstChild);
                    preSelectCountryObj.area        = getNodeText(nRS[0].getElementsByTagName('areaid')[0].firstChild);
                    preSelectCountryObj.weathercode = getNodeText(nRS[0].getElementsByTagName('weathercode')[0].firstChild);
                    preSelectCountryObj.geoname     = getNodeText(nRS[0].getElementsByTagName('name')[0].firstChild);
                    if (preSelectCountryObj.city == '') {preSelectCountryObj.city = 0;}
                    if (preSelectCountryObj.area == '') {preSelectCountryObj.area = 0;}
                    if (plsWaitPanel) {
                      plsWaitPanel.hide();
                    }
                    if (! inPost) {           
                      dlgSearch.show();
                    }
                  	$$$('acGeoSearch').value = preSelectCountryObj.geoname;
                    if (inPost) {
                      handleSearchHotels();
                    }                
                  } else {
                    if (plsWaitPanel) {
                      plsWaitPanel.hide();
                    }
                    handleFailure();  
                  }
                },
      failure: handleFailure,
      disableCaching: false
  } );
}


function handleSearchHotels() {
  // Transfer the preSelected values
  vCountry         = preSelectCountryObj.country;
  vArea            = preSelectCountryObj.city;
  vCity            = preSelectCountryObj.area;
  vWeatherCityCode = preSelectCountryObj.weathercode;
  
  // Check the validity of the form
  if (vCountry == "0" ||  vArea == "0") {
    $alert ("Please select a place to search for...", "Error: Empty Destination ", "error");
    return false;
  }
  
  if (arrival >= departure) {
    $alert ("Departure must be after arrival !!!", "Error: Wrong Dates", "error");
    return false;    
  }

  // If OK => Go On
  try {
    dlgSearch.hide();
  } catch (e) {
    //
  }
	gFlickPage = 1;
	
	// Populate the Latest Searches Array
	var tmpRecentSearchesObj = new recentSearchesObj (	preSelectCountryObj.country,
																											preSelectCountryObj.city,
																											preSelectCountryObj.area,
																											preSelectCountryObj.geoname,
																											preSelectCountryObj.weatherCode,
																											CheckIn.getValue(),
																											$$$('CheckIn').value,
																											CheckOut.getValue(),
																											$$$('CheckOut').value,
																											$$$('Rooms').value,
																											$$$('Adults').value,
																											$$$('Children').value
																										);
	var recentSearchesObjFound = false;
	for (idx = 0; idx < recentSearchesObjArray.length; idx++) {
			if	(
						recentSearchesObjArray[idx].country 					== tmpRecentSearchesObj.country &&
						recentSearchesObjArray[idx].city 							== tmpRecentSearchesObj.city &&
						recentSearchesObjArray[idx].area 							== tmpRecentSearchesObj.area &&
						recentSearchesObjArray[idx].geoname						== tmpRecentSearchesObj.geoname &&
						recentSearchesObjArray[idx].weatherCode 			== tmpRecentSearchesObj.weatherCode &&
						recentSearchesObjArray[idx].arrivalFormated 	== tmpRecentSearchesObj.arrivalFormated &&
						recentSearchesObjArray[idx].departureFormated == tmpRecentSearchesObj.departureFormated &&
						recentSearchesObjArray[idx].rooms 						== tmpRecentSearchesObj.rooms &&
						recentSearchesObjArray[idx].adults 						== tmpRecentSearchesObj.adults &&
						recentSearchesObjArray[idx].children 					== tmpRecentSearchesObj.children
					) {
				recentSearchesObjFound = true;
				break;
			} 
	}
	if (! recentSearchesObjFound) {
		recentSearchesObjArray[recentSearchesObjArray.length] = tmpRecentSearchesObj;	
		createRecentSearchesDIVoutput();
	}
	getData('availability', 'price');
}                     

function getData(inGetData, argOptions) {
		plsWaitPanel.show();

		tmpDepartureDate = departure.add(Date.DAY, -1);

    try {
      pageTracker._trackEvent('Request', inGetData, 
                              vCountry + '|' + vArea + '|' + vCity + '|' + 
                              arrival.format('Ymd') + '|' + tmpDepartureDate.format('Ymd') + '|' +
                              $$$('Rooms').value + 'x' + $$$('Adults').value + "_" + $$$('Children').value
                             );
    } catch (e) {
      //
    }

    if (inGetData == 'availability') {
      $$$('plsWaitText').innerHTML = 'Searching for hotels...';
      sURL =  'id=availability&locstr=' + 
              '&cnid=' + vCountry + 
              '&ctid=' + vArea + 
              '&arid=' + vCity + 
              '&dtFrom=' + arrival.format('Ymd') + 
              '&dtTo=' + tmpDepartureDate.format('Ymd') +
              '&order=' + argOptions +
              '&bcn=' + $$$('Rooms').value +
              '&bc=' + $$$('Adults').value +
                       "_" +
              		     $$$('Children').value +
              '&hlist=' +
              '&roomname=' +
              '&htlCat=' +
              '&htlTypes=' +
              '&htlFacils=';
      var con = new Ext.data.Connection({
          timeout: 60000
      });
      con.request ({
          url : baseURL,
          params: sURL,
          method: 'POST',
          success: handleSuccessHotels,
          failure: handleFailure,
          argument: argOptions,
          disableCaching: false
		  } );
		}
		
		if (inGetData == 'pricing' || inGetData == 'pricingAll') {
		  if (inGetData == 'pricing') {
        $$$('plsWaitText').innerHTML = "Asking Hotel Accounting Office";
        if (argOptions[1] == '0') {
          tmpParamRoomID = hotelsObjArray[argOptions[0]].roomid;
        } else {
          tmpParamRoomID = argOptions[1];
        }
        tmpCallBack = handleSuccessPricing; 
		  } else {
		    $$$('plsWaitText').innerHTML = "Asking Hotel Accounting Office";
		    tmpParamRoomID = '';
		    tmpCallBack = handleSuccessPricingAll;
		  }
		    
			sURL = 'id=pricing&hotelid=' + hotelsObjArray[argOptions[0]].hotelid + // hotelid -> locstr
             '&roomid=' + tmpParamRoomID + 
						 '&numRooms=' + $$$('Rooms').value +  
						 '&dtFrom=' + arrival.format('Ymd') +  
						 '&dtTo=' + tmpDepartureDate.format('Ymd') + 
		 				 '&cnid=' + hotelsObjArray[argOptions[0]].countrycode + 
             '&ctid=' + hotelsObjArray[argOptions[0]].citycode + 
             '&arid=' + hotelsObjArray[argOptions[0]].areacode +
             '&bc=' + $$$('Adults').value +
              		    "_" +
              		    $$$('Children').value +
             '&session_id=' + hotelsObjArray[argOptions[0]].session_id;
      var con = new Ext.data.Connection({
          timeout: 32000
      });
			con.request ({
          url : baseURL,
          params: sURL,
          method: 'POST',
          success: tmpCallBack,
          failure: handleFailure,
          argument: argOptions,
          disableCaching: true
		  })
    }
    
    if (inGetData == 'detailedPricing') {
      $$$('plsWaitText').innerHTML = 'Asking for detail pricing';
      sURL =  'id=detailedpricing&hotelid=' + hotelsObjArray[argOptions[0]].hotelid + 
              '&roomid=' + argOptions[1] + 
						  '&dtFrom=' + arrival.format('Ymd') +  
						  '&dtTo=' + tmpDepartureDate.format('Ymd') +
						  '&ad=' + $$$('Adults').value + 
						  '&ch=' + $$$('Children').value +
		 				  '&cnid=' + hotelsObjArray[argOptions[0]].countrycode + 
              '&ctid=' + hotelsObjArray[argOptions[0]].citycode + 
              '&arid=' + hotelsObjArray[argOptions[0]].areacode +
						  '&session_id=' + argOptions[2];

      var con = new Ext.data.Connection({
          timeout: 32000
      });
      con.request ({
          url : baseURL,
          params: sURL,
          method: 'POST',
          success: handleSuccessDetailedPricing,
          failure: handleFailure,
          argument: argOptions,
          disableCaching: false
		  } );
		}
};


function handleFailure (o) {
	if (plsWaitPanel) {
		plsWaitPanel.hide();
	}
	$alert ("We/'re sorry. A communication problem encountered <br/><br/>" +
	        "There is a network problem, at this moment. Please try again.<br/>" +
          "<hr/>Status Message:" + o.statusText, "Communication Error", "info"); 
}


function handleSuccessHotels(result, options){
  var arrSearchAreas, tmpMinPrice, tmpMaxPrice, idxLatLong;
 
	if ( trim(result.responseText) != '' && result.status == 200) {
    $$$('plsWaitText').innerHTML = 'Creating Hotel List';
    
    if (pnlSearch) {
      pnlSearch.hide();
    }
       
    glSort = result.argument;
       
    var nHotel   = result.responseXML.documentElement.getElementsByTagName('HOTELS')[0].getElementsByTagName('HOTEL');
    var nGeoData = result.responseXML.documentElement.getElementsByTagName('GEODATA');
    var nHotelLen = nHotel.length;

    tmpMinPrice = 99999999;
    tmpMaxPrice = 0;
    arrSearchAreas = [];
    hotelsRatingArray = new Array(5);
    hotelsObjArray = new Array (nHotelLen);
    idxLatLong = -1;
    for (idxRating=0; idxRating<=5; idxRating++) {
      hotelsRatingArray[idxRating] = new Array (0,0,999999999, 0);
    }

    for (idx=0; idx<nHotelLen; idx++) {
     
      var tmpSellPrice = getNodeText(nHotel[idx].getElementsByTagName('MAX_PRICE')[0].firstChild);
      var tmpAvailability = getNodeText(nHotel[idx].getElementsByTagName('AVAILABILITY')[0].firstChild);
      var tmpCityDescr = getNodeText(nHotel[idx].getElementsByTagName('CITY_DESCR')[0].firstChild);
      var tmpAreaDescr = getNodeText(nHotel[idx].getElementsByTagName('AREA_DESCR')[0].firstChild)
      
      //if (tmpSellPrice > '0' && tmpAvailability == '2') {
      //O elegxos bghke giati ton kanei o server!!!
       
        hotelsObjArray[idx] = new hotelsObj ( getNodeText(nHotel[idx].getElementsByTagName('HOTELID')[0].firstChild),
                                              getNodeText(nHotel[idx].getElementsByTagName('HOTELNAME')[0].firstChild).toUpperCase(),
                                              getNodeText(nHotel[idx].getElementsByTagName('HTL_MAP_POINT')[0].firstChild),
                                              getNodeText(nHotel[idx].getElementsByTagName('HOTEL_IMAGE')[0].firstChild),
                                              parseInt(tmpSellPrice),
                                              getNodeText(nHotel[idx].getElementsByTagName('AVAILABILITY')[0].firstChild),
                                              getNodeText(nHotel[idx].getElementsByTagName('ROOMDESCR')[0].firstChild),
                                              nHotel[idx].getElementsByTagName('ROOMDESCR')[0].getAttribute("roomid"),
                                              nHotel[idx].getElementsByTagName('ROOMDESCR')[0].getAttribute("boardDescr"),
                                              nHotel[idx].getElementsByTagName('ROOMDESCR')[0].getAttribute("board"),
                                              nHotel[idx].getElementsByTagName('ROOMDESCR')[0].getAttribute("moreRooms"),
                                              getNodeText(nHotel[idx].getElementsByTagName('HCAT_DESCR')[0].firstChild),                                           
                                              getNodeText(nHotel[idx].getElementsByTagName('HOTELTYPE')[0].firstChild),
                                              getNodeText(nHotel[idx].getElementsByTagName('COUNTRY_DESCR')[0].firstChild),
                                              tmpCityDescr,
                                              tmpAreaDescr,
                                              getNodeText(nGeoData[0].getElementsByTagName('COUNTRY_CODE')[0].firstChild),
                                              getNodeText(nGeoData[0].getElementsByTagName('CITY_CODE')[0].firstChild),
                                              getNodeText(nGeoData[0].getElementsByTagName('AREA_CODE')[0].firstChild),
                                              getNodeText(nHotel[idx].getElementsByTagName('SESSION_ID')[0].firstChild),
                                              getNodeText(nHotel[idx].getElementsByTagName('DEAL')[0].firstChild),
                                              false
                                            );
        if (tmpAreaDescr != '') { 
          tmpHotelArea = tmpAreaDescr.toUpperCase();
          
          if (arrSearchAreas.indexOf(tmpHotelArea) < 0) {
            arrSearchAreas.push (tmpHotelArea);         
          }
        }   
        
        if (hotelsObjArray[idx].hcatdescr.substr(0,1) == '*' ) {
          hotelsRatingArray[hotelsObjArray[idx].hcatdescr.length-1][0]++;
          hotelsRatingArray[hotelsObjArray[idx].hcatdescr.length-1][1] += parseInt(tmpSellPrice);
          if (hotelsRatingArray[hotelsObjArray[idx].hcatdescr.length-1][2] > parseInt(tmpSellPrice)) {
            hotelsRatingArray[hotelsObjArray[idx].hcatdescr.length-1][2] = parseInt(tmpSellPrice);
          }
          if (hotelsRatingArray[hotelsObjArray[idx].hcatdescr.length-1][3] < parseInt(tmpSellPrice)) {
            hotelsRatingArray[hotelsObjArray[idx].hcatdescr.length-1][3] = parseInt(tmpSellPrice);
          }
        }
        
        if (parseInt(tmpSellPrice) < parseInt(tmpMinPrice)) { tmpMinPrice = tmpSellPrice; } 
        if (parseInt(tmpSellPrice) > parseInt(tmpMaxPrice)) { tmpMaxPrice = tmpSellPrice; }
               
        if (objPreSelectedHotel.id == hotelsObjArray[idx].hotelid) {
          objPreSelectedHotel.hotelname = hotelsObjArray[idx].hotelname;
        }
      //}
    }
    
          
    if (hotelsObjArray.length < 1) {
      if (plsWaitPanel) {
        plsWaitPanel.hide();
      }
      $alert ("No hotels found for the given period, bed configuration and number of rooms. ", "", "info");
    } else {     
      if (nHotelLen != 0) {
        arrSearchAreas.sort();
        $$$('cityAreaFilter').options.length = 0;
        $$$('cityAreaFilter').options[$$$('cityAreaFilter').options.length] = new Option ('Show All', '0', true, true);
        for (idx=0, tmpIdx=arrSearchAreas.length; idx<tmpIdx; idx++) {
          $$$('cityAreaFilter').options[$$$('cityAreaFilter').options.length] = new Option (arrSearchAreas[idx], arrSearchAreas[idx], false, false);
        }
        
        glMinValue = Math.floor(tmpMinPrice / 100);
        glMaxValue = Math.floor(tmpMaxPrice / 100) + 1;

      
        // If have results then update the nav div with the new data
      	var sAdults = $$$('Adults').value;
      	var sChildren = $$$('Children').value;
      	var sRooms = $$$('Rooms').value;
      	var sCheckIn = $$$('CheckIn').value;
      	var sCheckOut = $$$('CheckOut').value;
      	arrOutput = [];
      	arrOutput.push( "Searching hotels for ", preSelectCountryObj.geoname,
                        " from ", sCheckIn, " to ", sCheckOut, ". ",
                        sRooms);
        if (sRooms > 1) {
          arrOutput.push(" rooms ");
        } else {
          arrOutput.push(" room ");
        }
        if (sRooms < 2) {
          arrOutput.push(" sleeping ");
        } else {
          arrOutput.push(" sleeping (each one) ");
        }
        arrOutput.push(  String.format(" <img src='./images/ico_pax{0}_{1}.gif' border='0' height='15' align='absmiddle'> ", $$$('Adults').value, $$$('Children').value) );
      
      	$$$('nav').innerHTML = arrOutput.join('');
      	
      	$$$('lblChangeSearch').innerHTML = 'New Search';
      	
        // Check if the Stay is up 7 days from today and set the Weather Forecast Title
        var oneDay = 1000*60*60*24;
        daysOfStay = Math.ceil( (arrival.getTime() - today.getTime() ) / oneDay) + 1;
        if (daysOfStay > 7) {
          Ext.getCmp('weatherForecast').setTitle('7 Days Weather Forecast');
        } else {
          Ext.getCmp('weatherForecast').setTitle('Weather Forecast for your stay');
        }
        
        // Update the Accordion
        var accordionItem  = Ext.getCmp('accordionREG').layout.activeItem;
        var accordionIndex = Ext.getCmp('accordionREG').items.indexOf(accordionItem);
        switch(accordionIndex) {
          case 1:
            forecastWeatherUpdate(accordionItem);
            break;
          case 2:
            flickrPhotosUpdate(accordionItem);
            break;
          case 3:
            panoramioPhotosUpdate(accordionItem);
            break;
          case 4:
            youtubeVideosUpdate(accordionItem);
            break;
          case 5:
            additionalAreaInfoUpdate(accordionItem);
            break;
          default:
            currentWeatherUpdate(accordionItem);
            break;
        }
      }

      clearFilters();
      if (objPreSelectedHotel.hotelname != '') {
        Ext.getCmp('hotelNameFilter').setValue(objPreSelectedHotel.hotelname);        
        pnl_lc_HotelName.expand(false);
      }

      doSortHotelList();
      
      $$$('newSearchHeaderDIV').style.visibility = ''; 
      if (gExpandLeft) {
        Ext.getCmp('filtersRegion').expand(true);
        Ext.getCmp('filtersRegion').show();
      } else {
        Ext.getCmp('filtersRegion').expand(false);
        Ext.getCmp('filtersRegion').hide();
      }
      Ext.getCmp('filtersRegion').ownerCt.doLayout();
              
      Ext.getCmp('accordionREG').expand(false);
      Ext.getCmp('accordionREG').hide();
      if (gExpandRight) {
        if (document.documentElement.clientWidth > 1024) {
          Ext.getCmp('accordionREG').expand(true);
        }
        Ext.getCmp('accordionREG').show();
      }
      Ext.getCmp('accordionREG').ownerCt.doLayout();
            
      setSlider( Math.round(glMinValue * glCurrencyRate), Math.round(glMaxValue * glCurrencyRate), true);     
    }
      
	} else {
    if (plsWaitPanel) {
      plsWaitPanel.hide();
    }
		$alert ("No hotels found for the given period, bed configuration and number of rooms.", "Notice: No Results", "info");
	}
}


function doSortHotelList() {
  try {
    Ext.getCmp('hotellist').body.update ('');
  } catch (e) {
    //
  }
  $$$('plsWaitText').innerHTML = 'Sorting and Filtering Hotels';
  if (! plsWaitPanel) {
    plsWaitPanel.show();
  }
  
  function doFixSortButtons() {
    if (glSortOrder == 'asc') {
      var iconCls = 'btnSortAsc';
    } else {
      var iconCls = 'btnSortDesc';
    }
    
    oBtnHtlListPrice.setIconClass( 'btnSortNone' );
    oBtnHtlListPrice.toggle(false);
    oBtnHtlListName.setIconClass( 'btnSortNone' );
    oBtnHtlListName.toggle(false);
    oBtnHtlListArea.setIconClass( 'btnSortNone' );
    oBtnHtlListArea.toggle(false);
    oBtnHtlListCategory.setIconClass( 'btnSortNone' );
    oBtnHtlListCategory.toggle(false);
    oBtnHtlListDeals.setIconClass( 'btnSortNone' );
    oBtnHtlListDeals.toggle(false);
    
    switch(glSort) {
      case 'price':
        oBtnHtlListPrice.setIconClass( iconCls );
        oBtnHtlListPrice.toggle(true);
        break;
      case 'name':
        oBtnHtlListName.setIconClass( iconCls );
        oBtnHtlListName.toggle(true);
        break;
      case 'location':
        oBtnHtlListArea.setIconClass( iconCls );
        oBtnHtlListArea.toggle(true);
        break;
      case 'stars':
        oBtnHtlListCategory.setIconClass( iconCls );
        oBtnHtlListCategory.toggle(true);
        break;
      case 'deals':
        oBtnHtlListDeals.setIconClass( iconCls );
        oBtnHtlListDeals.toggle(true);
    }
    
  }
 
  function doPrintHotelLocation(inCountry, inCity, inArea, inArrID) {
    tmpResult = '';
    if (inArea != '') {
      tmpResult = inArea.toUpperCase() + ', ';
    }
    
    if (inCity != '') {
      tmpResult += inCity.toUpperCase() + ', ';
    }
    
    tmpResult += inCountry.toUpperCase();
    return '<a href="javascript:void(null)" onClick="doShowStaticMap(' + inArrID + ');">' + tmpResult + '</a>';
  }
  
  function doPrintHotelListAction (inArrID, inWhat) {
    if (inWhat == 'book') {
      return ['getData(\'pricing\', [', inArrID, ', \'0\' ])'].join('');
    } else {
      return ['doShowHotelInfoPanel(', inArrID, ', \'', inWhat, '\')'].join('');
    }
  }

  function doPrintAdditionalRooms (inArrID, inWhat) {
    if ( hotelsObjArray[inArrID].morerooms != '') {
      if (hotelsObjArray[inArrID].morerooms == '0') {
        if (inWhat == 'descr') {
          return hotelsObjArray[inArrID].roomdescr;
        } else {
          return '';
        }
      } else {
        if (inWhat == 'descr') {
          return  ['<a href="javascript:void(null)" style="font-weight:bold;" onClick="getData(\'pricingAll\', [', inArrID, ', \'', hotelsObjArray[inArrID].roomid, '\' ] )">', hotelsObjArray[inArrID].roomdescr, '</a>'].join('');
        } else {
          return  ['<a href="javascript:void(null)" onClick="getData(\'pricingAll\', [', inArrID, ', \'', hotelsObjArray[inArrID].roomid, '\'] )"><img src="./cssImages/button_more.png" border="0" width="16" height="16" align="absmiddle"/> (More Rooms)</a>'].join('');
        }
      } 
    } else {
      return hotelsObjArray[inArrID].roomdescr;
    }   
  }
  
  function doPrintAdditionalBoards (inArrID, inPrintLink) {
    if ( hotelsObjArray[inArrID].morerooms != '') {
      if (hotelsObjArray[inArrID].morerooms == '0') {
        return hotelsObjArray[inArrID].roomdescr;
      } else {
        if (inPrintLink) {
          return  ['<a href="javascript:void(null)" style="font-weight:bold;" onClick="getData(\'pricingAll\', [', inArrID, ', \'', hotelsObjArray[inArrID].roomid, '\'] )">', hotelsObjArray[inArrID].boarddescr, '</a>',
                  '&nbsp;&nbsp<a href="javascript:void(null)" onClick="getData(\'pricingAll\', [', inArrID, ', \'', hotelsObjArray[inArrID].roomid, '\'] )"><img src="./cssImages/button_more.png" border="0" width="16" height="16" align="absmiddle"/> (More Rooms)</a>'].join('');
        } else {
          return  hotelsObjArray[inArrID].boarddescr;
        }
      } 
    } else {
      return hotelsObjArray[inArrID].boarddescr;
    }   
  }

  function doPrintHotelName (inArrID) {
    return ['<a href="javascript:void(null)" onclick="', doPrintHotelListAction(inArrID, 'Info'), '">', hotelsObjArray[inArrID].hotelname, '</a>'].join('');                  
  } 
  
  function doPrintDeal(inArrID) {
    if (hotelsObjArray[inArrID].deal != '' && hotelsObjArray[inArrID].deal.length <= 50) {
      return '<div style="font-weight:bold; color:#DB5B2E;"><img src="./cssImages/specialflag.png" align="absmiddle" width="16" height="16" border="0"> ' + hotelsObjArray[inArrID].deal + '</div>';
    } else {
      doPrintHotPrice(inArrID);
    }
  }
  
  function doPrintHotelMap(inArrID) {
    if (hotelsObjArray[inArrID].hotelcoords != '') {                                                  
      return  '<a href="javascript:void(null)" onClick="doShowStaticMap(' + inArrID + ');">' +
              '<img src="images/25px-Internet-web-browser.svg.png" border="0" width="16" height="16" align="absmiddle" />' +
              ' (Hotel Map)</a>';
    } else {
      return '';
    }    
  }
  
  function doPrintHotPrice(inArrID) {
    if (hotelsObjArray[inArrID].hcatdescr.substr(0,1) == '*' ) {
      var hotelRating = hotelsObjArray[inArrID].hcatdescr.length-1;
      var percentCheckForHotPrice = 25/100;
      var medPriceForRating = Math.round(hotelsRatingArray[hotelRating][1] / hotelsRatingArray[hotelRating][0]);
      medPriceForRating = Math.round(medPriceForRating - (medPriceForRating * percentCheckForHotPrice) );

      if ( hotelsObjArray[inArrID].hotelpriceMax <= medPriceForRating ) {
        return '<div style="float:right; font-weight:bold; color:#DB5B2E; width:70px;">Best Price</div>';
        /*
        return  '<div style="float:right; font-weight:bold; color:#333333;">' +
                'Med Price : ' + medPriceForRating + '<br />' +
                'Tot Price:  ' + hotelsRatingArray[hotelRating][1] + '<br />' +
                'Tot Hotels: ' + hotelsRatingArray[hotelRating][0] +
                '</div>';
        */
      } else {
        /*
        return  '<div style="float:right; font-weight:bold; color:#333333;">' +
                'Med Price : ' + medPriceForRating + '<br />' +
                'Tot Price:  ' + hotelsRatingArray[hotelRating][1] + '<br />' +
                'Tot Hotels: ' + hotelsRatingArray[hotelRating][0] +
                '</div>';
        */
        return '';
      }
    } else {
      return '';
    } 
    
  } 
  
  function doPrintHotelImage(inArrID) {
    if (hotelsObjArray[inArrID].hotelimage != '') {
      tmpHotelImage = hotelsObjArray[inArrID].hotelimage;
      tmpHotelImageCursor = 'pointer';
      tmpPrintLink = true; 
    } else {
      tmpHotelImage = 'http://www.spotahotel.com/cssImages/noPhoto.png';
      tmpHotelImageCursor = 'not-allowed';
      tmpPrintLink = false;
    }
    arrOutput = [];
    if (tmpPrintLink) {
      arrOutput.push('<a href="javascript:void(null)" onclick="' + doPrintHotelListAction(inArrID, 'Photos') + '">');
    }
    arrOutput.push('<img id="hotImg' + hotelsObjArray[inArrID].hotelid + '" name="' + inArrID + '" displayed="false" align="middle" width="80" height="80" border="0" alt="" onmouseover="this.style.cursor=\'' + tmpHotelImageCursor + '\'" />');
    if (tmpPrintLink) {
      arrOutput.push('<br/>see all photos</a>');
    }
    return arrOutput.join('');
  }


  function doPrintHotelLeft(inArrID) {
    var tmpDisplayPrice = hotelsObjArray[inArrID].hotelpriceMax * glCurrencyRate / 100;
    var tmpDisplayPricePerDay = hotelsObjArray[inArrID].hotelpriceMax / parseInt($$$('s_nights').value) * glCurrencyRate / 100 / parseInt($$$('Rooms').value);
    var tmpPerRoomOutput = '';
    var tmpPerRoomDescr = '';
    
    if ( parseInt($$$('s_nights').value) > 1 ) {
      tmpPerRoomDescr = 'per night';
      if ( parseInt($$$('Rooms').value) > 1) {
        tmpPerRoomDescr += '/room';
      }
    } else {
      if ( parseInt($$$('Rooms').value) > 1) {
        tmpPerRoomDescr = 'for'+ ' '+ ' room';
      } 
    }

    if (tmpPerRoomDescr != '') {
      tmpPerRoomOutput = ['<span style="font-size:11px;">(', glCurrency, ' ', tmpDisplayPricePerDay.toFixed(2), ' ' + tmpPerRoomDescr + ')</span><br />'].join(''); 
    }
    return ['<span style="font-size:12px; font-weight:bold;">Final Price</span><br />', 
            '<span style="font-size:16px;">', glCurrency, ' ', tmpDisplayPrice.toFixed(2), '</span><br />',
            '<span style="font-weight:bold;">for ', $$$('Rooms').value, iif( (parseInt($$$('Rooms').value) > 1), ' rooms', ' room'), '</span><br />',
            tmpPerRoomOutput,
            '<span style="color:#0000CC; font-size:14px; font-weight:bold;">',
            '<br /><a href="javascript:void(null)" onclick="', doPrintHotelListAction(inArrID, 'book'), '">Book</a>', 
            '</span>'].join('');
  }
  
  function doPrintHotelDescription(inArrID) {
    return ['<div style="text-align:left;">',
            '<table style="text-align:left;" width="100%"><tbody>',
            '<tr>',
              '<td width="125" style="font-weight:bold; text-align:right;" nowrap="nowrap">Type:</td>',
              '<td>', hotelsObjArray[inArrID].hoteltype, '</td>',
              '<td align="right">', doPrintDeal(inArrID), '</td>',
            '</tr>', 
            '<tr>',
              '<td style="font-weight:bold; text-align:right;" nowrap="nowrap">Location:</td>',
              '<td>', doPrintHotelLocation (hotelsObjArray[inArrID].countrydescr, hotelsObjArray[inArrID].citydescr, hotelsObjArray[inArrID].areadescr, inArrID), '</td>',
              '<td align="right">', doPrintHotelMap(inArrID), '</td>',
            '</tr>',
            '<tr>',
              '<td style="font-weight:bold; text-align:right;" nowrap="nowrap">Room:</td>',
              '<td>', doPrintAdditionalRooms(inArrID, 'descr'), '</td>',
              '<td align="right">', doPrintAdditionalRooms(inArrID, 'more'), '</td>',
            '</tr>',
            //'<tr><td style="font-weight:bold; text-align:right;" nowrap="nowrap">Board:</td><td>' + doPrintAdditionalBoards(inArrID) + '</td></tr>' +
            '<tr>',
              '<td style="font-weight:bold; text-align:right;" nowrap="nowrap">Board:</td>',
              '<td>', doPrintAdditionalBoards(inArrID, false), '</td>',
              '<td align="right"></td>',
            '</tr>', 
            '</tbody></table></div>'].join('');
  }
  
  function doPrintHotelCategory(inArrID) {
    var tmpHotelCatOut;
    if (hotelsObjArray[inArrID].hcatdescr.substr(0,1) == '*') {
      tmpHotelCatOut = ['<img src="./images/', hotelsObjArray[inArrID].hcatdescr.length, '_0_stars.gif" alt="" />'].join('');
    } else {
      tmpHotelCatOut = hotelsObjArray[inArrID].hcatdescr; 
    }
                       
    return '<div style="float:right;">' + tmpHotelCatOut + '</div>';
  }
  
  function hotelIsPrintable(inArrID) {
    var idx, tmpIdx;
      
    /* Check for City Area Filter */
    if ($$$('cityAreaFilter').options[$$$('cityAreaFilter').selectedIndex].value != '0') {
      if ( hotelsObjArray[inArrID].areadescr.toUpperCase() != $$$('cityAreaFilter').options[$$$('cityAreaFilter').selectedIndex].value) {
        return false;
      }  
    }

    /* Check for Star Rating Filter */
    if (glStars != '') {
      var tmpStarsString = [];
      var arrStars = glStars.split(',');
      for (idx=0, tmpIdx=arrStars.length; idx<tmpIdx; idx++) {
        if (arrStars[idx] != '') {
          if (arrStars[idx] != '0') {
            tmpStarsString.push( [':', str_repeat('*', arrStars[idx]), ','].join('') );
           }  
        }
      }
    }
    if (tmpStarsString != '') {
      if (tmpStarsString.indexOf(':' + hotelsObjArray[inArrID].hcatdescr + ',') < 0) {
        return false;
      } 
    }
    
    /* Check for Hotel Name Filter */      
    if (Ext.getCmp('hotelNameFilter').getValue() != '') {
      tmpHotelNameForFilter = hotelsObjArray[inArrID].hotelname.toUpperCase();
      if (objPreSelectedHotel.hotelname == '') {
        return (tmpHotelNameForFilter.indexOf(Ext.getCmp('hotelNameFilter').getValue().toUpperCase()) >= 0);
      } else {
        return (tmpHotelNameForFilter == Ext.getCmp('hotelNameFilter').getValue().toUpperCase());
      } 
    }
    
    /* Check for Price Range Filter */
    if ( (hotelsObjArray[inArrID].hotelpriceMax < (glMinValue * 100)) || (hotelsObjArray[inArrID].hotelpriceMax > (glMaxValue * 100)) ) {
      return false;
    }
    
    return true;
  }

  
  //$$$('hotelListHeaderButtons').style.display = 'none';
  //$$$('hotelListHeaderWait').style.display = '';
 
  // Select the Sort Type
  hotelsObj.prototype.toString = hotelsObjToString;
  
  hotelsObjArray.sort();
  if (glSortOrder == 'desc') {
    hotelsObjArray.reverse();
  }
 
  var hotelListDisplayOverhead = 10;
  var hotelsPrinted = 0;
  var hotelArrayLen = hotelsObjArray.length;
  var outputArray0 = [];
  var outputArray1 = [];
  var idx = 0; 
  
  while (idx<hotelArrayLen) {
    if ( hotelIsPrintable(idx) ) {
      hotelsPrinted++;
      hotelsObjArray[idx].shown = true;
      if (hotelsPrinted <= hotelListDisplayOverhead) {
        var tmpTRstyle = 'hotelDisplayed="true"';
      } else {
        var tmpTRstyle = 'hotelDisplayed="false" style="display:none;"'
      }
      tmpTableTR = ( [ '<tr valign="top" id="hot', hotelsObjArray[idx].hotelid, '" ', tmpTRstyle, '><td>',
                          '<table width="100%" class="hotelListTR" cellspacing="0" cellpadding="0"><tbody><tr>',
                          '<td width="180" valign="top" style="background-color:#EFEFEF; padding:3px;" nowrap="nowrap">',
                            doPrintHotelLeft(idx),
                          '</td><td valign="top" width="80">', 
                            doPrintHotelImage(idx),
                          '</td><td valign="top" style="text-align:left;">',
                            doPrintHotelCategory(idx),
                            '<span style="font-size:150%; padding-left:5px;">', doPrintHotelName(idx), '</span>',
                            doPrintHotelDescription(idx),
                          '</td></tr></tbody></table></td></tr>'].join('')
                    );
      if (hotelsPrinted <= hotelListDisplayOverhead) {
        outputArray0.push(tmpTableTR);
      } else {
        outputArray1.push(tmpTableTR);
      }

      if (objPreSelectedHotel.id == hotelsObjArray[idx].hotelid) {
        objPreSelectedHotel.idx = idx;
      }
    } else {
      hotelsObjArray[idx].shown = false;
    }

    idx++;
  }
  
  if (hotelsPrinted > 0 ) {
       
      
    for (idx=0; idx<=4; idx++) {
      if (hotelsRatingArray[idx][0] > 0) {
        $$$('starRating' + (idx+1)).disabled = false;
        $$$('starHotels' + (idx+1) + 'Num').innerHTML = [ '<span style="color:#999999"> ', hotelsRatingArray[idx][0], ' hotels</span>' ].join('');
        $$$('starHotels' + (idx+1) + 'Min').innerHTML = [ 'from ', glCurrency, ' ', (hotelsRatingArray[idx][2] * glCurrencyRate/100).toFixed(2), 
                                                          ' to ', (hotelsRatingArray[idx][3] * glCurrencyRate/100).toFixed(2) 
                                                        ].join('');
      } else {
        $$$('starRating' + (idx+1)).disabled = true;
        $$$('starHotels' + (idx+1) + 'Num').innerHTML = '-';
        $$$('starHotels' + (idx+1) + 'Min').innerHTML = '';
      }
    }
    
    var tmpHotelData0 =  [ '<div style="text-align:center;">',
                          '<table border="0" cellpadding="5" cellspacing="5" id="hotelListTable" align="center" style=" border-collapse: collapse; width:98%">',
                          '<tbody>',
                          outputArray0.join(''),
                          '</tbody></table>',
                          '</div>'
                        ].join('');                         
    var tmpHotelData1 =  [ '<div style="text-align:center;">',
                          '<table border="0" cellpadding="5" cellspacing="5" id="hotelListTable" align="center" style=" border-collapse: collapse; width:98%">',
                          '<tbody>',
                          outputArray0.join(''),
                          outputArray1.join(''),
                          '</tbody></table>',
                          '</div>'
                        ].join('');

    var newPanel = Ext.getCmp('centerRegion');
    if (! Ext.getCmp('hotellist') ) {
      var hotellist = newPanel.add(new Ext.Panel({
        id: 'hotellist',
        html: '',
        layout: 'fit',
        title: 'Hotel list',
        closable: false,
        enableTabScroll:true,
        border: false,
        //autoScroll: true,
        closable: false,
        tbar: [
          'Sort hotels by:',
          '-',
          oBtnHtlListPrice,
          '-',
          oBtnHtlListName,
          '-',
          oBtnHtlListArea,
          '-',
          oBtnHtlListCategory,
          '-',
          oBtnHtlListDeals,
          '-',
          oBtnHtlFilterPrice,
          oBtnHtlFilterCategory
          ]
      }) );
    }
    newPanel.setActiveTab(Ext.getCmp('hotellist'));
          
    if ( GBrowserIsCompatible() ) {
      if (! Ext.getCmp('hotellistmap') ) {
        var hotellistmap = newPanel.add(new Ext.Panel({
          id: 'hotellistmap',
          layout: 'fit',
          title: 'Map',
          contentEl: 'panelHotelListMap',
          closable: false,
          enableTabScroll: false,
          border: false,
          closable: false,
          listeners:  { show: function() {
                                                  createGoogleMap ('panelHotelListMap', null, null);
                                              }
                    }
        }) );
      }
    
      var mapDivHeight = Ext.getCmp('centerRegion').getInnerHeight() - Ext.getCmp('centerRegion').getFrameHeight(); 
      var tmpHotelMap = Ext.DomHelper.append('mapContainer', {
            tag:   "div", 
            id:    "panelHotelListMap", 
            cls:   "tab-content", 
            style: "width:100%; height:" + mapDivHeight + "px; position:absolute;" //height:380 ???
          }
      );
    }
              
    if (objPreSelectedHotel.idx != -1) {
      doShowHotelInfoPanel(objPreSelectedHotel.idx, 'Info');
    }
    
    doFixSortButtons();

    $$$('hotelsShown').innerHTML = hotelsPrinted;
    $$$('hotelsTotal').innerHTML = hotelArrayLen;

    var fullHotelListLoaded = false;
    Ext.getCmp('hotellist').body.update (tmpHotelData0);
    Ext.getCmp('hotellist').body.scrollTo('top',0);
    doCheckScroll(false);
    gScrollHotelsTimeoutID = 0;
    Ext.getCmp('hotellist').body.on('scroll', function() {
      if (! fullHotelListLoaded) {
        Ext.getCmp('hotellist').body.update (tmpHotelData1);
        fullHotelListLoaded = true;
      }
      if (gScrollHotelsTimeoutID != 0) {
        clearTimeout(gScrollHotelsTimeoutID);
      }
      gScrollHotelsTimeoutID = setTimeout("doCheckScroll(true)",500); 
    } );

    /* Enter the Right Click Events */
    /* 
    idx = 0;
    do {
      tmpElements = Ext.get('hot' + hotelsObjArray[idx].hotelid);
      tmpElements.on ({
                      'contextmenu': function(e) {
                        Ext.Msg.alert('Right Click on ' + hotelsObjArray[idx].hotelname);
                        e.preventDefault();
                      }, scope:this
                    });
      idx++;
    } while (idx<hotelsObjArray.length);
    */
    
    if (plsWaitPanel) {
      plsWaitPanel.hide();
    }    
  } else {
    if (plsWaitPanel) {
      plsWaitPanel.hide();
    }
    $alert ("No hotels found with these filters. Please narrow you filter selections.<br/><br/>Nothing changed.", "Notice: No Results", "info");
  }
  
  //$$$('hotelListHeaderButtons').style.display = '';
  //$$$('hotelListHeaderWait').style.display = 'none';  
}


function doCheckScroll (showMoreHotels) {  
  // Take care of 100 -> It's the image Height used to find prev and next images at the viewport
  var bodyTop = Ext.getCmp('hotellist').body.getTop() - 100;
  var bodyBot = bodyTop + Ext.getCmp('hotellist').body.getHeight() + 100;
  
  var hotelImages = Ext.getCmp('hotellist').body.query("img[displayed='false']");
  var foundImagesToDisplay = false;
  var tmpTR_ID = '';
  for (idx=0, tmpIdx=hotelImages.length; idx<tmpIdx; idx++) {
    var objIDX = hotelImages[idx].name;
    var imageTop = Ext.get(hotelImages[idx]).getTop();    
    if (imageTop >= bodyTop && imageTop <= bodyBot) {
      var tmpHotelImage = 'http://www.spotahotel.com/cssImages/noPhoto.png';
      if (hotelsObjArray[objIDX].hotelimage != '') {
        tmpHotelImage = hotelsObjArray[objIDX].hotelimage;
        tmpTR_ID      = hotelImages[idx].id.replace('hotImg', 'hot');
        
        if ( $$$(tmpTR_ID).getAttribute('displayed') == 'true' ) { 
          foundTR_ID = hotelImages[idx].id.replace('hotImg', 'hot');
        }
      }         
      hotelImages[idx].src = tmpHotelImage;
      hotelImages[idx].setAttribute('displayed', 'true');
      foundImagesToDisplay = true;
    } else {
      if (foundImagesToDisplay) {
        foundTR_ID = hotelImages[idx].id.replace('hotImg', 'hot');
        break;
      }
    }
  }
  
  if (showMoreHotels) {
    if (foundTR_ID != '') {
      var hotelTRs = Ext.getCmp('hotellist').body.query("tr[hotelDisplayed='false']");
      var howManyTRshown = 0;
      for (idx=0, tmpIdx=hotelTRs.length; idx<tmpIdx; idx++) {
        if (howManyTRshown <= 3000) {
          howManyTRshown++;
          hotelTRs[idx].style.display = '';
          try {
            hotelImages[idx].setAttribute('hotelDisplayed', 'true');
          } catch (e) {
            //
          }
        } else {
          if (plsWaitPanel) {
            plsWaitPanel.hide();
          }
          break;
        }              
      }      
    }
  }
}

  // SHOW THE TRANSFER
  /*
  transferData = [];
  if (inType == 'new') {
    var nTransferXML = hotelListXML.responseXML.documentElement;
		var nTransfers = nTransferXML.getElementsByTagName('LOCATION');
		for (idx=0; idx<nTransfers.length; idx++) {
		  $nDistance = nTransfers[idx].getAttribute('distance');
		  $nDuration = nTransfers[idx].getAttribute('duration');
		  if ($nDistance == '0' || $nDistance == '') {
        $nDistance = 'N/A'; 
      } else {
        $nDistance += ' km';
      }; 
		  if ($nDuration == '0' || $nDuration == '') {
        $nDuration = 'N/A'; 
      } else { 
        $nDuration += ' min';
      }
		  $nTotalPrice = nTransfers[idx].getAttribute('typenum') * nTransfers[idx].getAttribute('inRate');
		  transferData[idx] = [
                            nTransfers[idx].getAttribute('id'),
                            getNodeText(nTransfers[idx].firstChild),
                            nTransfers[idx].getAttribute('to'),
                            $nDistance,
                            $nDuration,
                            nTransfers[idx].getAttribute('inRate'),
                            nTransfers[idx].getAttribute('outRate'),
                            nTransfers[idx].getAttribute('bothRate'),
                            nTransfers[idx].getAttribute('ratePolicy'),
                            nTransfers[idx].getAttribute('typeid'),
                            nTransfers[idx].getAttribute('typenum'),
                            nTransfers[idx].getAttribute('typeName'),
                            $nTotalPrice
		                      ]
    }
  }
  */
function doSelectSortType(inSortType) {
  if (inSortType == glSort) { 
    changeOrderType = true; 
  } else {
    changeOrderType = false;
    glSortOrder = 'asc';
  }
   
  glSort = inSortType;
  
  if (changeOrderType) { 
    if (glSortOrder == 'asc') {
      glSortOrder = 'desc';
    } else {
      glSortOrder = 'asc';
    }
  }
  doSortHotelList();
}

function doResetFilter(inFilterType) {
  switch (inFilterType) {
    case 'price':
      setSlider( Math.round(glMinValue * glCurrencyRate), Math.round(glMaxValue * glCurrencyRate), true);
      break;
    case 'category':
      filterRating($$$('starRating0'));
      break;
  }
}



function filterRating(inRatingObj) {
  if (inRatingObj.value == '0') {
    for (idx=0; idx<=5; idx++) {
      if (idx != 0) {
        $$$('starRating' + idx).checked = false;
      }
    }
  } else {
    $$$('starRating0').checked = false;
  }
  
  var atLeastOnChecked = false;
  glStars = '';
  for (idx=0; idx<=5; idx++) {
    if (idx != 0) {
      if ( $$$('starRating' + idx).checked ) {
        atLeastOnChecked = true;
        glStars = glStars +  $$$('starRating' + idx).value + ',';
      }
    }
  }
  
  if (! atLeastOnChecked) {
    $$$('starRating0').checked = true;
    glStars = '0';
    oBtnHtlFilterCategory.setVisible(false);
  } else {
    oBtnHtlFilterCategory.setVisible(true);
  }
  
  doSortHotelList();
}



function filterHotelName(inAction) { 
  if (inAction == "reset") {
    Ext.getCmp('hotelNameFilter').setRawValue("");
    objPreSelectedHotel.id   = -1;
    objPreSelectedHotel.idx  = -1;
    objPreSelectedHotel.name = '';
  }
  doSortHotelList();
}


function doChangeCurrency() {
  glCurrency = $$$('currencies').options[$$$('currencies').selectedIndex].title;
  glCurrencyRate = $$$('currencies').options[$$$('currencies').selectedIndex].value;
  gl_pnl_lc_Slider.setTitle ( 'Price Range in: ' + glCurrency );
  setSlider( Math.round(glMinValue * glCurrencyRate), Math.round(glMaxValue * glCurrencyRate), true);   
  doSortHotelList();
}


function setSlider(inMinVal, inMaxVal, inNew) {
  priceSlider = Class.empty;
  priceSlider = new Slider($$$('gutterA'), $$$('minKnobA'), $('slider_bkg_img'), {
  	start: inMinVal,
  	end: inMaxVal,
  	offset: 10,
  	knobheight: 20,
  	onChange: function(pos){
      $$$('sliderLabelB').innerHTML = ' from ' + pos.minpos + '  up to  ' + pos.maxpos;
  	},
  	onComplete: function(pos) {
      if (! inNew) {
        /* Check for difference bigger than 20 trying to fix slider error*/
        if (pos.maxpos - pos.minpos > 20) {
          glMinValue = pos.minpos;
          glMaxValue = pos.maxpos;
          doSortHotelList();
        }
      } else {
        /* Not sure why it's working */
        inNew = false;
      }
      
      if (inMinVal == glMinValue && inMaxVal == glMaxValue) {
         oBtnHtlFilterPrice.setVisible(false);
      } else {
        oBtnHtlFilterPrice.setVisible(true);
      }
  	}
  }, $$$('maxKnobA'));
  priceSlider.setMin(inMinVal).setMax(inMaxVal);
}


function clearFilters() {
  glSort = 'price';
  glSortOrder = 'asc';
  glShowPhotos = 'true';
  
  glStars = '0';
  $$$('starRating0').checked = true;
  for (idxRating=2; idxRating<=5; idxRating++) {
    $$$('starRating' + idxRating).checked = false;
  }
}
                     

var resultArray = [];
var resultXML;

function initDlgRsvData() {
	dlgRsvData = new Ext.Window( {
    layout: 'fit',
    el: 'dlgRsvData',
    id: 'dlgRsvData',
    width:650,
    //height:200,
    autoHeight: true,
    closable: false,
    closeAction: 'hide',
    modal:true,
    resizable: false,
    shim: true,
    buttons: [ {
      text: 'Pricing per Day',
      id: 'btnRsvDetailed',
      applyTo: 'dlgRsvData_btn_day',
      icon: './cssImages/button_details.png',
      iconCls: 'x-btn-text-icon',
      handler: function () { getDataForDetailedPricing(); }
    }, {
      text: 'Cancellation Policy and Terms ',
      id: 'btnRsvPolicy',
      applyTo: 'dlgRsvData_btn_policy',
      icon: './cssImages/button_policy.png',
      iconCls: 'x-btn-text-icon',
      handler: function () { doShowCNPolicy('view'); }
    }, {
      text: 'Pay ',
      id: 'btnRsvPay',
      applyTo: 'dlgRsvData_btn_pay',
      icon: './cssImages/button_creditcards.png',
      iconCls: 'x-btn-text-icon',
      handler: handleReserve
    }, {
      text: 'Cancel  ',
      id: 'btnRsvCancel',
      applyTo: 'dlgRsvData_btn_cancel',
      icon: './cssImages/button_cancel.png',
      iconCls: 'x-btn-text-icon',
      handler: function() { dlgRsvData.hide(); }
    }]
  });

  Ext.EventManager.onWindowResize( function() {
                                      if ( dlgRsvData.container != undefined) {
                                        dlgRsvData.center();
                                      }
                                    }, dlgRsvData);
}


function getDataForDetailedPricing() {
  var nResponse = resultXML.documentElement;
  var nRoom = nResponse.getElementsByTagName('ROOM');
  getData('detailedPricing', [glSelectedHotelIndex, getNodeText(nRoom[0].getElementsByTagName('ROOM_ID')[0].firstChild), getNodeText(nRoom[0].getElementsByTagName('SESSION_ID')[0].firstChild)] );
}


function handleSuccessDetailedPricing(result, options){
  if ( trim(result.responseText) != '' && result.status == 200) {
    plsWaitPanel.hide();

    var dtPricingResponse = result.responseXML.documentElement;
    var nDetailedDay = dtPricingResponse.getElementsByTagName('DETAILEDDAY');
    var tmpDetails = '';
    for (idx=0, tmpIdx=nDetailedDay.length; idx<tmpIdx; idx++) {
      tmpRemarks = getNodeText(nDetailedDay[idx].getElementsByTagName('REMARKS')[0].firstChild);
      if (getNodeText(nDetailedDay[idx].getElementsByTagName('REMARKS')[0].firstChild) == '') {
        tmpRemarks = 'None';
      }
      tmpDetails += '<tr>' +
                    '<td>Stay Day ' + (idx+1) + '</td>' +
                    '<td>' + getNodeText(nDetailedDay[idx].getElementsByTagName('DATE')[0].firstChild) + '</td>' +
                    '<td>' + getNodeText(nDetailedDay[idx].getElementsByTagName('HPRD_DESCR')[0].firstChild) + '</td>' +
                    '<td>' + glCurrency + ' ' + parseFloat(getNodeText(nDetailedDay[idx].getElementsByTagName('PRICE_MAX')[0].firstChild) * $$$('Rooms').value * glCurrencyRate / 100).toFixed(2) + '</td>' +
                    '<td>' + tmpRemarks + '</td>' +
                    '</tr>';
      detailedPricingRoomDescr = getNodeText(nDetailedDay[idx].getElementsByTagName('ROOM_DESCR')[0].firstChild)
    }
    
    if (tmpDetails != '') {
      tmpDetails =  '<div style="padding:5px;">' +
                    '<table border="0" cellspacing="5" cellpadding="5" width="100%"  style="border: solid 1px black;"><tbody>' +
                    '<tr style="font-size:14px; font-weight: bold;">' +
                    '<td colspan="4">Day by Day Pricing for selected room type <br/>' + detailedPricingRoomDescr + '</td>' +
                    '</tr>' +
                    '<tr style="font-weight:bold;">' +
                    '<td >Stay Day  </td><td>Date</td><td>Pricing Period </td></td><td>Daily Price </td><td>Remarks </td>' +
                    '</tr>' +
                    tmpDetails +
                    '</tbody></table></div>';
                    
      var dlgDetailedPricing = new Ext.Window ({
        layout: 'fit',
        id: 'dlgDetailedPricing',
        title: 'Detailed Pricing hotel ' + hotelsObjArray[glSelectedHotelIndex].hotelname ,
        width: 500,
        //height:200,
        autoHeight: true,
        closable: true,
        closeAction: 'close',
        modal:true,
        resizable: true,
        shim: true,
        buttons: [ {
          text: 'Close ', 
          icon: './cssImages/button_cancel.png',
          iconCls: 'x-btn-text-icon',
          handler: function() { dlgDetailedPricing.close(); }
        }]
      });
      
      Ext.getCmp('dlgDetailedPricing').html = tmpDetails;
      dlgDetailedPricing.show();
    } else {
      alert('The hotel\'s reception office is unavailable at this moment.<br/> Please try again later', 'System Unavailable', '');
    }
  } else {
    alert('The hotel\'s reception office is unavailable at this moment.<br/> Please try again later', 'System Unavailable', 'error');
    plsWaitPanel.hide();
  }
}


function doShowCNPolicy(inButton) {
  /* Construct the CN Policy */
  tmpCNPolicy = '';
  var nPricing = resultXML.documentElement;
  var nCNPolicy = nPricing.getElementsByTagName('CNPOLICY');
  for (idx=0, tmpIdx=nCNPolicy.length; idx<tmpIdx; idx++) {
    tmpCNPolicy +=  '<tr>' +
                    '<td>Cancelling </td>' + 
                    '<td align="center" nowrap="nowrap"><span style="font-weight:bold;">' + getNodeText(nCNPolicy[idx].getElementsByTagName('FROM_DAYS')[0].firstChild) + 
                    " - " + 
                    getNodeText(nCNPolicy[idx].getElementsByTagName('TO_DAYS')[0].firstChild) +
                    ' days</span><br/>before arrival' +
                    '</td><td align="center" nowrap="nowrap">or between<br/><span style="font-weight:bold;">' + getNodeText(nCNPolicy[idx].getElementsByTagName('FROM_DATE')[0].firstChild) +
                    ' and ' +
                    getNodeText(nCNPolicy[idx].getElementsByTagName('TO_DATE')[0].firstChild) + '</span>' +
                    '</td><td align="center" nowrap="nowrap">Cancellation fees<br/>are<span style="font-weight:bold;">' +  parseFloat( getNodeText(nCNPolicy[idx].getElementsByTagName('CN_PERC')[0].firstChild) / 100).toFixed(2) +
                    '%</span>of the total price' +
                    '</tr>';                          
  }
  if (trim(tmpCNPolicy) == '') {
    tmpHotelPolicy = '<tr><td colspan="4">The hotel has no a specific cancelation policy. </td></tr>';
  }    

  var tmpHotelPolicy = getNodeText(nPricing.getElementsByTagName('HTLPOLICY')[0].firstChild);
  if (trim(tmpHotelPolicy) == '') {
    tmpHotelPolicy = '<tr><td colspan="4">The hotel has no a specific policy. </td></tr>';
  } else {
    tmpHotelPolicy = '<tr><td colspan="4">' + tmpHotelPolicy + '</td></tr>';
  }
  
  var dlgCNPolicy = new Ext.Window ({
    layout: 'fit',
    id: 'dlgCNPolicy',
    title: 'Policies for hotel ' + hotelsObjArray[glSelectedHotelIndex].hotelname ,
    width: 550,
    //height:200,
    autoHeight: true,
    closable: true,
    closeAction: 'close',
    modal:true,
    resizable: true,
    shim: true,
    buttons: [ {
        text: 'Close ', 
        icon: './cssImages/button_cancel.png',
        iconCls: 'x-btn-text-icon',
        handler: function() { $$$('acceptCNPolicy').disabled = true;
                              $$$('acceptCNPolicy').checked = false;
                              dlgCNPolicy.close();  
                            }
      }, {
        id: 'btnDlgCNPolicy',
        text: 'Accept ', 
        icon: './cssImages/button_policy.png',
        iconCls: 'x-btn-text-icon',
        handler: function() { $$$('acceptCNPolicy').disabled = false;
                              $$$('acceptCNPolicy').checked = true; 
                              dlgCNPolicy.close(); 
                            }
      }
    ]
  });
  
  if (inButton == 'view') {
    //
  } else {
    Ext.getCmp('btnDlgCNPolicy').show();
  }
  
  tmpCNPolicy = '<div style="padding:5px;">' +
                '<table border="0" cellspacing="5" cellpadding="5" width="100%"  style="border: solid 1px black;"><tbody>' +
                '<tr>' +
                '<td colspan="4" style="font-size:14px; font-weight: bold;">Cancellation Policy </td>' +
                '</tr>' +
                tmpCNPolicy +
                '<tr>' +
                '<td colspan="4" style="font-size:14px; font-weight: bold; padding-top:10px;">Hotel Policy </td>' +
                '<tr>' +
                tmpHotelPolicy +
                '</tr>';
  if (inButton == 'view') {
    Ext.getCmp('btnDlgCNPolicy').hide();
    tmpCNPolicy += '</tbody></table></div>';
  } else {
    Ext.getCmp('btnDlgCNPolicy').show();
    tmpCNPolicy +=  '<tr>' +
                    '<td colspan="4" style="font-size:14px; font-weight: bold; padding-top:20px;">Terms Of Reservation</td>' +
                    '<tr><td colspan="4" style="text-align:center;">' +
                    '<textarea cols="70" rows="8" readonly="readonly" style="font-size:11px;" onclick="doShowBigPolicy();">' + $$$('termsofuse').innerHTML + '</textarea>' +
                    '</td></tr></tbody></table></div>'; 
  }
  Ext.getCmp('dlgCNPolicy').html = tmpCNPolicy;
  dlgCNPolicy.show();
  $$$('acceptCNPolicy').disabled = false;
}


function doShowBigPolicy() {
  var dlgReservPolicyBig = new Ext.Window ({
    layout: 'fit',
    id: 'dlgReservPolicyBig',
    title: 'Terms Of Reservation' ,
    width: 500,
    height:350,
    autoHeight: false,
    closable: true,
    closeAction: 'close',
    modal:true,
    resizable: true,
    autoScroll:true,
    shim: true,
    buttons: [ {
        text: 'Close', 
        icon: './cssImages/button_cancel.png',
        iconCls: 'x-btn-text-icon',
        handler: function() { dlgReservPolicyBig.close(); }
      }
    ]
  });
  Ext.getCmp('dlgReservPolicyBig').html = '<span style="font-size:11px;">' + $$$('termsofuse').innerHTML.replace(/\n/g, '<br/><br/>') + '</span>';
  dlgReservPolicyBig.show();
}


function handleReserve() {
 
  function isFullValidEmail(inEmail) {
    var tmpResult = false;
    
    plsWaitPanel.show();
    $$$('plsWaitText').innerHTML = 'Validating your Data';
    
    Ext.Ajax.request({
      timeout: 3000,
      autoAbort: true,
      async: false,
      url: baseURL,
      params: 'id=validateEmail&email=' + inEmail,
      method: 'POST',
      success: function(result, options){
        if ( trim(result.responseText) != '' && result.status == 200) {
          if (trim(result.responseText) == 'valid') {
            tmpResult = true;
          }
        }
      },
      failure: function(o) {
        tmpResult = false;
      }
    });
    
    plsWaitPanel.hide();
    
    return tmpResult;
  }
  
  if ($$$('bookLeaderSurname').value == '') {
    $alert('Please enter Leader\'s Full Name ', 'Wrong Data', '');
    $$$('bookLeaderSurname').bgColor = "#FFFFCC";
    $$$('bookLeaderSurname').focus();
    return false;
  }
  
  if ($$$('bookLeaderFirstName').value == '') {
    $alert('Please enter Leader\'s Full Name ', 'Wrong Data', '');
    $$$('bookLeaderFirstName').bgColor = "#FFFFCC";
    $$$('bookLeaderFirstName').focus();
    return false;
  }
  
  // Fix the length of the Leader Name, because of HnB bug (up to 20 characters) 
  if ($$$('bookLeaderSurname').value.length + $$$('bookLeaderFirstName').value.length > 15) {
    $$$('bookLeaderSurname').value = $$$('bookLeaderSurname').value.substring(0,13);
    $$$('bookLeaderFirstName').value = $$$('bookLeaderFirstName').value.substring(0,15-$$$('bookLeaderSurname').value.length);
    doEnterRoom1Adult1Value();     
  }
   
  if ($$$('bookPhone').value == '' && $$$('bookMobile').value == '') {
    //$alert('Please enter Contact Phone. ', 'Wrong Data', '');
    $alert('Phone Number of Mobile Phone number is required', 'Wrong Data', '');
    $$$('bookPhone').bgColor = "#FFFFCC";
    $$$('bookPhone').focus();
    return false;
  }
  
  if ($$$('bookEmail').value == '') {
    $alert('Please enter Contact Email. ', 'Wrong Data', '');
    $$$('bookEmail').bgColor = "#FFFFCC";
    $$$('bookEmail').focus();
    return false;
  }
  
  if (! Ext.form.VTypes.email($$$('bookEmail').value) ) {
    $alert('Please enter a Valid Contact Email in the format user@domain.com ', 'Wrong Data', '');
    $$$('bookEmail').bgColor = "#FFFFCC";
    $$$('bookEmail').focus();
    return false;
  }
  
  
  if (! isFullValidEmail($$$('bookEmail').value) ) {
    $alert('Your email provider cannot validate your email address ', 'Wrong Data', '');
    $$$('bookEmail').bgColor = "#FFFFCC";
    $$$('bookEmail').focus();
    return false;
  }
   
  for (idx=1; idx<=$$$('Rooms').value; idx++) {
    for (idxAdult=1; idxAdult<=$$$('Adults').value; idxAdult++) {
      tmpID = 'room' + idx + 'adult' + idxAdult;
      if ($$$(tmpID).value == '') {
        $alert('Please enter Name for Adult #' + idxAdult + 'of room # ' + idx, 'Wrong Data', '');
        $$$(tmpID).bgColor = "#FFFFCC";
        $$$(tmpID).focus();
        return false;
      }   
    }
    for (idxChild=1; idxChild<=$$$('Children').value; idxChild++) {
      tmpID = 'room' + idx + 'child' + idxChild;
      if ($$$(tmpID).value == '') {
        $alert('Please enter Name for Child #' + idxChild + ' of room #   ' + idx, 'Wrong Data', '');
        $$$(tmpID).bgColor = "#FFFFCC";
        $$$(tmpID).focus();
        return false;
      }   
    }
    tmpRoomData += '<br/><br/>';
  }
  
  if ( ! $$$('acceptCNPolicy').checked ) {
    if ($$$('acceptCNPolicy').disabled) {
      $alert('You must read and accept the cancellation policy! ', 'You didn\' read and accepted the policy ', '');
    } else {
      $alert('You must accept the cancellation policy! ', 'You didn\' accepted the policy ', '');
    }
    return false;
  }
  
  $$$('room1adult1').value = $$$('bookLeader').value;
  
  // $alert ( '<textarea rows="10" cols="55">' + constructXML() + '</textarea>', '', '' );
  var nResponse = resultXML.documentElement;
  var nRoom = nResponse.getElementsByTagName('ROOM');
  $$$('frmpay_price').value =    getNodeText(nRoom[0].getElementsByTagName('PRICE_STAY_MAX')[0].firstChild);  
  $$$('frmpay_currency').value = glCurrency;
  $$$('frmpay_merchand').value = '90002021';
  $$$('frmpay_rate').value =     glCurrencyRate;
  $$$('frmpay_leader').value =   $$$('bookLeader').value;
  $$$('frmpay_rooms').value =    $$$('Rooms').value;
  $$$('frmpay_msg').value =      constructXML();
    
  alert('A secure connection in a new window (popup window) will be opened.\n\nPlease make sure our web site (www.spotahotel.com) is allowed to open popup windows at your browser.')
  $$$('plsWaitText').innerHTML = "Opening a secure connection in a new window";
  plsWaitPanel.show();

  $$$('btnRsvDetailed').style.display = 'none';
  $$$('btnRsvPolicy').style.display = 'none';
  $$$('btnRsvPay').style.display = 'none';
  $$$('btnRsvCancel').style.display = 'none';

  window.open('','payWindow','width=600,height=450,toolbar=no,location=no,status=no,directories=no,scrollbars=yes,menubar=no,copyhistory=no');

  $$$('payForm').target = 'payWindow';
  $$$('payForm').action = 'https://www.spotahotel.com/pay.php?sid=o8llfarpe3dsf5hpta872f8uj171fj9u';
  $$$('payForm').submit();
  
  plsWaitPanel.hide();
  
  dlgRsvData.hide();
}

function handleSuccessPricingAll(result, options){ 
 if ( trim(result.responseText) != '' && result.status == 200) {
    plsWaitPanel.hide();
    
    resultXML = result.responseXML;
    glSelectedHotelIndex = result.argument[0];

    var nPricing = result.responseXML.documentElement;
    var nRoom = nPricing.getElementsByTagName('ROOM');
    tmpRoomData = '';
    tmpRoomName = '';
    for (idx=0, tmpIdx=nRoom.length; idx<tmpIdx; idx++) {
      if ( getNodeText(nRoom[idx].getElementsByTagName('ROOM_DESCR')[0].firstChild) != '') {
        tmpRoomName = getNodeText(nRoom[idx].getElementsByTagName('ROOM_DESCR')[0].firstChild);
      }
      if ( getNodeText(nRoom[idx].getElementsByTagName('ADULTS')[0].firstChild) == $$$('Adults').value &&
           getNodeText(nRoom[idx].getElementsByTagName('CHILDS')[0].firstChild) == $$$('Children').value &&
           parseInt(getNodeText(nRoom[idx].getElementsByTagName('AVAILROOMS')[0].firstChild)) >= parseInt($$$('Rooms').value)  
         ) {
         
        bookPriceCur = parseFloat(getNodeText(nRoom[idx].getElementsByTagName('PRICE_STAY_MAX')[0].firstChild) * $$$('Rooms').value * glCurrencyRate / 100).toFixed(2)

        tmpRoomData +=  '<tr>' +
                        '<td nowrap="nowrap">' + tmpRoomName + '</td>' +
                        '<td nowrap="nowrap">' + getNodeText(nRoom[idx].getElementsByTagName('BOARDDESCR')[0].firstChild) + '</td>' +
                        '<td nowrap="nowrap">' + glCurrency + '</td><td align="right">' + bookPriceCur + '</td>' +
                        '<td nowrap="nowrap"><a href="javascript:void(null)" onclick="Ext.getCmp(\'dlgAdditionalPricing\').close(); getData(\'pricing\', [' + glSelectedHotelIndex + ',\'' + getNodeText(nRoom[idx].getElementsByTagName('ROOM_ID')[0].firstChild)  + '\']);">Book this Room Type</a></td>' +
                        '</tr>';
      }
    }
    
    if (tmpRoomData != '') {
      tmpRoomData = '<div style="padding:5px;">' +
                    '<table border="0" cellspacing="5" cellpadding="5" width="100%"  style="border: solid 1px black;"><tbody>' +
                    '<tr style="font-size:14px; font-weight: bold;">' +
                    '<td>Room Name </td><td>Board </td><td colspan="2">Price </td><td>&nbsp;</td>' +
                    '</tr>' +
                    tmpRoomData + 
                    '</tbody></table></div>';
      var dlgAdditionalPricing = new Ext.Window ({
        layout: 'fit',
        id: 'dlgAdditionalPricing',
        title: 'Additional Room Types for hotel ' + hotelsObjArray[glSelectedHotelIndex].hotelname ,
        width: 450,
        //height:200,
        autoHeight: true,
        closable: true,
        closeAction: 'close',
        modal:true,
        resizable: true,
        shim: true,
        buttons: [ {
          text: 'Close ', 
          icon: './cssImages/button_cancel.png',
          iconCls: 'x-btn-text-icon',
          handler: function() { dlgAdditionalPricing.close(); }
        }]
      });
      
      Ext.getCmp('dlgAdditionalPricing').html = tmpRoomData;
      dlgAdditionalPricing.show();
    } else {
      alert('The hotel\'s reception office is unavailable at this moment.<br/> Please try again later', 'System Unavailable', 'error');
      plsWaitPanel.hide();
    }
        
  } else {
    alert('The hotel\'s reception office is unavailable at this moment.<br/> Please try again later', 'System Unavailable', 'error');
    plsWaitPanel.hide();
  }
}


function handleSuccessPricing(result, options){
  var bookPriceEUR, bookPriceCur, tmpOut, bookRoomDescr, bookBoardDescr;
  
  if ( trim(result.responseText) != '' && result.status == 200) {
    plsWaitPanel.hide();
    
    resultXML = result.responseXML;
    var nPricing = result.responseXML.documentElement;
    var nRoom = nPricing.getElementsByTagName('ROOM');
    isRoomStillAvailable = true;
    for (idx=0, tmpIdx=nRoom.length; idx<tmpIdx; idx++) {
      if ( parseInt(getNodeText(nRoom[idx].getElementsByTagName('AVAILROOMS')[0].firstChild)) < parseInt($$$('Rooms').value) ) {
        isRoomStillAvailable = false;
      } else {
        bookPriceEUR = parseFloat(getNodeText(nRoom[idx].getElementsByTagName('PRICE_STAY_MAX')[0].firstChild) * $$$('Rooms').value / 100).toFixed(2);
        bookPriceCur = parseFloat(getNodeText(nRoom[idx].getElementsByTagName('PRICE_STAY_MAX')[0].firstChild) * $$$('Rooms').value * glCurrencyRate / 100).toFixed(2)
        bookRoomDescr = getNodeText(nRoom[idx].getElementsByTagName('ROOM_DESCR')[0].firstChild);
        bookBoardDescr = getNodeText(nRoom[idx].getElementsByTagName('BOARDDESCR')[0].firstChild)
      }
    } 
    
    if (isRoomStillAvailable) {
      glSelectedHotelIndex = result.argument[0];    
      
      tmpBedConfig = $$$('Adults').value;
      if ($$$('Adults').value > 1) {
        tmpBedConfig += ' Adults ';
      } else {
        tmpBedConfig += ' Adult ';
      }
      if ($$$('Children').value != 0) {
        tmpBedConfig += ' - ' + $$$('Children').value;
        if ($$$('Adults').value == 1) {
          tmpBedConfig += ' Child';
        } else {
          tmpBedConfig += ' Children';
        }  
      }
      tmpBedConfig += ' <img src="./images/ico_pax' + $$$('Adults').value + '_' + $$$('Children').value + '.gif" border="0">'; 
      
      tmpOut =  '<table><tbody' +
                '<tr><td nowrap="nowrap" style="text-align:right">Hotel Name:</td><td colspan="3" style="font-size:14px;">' + hotelsObjArray[glSelectedHotelIndex].hotelname + '</td></tr>' + 
                '<tr><td nowrap="nowrap" style="text-align:right">Hotel Location: </td><td colspan="3" style="font-size:14px;">' + $$$('acGeoSearch').value + '</td></tr>' +
                '<tr><td nowrap="nowrap"style="text-align:right">Number of Rooms: </td><td colspan="3" style="font-size:14px;">' + $$$('Rooms').value + '</td></tr>' +
                '<tr><td nowrap="nowrap"style="text-align:right">Room Type: </td><td colspan="3" style="font-size:14px;">' + bookRoomDescr + '</td></tr>' +
                '<tr><td nowrap="nowrap"style="text-align:right">Board Type:</td><td colspan="3" style="font-size:14px;">' + bookBoardDescr + '</td></tr>' +
                '<tr><td nowrap="nowrap"style="text-align:right">Each Room is Sleeping: </td><td colspan="3" style="font-size:14px;">' + tmpBedConfig + '</td></tr>' +
                '<tr><td nowrap="nowrap"style="text-align:right">Stay Period: </td><td colspan="3" style="font-size:14px;">' + $$$('CheckIn').value + ' - ' + $$$('CheckOut').value + ' (' + $$$('s_nights').value + ' nights)</td></tr>' +
                '<tr><td nowrap="nowrap"style="text-align:right">Final Price on Selected Currency:</td><td style="text-align:right; font-size:14px;">' + glCurrency + '</td><td style="text-align:right; font-size:14px;">' + bookPriceCur + '</td><td style="width:100%"></td></tr>';
      if ( glCurrency != 'EUR' ) {
        tmpOut += '<tr onclick="doShowDifferentCurrencyInfo()" style="cursor:pointer;">' +
                  '<td nowrap="nowrap"style="text-align:right">Final Price on Payment Currency:</td>' +
                  '<td style="text-align:right; font-size:14px;">EUR</td><td style="text-align:right; font-size:14px;">' + bookPriceEUR + '</td>' +
                  '<td style="width:100%"><img src="./cssImages/button_info.png" align="absmiddle"></td></tr>';
      }
      tmpOut += '</tbody></table><hr/>';   
      $$$('dlgRsvDataBodyHeader').innerHTML = tmpOut;
      
      // Create the Adult/Child table per Room
      tmpRoomData = '<table><tbody>';
      tmpRoomData += '<tr><td>&nbsp;</td>';
      for (idx=1; idx<=$$$('Rooms').value; idx++) {
        tmpRoomData += '<td style="font-size:12px;">Room # ' + idx + '</td>';
      }
      tmpRoomData += '</tr>';
      
      for (idxAdult=1; idxAdult<=$$$('Adults').value; idxAdult++) {
        tmpRoomData += '<tr><td>Adult #' + idxAdult + ':</td>';
        for (idx=1; idx<=$$$('Rooms').value; idx++) {
          tmpRoomData += '<td><input type="text" id="room' + idx + 'adult' + idxAdult + '" value="TBA" class="inputGeneral" style="width:50px;" onfocus="fieldExpand(this,200)" onblur="fieldExpand(this,50)" /></td>';      
        }
        tmpRoomData += '</tr>';
      }
      
      for (idxChild=1; idxChild<=$$$('Children').value; idxChild++) {
        tmpRoomData += '<tr><td>Child # ' + idxChild + ':</td>';
        for (idx=1; idx<=$$$('Rooms').value; idx++) {
          tmpRoomData += '<td>' + 
                         '<input type="text" id="room' + idx + 'child' + idxChild + '" value="TBA" class="inputGeneral" style="width:50px; z-index:100;" onfocus="fieldExpand(this,200);" onblur="fieldExpand(this,50);" />' +
                         '&nbsp;&nbsp;Age: <select id="room' + idx + 'childage' + idxChild + '" style="width:50px; display:inline;" />';
          for (idxAges=1; idxAges<12; idxAges++) {
            tmpSelected = '';
            if (idxAges == 6) {
              tmpSelected = 'selected="selected"';
            }
            tmpRoomData += '<option value="' + idxAges + '"' + tmpSelected + '>' + idxAges + '</option';          
          }                               
          tmpRoomData += '</select>' +
                         '</td>';      
        }
        tmpRoomData += '</tr>';
      }
       
      tmpRoomData += '</tbody></table>';
      $$$('dlgRsvDataBodyRooms').innerHTML = tmpRoomData;
       
      // Initialize the values
      if ($$$('bookLeader').value != '') {
        $$$('room1adult1').value = $$$('bookLeader').value;
      } else {
        $$$('room1adult1').value = 'TBA';
      }
      $$$('acceptCNPolicy').disabled = true;
      $$$('acceptCNPolicy').checked = false;
      
      $$$('btnRsvDetailed').style.display = '';
      $$$('btnRsvPolicy').style.display = '';
      $$$('btnRsvPay').style.display = '';
      $$$('btnRsvCancel').style.display = '';
      dlgRsvData.show();
    } else {
      $alert('We\'re sorry, but the requested room type is no more available.<br/><br/>The available rooms have been taken. ', 'Room Unavailable', 'info');
      plsWaitPanel.hide();
    }
      
  } else {
    $alert('The hotel\'s reception office is unavailable at this moment.<br/> Please try again later ', 'System Unavailable', 'error');
    plsWaitPanel.hide();
  }
}


function constructXML() {
  var nResponse = resultXML.documentElement;
  var nRoom = nResponse.getElementsByTagName('ROOM');
   
  xmlReservation =  "<RV_PRICE>" + Math.round(getNodeText(nRoom[0].getElementsByTagName('PRICE_STAY_MAX')[0].firstChild) * $$$('Rooms').value) + "</RV_PRICE>" +
                    "<RV_LEADER>" + $$$('bookLeader').value + "</RV_LEADER>" +
                    "<RV_TEL>" + $$$('bookPhone').value + "</RV_TEL>" +
                    "<RV_MOBILE>" + $$$('bookMobile').value + "</RV_MOBILE>" +
                    "<RV_MAIL>" + $$$('bookEmail').value + "</RV_MAIL>" +
                    "<RV_COMMENTS>" + $$$('bookComments').value + "</RV_COMMENTS>" +
                    "<RV_CUSTOM_DATA></RV_CUSTOM_DATA>"; 
  
  xmlElements = "";
  for (idx=1; idx<=$$$('Rooms').value; idx++) {
    xmlElements += "<ELEMENTS id='" + (idx) + "'>" +
                   "<HOTEL_ID>" + hotelsObjArray[glSelectedHotelIndex].hotelid + "</HOTEL_ID>" +
                   "<HOTELNAME>" + hotelsObjArray[glSelectedHotelIndex].hotelname + "</HOTELNAME>" +
                   "<CITY_ABBR>" + vArea + "</CITY_ABBR>" +
                   "<AREA_ABBR>" + vCity + "</AREA_ABBR>" +
                   "<ROOM_ID>" + getNodeText(nRoom[0].getElementsByTagName('ROOM_ID')[0].firstChild) + "</ROOM_ID>" +
                   "<ROOMNAME>" + getNodeText(nRoom[0].getElementsByTagName('ROOM_DESCR')[0].firstChild) + "</ROOMNAME>" +
                   "<PAX_AD>" + $$$('Adults').value + "</PAX_AD>" +
                   "<PAX_CH>" + $$$('Children').value + "</PAX_CH>" +
                   "<ELEM_CURRENCY rate='" + glCurrencyRate + "'>" + glCurrency + "</ELEM_CURRENCY>" +
                   "<ELEM_BUY>" + getNodeText(nRoom[0].getElementsByTagName('PRICE_STAY_BUY')[0].firstChild) + "</ELEM_BUY>" +
                   "<ELEM_AGNC>" + getNodeText(nRoom[0].getElementsByTagName('PRICE_STAY_AGNC')[0].firstChild) + "</ELEM_AGNC>" +
                   "<ELEM_MIN>" + getNodeText(nRoom[0].getElementsByTagName('PRICE_STAY_SELL')[0].firstChild) + "</ELEM_MIN>" +
                   "<ELEM_MAX>" + getNodeText(nRoom[0].getElementsByTagName('PRICE_STAY_MAX')[0].firstChild) + "</ELEM_MAX>" +
                   "<ELEM_AVAILROOMS>" + getNodeText(nRoom[0].getElementsByTagName('AVAILROOMS')[0].firstChild) + "</ELEM_AVAILROOMS>" +                   
                   "<ELEM_ARRIVAL>" + arrival.format('Ymd') + "</ELEM_ARRIVAL>" +
                   "<ELEM_DEPARTURE>" + departure.add(Date.DAY, -1).format('Ymd') + "</ELEM_DEPARTURE>" +
                   "<ELEM_MARGIN_PERC>" + getNodeText(nRoom[0].getElementsByTagName('MARGIN_PERC')[0].firstChild) + "</ELEM_MARGIN_PERC>" +
                   "<ELEM_BOARD>" + getNodeText(nRoom[0].getElementsByTagName('BOARD')[0].firstChild) + "</ELEM_BOARD>" +
                   "<SESSION_ID>" + getNodeText(nRoom[0].getElementsByTagName('SESSION_ID')[0].firstChild) + "</SESSION_ID>" +
                   "<HOTEL_CURRENCY rate='" + nRoom[0].getElementsByTagName('HOTEL_CURRENCY')[0].getAttribute("rate") + "'>" + getNodeText(nRoom[0].getElementsByTagName('HOTEL_CURRENCY')[0].firstChild) + "</HOTEL_CURRENCY>";
    for (idxAdult=1; idxAdult<=$$$('Adults').value; idxAdult++) {
      var tmpElemID = 'room' + idx + 'adult' + idxAdult
      xmlElements += "<CUSTOMER>" +
                     "<ELEM_CUSTOMER>" + trim($$$(tmpElemID).value) + "</ELEM_CUSTOMER>" +
			               "<ELEM_TYPE>AD</ELEM_TYPE>" +
                     "<ELEM_AGE>0</ELEM_AGE>" +
                     "</CUSTOMER>";
    }
    for (idxChild=1; idxChild<=$$$('Children').value; idxChild++) {
      var tmpElemID = 'room' + idx + 'child' + idxChild;
      var tmpElemAgeID = 'room' + idx + 'childage' + idxChild;
      xmlElements += "<CUSTOMER>" +
                     "<ELEM_CUSTOMER>" + trim($$$(tmpElemID).value) + "</ELEM_CUSTOMER>" +
			               "<ELEM_TYPE>CH</ELEM_TYPE>" +
                     "<ELEM_AGE>" + $$$(tmpElemAgeID).value + "</ELEM_AGE>" +
                     "</CUSTOMER>";
    }
    
    xmlElements += "</ELEMENTS>";
  }
  
  return "<RESERVATION>" + xmlReservation + xmlElements + "</RESERVATION>";
} 


function fieldExpand(elem, inWidth) {
  elem.style.width = inWidth + "px";
}

function doShowDifferentCurrencyInfo() {
  var infoText =  'The payment will be in a different currency because our hotels are sold only in EUR.<br/>Your credit card AT THE FINAL STEP will be charged with the amount and the currency listed here.<br/><br/>Your Credit Card issuer will convert the currency according their currency rate. ';
   
  $alert(infoText, "Why the payment currency is different from the selected?", "info"); 
}


function doEnterRoom1Adult1Value() {
  $$$('room1adult1').value = $$$('bookLeaderSurname').value + '/' + $$$('bookLeaderFirstName').value + '/' + $$$('bookLeaderTitle').value;
  $$$('bookLeader').value = $$$('room1adult1').value;   
}


function doAlertMobilePhoneInfo() {
  $alert( 'Providing your mobile phone you:<br/>' +
          '- are receiving an SMS message with your reservation details and the voucher retrieve info<br/>' +
          '- we can communicate immedietly with you in case of a problem' +
          '<br/><br/>Please, <strong>insert your mobile phone including the international dial code</strong> (e.x. 30xxxxxxxxxx, where 30 is the internation dial code and xxxxxxxxxx is your mobile phone number)',
          'Mobile Phone');
}var doChanges = true;

function initCalendars() {
  today = new Date();
  today.setHours(0,0,0,0);
  arrival = arrival.add(Date.DAY, +2);
  arrival.setHours(0,0,0,0);
  
  CheckIn = new Ext.ux.form.DateFieldPlus({
    id: 'CheckIn',
    usePickerPlus: true,
    noOfMonth: 2,
    noOfMonthPerRow: 2,
    multiSelection: false,
    markNationalHolidays: false,
    useQuickTips:false,
    width: 120,
    showActiveDate:false,
    showWeekNumber:false,
    renderTodayButton:false,
    showToday:false,
    allowBlank: false,
    readOnly: true,
    format : 'D d/m/Y',
    minValue: today,
    renderTo: 'calendar1Container'        
  });
  CheckIn.setValue (arrival);
  CheckIn.on('valid', setDate1);

  CheckOut = new Ext.ux.form.DateFieldPlus({
    id: 'CheckOut',
    usePickerPlus: true,
    noOfMonth: 2,
    noOfMonthPerRow: 2,
    multiSelection: false,
    markNationalHolidays: false,
    useQuickTips:false,
    width: 120,
    showActiveDate:false,
    showWeekNumber:false,
    renderTodayButton:false,
    showToday:false,
    allowBlank: false,
    readOnly: true,
    format : 'D d/m/Y',
    minValue: arrival.add(Date.DAY, +1 ),
    maxValue: arrival.add(Date.DAY, +31 ),
    renderTo: 'calendar2Container'      
  });
  CheckOut.setValue(arrival.add(Date.DAY, +1 ));
  CheckOut.on('valid', setDate2);
    
  calcDeparture();
}


function setDate1() {
  if (doChanges) {
    arrival = CheckIn.getValue();
    calcDeparture();
  }
}

function setDate2() {
  if (doChanges) {
    departure = CheckOut.getValue();
    calcNights();
  }
}

function calcDeparture (inNights) {
  doChanges = false;
  
	if ( isNaN(inNights) ) {		
		inNights = $$$('s_nights').value;
  }
	CheckOut.setMinValue ( arrival.add(Date.DAY, +1) );
	CheckOut.setMaxValue ( arrival.add(Date.DAY, +31) );
	departure = arrival.add( Date.DAY, Number(inNights) );
	CheckOut.setValue(departure);

	
  doChanges = true;
}


function calcNights() {
  if (doChanges) {
    doChanges = false;

    nights = (departure - arrival) / (24*60*60*1000);
    nights = Math.round(nights);
    Ext.getCmp('cbNights').setValue(nights);  
    $$$('s_nights').value = nights;
    
    doChanges = true;
  } else { alert ('no'); }
}
eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('C g2={\'3x\':\'1.eZ\',\'57\':\'1.gs\'};C 1B=o(F){F=F||{};C 6N=F.6N||o(){};C 6O=F.6O;6O=(6O!==M);C 5J=F.5J;C 1v=F.1v;C 7V=F.7V;C 14=F.14;C 1c=1v||5J;1c.9m=1B;1c.$3u={14:\'fL\'};if(5J&&1v)1c.2j=5J.2j;1c.2j.9m=1c;if(14){C 3u=14.4r();1c.2j.$3u={14:3u};1B.a7(1c,3u)}C 5g=o(15,14,2s,9O){if(!7V||9O||!15.2j[14])15.2j[14]=2s;if(6O)1B.ae(15,14,7V);6N.1L(15,14,2s);B 15};1c.1l=o(a1,a2,a3){if(3q a1==\'1T\')B 5g(c,a1,a2,a3);R(C p in a1)5g(c,p,a1[p],a2);B c};1c.5h=o(7C,U,9O){7C=c.2j[7C];if(7C)5g(c,U,7C,9O);B c};B 1c};1B.1l=o(5a,1k){R(C i=0,l=5a.W;i<l;i++)5a[i].1l(1k)};1B.ae=o(1c,U,2c){if((!2c||!1c[U])&&3q 1c.2j[U]==\'o\')1c[U]=o(){C 1D=1t.2j.2W.1L(O);B 1c.2j[U].3E(1D.9h(),1D)}};1B.a7=o(1c,3u){if(!1c.J)1c.J=o(1H){B($J(1H)===3u)}};(o(5a){R(C 14 in 5a)1B.a7(5a[14],14.4r())})({\'cO\':cO,\'1B\':1B,\'62\':62});(o(5a){R(C 14 in 5a)K 1B({14:14,1v:5a[14],7V:T})})({\'2T\':2T,\'bK\':bK,\'4V\':4V,\'1t\':1t,\'9c\':9c,\'7M\':7M});(o(1c,4e){R(C i=0,l=4e.W;i<l;i++)1B.ae(1c,4e[i],T);B O.2h})(1t,[\'dT\',\'1K\',\'fJ\',\'9h\',\'g7\',\'96\',\'gt\',\'4S\',\'5F\',\'2W\',\'9J\',\'dB\',\'4m\',\'dA\'])(2T,[\'6a\',\'ar\',\'4S\',\'4m\',\'dA\',\'17\',\'2Z\',\'8W\',\'2W\',\'5w\',\'7u\',\'cV\',\'4r\',\'8f\',\'dB\']);o $3B(15){B!!(15||15===0)};o $77(2F){f8(2F);f5(2F);B Y};o $3D(15){B(15!=ev)};o $1Y(){};o $O(i){B o(){B O[i]}};o $7c(I){B(3q I==\'o\')?I:o(){B I}};o $1z(b8,7Q){R(C L in(7Q||{}))b8[L]=7Q[L];B b8};o $6U(1c){C 73=Y;1W($J(1c)){Q\'1c\':73={};R(C p in 1c)73[p]=$6U(1c[p]);1e;Q\'23\':73=[];R(C i=0,l=1c.W;i<l;i++)73[i]=$6U(1c[i]);1e;3T:B 1c}B 73};o $3G(){C 7R={};R(C i=0,l=O.W;i<l;i++){C 1c=O[i];if($J(1c)!=\'1c\')4U;R(C L in 1c){C 9b=1c[L],9f=7R[L];7R[L]=(9f&&$J(9b)==\'1c\'&&$J(9f)==\'1c\')?$3G(9f,9b):$6U(9b)}}B 7R};o $5G(){R(C i=0,l=O.W;i<l;i++){if($3D(O[i]))B O[i]}B Y};o $bM(4l,1R){B 1m.8F(1m.bM()*(1R-4l+1)+4l)};o $4s(15){C J=$J(15);B(J)?((J!=\'23\'&&J!=\'O\')?[15]:15):[]};C $3H=7M.1d||o(){B K 7M().c0()};o $4b(fn,V,1D){4b{B fn.3E(V,$4s(1D))}dN(e){B M}};o $J(15){if(15==ev)B M;if(15.$3u)B(15.$3u.14==\'5L\'&&!fl(15))?M:15.$3u.14;if(15.cI){1W(15.3R){Q 1:B\'G\';Q 3:B(/\\S/).3k(15.cH)?\'eq\':\'df\'}}1i if(3q 15.W==\'5L\'){if(15.2h)B\'O\';1i if(15.1H)B\'7d\'}B 3q 15};C 1o=K 1B({14:\'1o\',1v:o(1c){if($J(1c)==\'5T\')1c=$6U(1c.eI());R(C L in 1c){if(!c[L])c[L]=1c[L]}B c}});1o.1l({f6:o(){C W=0;R(C L in c){if(c.4K(L))W++}B W},8O:o(fn,V){R(C L in c){if(c.4K(L))fn.1L(V,c[L],L,c)}},eI:o(){C 6h={};R(C L in c){if(c.4K(L))6h[L]=c[L]}B 6h}});1o.5h(\'8O\',\'1b\');o $H(1c){B K 1o(1c)};1t.1l({8O:o(fn,V){R(C i=0,l=c.W;i<l;i++)fn.1L(V,c[i],i,c)}});1t.5h(\'8O\',\'1b\');o $A(5K){if($J(5K)==\'7d\'){C 23=[];R(C i=0,l=5K.W;i<l;i++)23[i]=5K[i];B 23}B 1t.2j.2W.1L(5K)};o $1b(5K,fn,V){C J=$J(5K);((J==\'O\'||J==\'7d\'||J==\'23\')?1t:1o).1b(5K,fn,V)};C 1p=K 1o({1x:{14:\'f9\',3x:\'\'},bj:{14:(82.fp.17(/fb|2k|fe|f3/i)||[\'f7\'])[0].4r()},8K:{36:!!(1X.8J),2r:!!(1n.cS),ff:!!(1X.fg)}});if(1X.fq)1p.1x.14=\'aR\';1i if(1X.8u)1p.1x={14:\'3h\',3x:(1p.8K.36)?5:4};1i if(!82.f1)1p.1x={14:\'6I\',3x:(1p.8K.2r)?fr:fs};1i if(1n.ft!=Y)1p.1x.14=\'aE\';1p.1x[1p.1x.14]=1p.1x[1p.1x.14+1p.1x.3x]=T;1p.bj[1p.bj.14]=T;o $9q(1r){if(!1r)B 1r;if(1X.dW){1X.dW(1r)}1i{C 2l=1n.bY(\'2l\');2l.cL(\'J\',\'1r/58\');2l.1r=1r;1n.78.7E(2l);1n.78.eE(2l)}B 1r};1B.5i=0;C 3Q=K 1B({14:\'3Q\',5J:1X.3Q,1v:o(2k){if(!2k.N){2k.N=$1Y;if(1p.1x.6I)2k.1n.bY("3a");2k.N.2j=(1p.1x.6I)?1X["[[eW.2j]]"]:{}}2k.29=1B.5i++;B $1z(2k,3Q.4v)},6N:o(U,I){1X[U]=3Q.4v[U]=I}});3Q.4v={$3u:{14:\'1X\'}};K 3Q(1X);C 3t=K 1B({14:\'3t\',5J:1X.3t,1v:o(21){21.78=21.5r(\'78\')[0];21.22=21.5r(\'22\')[0];21.1X=21.ef||21.eo;if(1p.1x.f0)$4b(o(){21.eX("eS",M,T)});21.29=1B.5i++;B $1z(21,3t.4v)},6N:o(U,I){1n[U]=3t.4v[U]=I}});3t.4v={$3u:{14:\'1n\'}};K 3t(1n);1t.1l({7J:o(fn,V){R(C i=0,l=c.W;i<l;i++){if(!fn.1L(V,c[i],i,c))B M}B T},1N:o(fn,V){C 45=[];R(C i=0,l=c.W;i<l;i++){if(fn.1L(V,c[i],i,c))45.1K(c[i])}B 45},6h:o(){B c.1N($O(0))},4m:o(1H,1j){C 2B=c.W;R(C i=(1j<0)?1m.1R(0,2B+1j):1j||0;i<2B;i++){if(c[i]===1H)B i}B-1},33:o(fn,V){C 45=[];R(C i=0,l=c.W;i<l;i++)45[i]=fn.1L(V,c[i],i,c);B 45},dP:o(fn,V){R(C i=0,l=c.W;i<l;i++){if(fn.1L(V,c[i],i,c))B T}B M},9B:o(3v){C 15={},W=1m.4l(c.W,3v.W);R(C i=0;i<W;i++)15[3v[i]]=c[i];B 15},2m:o(1c){C 1C={};R(C i=0,l=c.W;i<l;i++){R(C L in 1c){if(1c[L](c[i])){1C[L]=c[i];3r 1c[L];1e}}}B 1C},2e:o(1H,1j){B c.4m(1H,1j)!=-1},1z:o(23){R(C i=0,j=23.W;i<j;i++)c.1K(23[i]);B c},7P:o(){B(c.W)?c[c.W-1]:Y},eY:o(){B(c.W)?c[$bM(0,c.W-1)]:Y},34:o(1H){if(!c.2e(1H))c.1K(1H);B c},3G:o(23){R(C i=0,l=23.W;i<l;i++)c.34(23[i]);B c},4d:o(1H){R(C i=c.W;i--;i){if(c[i]===1H)c.96(i,1)}B c},1Y:o(){c.W=0;B c},4E:o(){C 23=[];R(C i=0,l=c.W;i<l;i++){C J=$J(c[i]);if(!J)4U;23=23.4S((J==\'23\'||J==\'7d\'||J==\'O\')?1t.4E(c[i]):c[i])}B 23},5V:o(23){if(c.W!=3)B Y;C 1P=c.33(o(I){if(I.W==1)I+=I;B I.3W(16)});B(23)?1P:\'1P(\'+1P+\')\'},5S:o(23){if(c.W<3)B Y;if(c.W==4&&c[3]==0&&!23)B\'9U\';C 4n=[];R(C i=0;i<3;i++){C 4k=(c[i]-0).9J(16);4n.1K((4k.W==1)?\'0\'+4k:4k)}B(23)?4n:\'#\'+4n.5F(\'\')}});bK.1l({1z:o(1k){R(C U in 1k)c[U]=1k[U];B c},4Q:o(F){C 2U=c;F=F||{};B o(P){C 1D=F.O;1D=$3D(1D)?$4s(1D):1t.2W(O,(F.P)?1:0);if(F.P)1D=[P||1X.P].1z(1D);C 5P=o(){B 2U.3E(F.V||Y,1D)};if(F.2S)B go(5P,F.2S);if(F.68)B gq(5P,F.68);if(F.8N)B $4b(5P);B 5P()}},4g:o(1D,V){B c.4Q({\'O\':1D,\'V\':V})},8N:o(1D,V){B c.4Q({\'O\':1D,\'V\':V,\'8N\':T})()},V:o(V,1D){B c.4Q({\'V\':V,\'O\':1D})},av:o(V,1D){B c.4Q({\'V\':V,\'P\':T,\'O\':1D})},2S:o(2S,V,1D){B c.4Q({\'2S\':2S,\'V\':V,\'O\':1D})()},68:o(dC,V,1D){B c.4Q({\'68\':dC,\'V\':V,\'O\':1D})()},e2:o(1D,V){B c.3E(V,$4s(1D))}});4V.1l({2g:o(4l,1R){B 1m.4l(1R,1m.1R(4l,c))},3l:o(7q){7q=1m.4J(10,7q||0);B 1m.3l(c*7q)/7q},7y:o(fn,V){R(C i=0;i<c;i++)fn.1L(V,i,c)},dU:o(){B 8b(c)},3W:o(3i){B 5Q(c,3i||10)}});4V.5h(\'7y\',\'1b\');(o(dM){C 4e={};dM.1b(o(14){if(!4V[14])4e[14]=o(){B 1m[14].3E(Y,[c].4S($A(O)))}});4V.1l(4e)})([\'gp\',\'dQ\',\'gj\',\'gb\',\'g9\',\'gd\',\'bv\',\'gg\',\'8F\',\'gf\',\'1R\',\'4l\',\'4J\',\'bl\',\'eD\',\'ge\']);2T.1l({3k:o(8G,12){B((3q 8G==\'1T\')?K 9c(8G,12):8G).3k(c)},2e:o(1T,4C){B(4C)?(4C+c+4C).4m(4C+1T+4C)>-1:c.4m(1T)>-1},6i:o(){B c.2Z(/^\\s+|\\s+$/g,\'\')},6h:o(){B c.2Z(/\\s+/g,\' \').6i()},9z:o(){B c.2Z(/-\\D/g,o(17){B 17.6a(1).8f()})},c8:o(){B c.2Z(/[A-Z]/g,o(17){B(\'-\'+17.6a(0).4r())})},5j:o(){B c.2Z(/\\b[a-z]/g,o(17){B 17.8f()})},c4:o(){B c.2Z(/([-.*+?^${}()|[\\]\\/\\\\])/g,\'\\\\$1\')},3W:o(3i){B 5Q(c,3i||10)},dU:o(){B 8b(c)},5V:o(23){C 4n=c.17(/^#?(\\w{1,2})(\\w{1,2})(\\w{1,2})$/);B(4n)?4n.2W(1).5V(23):Y},5S:o(23){C 1P=c.17(/\\d{1,3}/g);B(1P)?1P.5S(23):Y},aU:o(3V){C 8E=\'\';C 1r=c.2Z(/<2l[^>]*>([\\s\\S]*?)<\\/2l>/gi,o(){8E+=O[1]+\'\\n\';B\'\'});if(3V===T)$9q(8E);1i if($J(3V)==\'o\')3V(8E,1r);B 1r}});1o.1l({gF:62.2j.4K,8e:o(I){R(C L in c){if(c.4K(L)&&c[L]===I)B L}B Y},7N:o(I){B(1o.8e(c,I)!==Y)},1z:o(1k){1o.1b(1k,o(I,L){1o.1g(c,L,I)},c);B c},3G:o(1k){1o.1b(1k,o(I,L){1o.34(c,L,I)},c);B c},4d:o(L){if(c.4K(L))3r c[L];B c},1f:o(L){B(c.4K(L))?c[L]:Y},1g:o(L,I){if(!c[L]||c.4K(L))c[L]=I;B c},1Y:o(){1o.1b(c,o(I,L){3r c[L]},c);B c},34:o(L,I){C k=c[L];if(!$3D(k))c[L]=I;B c},33:o(fn,V){C 45=K 1o;1o.1b(c,o(I,L){45.1g(L,fn.1L(V,I,L,c))},c);B 45},1N:o(fn,V){C 45=K 1o;1o.1b(c,o(I,L){if(fn.1L(V,I,L,c))45.1g(L,I)},c);B 45},7J:o(fn,V){R(C L in c){if(c.4K(L)&&!fn.1L(V,c[L],L))B M}B T},dP:o(fn,V){R(C L in c){if(c.4K(L)&&fn.1L(V,c[L],L))B T}B M},gI:o(){C 3v=[];1o.1b(c,o(I,L){3v.1K(L)});B 3v},gK:o(){C 1V=[];1o.1b(c,o(I){1V.1K(I)});B 1V},7K:o(){C 6J=[];1o.1b(c,o(I,L){$4s(I).1b(o(3U){6J.1K(L+\'=\'+aB(3U))})});B 6J.5F(\'&\')}});1o.5h(\'8e\',\'4m\').5h(\'7N\',\'2e\').5h(\'4d\',\'5I\');C 6u=K 1B({14:\'6u\',1v:o(P,2k){2k=2k||1X;P=P||2k.P;if(P.$7Q)B P;c.$7Q=T;C J=P.J;C 5y=P.5y||P.gD;3N(5y&&5y.3R==3)5y=5y.3b;if(J.17(/aL|6X/)){C 9X=(P.dR)?P.dR/gv:-(P.gu||0)/ 3} 1i if (J.3k(/L/)){C 6H=P.ds||P.gx;C L=6u.eA.8e(6H);if(J==\'cq\'){C 8z=6H-gy;if(8z>0&&8z<13)L=\'f\'+8z}L=L||2T.gA(6H).4r()}1i if(J.17(/(9D|4T|gz)/i)){C 2X={x:P.bE||P.dg+2k.1n.7f.8q,y:P.bF||P.di+2k.1n.7f.8t};C 9t={x:P.bE?P.bE-2k.cb:P.dg,y:P.bF?P.bF-2k.cc:P.di};C 9V=(P.ds==3)||(P.fK==2);C 3I=Y;if(J.17(/7b|90/)){1W(J){Q\'aK\':3I=P.9K||P.fM;1e;Q\'aH\':3I=P.9K||P.bA}if((o(){3N(3I&&3I.3R==3)3I=3I.3b}).4Q({8N:1p.1x.aE})()===M)3I=M}}B $1z(c,{P:P,J:J,2X:2X,9t:9t,9V:9V,9X:9X,9K:3I,5y:5y,6H:6H,L:L,9h:P.fG,fz:P.fy,fw:P.fA,fB:P.fE})}});6u.eA=K 1o({\'fD\':13,\'fC\':38,\'fO\':40,\'2n\':37,\'3Y\':39,\'fP\':27,\'g1\':32,\'g0\':8,\'g3\':9,\'3r\':46});6u.1l({4q:o(){B c.9r().9L()},9r:o(){if(c.P.9r)c.P.9r();1i c.P.g4=T;B c},9L:o(){if(c.P.9L)c.P.9L();1i c.P.g6=M;B c}});C 1E=K 1B({14:\'1E\',1v:o(1k){1k=1k||{};C 3z=o(){R(C U in c)c[U]=$6U(c[U]);c.1Q=Y;[\'4M\',\'3F\'].1b(o(7j){if(!c[7j])B;1E[7j](c,c[7j]);3r c[7j]},c);c.9m=3z;C 2U=(O[0]!==$1Y&&c.1v)?c.1v.3E(c,O):c;if(c.F&&c.F.1v)c.F.1v.1L(c);B 2U};$1z(3z,c);3z.9m=1E;3z.2j=1k;B 3z}});1E.1l({1l:o(){1E.4M(c.2j,1t.2W(O));B c}});1E.4M=o(2U,d5){$4s(d5).1b(o(3z){$1z(2U,($J(3z)==\'76\')?K 3z($1Y):3z)})};1E.3F=o(2U,3z){3z=K 3z($1Y);R(C U in 3z){C eb=3z[U];C ec=2U[U];2U[U]=(o(4Z,4D){if($3D(4D)&&4Z!=4D){C J=$J(4D);if(J!=$J(4Z))B 4D;1W(J){Q\'o\':B o(){4D.1Q=2U.1Q=4Z.V(c);C I=4D.3E(c,O);2U.1Q=4D.1Q;B I};Q\'1c\':B $3G(4Z,4D);3T:B 4D}}B 4Z})(eb,ec)}};1E.2j.1z=o(1k){1k.3F=c;B K 1E(1k)};C bH=K 1E({3S:o(){c.$3S=(c.$3S||[]).1z(O);B c},bV:o(){if(c.$3S&&c.$3S.W)c.$3S.9h().3E(c,O);B c},eG:o(){if(c.$3S)c.$3S.1Y();B c}});C 3m=K 1E({1M:o(J,fn,9i){if(fn!=$1Y){c.$1a=c.$1a||{};c.$1a[J]=c.$1a[J]||[];c.$1a[J].34(fn);if(9i)fn.9i=T}B c},79:o(1a){R(C J in 1a)c.1M(J,1a[J]);B c},1u:o(J,1D,2S){if(!c.$1a||!c.$1a[J])B c;c.$1a[J].1b(o(fn){fn.4Q({\'V\':c,\'2S\':2S,\'O\':1D})()},c);B c},3j:o(J,fn){if(!c.$1a||!c.$1a[J])B c;if(!fn.9i)c.$1a[J].4d(fn);B c},9e:o(J){R(C e in c.$1a){if(J&&J!=e)4U;C ad=c.$1a[e];R(C i=ad.W;i--;i)c.3j(e,ad[i])}B c}});C 5l=K 1E({5m:o(){c.F=$3G.e2([c.F].1z(O));if(!c.1M)B c;R(C 3V in c.F){if($J(c.F[3V])!=\'o\'||!(/^94[A-Z]/).3k(3V))4U;c.1M(3V,c.F[3V]);3r c.F[3V]}B c}});3t.1l({ep:o(1I,2A){if(1p.1x.3h&&2A){[\'14\',\'J\',\'8c\'].1b(o(2v){if(!2A[2v])B;1I+=\' \'+2v+\'="\'+2A[2v]+\'"\';if(2v!=\'8c\')3r 2A[2v]});1I=\'<\'+1I+\'>\'}B $.G(c.bY(1I)).1g(2A)},eP:o(1r){B c.g5(1r)},3A:o(){B c},3X:o(){B c.ef||c.eo}});C N=K 1B({14:\'N\',5J:1X.N,1v:o(1I,2A){C ab=N.eh.1f(1I);if(ab)B ab(2A);if(3q 1I==\'1T\')B 1n.ep(1I,2A);B $(1I).1g(2A)},6N:o(L,I){if(!1t[L])2E.1l(L,2E.ei(L));N.4v[L]=I}});N.4v={$3u:{14:\'G\'}};N.eh=K 1o;C eg=K 1B({14:\'eg\',6O:M,1v:o(){1B.5i++;C 12=1t.2m(O,{1k:62.J,3a:$3D});C 2A=12.1k||{};C 3a=$(12.3a)||M;C 4A=2A.4A||$1Y;3r 2A.4A;2A.id=2A.14=$5G(2A.id,2A.14,3a.id,3a.14,\'fS\'+1B.5i);((3a=3a||K N(\'3a\'))).1g(2A);C 9T=o(){C 7X=$4b(o(){B 3a.ap.5C.7X});if(7X&&7X==1X.5C.7X){3a.1X=3a.ap;C 2k=K 3Q(3a.1X);C 21=K 3t(3a.1X.1n);$1z(2k.N.2j,N.4v)}4A.1L(3a.ap)};(!1X.fR[2A.id])?3a.6x(\'2w\',9T):9T();B 3a}});C 2E=K 1B({1v:o(19,F){F=$1z({4c:T,6c:T},F);19=19||[];if(F.4c||F.6c){C 6p={};C 5R=[];R(C i=0,l=19.W;i<l;i++){C el=$.G(19[i],!F.6c);if(F.4c){if(6p[el.29])4U;6p[el.29]=T}5R.1K(el)}19=5R}B(F.6c)?$1z(19,c):19}});2E.1l({dy:o(1N){if(!1N)B c;B K 2E(c.1N((3q 1N==\'1T\')?o(1H){B 1H.17(1N)}:1N))}});2E.ei=o(U){B o(){C 2G=[];C 19=T;R(C i=0,j=c.W;i<j;i++){C 5P=c[i][U].3E(c[i],O);2G.1K(5P);if(19)19=($J(5P)==\'G\')}B(19)?K 2E(2G):2G}};3Q.1l({$:o(el,5O){if(el&&el.$1S)B el;C J=$J(el);B($[J])?$[J](el,5O,c.1n):Y},$$:o(2H){if(O.W==1&&3q 2H==\'1T\')B c.1n.5W(2H);C 19=[];C 1D=1t.4E(O);R(C i=0,l=1D.W;i<l;i++){C 1H=1D[i];1W($J(1H)){Q\'G\':1H=[1H];1e;Q\'1T\':1H=c.1n.5W(1H,T);1e;3T:1H=M}if(1H)19.1z(1H)}B K 2E(19)},3A:o(){B c.1n},3X:o(){B c}});$.1T=o(id,5O,21){id=21.9l(id);B(id)?$.G(id,5O):Y};$.G=o(el,5O){el.29=el.29||[1B.5i++];if(!5O&&43.cw(el)&&!el.$3u)$1z(el,N.4v);B el};$.eq=$.1X=$.1n=$O(0);$.5L=o(29){B 43.2E[29]||Y};1B.1l([N,3t],{9s:o(2H,5O){B $(c.5W(2H,T)[0]||Y,5O)},5W:o(87,2a){87=87.5w(\',\');C 19=[];C 4c=(87.W>1);87.1b(o(1I){C 6o=c.5r(1I.6i());(4c)?19.1z(6o):19=6o},c);B K 2E(19,{4c:4c,6c:!2a})}});N.8v={1f:o(29){B(c[29]=c[29]||{})}};N.5v=K 1o({aG:o(2J,G){if(G.3b)G.3b.bm(2J,G)},aC:o(2J,G){if(!G.3b)B;C 6b=G.5f;(6b)?G.3b.bm(2J,6b):G.3b.7E(2J)},31:o(2J,G){G.7E(2J)},2i:o(2J,G){C 7w=G.97;(7w)?G.bm(2J,7w):G.7E(2J)}});N.5v.db=N.5v.31;N.5v.1b(o(I,L){C bp=L.5j();N.1l(\'3c\'+bp,o(el){N.5v[L](c,$(el,T));B c});N.1l(\'7B\'+bp,o(el){N.5v[L]($(el,T),c);B c})});N.1l({3A:o(){B c.9y},3X:o(){B c.9y.3X()},9l:o(id,2a){C el=c.9y.9l(id);if(!el)B Y;R(C 1Q=el.3b;1Q!=c;1Q=1Q.3b){if(!1Q)B Y}B $.G(el,2a)},1g:o(2P,I){1W($J(2P)){Q\'1c\':R(C p in 2P)c.1g(p,2P[p]);1e;Q\'1T\':C U=N.3n.1f(2P);(U&&U.1g)?U.1g.3E(c,1t.2W(O,1)):c.ag(2P,I)}B c},1f:o(2P){C U=N.3n.1f(2P);B(U&&U.1f)?U.1f.3E(c,1t.2W(O,1)):c.8P(2P)},5I:o(2P){C U=N.3n.1f(2P);(U&&U.5I)?U.5I.3E(c,1t.2W(O,1)):c.7T(2P);B c},17:o(1I){B(!1I||N.1f(c,\'1I\')==1I)},3c:o(el,49){N.5v.1f(49||\'31\')(c,$(el,T));B c},dv:o(el,49){el=$(el,T);B c.eC(el).7B(el)},7B:o(el,49){N.5v.1f(49||\'31\')($(el,T),c);B c},fU:o(1r,49){B c.7B(c.3A().eP(1r),49)},dh:o(){1t.4E(O).1b(o(G){c.7E($(G,T))},c);B c},7p:o(){B c.3b.eE(c)},3L:o(ey){C 2d=K N(\'5n\').7B(c.fW(ey!==M));1t.1b(2d.5r(\'*\'),o(G){if(G.id)G.cE(\'id\')});B K N(\'5n\').1g(\'22\',2d.bL).d3()},eC:o(el){el=$(el,T);el.3b.fV(c,el);B c},aV:o(2o){B c.2o.2e(2o,\' \')},c3:o(2o){if(!c.aV(2o))c.2o=(c.2o+\' \'+2o).6h();B c},c7:o(2o){c.2o=c.2o.2Z(K 9c(\'(^|\\\\s)\'+2o+\'(?:\\\\s|$)\'),\'$1\').6h();B c},fX:o(2o){B c.aV(2o)?c.c7(2o):c.c3(2o)},6D:o(U){C 1C=Y;if(c.9p){1C=c.9p[U.9z()]}1i{C 69=c.3X().6D(c,Y);if(69)1C=69.fT([U.c8()])}B 1C},1Y:o(){C 19=$A(c.5r(\'*\'));19.1b(o(G){$4b(N.2j.7p,G)});43.cr(19);$4b(N.2j.1g,c,[\'22\',\'\']);B c},e6:o(){43.8Y(c.1Y().7p());B Y},7K:o(){C 6J=[];c.5W(\'ca, bc, fQ\',T).1b(o(el){C 14=el.14,J=el.J,I=N.1f(el,\'I\');if(I===M||!14||el.8l)B;$4s(I).1b(o(3U){6J.1K(14+\'=\'+aB(3U))})});B 6J.5F(\'&\')},8P:o(2v){C 3w=N.7t,L=3w.7s[2v];C I=(L)?c[L]:c.fY(2v);B(3w.6B[2v])?!!I:I},fZ:o(){C 1D=$A(O);B 1D.33(o(c9){B c.8P(c9)},c).9B(1D)},ag:o(2v,I){C 3w=N.7t,L=3w.7s[2v],7N=$3D(I);if(L&&3w.6B[2v])I=(I||!7N)?T:M;1i if(!7N)B c.7T(2v);(L)?c[L]=I:c.cL(2v,I);B c},b3:o(1S){R(C 2v in 1S)c.ag(2v,1S[2v]);B c},7T:o(2v){C 3w=N.7t,L=3w.7s[2v],cN=(L&&3w.6B[2v]);(L)?c[L]=(cN)?M:\'\':c.cE(2v);B c},fF:o(){1t.1b(O,c.7T,c);B c}});(o(){C 41=o(G,41,18,17,56,2a){C el=G[18||41];C 19=[];3N(el){if(el.3R==1&&N.17(el,17)){19.1K(el);if(!56)1e}el=el[41]}B(56)?K 2E(19,{4c:M,6c:!2a}):$(19[0],2a)};N.1l({fN:o(17,2a){B 41(c,\'7L\',Y,17,M,2a)},d8:o(17,2a){B 41(c,\'7L\',Y,17,T,2a)},fH:o(17,2a){B 41(c,\'5f\',Y,17,M,2a)},fI:o(17,2a){B 41(c,\'5f\',Y,17,T,2a)},d3:o(17,2a){B 41(c,\'5f\',\'97\',17,M,2a)},7P:o(17,2a){B 41(c,\'7L\',\'g8\',17,M,2a)},8n:o(17,2a){B 41(c,\'3b\',Y,17,M,2a)},cX:o(17,2a){B 41(c,\'3b\',Y,17,T,2a)},84:o(17,2a){B 41(c,\'5f\',\'97\',17,T,2a)},cB:o(el){if(!(el=$(el,T)))B M;B N.cX(el,c.1f(\'1I\'),T).2e(c)}})})();N.5h(\'7p\',\'4d\').5h(\'7P\',\'gB\');N.3n=K 1o;N.3n.1F={1g:o(1F){c.1F.bo=1F},1f:o(){B c.1F.bo},5I:o(){c.1F.bo=\'\'}};N.3n.I={1f:o(){1W(N.1f(c,\'1I\')){Q\'bc\':C 1V=[];1t.1b(c.F,o(3V){if(3V.cf)1V.1K(3V.I)});B(c.cg)?1V:1V[0];Q\'ca\':if([\'gw\',\'gC\'].2e(c.J)&&!c.8c)B M;3T:B $5G(c.I,M)}}};N.3n.1I={1f:o(){B c.8V.4r()}};N.3n.22={1g:o(){B c.bL=1t.4E(O).5F(\'\')}};N.1l({gJ:o(){B c.1f(\'1r\')},gH:o(1r){B c.1g(\'1r\',1r)},gE:o(){B c.1g(\'22\',O)},fv:o(){B c.1f(\'22\')},gG:o(){B c.1f(\'1I\')}});1B.1l([N,3Q,3t],{6x:o(J,fn){if(c.cm)c.cm(J,fn,M);1i c.gc(\'94\'+J,fn);B c},ct:o(J,fn){if(c.cn)c.cn(J,fn,M);1i c.ga(\'94\'+J,fn);B c},1U:o(U,bW){C 6g=N.8v.1f(c.29);C 2P=6g[U];if($3D(bW)&&!$3D(2P))2P=6g[U]=bW;B $5G(2P)},5u:o(U,I){C 6g=N.8v.1f(c.29);6g[U]=I;B c},gh:o(U){C 6g=N.8v.1f(c.29);3r 6g[U];B c}});N.7t=K 1o({7s:{\'22\':\'bL\',\'76\':\'2o\',\'R\':\'gn\',\'1r\':(1p.1x.3h)?\'cR\':\'cQ\'},6B:[\'gk\',\'gl\',\'gm\',\'gL\',\'f4\',\'8c\',\'8l\',\'eV\',\'cg\',\'cf\',\'eU\',\'cM\'],aI:[\'I\',\'eQ\',\'eT\',\'eR\',\'fu\',\'fk\',\'fj\',\'fh\',\'fi\',\'fm\',\'fo\']});(o(3w){C 7g=3w.6B,aT=3w.aI;3w.6B=7g=7g.9B(7g);1o.1z(1o.3G(3w.7s,7g),aT.9B(aT.33(o(v){B v.4r()})));3w.4d(\'aI\')})(N.7t);C 43={2E:{},cv:{1c:1,fd:1,fc:1,fa:1},cw:o(el){if(el.$1S)B T;if(43.cv[el.8V])B M;43.2E[el.29]=el;el.$1S={};B T},cr:o(19){R(C i=19.W,el;i--;i)43.8Y(19[i])},8Y:o(el){if(!el||!el.$1S)B;3r 43.2E[el.29];if(el.1U(\'1a\'))el.9e();R(C p in el.$1S)el.$1S[p]=Y;if(1p.1x.3h){R(C d in N.4v)el[d]=Y}el.$1S=el.29=Y},1Y:o(){R(C 29 in 43.2E)43.8Y(43.2E[29])}};1X.6x(\'a0\',o(){1X.6x(\'9F\',43.1Y);if(1p.1x.3h)1X.6x(\'9F\',f2)});N.3n.1a={1g:o(1a){c.79(1a)}};1B.1l([N,3Q,3t],{1M:o(J,fn){C 1a=c.1U(\'1a\',{});1a[J]=1a[J]||{\'3v\':[],\'1V\':[]};if(1a[J].3v.2e(fn))B c;1a[J].3v.1K(fn);C 7O=J,47=N.3m.1f(J),3o=fn,2U=c;if(47){if(47.a4)47.a4.1L(c,fn);if(47.3o){3o=o(P){if(47.3o.1L(c,P))B fn.1L(c,P);B M}}7O=47.3i||7O}C 95=o(){B fn.1L(2U)};C 9Q=N.bf[7O]||0;if(9Q){if(9Q==2){95=o(P){P=K 6u(P,2U.3X());if(3o.1L(2U,P)===M)P.4q()}}c.6x(7O,95)}1a[J].1V.1K(95);B c},3j:o(J,fn){C 1a=c.1U(\'1a\');if(!1a||!1a[J])B c;C 1q=1a[J].3v.4m(fn);if(1q==-1)B c;C L=1a[J].3v.96(1q,1)[0];C I=1a[J].1V.96(1q,1)[0];C 47=N.3m.1f(J);if(47){if(47.cu)47.cu.1L(c,fn);J=47.3i||J}B(N.bf[J])?c.ct(J,I):c},79:o(1a){R(C P in 1a)c.1M(P,1a[P]);B c},9e:o(J){C 1a=c.1U(\'1a\');if(!1a)B c;if(!J){R(C 8Q in 1a)c.9e(8Q);1a=Y}1i if(1a[J]){3N(1a[J].3v[0])c.3j(J,1a[J].3v[0]);1a[J]=Y}B c},1u:o(J,1D,2S){C 1a=c.1U(\'1a\');if(!1a||!1a[J])B c;1a[J].3v.1b(o(fn){fn.4Q({\'V\':c,\'2S\':2S,\'O\':1D})()},c);B c},cs:o(1j,J){1j=$(1j);C 80=1j.1U(\'1a\');if(!80)B c;if(!J){R(C 8Q in 80)c.cs(1j,8Q)}1i if(80[J]){80[J].3v.1b(o(fn){c.1M(J,fn)},c)}B c}});N.bf={\'9D\':2,\'hu\':2,\'83\':2,\'5U\':2,\'jc\':2,\'6X\':2,\'aL\':2,\'aK\':2,\'aH\':2,\'5D\':2,\'dp\':2,\'jb\':2,\'cq\':2,\'jd\':2,\'je\':2,\'jf\':2,\'ja\':2,\'5N\':2,\'7e\':2,\'bc\':2,\'j9\':2,\'2w\':1,\'9F\':1,\'a0\':1,\'j4\':1,\'j3\':1,\'d4\':1,\'ba\':1,\'eu\':1,\'aZ\':1,\'3M\':1};(o(){C aD=o(P){C 3I=P.9K;if(!3I)B T;B($J(c)!=\'1n\'&&3I!=c&&3I.bx!=\'j5\'&&!c.cB(3I))};N.3m=K 1o({cj:{3i:\'aK\',3o:aD},cd:{3i:\'aH\',3o:aD},6X:{3i:(1p.1x.aE)?\'aL\':\'6X\'}})})();N.3n.5E={1g:o(5E){c.65(5E)}};N.3n.1Z={1g:o(1Z,cA){if(!cA){if(1Z==0){if(c.1F.59!=\'5B\')c.1F.59=\'5B\'}1i{if(c.1F.59!=\'aa\')c.1F.59=\'aa\'}}if(!c.9p||!c.9p.j6)c.1F.co=1;if(1p.1x.3h)c.1F.1N=(1Z==1)?\'\':\'8R(1Z=\'+1Z*4w+\')\';c.1F.1Z=1Z;c.5u(\'1Z\',1Z)},1f:o(){B c.1U(\'1Z\',1)}};N.1l({j8:o(I){B c.1g(\'1Z\',I,T)},j7:o(){B c.1f(\'1Z\')},2y:o(U,I){1W(U){Q\'1Z\':B c.1g(\'1Z\',8b(I));Q\'cz\':U=(1p.1x.3h)?\'cx\':\'cy\'}U=U.9z();if($J(I)!=\'1T\'){C 33=(N.8y.1f(U)||\'@\').5w(\' \');I=$4s(I).33(o(3U,i){if(!33[i])B\'\';B($J(3U)==\'5L\')?33[i].2Z(\'@\',1m.3l(3U)):3U}).5F(\' \')}1i if(I==2T(4V(I))){I=1m.3l(I)}c.1F[U]=I;B c},3K:o(U){1W(U){Q\'1Z\':B c.1f(\'1Z\');Q\'cz\':U=(1p.1x.3h)?\'cx\':\'cy\'}U=U.9z();C 1C=c.1F[U];if(!$3B(1C)){1C=[];R(C 1F in N.7m){if(U!=1F)4U;R(C s in N.7m[1F])1C.1K(c.3K(s));B 1C.5F(\' \')}1C=c.6D(U)}if(1C){1C=2T(1C);C 1G=1C.17(/jg?\\([\\d\\s,]+\\)/);if(1G)1C=1C.2Z(1G[0],1G[0].5S())}if(1p.1x.aR||(1p.1x.3h&&!$3B(5Q(1C)))){if(U.3k(/^(2C|2p)$/)){C 1V=(U==\'2p\')?[\'2n\',\'3Y\']:[\'2i\',\'31\'],3e=0;1V.1b(o(I){3e+=c.3K(\'51-\'+I+\'-2p\').3W()+c.3K(\'4X-\'+I).3W()},c);B c[\'2b\'+U.5j()]-3e+\'1w\'}if(1p.1x.aR&&2T(1C).3k(\'1w\'))B 1C;if(U.3k(/(51(.+)ce|2Q|4X)/))B\'dD\'}B 1C},65:o(5E){R(C 1F in 5E)c.2y(1F,5E[1F]);B c},ax:o(){C 1C={};1t.1b(O,o(L){1C[L]=c.3K(L)},c);B 1C}});N.8y=K 1o({2p:\'@1w\',2C:\'@1w\',2n:\'@1w\',2i:\'@1w\',31:\'@1w\',3Y:\'@1w\',jh:\'@1w\',jq:\'@1w\',jp:\'1P(@, @, @)\',jr:\'@1w @1w\',1G:\'1P(@, @, @)\',js:\'@1w\',jt:\'@1w\',jo:\'@1w\',jn:\'jj(@1w @1w @1w @1w)\',2Q:\'@1w @1w @1w @1w\',4X:\'@1w @1w @1w @1w\',51:\'@1w @ 1P(@, @, @) @1w @ 1P(@, @, @) @1w @ 1P(@, @, @)\',bP:\'@1w @1w @1w @1w\',bQ:\'@ @ @ @\',bN:\'1P(@, @, @) 1P(@, @, @) 1P(@, @, @) 1P(@, @, @)\',ji:\'@\',\'co\':\'@\',jk:\'@\',jl:\'@1w\',1Z:\'@\'});N.7m={\'2Q\':{},\'4X\':{},\'51\':{},\'bP\':{},\'bQ\':{},\'bN\':{}};[\'jm\',\'j2\',\'j1\',\'iI\'].1b(o(bO){C 4P=N.7m;C 6z=N.8y;[\'2Q\',\'4X\'].1b(o(1F){C bt=1F+bO;4P[1F][bt]=6z[bt]=\'@1w\'});C bd=\'51\'+bO;4P.51[bd]=6z[bd]=\'@1w @ 1P(@, @, @)\';C 8d=bd+\'ce\',8i=bd+\'iH\',8B=bd+\'44\';4P[bd]={};4P.bP[8d]=4P[bd][8d]=6z[8d]=\'@1w\';4P.bQ[8i]=4P[bd][8i]=6z[8i]=\'@\';4P.bN[8B]=4P[bd][8B]=6z[8B]=\'1P(@, @, @)\'});(o(){o $2u(el){B el.8V.4r()==\'2u\'};N.1l({9G:o(){if($2u(c))B T;B(N.6D(c,\'1J\')!=\'iJ\')},bi:o(){if($2u(c))B Y;if(!1p.1x.3h)B $(c.au);C el=c;3N((el=el.3b)){if(N.9G(el))B $(el)}B Y},52:o(){if($2u(c))B c.3X().52();B{x:c.6M,y:c.6L}},6Y:o(){if($2u(c))B c.3X().6Y();B{x:c.8w,y:c.7k}},5M:o(){if($2u(c))B c.3X().5M();B{x:c.8q,y:c.8t}},9M:o(x,y){if($2u(c))B c.3X().9M(x,y);c.8q=x;c.8t=y;B c},4t:o(7r){if($2u(c))B{x:0,y:0};C el=c,1J={x:0,y:0};3N(el){1J.x+=el.iK;1J.y+=el.iL;el=el.au}C bJ=(7r)?$(7r).4t():{x:0,y:0};B{x:1J.x-bJ.x,y:1J.y-bJ.y}},8a:o(G){if($2u(c))B c.3X().8a();C 1J=c.4t(G),3e=c.52();C 15={\'2i\':1J.y,\'2n\':1J.x,\'2p\':3e.x,\'2C\':3e.y};15.3Y=15.2n+15.2p;15.31=15.2i+15.2C;B 15},bT:o(){B c.4t(c.bi())},aw:o(15){B{2n:15.x-(c.6D(\'2Q-2n\').3W()||0),2i:15.y-(c.6D(\'2Q-2i\').3W()||0)}},1J:o(15){B c.65(c.aw(15))}})})();1B.1l([3Q,3t],{52:o(){C 2u=c.3A().2u,22=c.3A().7f;if(1p.1x.8g)B{x:c.iG,y:c.iF};B{x:22.ch,y:22.ci}},5M:o(){C 22=c.3A().7f;B{x:$5G(c.cb,22.8q),y:$5G(c.cc,22.8t)}},6Y:o(){C 22=c.3A().7f,2u=c.3A().2u;if(1p.1x.3h)B{x:1m.1R(22.ch,22.8w),y:1m.1R(22.ci,22.7k)};if(1p.1x.6I)B{x:2u.8w,y:2u.7k};B{x:22.8w,y:22.7k}},4t:o(){B{x:0,y:0}},8a:o(){C 3e=c.52();B{2i:0,2n:0,2C:3e.y,2p:3e.x,31:3e.y,3Y:3e.x}}});1B.1l([3Q,3t,N],{iA:o(){B c.52().y},iz:o(){B c.52().x},iB:o(){B c.5M().y},iC:o(){B c.5M().x},iE:o(){B c.6Y().y},iD:o(){B c.6Y().x},iM:o(){B c.4t().y},iN:o(){B c.4t().x}});1B.1l([N,3t],{5W:o(88,2a){C 26={};88=88.5w(\',\');C 19=[],j=88.W;C 4c=(j>1);R(C i=0;i<j;i++){C 2H=88[i],2G=[],bZ=[];2H=2H.6i().2Z(1A.cC,o(17){if(17.6a(2))17=17.6i();bZ.1K(17.6a(0));B\':)\'+17.6a(1)}).5w(\':)\');R(C k=0,l=2H.W;k<l;k++){C bR=1A.4R(2H[k]);if(!bR)B[];C 2d=1A.9R.bD(2G,bZ[k-1]||M,c,bR,26);if(!2d)1e;2G=2d}C 6o=1A.9R.bz(2G,c);19=(4c)?19.4S(6o):6o}B K 2E(19,{4c:4c,6c:!2a})}});3Q.1l({$E:o(2H){B c.1n.9s(2H)}});C 1A={cD:(/:([^-:(]+)[^:(]*(?:\\((["\']?)(.*?)\\2\\))?|\\[(\\w+)(?:([!*^$~|]?=)(["\']?)(.*?)\\6)?\\]|\\.[\\w-]+|#[\\w-]+|\\w+|\\*/g),cC:(/\\s*([+>~\\s])[a-jv-Z#.*\\s]/g)};1A.4R=o(2H){C 12={1I:\'*\',id:Y,6n:[],1S:[],6f:[]};2H=2H.2Z(1A.cD,o(4k){1W(4k.6a(0)){Q\'.\':12.6n.1K(4k.2W(1));1e;Q\'#\':12.id=4k.2W(1);1e;Q\'[\':12.1S.1K([O[4],O[5],O[7]]);1e;Q\':\':C 7S=1A.2Y.1f(O[1]);if(!7S){12.1S.1K([O[1],O[3]?\'=\':\'\',O[3]]);1e}C 4B={\'14\':O[1],\'2q\':7S,\'1h\':(7S.2q)?7S.2q(O[3]):O[3]};12.6f.1K(4B);1e;3T:12.1I=4k}B\'\'});B 12};1A.2Y=K 1o;1A.9W={bD:o(2G,4C,2J,12){C 2d=\'\';1W(4C){Q\' \':2d+=\'//\';1e;Q\'>\':2d+=\'/\';1e;Q\'+\':2d+=\'/8A-6k::*[1]/2U::\';1e;Q\'~\':2d+=\'/8A-6k::\';1e}2d+=(2J.iW)?\'bG:\'+12.1I:12.1I;C i;R(i=12.6f.W;i--;i){C 4B=12.6f[i];if(4B.2q&&4B.2q.2r)2d+=4B.2q.2r(4B.1h);1i 2d+=($3B(4B.1h))?\'[@\'+4B.14+\'="\'+4B.1h+\'"]\':\'[@\'+4B.14+\']\'}if(12.id)2d+=\'[@id="\'+12.id+\'"]\';R(i=12.6n.W;i--;i)2d+=\'[2e(4S(" ", @76, " "), " \'+12.6n[i]+\' ")]\';R(i=12.1S.W;i--;i){C 1O=12.1S[i];1W(1O[1]){Q\'=\':2d+=\'[@\'+1O[0]+\'="\'+1O[2]+\'"]\';1e;Q\'*=\':2d+=\'[2e(@\'+1O[0]+\', "\'+1O[2]+\'")]\';1e;Q\'^=\':2d+=\'[iY-iZ(@\'+1O[0]+\', "\'+1O[2]+\'")]\';1e;Q\'$=\':2d+=\'[cV(@\'+1O[0]+\', 1T-W(@\'+1O[0]+\') - \'+1O[2].W+\' + 1) = "\'+1O[2]+\'"]\';1e;Q\'!=\':2d+=\'[@\'+1O[0]+\'!="\'+1O[2]+\'"]\';1e;Q\'~=\':2d+=\'[2e(4S(" ", @\'+1O[0]+\', " "), " \'+1O[2]+\' ")]\';1e;Q\'|=\':2d+=\'[2e(4S("-", @\'+1O[0]+\', "-"), "-\'+1O[2]+\'-")]\';1e;3T:2d+=\'[@\'+1O[0]+\']\'}}2G.1K(2d);B 2G},bz:o(2G,2J){C 19=[];C 21=2J.3A();C 2r=21.cS(\'.//\'+2G.5F(\'\'),2J,1A.9W.cT,j0.iV,Y);R(C i=0,j=2r.iU;i<j;i++)19[i]=2r.iP(i);B 19},cT:o(bx){B(bx==\'bG\')?\'cY://dK.iO.iQ/iR/bG\':M}};1A.6j={bD:o(2G,4C,2J,12,26){C 4H=[];C 1I=12.1I;if(4C){C 6p={},2R,4j,1H,k,l;C 5g=o(2R){2R.29=2R.29||[1B.5i++];if(!6p[2R.29]&&1A.6j.17(2R,12,26)){6p[2R.29]=T;4H.1K(2R);B T}B M};R(C i=0,j=2G.W;i<j;i++){1H=2G[i];1W(4C){Q\' \':4j=1H.5r(1I);12.1I=M;R(k=0,l=4j.W;k<l;k++)5g(4j[k]);1e;Q\'>\':4j=1H.6t;R(k=0,l=4j.W;k<l;k++){if(4j[k].3R==1)5g(4j[k])}1e;Q\'+\':3N((1H=1H.5f)){if(1H.3R==1){5g(1H);1e}}1e;Q\'~\':3N((1H=1H.5f)){if(1H.3R==1&&5g(1H))1e}1e}}B 4H}if(12.id){el=2J.9l(12.id,T);12.id=M;B(el&&1A.6j.17(el,12,26))?[el]:M}1i{2G=2J.5r(1I);12.1I=M;R(C m=0,n=2G.W;m<n;m++){if(1A.6j.17(2G[m],12,26))4H.1K(2G[m])}}B 4H},bz:$O(0)};1A.6j.17=o(el,12,26){26=26||{};if(12.id&&12.id!=el.id)B M;if(12.1I&&12.1I!=\'*\'&&12.1I!=el.8V.4r())B M;C i;R(i=12.6n.W;i--;i){if(!el.2o||!el.2o.2e(12.6n[i],\' \'))B M}R(i=12.1S.W;i--;i){C 1O=12.1S[i];C 1C=N.2j.8P.1L(el,1O[0]);if(!1C)B M;if(!1O[1])4U;C 3o;1W(1O[1]){Q\'=\':3o=(1C==1O[2]);1e;Q\'*=\':3o=(1C.2e(1O[2]));1e;Q\'^=\':3o=(1C.7u(0,1O[2].W)==1O[2]);1e;Q\'$=\':3o=(1C.7u(1C.W-1O[2].W)==1O[2]);1e;Q\'!=\':3o=(1C!=1O[2]);1e;Q\'~=\':3o=1C.2e(1O[2],\' \');1e;Q\'|=\':3o=1C.2e(1O[2],\'-\')}if(!3o)B M}R(i=12.6f.W;i--;i){if(!12.6f[i].2q.1N.1L(el,12.6f[i].1h,26))B M}B T};1A.9R=(1p.8K.2r)?1A.9W:1A.6j;N.1l({17:o(2H){B(!2H||1A.6j.17(c,1A.4R(2H)))}});1A.2Y.ju={2r:o(){B\'[an(@8l)]\'},1N:o(){B!(c.8l)}};1A.2Y.1Y={2r:o(){B\'[an(8L())]\'},1N:o(){B!(c.cR||c.cQ||\'\').W}};1A.2Y.2e={2r:o(1h){B\'[2e(1r(), "\'+1h+\'")]\'},1N:o(1h){R(C i=c.6t.W;i--;i){C 2R=c.6t[i];if(2R.cI&&2R.3R==3&&2R.cH.2e(1h))B T}B M}};1A.2Y.4G={2q:o(1h){1h=(1h)?1h.17(/^([+-]?\\d*)?([jz]+)?([+-]?\\d*)?$/):[Y,1,\'n\',0];if(!1h)B M;C al=5Q(1h[1]);C a=($3B(al))?al:1;C 2D=1h[2]||M;C b=5Q(1h[3])||0;b=b-1;3N(b<1)b+=a;3N(b>=a)b-=a;1W(2D){Q\'n\':B{\'a\':a,\'b\':b,\'2D\':\'n\'};Q\'cJ\':B{\'a\':2,\'b\':0,\'2D\':\'n\'};Q\'cF\':B{\'a\':2,\'b\':1,\'2D\':\'n\'};Q\'7w\':B{\'a\':0,\'2D\':\'2I\'};Q\'6S\':B{\'2D\':\'6S\'};Q\'6Z\':B{\'2D\':\'6Z\'};3T:B{\'a\':(a-1),\'2D\':\'2I\'}}},2r:o(1h){1W(1h.2D){Q\'n\':B\'[61(ay-6k::*) 5A \'+1h.a+\' = \'+1h.b+\']\';Q\'6S\':B\'[61(8A-6k::*) = 0]\';Q\'6Z\':B\'[an(ay-6k::* 7W 8A-6k::*)]\';3T:B\'[61(ay-6k::*) = \'+1h.a+\']\'}},1N:o(1h,26){C 61=0,el=c;1W(1h.2D){Q\'n\':26.7l=26.7l||{};if(!26.7l[c.29]){C 4j=c.3b.6t;R(C i=0,l=4j.W;i<l;i++){C 2R=4j[i];if(2R.3R!=1)4U;2R.29=2R.29||[1B.5i++];26.7l[2R.29]=61++}}B(26.7l[c.29]%1h.a==1h.b);Q\'6S\':3N((el=el.5f)){if(el.3R==1)B M}B T;Q\'6Z\':C 9x=el;3N((9x=9x.7L)){if(9x.3R==1)B M}C 6b=el;3N((6b=6b.5f)){if(6b.3R==1)B M}B T;Q\'2I\':3N((el=el.7L)){if(el.3R==1&&++61>1h.a)B M}B T}B M}};1A.2Y.1z({\'cF\':{2q:o(){B{\'a\':2,\'b\':1,\'2D\':\'n\'}},2r:1A.2Y.4G.2r,1N:1A.2Y.4G.1N},\'cJ\':{2q:o(){B{\'a\':2,\'b\':0,\'2D\':\'n\'}},2r:1A.2Y.4G.2r,1N:1A.2Y.4G.1N},\'7w\':{2q:o(){B{\'a\':0,\'2D\':\'2I\'}},2r:1A.2Y.4G.2r,1N:1A.2Y.4G.1N},\'6S\':{2q:o(){B{\'2D\':\'6S\'}},2r:1A.2Y.4G.2r,1N:1A.2Y.4G.1N},\'6Z\':{2q:o(){B{\'2D\':\'6Z\'}},2r:1A.2Y.4G.2r,1N:1A.2Y.4G.1N}});N.3m.6G={a4:o(fn){if(1p.af)B fn.1L(c);C 2U=c,2k=c.3X(),21=c.3A();C 6G=o(){if(!O.2h.cK){O.2h.cK=T;fn.1L(2U)};B T};C cP=(1p.1x.6I)?[\'af\',\'6s\']:\'6s\';C 2c=o(2J){if(cP.2e(2J.7Y))B 6G();B M};if(21.7Y&&1p.1x.6I){(o(){if(!2c(21))O.2h.2S(50)})()}1i if(21.7Y&&1p.1x.3h){C 2l=$(\'ac\');if(!2l){C 6v=(2k.5C.jQ==\'jR:\')?\'//:\':\'58:jS(0)\';21.85(\'<2l id="ac" cM 6v="\'+6v+\'"></2l>\');2l=$(\'ac\')}if(!2c(2l))2l.1M(\'ba\',2c.4g(2l))}1i{2k.1M(\'2w\',6G);21.1M(\'d4\',6G)}B Y}};1X.1M(\'6G\',o(){1p.af=T});C 4f=K 1o({6w:o(15){1W($J(15)){Q\'1T\':B\'"\'+15.2Z(/[\\jO-\\jB\\\\"]/g,4f.$c5)+\'"\';Q\'23\':B\'[\'+2T(15.33(4f.6w).1N($3D))+\']\';Q\'1c\':Q\'5T\':C 1T=[];1o.1b(15,o(I,L){C 6q=4f.6w(I);if(6q)1T.1K(4f.6w(L)+\':\'+6q)});B\'{\'+2T(1T)+\'}\';Q\'5L\':Q\'jC\':B 2T(15);Q M:B\'Y\'}B Y},$c2:{\'\\b\':\'\\\\b\',\'\\t\':\'\\\\t\',\'\\n\':\'\\\\n\',\'\\f\':\'\\\\f\',\'\\r\':\'\\\\r\',\'"\':\'\\\\"\',\'\\\\\':\'\\\\\\\\\'},$c5:o(8I){B 4f.$c2[8I]||\'\\\\jD\'+1m.8F(8I.ar()/16).9J(16)+(8I.ar()%16).9J(16)},b4:o(1T,67){if($J(1T)!=\'1T\'||!1T.W)B Y;if(67&&!(/^[,:{}\\[\\]0-9.\\-+jA-u \\n\\r\\t]*$/).3k(1T.2Z(/\\\\./g,\'@\').2Z(/"[^"\\\\\\n\\r]*"/g,\'\')))B Y;B e8(\'(\'+1T+\')\')}});1B.1l([1o,1t,2T,4V],{jP:o(){B 4f.6w(c)}});C 4F=K 1E({4M:5l,F:{6l:M,8x:M,4p:M,67:M,1n:1n},1v:o(L,F){c.L=L;c.5m(F)},85:o(I){I=aB(I);if(c.F.8x)I+=\'; 8x=\'+c.F.8x;if(c.F.6l)I+=\'; 6l=\'+c.F.6l;if(c.F.4p){C 9o=K 7M();9o.jw(9o.c0()+c.F.4p*24*60*60*a8);I+=\'; jx=\'+9o.jy()}if(c.F.67)I+=\'; 67\';c.F.1n.c6=c.L+\'=\'+I;B c},b5:o(){C I=c.F.1n.c6.17(\'(?:^|;)\\\\s*\'+c.L.c4()+\'=([^;]*)\');B I?jE(I[1]):Y},5I:o(){K 4F(c.L,$3G(c.F,{4p:-1})).85(\'\');B c}});4F.1g=o(L,I,F){B K 4F(L,F).85(I)};4F.1f=o(L){B K 4F(L).b5()};4F.4d=o(L,F){B K 4F(L,F).5I()};C 44=K 1B({1v:o(1G,J){if(O.W>=3){J="1P";1G=1t.2W(O,0,3)}1i if(3q 1G==\'1T\'){if(1G.17(/1P/))1G=1G.5S().5V(T);1i if(1G.17(/2L/))1G=1G.7v();1i 1G=1G.5V(T)}J=J||\'1P\';1W(J){Q\'2L\':C 5x=1G;1G=1G.7v();1G.2L=5x;1e;Q\'4n\':1G=1G.5V(T);1e}1G.1P=1G.2W(0,3);1G.2L=1G.2L||1G.8j();1G.4n=1G.5S();B $1z(1G,c)}});44.1l({7R:o(){C 9j=1t.2W(O);C 8R=($J(9j.7P())==\'5L\')?9j.dT():50;C 1P=c.2W();9j.1b(o(1G){1G=K 44(1G);R(C i=0;i<3;i++)1P[i]=1m.3l((1P[i]/ 4w * (4w - 8R)) + (1G[i] /4w*8R))});B K 44(1P,\'1P\')},jF:o(){B K 44(c.33(o(I){B 6K-I}))},jL:o(I){B K 44([I,c.2L[1],c.2L[2]],\'2L\')},gM:o(9H){B K 44([c.2L[0],9H,c.2L[2]],\'2L\')},jM:o(9H){B K 44([c.2L[0],c.2L[1],9H],\'2L\')}});o $jN(r,g,b){B K 44([r,g,b],\'1P\')};o $jK(h,s,b){B K 44([h,s,b],\'2L\')};o $jJ(4n){B K 44(4n,\'4n\')};1t.1l({8j:o(){C 7z=c[0],7x=c[1],8m=c[2];C 4h,8p,bU;C 1R=1m.1R(7z,7x,8m),4l=1m.4l(7z,7x,8m);C 2f=1R-4l;bU=1R/6K;8p=(1R!=0)?2f/1R:0;if(8p==0){4h=0}1i{C bX=(1R-7z)/2f;C gr=(1R-7x)/2f;C br=(1R-8m)/2f;if(7z==1R)4h=br-gr;1i if(7x==1R)4h=2+bX-br;1i 4h=4+gr-bX;4h/=6;if(4h<0)4h++}B[1m.3l(4h*e3),1m.3l(8p*4w),1m.3l(bU*4w)]},7v:o(){C br=1m.3l(c[2]/4w*6K);if(c[1]==0){B[br,br,br]}1i{C 4h=c[0]%e3;C f=4h%60;C p=1m.3l((c[2]*(4w-c[1]))/jG*6K);C q=1m.3l((c[2]*(e4-c[1]*f))/e5*6K);C t=1m.3l((c[2]*(e4-c[1]*(60-f)))/e5*6K);1W(1m.8F(4h/60)){Q 0:B[br,t,p];Q 1:B[q,br,p];Q 2:B[p,br,t];Q 3:B[p,q,br];Q 4:B[t,p,br];Q 5:B[br,p,q]}}B M}});2T.1l({8j:o(){C 1P=c.17(/\\d{1,3}/g);B(1P)?2L.8j():Y},7v:o(){C 2L=c.17(/\\d{1,3}/g);B(2L)?2L.7v():Y}});C 4x=o(6l,F){if(!4x.7n)4x.ew();C 4O=\'jH\'+1B.5i++;F=$3G({id:4O,2C:1,2p:1,3J:Y,1k:{},12:{jI:\'iX\',ix:\'hm\',hl:\'9U\',hn:T},1a:{},7A:{}},F);C 12=F.12,7A=F.7A,id=F.id;C 1k=$1z({2C:F.2C,2p:F.2p},F.1k);4x.3m[4O]={};R(C P in F.1a){4x.3m[4O][P]=o(){F.1a[P].1L($(F.id))};7A[P]=\'4x.3m.\'+4O+\'.\'+P}12.ho=1o.7K(7A);if(1p.1x.3h){1k.hp=\'hk:hj-hf-he-hg-hh\';12.hi=6l}1i{1k.J=\'9E/x-hq-hr\';1k.2t=6l}C 57=\'<1c id="\'+F.id+\'"\';R(C U in 1k)57+=\' \'+U+\'="\'+1k[U]+\'"\';57+=\'>\';R(C 98 in 12)57+=\'<98 14="\'+98+\'" I="\'+12[98]+\'" />\';57+=\'</1c>\';B($(F.3J)||K N(\'5n\')).1g(\'22\',57).97};4x.1z({3m:{},hz:o(15,fn){C ed=15.hA(\'<e7 14="\'+fn+\'" hB="58">\'+hC(O,2)+\'</e7>\');B e8(ed)},hy:o(){if(!$3D(4x.a5)){C 3x;if(82.et&&82.hx.W){3x=82.et["ht hs"];if(3x&&3x.ea)3x=3x.ea}1i if(1p.1x.3h){3x=$4b(o(){B K 8u("es.es").iy("$3x")})}4x.a5=(3q 3x==\'1T\')?5Q(3x.17(/\\d+/)[0]):0}B 4x.a5},ew:o(){4x.7n=T;1X.1M(\'a0\',o(){hv=hw=$1Y});if(!1p.1x.3h)B;1X.1M(\'9F\',o(){1t.1b(1n.5r(\'1c\'),o(15){15.1F.5d=\'7H\';R(C p in 15){if(3q 15[p]==\'o\')15[p]=$1Y}})})}});C hd=K 1E({1v:o(){c.9C=1t.4E(O);c.1a={};c.5z={}},1M:o(J,fn){c.5z[J]=c.5z[J]||{};c.1a[J]=c.1a[J]||[];if(c.1a[J].2e(fn))B M;1i c.1a[J].1K(fn);c.9C.1b(o(4O,i){4O.1M(J,c.2c.V(c,[J,4O,i]))},c);B c},2c:o(J,4O,i){c.5z[J][i]=T;C 7J=c.9C.7J(o(4D,j){B c.5z[J][j]||M},c);if(!7J)B;c.5z[J]={};c.1a[J].1b(o(P){P.1L(c,c.9C,4O)},c)}});C 1s=K 1E({4M:[bH,3m,5l],F:{eM:50,55:M,4p:eF,2m:\'dY\',3C:o(p){B-(1m.bv(1m.bu*p)-1)/2}},1v:o(F){c.4g=c.4g||c;c.5m(F);c.F.4p=1s.eN[c.F.4p]||c.F.4p.3W();C 6Q=c.F.6Q;if(6Q===M)c.F.2m=\'28\'},2z:o(){C 3H=$3H();if(3H<c.3H+c.F.4p){C 2f=c.F.3C((3H-c.3H)/c.F.4p);c.1g(c.2V(c.1j,c.1y,2f))}1i{c.1g(c.2V(c.1j,c.1y,1));c.6s()}},1g:o(1d){B 1d},2V:o(1j,1y,2f){B 1s.2V(1j,1y,2f)},2c:o(){if(!c.2F)B T;1W(c.F.2m){Q\'28\':c.28();B T;Q\'3S\':c.3S(c.18.V(c,O));B M}B M},18:o(1j,1y){if(!c.2c(1j,1y))B c;c.1j=1j;c.1y=1y;c.3H=0;c.a9();c.5e();B c},6s:o(){B(!c.9v())?c:c.2O()},28:o(){B(!c.9v())?c:c.72()},5e:o(){B c.1u(\'5e\',c.4g)},2O:o(){B c.1u(\'2O\',c.4g).bV()},72:o(){B c.1u(\'72\',c.4g).eG()},hc:o(){c.9v();B c},gV:o(){c.a9();B c},9v:o(){if(!c.2F)B M;c.3H=$3H()-c.3H;c.2F=$77(c.2F);B T},a9:o(){if(c.2F)B M;c.3H=$3H()-c.3H;c.2F=c.2z.68(1m.3l(a8/c.F.eM),c);B T}});1s.2V=o(1j,1y,2f){B(1y-1j)*2f+1j};1s.eN={\'gU\':do,\'gW\':eF,\'gX\':a8};1s.5Y=K 1E({3F:1s,8M:o(G,U,1V){1V=$4s(1V);C ez=1V[1];if(!$3B(ez)){1V[1]=1V[0];1V[0]=G.3K(U)}C 3y=1V.33(c.4R);B{1j:3y[0],1y:3y[1]}},4R:o(I){I=$7c(I)();I=(3q I==\'1T\')?I.5w(\' \'):$4s(I);B I.33(o(3U){3U=2T(3U);C 4H=M;1s.5Y.ao.1b(o(2q,L){if(4H)B;C 3y=2q.4R(3U);if($3B(3y))4H={\'I\':3y,\'2q\':2q}});4H=4H||{I:3U,2q:1s.5Y.ao.2T};B 4H})},2V:o(1j,1y,2f){C 69=[];(1m.4l(1j.W,1y.W)).7y(o(i){69.1K({\'I\':1j[i].2q.2V(1j[i].I,1y[i].I,2f),\'2q\':1j[i].2q})});69.$3u={14:\'fx:9A:I\'};B 69},6F:o(I,55){if($J(I)!=\'fx:9A:I\')I=c.4R(I);C 5R=[];I.1b(o(4k){5R=5R.4S(4k.2q.6F(4k.I,55))});B 5R},8S:o(G,U,I){G.2y(U,c.6F(I,c.F.55))},8W:o(2H){C 1y={};1t.1b(1n.gY,o(at,j){C as=at.as||at.gT;1t.1b(as,o(71,i){if(!71.1F||!71.e0||!71.e0.3k(\'^\'+2H+\'$\'))B;N.8y.1b(o(I,1F){if(!71.1F[1F]||N.7m[1F])B;I=71.1F[1F];1y[1F]=(I.3k(/^1P/))?I.5S():I})})});B 1y}});1s.5Y.ao=K 1o({44:{4R:o(I){if(I.17(/^#[0-9a-f]{3,6}$/i))B I.5V(T);B((I=I.17(/(\\d+),\\s*(\\d+),\\s*(\\d+)/)))?[I[1],I[2],I[3]]:M},2V:o(1j,1y,2f){B 1j.33(o(I,i){B 1m.3l(1s.2V(1j[i],1y[i],2f))})},6F:o(I){B I.33(4V)}},4V:{4R:o(I){B 8b(I)},2V:o(1j,1y,2f){B 1s.2V(1j,1y,2f)},6F:o(I,55){B(55)?I+55:I}},2T:{4R:$7c(M),2V:$O(1),6F:$O(0)}});1s.9P=K 1E({3F:1s.5Y,1v:o(G,U,F){c.G=c.4g=$(G);c.U=U;O.2h.1Q(F)},1g:o(1d){c.8S(c.G,c.U,1d);B c},18:o(){C aA=1t.2W(O);if(!c.2c(aA))B c;C 3y=c.8M(c.G,c.U,aA);B O.2h.1Q(3y.1j,3y.1y)}});N.3n.2K={1g:o(F){C 2K=c.1U(\'2K\');if(2K)2K.28();B c.5u(\'2K\',K 1s.9P(c,Y,$1z({2m:\'28\'},F)))},1f:o(U,F){if(F||!c.1U(\'2K\'))c.1g(\'2K\',F);C 2K=c.1U(\'2K\');2K.U=U;B 2K}};N.1l({2K:o(U){C 2K=c.1f(\'2K\',U);2K.18.3E(2K,1t.2W(O,1));B c},5o:o(4o){C 5o=c.1f(\'2K\',\'1Z\');4o=$5G(4o,\'6P\');1W(4o){Q\'in\':5o.18(1);1e;Q\'90\':5o.18(0);1e;Q\'4z\':5o.1g(1);1e;Q\'4W\':5o.1g(0);1e;Q\'6P\':5o.18((o(){B(c.3K(\'59\')==\'5B\')?1:0}).V(c));1e;3T:5o.18.3E(5o,O)}B c},gS:o(18,2x){if(!2x){C 1F=c.3K(\'dq-1G\');2x=(1F==\'9U\')?\'#gO\':1F}c.1f(\'2K\',\'dq-1G\').18(18||\'#gN\',2x);B c},7F:o(U,F){B K 1s.9P(c,U,F)}});1s.8Z=K 1E({3F:1s.5Y,1v:o(G,F){c.G=c.4g=$(G);O.2h.1Q(F)},1g:o(1d){if(3q 1d==\'1T\')1d=c.8W(1d);R(C p in 1d)c.8S(c.G,p,1d[p]);B c},2V:o(1j,1y,2f){C 1d={};R(C p in 1j)1d[p]=O.2h.1Q(1j[p],1y[p],2f);B 1d},18:o(1k){if(!c.2c(1k))B c;if(3q 1k==\'1T\')1k=c.8W(1k);C 1j={},1y={};R(C p in 1k){C 3y=c.8M(c.G,p,1k[p]);1j[p]=3y.1j;1y[p]=3y.1y}B O.2h.1Q(1j,1y)}});N.3n.4u={1g:o(F){C 4u=c.1U(\'4u\');if(4u)4u.28();B c.5u(\'4u\',K 1s.8Z(c,$1z({2m:\'28\'},F)))},1f:o(F){if(F||!c.1U(\'4u\'))c.1g(\'4u\',F);B c.1U(\'4u\')}};N.1l({4u:o(2A){c.1f(\'4u\').18(2A);B c},5k:o(F){B K 1s.8Z(c,F)}});1s.dt=K 1E({3F:1s,F:{2M:\'aj\'},1v:o(G,F){c.1M(\'2O\',o(){c.6T=(c.3g[\'2b\'+c.4N.5j()]!=0);if(c.6T){c.3g.2y(c.4N,\'gQ\');if(1p.1x.8g)c.G.7p().3c(c.3g)}},T);c.G=c.4g=$(G);O.2h.1Q(F);C 3g=c.G.1U(\'3g\');c.3g=3g||K N(\'5n\',{5E:$1z(c.G.ax(\'2Q\',\'1J\'),{\'de\':\'5B\'})}).dv(c.G);c.G.5u(\'3g\',c.3g).2y(\'2Q\',0);c.1d=[];c.6T=T},aj:o(){c.2Q=\'2Q-2i\';c.4N=\'2C\';c.2b=c.G.6L},ak:o(){c.2Q=\'2Q-2n\';c.4N=\'2p\';c.2b=c.G.6M},1g:o(1d){c.G.2y(c.2Q,1d[0]);c.3g.2y(c.4N,1d[1]);B c},2V:o(1j,1y,2f){C 1d=[];(2).7y(o(i){1d[i]=1s.2V(1j[i],1y[i],2f)});B 1d},18:o(4o,2M){if(!c.2c(4o,2M))B c;c[2M||c.F.2M]();C 2Q=c.G.3K(c.2Q).3W();C 4N=c.3g.3K(c.4N).3W();C aP=[[2Q,4N],[0,c.2b]];C bB=[[2Q,4N],[-c.2b,0]];C 18;1W(4o){Q\'in\':18=aP;1e;Q\'90\':18=bB;1e;Q\'6P\':18=(c.3g[\'2b\'+c.4N.5j()]==0)?aP:bB}B O.2h.1Q(18[0],18[1])},gR:o(2M){B c.18(\'in\',2M)},gZ:o(2M){B c.18(\'90\',2M)},4W:o(2M){c[2M||c.F.2M]();c.6T=M;B c.1g([-c.2b,0])},4z:o(2M){c[2M||c.F.2M]();c.6T=T;B c.1g([0,c.2b])},6P:o(2M){B c.18(\'6P\',2M)}});N.3n.3s={1g:o(F){C 3s=c.1U(\'3s\');if(3s)3s.28();B c.5u(\'3s\',K 1s.dt(c,$1z({2m:\'28\'},F)))},1f:o(F){if(F||!c.1U(\'3s\'))c.1g(\'3s\',F);B c.1U(\'3s\')}};N.1l({3s:o(4o){4o=4o||\'6P\';C 3s=c.1f(\'3s\');1W(4o){Q\'4W\':3s.4W();1e;Q\'4z\':3s.4z();1e;3T:3s.18(4o)}B c}});1s.dL=K 1E({3F:1s,F:{2b:{\'x\':0,\'y\':0},d9:T},1v:o(G,F){c.G=c.4g=$(G);O.2h.1Q(F);C 28=c.28.V(c,M);if($J(c.G)!=\'G\')c.G=$(c.G.3A().2u);C by=c.G;if(c.F.d9){c.1M(\'5e\',o(){by.1M(\'6X\',28)},T);c.1M(\'2O\',o(){by.3j(\'6X\',28)},T)}},1g:o(){C 1d=1t.4E(O);c.G.9M(1d[0],1d[1])},2V:o(1j,1y,2f){C 1d=[];(2).7y(o(i){1d.1K(1s.2V(1j[i],1y[i],2f))});B 1d},18:o(x,y){if(!c.2c(x,y))B c;C dc=c.G.52(),d7=c.G.6Y(),3M=c.G.5M(),1V={\'x\':x,\'y\':y};R(C z in 1V){C 1R=d7[z]-dc[z];if($3B(1V[z]))1V[z]=($J(1V[z])==\'5L\')?1V[z].2g(0,1R):1R;1i 1V[z]=3M[z];1V[z]+=c.F.2b[z]}B O.2h.1Q([3M.x,3M.y],[1V.x,1V.y])},h0:o(){B c.18(M,0)},h8:o(){B c.18(0,M)},h9:o(){B c.18(\'3Y\',M)},ha:o(){B c.18(M,\'31\')},bA:o(el){C 1J=$(el).4t(c.G);B c.18(1J.x,1J.y)}});(o(){C 5x=1s.2j.1v;1s.2j.1v=o(F){5x.1L(c,F);C 4L=c.F.3C;if(3q 4L==\'1T\'&&(4L=4L.5w(\':\'))){C 3i=1s.6W;3i=3i[4L[0]]||3i[4L[0].5j()];if(4L[1])3i=3i[\'hb\'+4L[1].5j()+(4L[2]?4L[2].5j():\'\')];c.F.3C=3i}}})();1s.bs=o(3C,12){12=$4s(12);B $1z(3C,{h7:o(1q){B 3C(1q,12)},h6:o(1q){B 1-3C(1-1q,12)},h2:o(1q){B(1q<=0.5)?3C(2*1q,12)/2:(2-3C(2*(1-1q),12))/2}})};1s.6W=K 1o({h1:$O(0)});1s.6W.1z=o(bn){R(C 3C in bn)1s.6W[3C]=K 1s.bs(bn[3C])};1s.6W.1z({h3:o(p,x){B 1m.4J(p,x[0]||6)},h4:o(p){B 1m.4J(2,8*(p-1))},h5:o(p){B 1-1m.bl(1m.dQ(p))},hD:o(p){B 1-1m.bl((1-p)*1m.bu/2)},hE:o(p,x){x=x[0]||1.ie;B 1m.4J(p,2)*((x+1)*p-x)},ig:o(p){C I;R(C a=0,b=1;1;a+=b,b/=2){if(p>=(7-4*a)/11){I=-1m.4J((11-6*a-11*p)/4,2)+b*b;1e}}B I},ih:o(p,x){B 1m.4J(2,10*--p)*1m.bv(20*p*1m.bu*(x[0]||1)/3)}});[\'ii\',\'ic\',\'ib\',\'i7\'].1b(o(3C,i){1s.6W[3C]=K 1s.bs(o(p){B 1m.4J(p,[i+2])})});C 54=K 1E({4M:[bH,3m,5l],F:{3p:\'\',2t:\'\',66:{},aQ:T,2s:\'7i\',2m:\'dY\',6V:Y,dH:T,dI:T,7D:\'i6-8\',9n:M,dE:M},aN:o(){B(1X.8J)?K 8J():((1X.8u)?K 8u(\'i8.i9\'):M)},1v:o(F){if(!(c.36=c.aN()))B;c.5m(F);c.F.6V=c.F.6V||c.6V;c.66=K 1o(c.F.66).1z({\'X-ia-ij\':\'8J\',\'e1\':\'1r/58, 1r/22, 9E/64, 1r/64, */*\'})},bk:o(){if(c.36.7Y!=4||!c.6E)B;c.6E=M;c.6R=0;$4b(o(){c.6R=c.36.6R},c);if(c.F.6V.1L(c,c.6R)){c.63={1r:c.36.ik,64:c.36.it};c.9I(c.63.1r,c.63.64)}1i{c.63={1r:Y,64:Y};c.dF()}c.36.aO=$1Y},6V:o(){B((c.6R>=iu)&&(c.6R<iv))},dz:o(1r){if(c.F.dE||(/(iw|is)2l/).3k(c.dG(\'dJ-J\')))B $9q(1r);B 1r.aU(c.F.9n)},9I:o(1r,64){c.7h(c.dz(1r),64)},7h:o(){c.1u(\'2O\',O).1u(\'7h\',O).bV()},dF:o(){c.bS()},bS:o(){c.1u(\'2O\').1u(\'bS\',c.36)},ir:o(14,I){c.66.1g(14,I);B c},dG:o(14){B $4b(o(){B c.im(14)},c.36)||Y},2c:o(){if(!c.6E)B T;1W(c.F.2m){Q\'28\':c.28();B T;Q\'3S\':c.3S(c.35.V(c,O));B M}B M},35:o(F){if(!c.2c(F))B c;c.6E=T;C J=$J(F);if(J==\'1T\'||J==\'G\')F={2t:F};C 5x=c.F;F=$1z({2t:5x.2t,3p:5x.3p,2s:5x.2s},F);C 2t=F.2t,3p=F.3p,2s=F.2s;1W($J(2t)){Q\'G\':2t=$(2t).7K();1e;Q\'1c\':Q\'5T\':2t=1o.7K(2t)}if(c.F.dH&&[\'il\',\'3r\'].2e(2s)){C 8D=\'8D=\'+2s;2t=(2t)?8D+\'&\'+2t:8D;2s=\'7i\'}if(c.F.dI&&2s==\'7i\'){C 7D=(c.F.7D)?\'; io=\'+c.F.7D:\'\';c.66.1g(\'dJ-J\',\'9E/x-dK-ip-iq\'+7D)}if(2t&&2s==\'1f\'){3p=3p+(3p.2e(\'?\')?\'&\':\'?\')+2t;2t=Y}c.36.6T(2s.8f(),3p,c.F.aQ);c.36.aO=c.bk.V(c);c.66.1b(o(I,L){4b{c.36.i5(L,I)}dN(e){c.1u(\'i4\',[e,L,I])}},c);c.1u(\'hN\');c.36.35(2t);if(!c.F.aQ)c.bk();B c},28:o(){if(!c.6E)B c;c.6E=M;c.36.aZ();c.36.aO=$1Y;c.36=c.aN();c.1u(\'72\');B c}});(o(){C 4e={};[\'1f\',\'7i\',\'hM\',\'hO\',\'hP\',\'hQ\'].1b(o(2s){4e[2s]=o(){C 12=1t.2m(O,{3p:2T.J,2t:$3D});B c.35($1z(12,{2s:2s.4r()}))}});54.1l(4e)})();N.3n.35={1f:o(F){if(F||!c.1U(\'35\'))c.1g(\'35\',F);B c.1U(\'35\')},1g:o(F){C 35=c.1U(\'35\');if(35)35.28();B c.5u(\'35\',K 54($1z({2t:c,2m:\'28\',2s:c.1f(\'2s\')||\'7i\',3p:c.1f(\'hL\')},F)))}};N.1l({35:o(3p){C aS=c.1f(\'35\');aS.35({2t:c,3p:3p||aS.F.3p});B c}});54.da=K 1E({3F:54,F:{9N:M,9n:T,1N:M},dS:o(1r){C 17=1r.17(/<2u[^>]*>([\\s\\S]*?)<\\/2u>/i);B(17)?17[1]:1r},9I:o(1r){C 70=c.F,3Z=c.63;3Z.22=c.dS(1r).aU(o(2l){3Z.58=2l});C 8L=K N(\'5n\',{22:3Z.22});3Z.19=8L.5W(\'*\');3Z.aF=(70.1N)?3Z.19.dy(70.1N):$A(8L.6t).1N(o(el){B($J(el)!=\'df\')});if(70.9N)$(70.9N).1Y().dh(3Z.aF);if(70.9n)$9q(3Z.58);c.7h(3Z.aF,3Z.19,3Z.22,3Z.58)}});N.3n.2w={1f:o(F){if(F||!c.1U(\'2w\'))c.1g(\'2w\',F);B c.1U(\'2w\')},1g:o(F){C 2w=c.1U(\'2w\');if(2w)2w.28();B c.5u(\'2w\',K 54.da($1z({2m:\'28\',9N:c,2s:\'1f\'},F)))}};N.1l({2w:o(){c.1f(\'2w\').35(1t.2m(O,{2t:62.J,3p:2T.J}));B c}});54.4f=K 1E({3F:54,F:{67:T},1v:o(F){O.2h.1Q(F);c.66.1z({\'e1\':\'9E/6q\',\'X-54\':\'4f\'})},9I:o(1r){c.63.6q=4f.b4(1r,c.F.67);c.7h(c.63.6q,1r)}});C 6m=K 1E({4M:[3m,5l],F:{6e:6,55:\'1w\',5X:M,2g:M,5b:M,4Y:{x:\'2n\',y:\'2i\'}},1v:o(){C 12=1t.2m(O,{\'F\':62.J,\'G\':$3D});c.G=$(12.G);c.1n=c.G.3A();c.5m(12.F||{});C bb=$J(c.F.5b);c.b9=(bb==\'23\'||bb==\'7d\')?$$(c.F.5b):$(c.F.5b)||c.G;c.4T={\'1d\':{},\'1q\':{}};c.I={\'18\':{},\'1d\':{}};c.8U=(1p.1x.3h)?\'dp\':\'5U\';c.3P={18:c.18.V(c),2c:c.2c.V(c),4a:c.4a.V(c),4q:c.4q.V(c),28:c.28.V(c),8T:$7c(M)};c.aW()},aW:o(){c.b9.1M(\'5U\',c.3P.18);B c},am:o(){c.b9.3j(\'5U\',c.3P.18);B c},18:o(P){c.1u(\'hK\',c.G);c.4T.18=P.2X;C 2g=c.F.2g;c.2g={\'x\':[],\'y\':[]};R(C z in c.F.4Y){if(!c.F.4Y[z])4U;c.I.1d[z]=c.G.3K(c.F.4Y[z]).3W();c.4T.1q[z]=P.2X[z]-c.I.1d[z];if(2g&&2g[z]){R(C i=2;i--;i){if($3B(2g[z][i]))c.2g[z][i]=$7c(2g[z][i])()}}}if($J(c.F.5X)==\'5L\')c.F.5X={\'x\':c.F.5X,\'y\':c.F.5X};c.1n.79({5D:c.3P.2c,83:c.3P.28});c.1n.1M(c.8U,c.3P.8T)},2c:o(P){C ex=1m.3l(1m.eD(1m.4J(P.2X.x-c.4T.18.x,2)+1m.4J(P.2X.y-c.4T.18.y,2)));if(ex>c.F.6e){c.28();c.1n.79({5D:c.3P.4a,83:c.3P.4q});c.1u(\'5e\',c.G).1u(\'hF\',c.G)}},4a:o(P){c.4T.1d=P.2X;R(C z in c.F.4Y){if(!c.F.4Y[z])4U;c.I.1d[z]=c.4T.1d[z]-c.4T.1q[z];if(c.F.2g&&c.2g[z]){if($3B(c.2g[z][1])&&(c.I.1d[z]>c.2g[z][1])){c.I.1d[z]=c.2g[z][1]}1i if($3B(c.2g[z][0])&&(c.I.1d[z]<c.2g[z][0])){c.I.1d[z]=c.2g[z][0]}}if(c.F.5X[z])c.I.1d[z]-=(c.I.1d[z]%c.F.5X[z]);c.G.2y(c.F.4Y[z],c.I.1d[z]+c.F.55)}c.1u(\'9Z\',c.G)},28:o(P){c.1n.3j(\'5D\',c.3P.2c);c.1n.3j(\'83\',c.3P.28);if(P){c.1n.3j(c.8U,c.3P.8T);c.1u(\'72\',c.G)}},4q:o(P){c.1n.3j(c.8U,c.3P.8T);c.1n.3j(\'5D\',c.3P.4a);c.1n.3j(\'83\',c.3P.4q);if(P)c.1u(\'2O\',c.G)}});N.1l({hH:o(F){B K 6m(c,$3G({4Y:{\'x\':\'2p\',\'y\':\'2C\'}},F))}});6m.en=K 1E({3F:6m,F:{4I:[],3J:M},1v:o(G,F){O.2h.1Q(G,F);c.4I=$$(c.F.4I);c.3J=$(c.F.3J);C 1J=(c.G.9G())?c.G.3K(\'1J\'):\'az\';c.G.1J(c.G.bT()).2y(\'1J\',1J)},18:o(P){if(c.42){c.42.1u(\'eH\',[c.G,c]);c.42=Y}if(c.3J){C el=c.G,bh=c.3J,89=bh.8a(el.bi()),6A={},6r={};[\'2i\',\'3Y\',\'31\',\'2n\'].1b(o(86){6A[86]=bh.3K(\'4X-\'+86).3W();6r[86]=el.3K(\'2Q-\'+86).3W()},c);C 2p=el.6M+6r.2n+6r.3Y,2C=el.6L+6r.2i+6r.31;C x=[89.2n+6A.2n,89.3Y-6A.3Y-2p];C y=[89.2i+6A.2i,89.31-6A.31-2C];c.F.2g={x:x,y:y}}O.2h.1Q(P)},eL:o(el){el=el.8a();C 1d=c.4T.1d;B(1d.x>el.2n&&1d.x<el.3Y&&1d.y<el.31&&1d.y>el.2i)},b7:o(){C 42=c.4I.1N(c.eL,c).7P();if(c.42!=42){if(c.42)c.42.1u(\'eH\',[c.G,c]);c.42=42?42.1u(\'7b\',[c.G,c]):Y}},4a:o(P){O.2h.1Q(P);if(c.4I.W)c.b7()},4q:o(P){c.b7();if(c.42)c.42.1u(\'hI\',[c.G,c]);1i c.G.1u(\'hJ\',c);B O.2h.1Q(P)}});N.1l({dm:o(F){B K 6m.en(c,F)}});1A.2Y.4j={2q:o(1h){1h=(1h)?1h.17(/^([-+]?\\d*)?([\\-+:])?([-+]?\\d*)?$/):[Y,0,M,0];if(!1h)B M;1h[1]=5Q(1h[1])||0;C aY=5Q(1h[3]);1h[3]=($3B(aY))?aY:0;1W(1h[2]){Q\'-\':Q\'+\':Q\':\':B{\'a\':1h[1],\'b\':1h[3],\'2D\':1h[2]};3T:B{\'a\':1h[1],\'b\':0,\'2D\':\'2I\'}}},2r:o(1h){C 34=\'\';C 2B=\'61(../2R::*)\';C a=1h.a+\' + \'+((1h.a<0)?2B:0);C b=1h.b+\' + \'+((1h.b<0)?2B:0);C 1q=\'1J()\';1W(1h.2D){Q\'-\':b=\'((\'+a+\' - \'+b+\') 5A (\'+2B+\'))\';a+=\' + 1\';b+=\' + 1\';34=\'(\'+b+\' < 1 9d (\'+1q+\' <= \'+a+\' 7W \'+1q+\' >= (\'+b+\' + \'+2B+\')\'+\')) 7W (\'+1q+\' <= \'+a+\' 9d \'+1q+\' >= \'+b+\')\';1e;Q\'+\':b=\'((\'+a+\' + \'+b+\') 5A ( \'+2B+\'))\';Q\':\':a+=\' + 1\';b+=\' + 1\';34=\'(\'+b+\' < \'+a+\' 9d (\'+1q+\' >= \'+a+\' 7W \'+1q+\' <= \'+b+\')) 7W (\'+1q+\' >= \'+a+\' 9d \'+1q+\' <= \'+b+\')\';1e;3T:34=(a+\' + 1\')}B\'[\'+34+\']\'},1N:o(1h,26){26.i=26.i||0;26.56=26.56||c.3b.6t;26.2B=26.2B||26.56.W;C i=26.i;C 2B=26.2B;C 56=26.56;C 34=M;C a=1h.a+((1h.a<0)?2B:0);C b=1h.b+((1h.b<0)?2B:0);1W(1h.2D){Q\'-\':b=(a-b)%2B;34=(b<0)?(i<=a||i>=(b+2B)):(i<=a&&i>=b);1e;Q\'+\':b=(b+a)%2B;Q\':\':34=(b<a)?(i>=a||i<=b):(i>=a&&i<=b);1e;3T:34=(56[a]==c)}26.i++;B 34}};1o.4F=K 1E({3F:4F,F:{ee:T},1v:o(14,F){c.1Q(14,F);c.2w()},ek:o(){C I=4f.6w(c.5T);if(I.W>hR)B M;if(I.W==2)c.5I();1i c.85(I);B T},2w:o(){c.5T=K 1o(4f.b4(c.b5(),T));B c}});(o(){C 4e={};1o.1b(1o.2j,o(2s,14){4e[14]=o(){C I=2s.3E(c.5T,O);if(c.F.ee)c.ek();B I}});1o.4F.1l(4e)})();C i0=K 1E({4M:[3m,5l],F:{6e:4,5b:M,aX:M,9S:M,dl:0.7,dn:0.3},1v:o(3f,F){c.5m(F);c.19=[];c.3f=[];c.9w=T;c.aJ($$($(3f)||3f));if(c.F.aX)c.7F=K 1s.8Z(Y,$3G({4p:do,2m:\'28\'},c.F.aX))},aW:o(){c.aJ(c.3f);B c},am:o(){c.3f=c.d6(c.3f);B c},dj:o(){1t.4E(O).1b(o(G){c.19.1K(G);C 18=G.1U(\'7a:18\',c.18.av(c,G));C 4y=G.1U(\'7a:4y\',c.4y.V(c,G));(c.F.5b?G.9s(c.F.5b)||G:G).1M(\'5U\',18);G.1M(\'7b\',4y)},c);B c},aJ:o(){1t.4E(O).1b(o(2N){c.3f.1K(2N);c.dj(2N.84());2N.1M(\'7b\',2N.1U(\'7a:4y\',c.4y.V(c,[2N,\'db\'])))},c);B c},dX:o(){C 19=[];1t.4E(O).1b(o(G){19.1K(G);c.19.4d(G);C 18=G.1U(\'7a:18\');C 4y=G.1U(\'7a:4y\');(c.F.5b?G.9s(c.F.5b)||G:G).3j(\'5U\',18);G.3j(\'7b\',4y)},c);B 19},d6:o(){C 3f=[];1t.4E(O).1b(o(2N){3f.1K(2N);c.3f.4d(2N);c.dX(2N.84());2N.3j(\'7b\',2N.1U(\'7a:4y\'))},c);B 3f},dk:o(G){B G.3L(T).65({\'2Q\':\'dD\',\'1J\':\'az\',\'59\':\'5B\'}).3c(c.2N).1J(G.bT())},aq:o(){C 4I=c.2N.84();if(!c.F.9S)4I=c.3f.4S(4I).4d(c.2N);B 4I.4d(c.3L).4d(c.G)},4y:o(G,49){if(49){c.2N=G;c.4a.4I=c.aq()}49=49||(c.G.d8().2e(G)?\'aG\':\'aC\');c.G.3c(G,49);c.1u(\'hZ\',[c.G,c.3L])},18:o(P,G){if(!c.9w)B;c.9w=M;c.G=G;c.1Z=G.1f(\'1Z\');c.2N=G.8n();c.3L=c.dk(G);c.4a=c.3L.dm({6e:c.F.6e,3J:c.F.9S&&c.3L.8n(),4I:c.aq(),5e:o(){P.4q();c.3L.1g(\'1Z\',c.F.dl);c.G.1g(\'1Z\',c.F.dn);c.1u(\'5e\',[c.G,c.3L])}.V(c),72:c.7e.V(c),2O:c.2x.V(c)});c.4a.18(P)},2x:o(){c.G.1g(\'1Z\',c.1Z);c.4a.am();if(c.7F){C a6=c.G.ax(\'2p\',\'2C\');C 1q=c.3L.aw(c.G.4t(c.3L.au),c.3L.8n().9G());c.7F.G=c.3L;c.7F.18({\'2i\':1q.2i,\'2n\':1q.2n,\'2p\':a6.2p,\'2C\':a6.2C,\'1Z\':0.25}).3S(c.7e.V(c))}1i{c.7e()}},7e:o(){c.9w=T;c.3L.e6();c.1u(\'2O\',c.G)},hY:o(2I,c1){C ai=c.3f.33(o(2N){B 2N.84().33(c1||o(G,2I){B G.1f(\'id\')},c)},c);if(c.3f.W==1)2I=0;B $3B(2I)&&2I>=0&&2I<c.3f.W?ai[2I]:ai}});C hU=K 1E({4M:[3m,5l],F:{eO:o(5q){5q.2y(\'59\',\'aa\')},dw:o(5q){5q.2y(\'59\',\'5B\')},bI:30,d1:4w,d0:4w,2o:\'hT\',7G:{\'x\':16,\'y\':16},7n:M},1v:o(19,F){c.5m(F);19=$$(19);c.1n=(19.W)?19[0].9y:1n;c.5t=K N(\'5n\',{\'76\':c.F.2o+\'-5q\',\'5E\':{\'1J\':\'az\',\'2i\':\'0\',\'2n\':\'0\',\'59\':\'5B\'}},c.1n).3c(c.1n.2u);c.3g=K N(\'5n\').3c(c.5t);19.1b(c.57,c)},57:o(el){el.$1S.5H=(el.75&&el.1f(\'1I\')==\'a\')?el.75.2Z(\'cY://\',\'\'):(el.dd||M);if(el.74){C 9u=el.74.5w(\'::\');if(9u.W>1){el.$1S.5H=9u[0].6i();el.$1S.7o=9u[1].6i()}1i{el.$1S.7o=el.74}el.7T(\'74\')}1i{el.$1S.7o=M}if(el.$1S.5H&&el.$1S.5H.W>c.F.bI)el.$1S.5H=el.$1S.5H.7u(0,c.F.bI-1)+"&hV;";el.1M(\'cj\',o(P){c.18(el);if(!c.F.7n)c.9Y(P);1i c.1J(el)}.V(c));if(!c.F.7n)el.1M(\'5D\',c.9Y.V(c));C 2x=c.2x.V(c);el.1M(\'cd\',2x)},18:o(el){c.3g.1Y();if(el.$1S.5H){c.74=K N(\'cp\').3c(K N(\'5n\',{\'76\':c.F.2o+\'-74\'}).3c(c.3g)).1g(\'22\',el.$1S.5H)}if(el.$1S.7o){c.1r=K N(\'cp\').3c(K N(\'5n\',{\'76\':c.F.2o+\'-1r\'}).3c(c.3g)).1g(\'22\',el.$1S.7o)}$77(c.2F);c.2F=c.4z.2S(c.F.d1,c)},2x:o(P){$77(c.2F);c.2F=c.4W.2S(c.F.d0,c)},1J:o(G){C 1q=G.4t();c.5t.65({\'2n\':1q.x+c.F.7G.x,\'2i\':1q.y+c.F.7G.y})},9Y:o(P){C 21=c.1n.52();C 3M=c.1n.5M();C 5q={\'x\':c.5t.6M,\'y\':c.5t.6L};C 2P={\'x\':\'2n\',\'y\':\'2i\'};R(C z in 2P){C 1q=P.2X[z]+c.F.7G[z];if((1q+5q[z]-3M[z])>21[z])1q=P.2X[z]-c.F.7G[z]-5q[z];c.5t.2y(2P[z],1q)}},4z:o(){if(c.F.ej)c.2F=c.4W.2S(c.F.ej,c);c.1u(\'eO\',[c.5t])},4W:o(){c.1u(\'dw\',[c.5t])}});C hW=K 1E({3F:1s.dL,1v:o(F,G){G=$(G);C 21=G.3A(),2k=G.3X();O.2h.1Q(21,F);c.7U=(c.F.7U)?$$(c.F.7U):$$(21.7U);C 5C=2k.5C.75.17(/^[^#]*/)[0]+\'#\';c.7U.1b(o(2m){if(2m.75.4m(5C)!=0)B;C 5p=2m.75.7u(5C.W);if(5p&&$(5p))c.eJ(2m,5p)},c);if(!1p.1x.8g)c.1M(\'2O\',o(){2k.5C.5T=c.5p},T)},eJ:o(2m,5p){2m.1M(\'9D\',o(P){c.5p=5p;c.bA(5p);P.4q()}.V(c))}});C hX=K 1E({4M:[3m,5l],F:{7I:20,b6:1,6C:o(x,y){c.G.9M(x,y)}},1v:o(G,F){c.5m(F);c.G=$(G);c.8h=($J(c.G)!=\'G\')?$(c.G.3A().2u):c.G;c.2F=Y},18:o(){c.bw=c.cG.V(c);c.8h.1M(\'5D\',c.bw)},4q:o(){c.8h.3j(\'5D\',c.bw);c.2F=$77(c.2F)},cG:o(P){c.2X=(c.8h.1f(\'1I\')==\'2u\')?P.9t:P.2X;if(!c.2F)c.2F=c.3M.68(50,c)},3M:o(){C 3e=c.G.52(),3M=c.G.5M(),1q=c.G.4t(),5N={\'x\':0,\'y\':0};R(C z in c.2X){if(c.2X[z]<(c.F.7I+1q[z])&&3M[z]!=0)5N[z]=(c.2X[z]-c.F.7I-1q[z])*c.F.b6;1i if(c.2X[z]+c.F.7I>(3e[z]+1q[z])&&3e[z]+3e[z]!=3M[z])5N[z]=(c.2X[z]-3e[z]+c.F.7I-1q[z])*c.F.b6}if(5N.y||5N.x)c.1u(\'6C\',[3M.x+5N.x,3M.y+5N.y])}});C eK=K 1o({58:o(5c,1k){1k=$1z({4A:$1Y,1n:1n,2c:$7c(T)},1k);C 2l=K N(\'2l\',{\'6v\':5c,\'J\':\'1r/58\'});C 2w=1k.4A.V(2l),2c=1k.2c,21=1k.1n;3r 1k.4A;3r 1k.2c;3r 1k.1n;2l.79({2w:2w,ba:o(){if(c.7Y==\'6s\')2w()}}).b3(1k);if(1p.1x.8g)C 5z=(o(){if(!$4b(2c))B;$77(5z);2w()}).68(50);B 2l.3c(21.78)},9A:o(5c,1k){B K N(\'2m\',$3G({\'dd\':\'i3\',\'i2\':\'i1\',\'J\':\'1r/9A\',\'75\':5c},1k)).3c(1n.78)},3d:o(5c,1k){1k=$3G({\'4A\':$1Y,\'eB\':$1Y,\'e9\':$1Y},1k);C 3d=K hS();C G=$(3d)||K N(\'b0\');[\'2w\',\'aZ\',\'eu\'].1b(o(14){C J=\'94\'+14;C P=1k[J];3r 1k[J];3d[J]=o(){if(!3d)B;if(!G.3b){G.2p=3d.2p;G.2C=3d.2C}3d=3d.4A=3d.eB=3d.e9=Y;P.2S(1,G,G);G.1u(14,G,1)}});3d.6v=G.6v=5c;if(3d&&3d.6s)3d.4A.2S(1);B G.b3(1k)},9g:o(5Z,F){F=$3G({2O:$1Y,er:$1Y},F);if(!5Z.1K)5Z=[5Z];C 9g=[];C 9k=0;5Z.1b(o(5c){C b0=K eK.3d(5c,{\'4A\':o(){F.er.1L(c,9k,5Z.4m(5c));9k++;if(9k==5Z.W)F.2O()}});9g.1K(b0)});B K 2E(9g)}});1s.2E=K 1E({3F:1s.5Y,1v:o(19,F){c.19=c.4g=$$(19);O.2h.1Q(F)},2V:o(1j,1y,2f){C 1d={};R(C i in 1j){C 81=1j[i],91=1y[i],7Z=1d[i]={};R(C p in 81)7Z[p]=O.2h.1Q(81[p],91[p],2f)}B 1d},1g:o(1d){R(C i in 1d){C 7Z=1d[i];R(C p in 7Z)c.8S(c.19[i],p,7Z[p])}B c},18:o(15){if(!c.2c(15))B c;C 1j={},1y={};R(C i in 15){C bg=15[i],81=1j[i]={},91=1y[i]={};R(C p in bg){C 3y=c.8M(c.19[i],p,bg[p]);81[p]=3y.1j;91[p]=3y.1y}}B O.2h.1Q(1j,1y)}});C hG=K 1E({3F:1s.2E,F:{5d:0,4z:M,2C:T,2p:M,1Z:T,8s:M,8r:M,6Q:M,93:M},1v:o(){C 12=1t.2m(O,{\'3J\':N.J,\'F\':62.J,\'4i\':$3D,\'19\':$3D});O.2h.1Q(12.19,12.F);c.4i=$$(12.4i);c.3J=$(12.3J);c.4Z=-1;if(c.F.93)c.F.6Q=T;if($3B(c.F.4z)){c.F.5d=M;c.4Z=c.F.4z}if(c.F.18){c.F.5d=M;c.F.4z=M}c.5k={};if(c.F.1Z)c.5k.1Z=\'dV\';if(c.F.2p)c.5k.2p=c.F.8r?\'dZ\':\'6M\';if(c.F.2C)c.5k.2C=c.F.8s?\'dx\':\'7k\';R(C i=0,l=c.4i.W;i<l;i++)c.du(c.4i[i],c.19[i]);c.19.1b(o(el,i){if(c.F.4z===i){c.1u(\'dr\',[c.4i[i],el])}1i{R(C fx in c.5k)el.2y(fx,0)}},c);if($3B(c.F.5d))c.5d(c.F.5d)},du:o(53,G,1q){53=$(53);G=$(G);C 3k=c.4i.2e(53);C 2B=c.4i.W;c.4i.34(53);c.19.34(G);if(2B&&(!3k||1q)){1q=$5G(1q,2B-1);53.3c(c.4i[1q],\'aG\');G.3c(53,\'aC\')}1i if(c.3J&&!3k){53.3c(c.3J);G.3c(c.3J)}C dO=c.4i.4m(53);53.1M(\'9D\',c.5d.V(c,dO));if(c.F.2C)G.65({\'4X-2i\':0,\'51-2i\':\'7H\',\'4X-31\':0,\'51-31\':\'7H\'});if(c.F.2p)G.65({\'4X-2n\':0,\'51-2n\':\'7H\',\'4X-3Y\':0,\'51-3Y\':\'7H\'});G.dV=1;if(c.F.8r)G.dZ=c.F.8r;if(c.F.8s)G.dx=c.F.8s;G.2y(\'de\',\'5B\');if(!3k){R(C fx in c.5k)G.2y(fx,0)}B c},5d:o(2I){2I=($J(2I)==\'G\')?c.19.4m(2I):2I;if((c.2F&&c.F.6Q)||(2I===c.4Z&&!c.F.93))B c;c.4Z=2I;C 15={};c.19.1b(o(el,i){15[i]={};C 4W=(i!=2I)||(c.F.93&&(el.6L>0));c.1u(4W?\'gP\':\'dr\',[c.4i[i],el]);R(C fx in c.5k)15[i][fx]=4W?0:el[c.5k[fx]]},c);B c.18(15)}});C bq=K 1E({F:{6C:1E.1Y,2O:1E.1Y,8o:o(1q){c.bC.2y(c.p,1q)},18:0,2x:4w,2b:0,em:20,2M:\'ak\'},1v:o(el,5s,F,48){c.G=$(el);c.5s=$(5s);c.5m(F);c.b1=-1;c.be=-1;c.2z=-1;c.F.8k=c.F.2x-c.F.18;if(48!=Y)c.48=$(48);1i c.G.1M(\'5U\',c.cU.av(c));C 5A,2b;1W(c.F.2M){Q\'ak\':c.z=\'x\';c.p=\'2n\';5A={\'x\':\'2n\',\'y\':M};2b=\'6M\';1e;Q\'aj\':c.z=\'y\';c.p=\'2i\';5A={\'x\':M,\'y\':\'2i\'};2b=\'6L\'}c.1R=c.G[2b]-c.5s[2b]+(c.F.2b*2);c.ck=c.5s[2b]/2;c.cW=c.G[\'1f\'+c.p.5j()].V(c.G);c.5s.2y(\'1J\',\'7r\').2y(c.p,-c.F.2b);if(48!=Y){c.b2=-1;c.aM=-1;c.3O=c.F.2x;c.48.2y(\'1J\',\'7r\').2y(c.p,+c.1R-c.F.2b).2y(\'31\',c.F.em)}C 8X={};8X[c.z]=[-c.F.2b,c.1R-c.F.2b];c.4a=K 6m(c.5s,{2g:8X,4Y:5A,6e:0,5e:o(){c.6d()}.V(c),9Z:o(){c.6d()}.V(c),2O:o(){c.6d();c.2x()}.V(c)});if(48!=Y){c.cl=K 6m(c.48,{2g:8X,4Y:5A,6e:0,5e:o(){c.6d(1)}.V(c),9Z:o(){c.6d(1)}.V(c),2O:o(){c.6d(1);c.2x()}.V(c)})}if(c.F.1v)c.F.1v.1L(c)},iS:o(cZ){c.2z=cZ.2g(c.F.18,c.F.2x);c.6y();c.2x();c.bC=c.5s;c.1u(\'8o\',c.ah(c.2z));B c},iT:o(d2){c.3O=d2.2g(c.F.18,c.F.2x);c.6y(1);c.2x();c.bC=c.48;c.1u(\'8o\',c.ah(c.3O));B c},cU:o(P){C 1J=P.2X[c.z]-c.cW()-c.ck;1J=1J.2g(-c.F.2b,c.1R-c.F.2b);c.2z=c.99(1J);c.6y();c.2x();c.1u(\'8o\',1J)},6d:o(92){if(92==Y){c.2z=c.99(c.4a.I.1d[c.z]);c.6y()}1i{c.3O=c.99(c.cl.I.1d[c.z]);c.6y(1)}},6y:o(92){if(92==Y){if(c.b1!=c.2z){c.b1=c.2z}}1i{if(c.b2!=c.3O){c.b2=c.3O}}if(c.48!=Y){if(c.2z<c.3O)c.1u(\'6C\',{8C:c.2z,8H:c.3O});1i c.1u(\'6C\',{8C:c.3O,8H:c.2z})}1i{c.1u(\'6C\',c.2z)}},2x:o(){if(c.be!==c.2z||(c.48!=Y&&c.aM!=c.3O)){c.be=c.2z;if(c.48!=Y){c.aM=c.3O;if(c.2z<c.3O)c.1u(\'2O\',{8C:c.2z+\'\',8H:c.3O+\'\'});1i c.1u(\'2O\',{8C:c.3O+\'\',8H:c.2z+\'\'})}1i{c.1u(\'2O\',c.2z+\'\')}}},99:o(1J){B 1m.3l((1J+c.F.2b)/c.1R*c.F.8k)+c.F.18},ah:o(2z){B(c.1R*2z/c.F.8k)-(c.1R*c.F.18/c.F.8k)-c.F.2b}});bq.1l(K 3m);bq.1l(K 5l);',62,1233,'||||||||||||this||||||||||||function|||||||||||||return|var|||options|element||value|type|new|key|false|Element|arguments|event|case|for||true|property|bind|length||null||||params||name|obj||match|start|elements|events|each|object|now|break|get|set|argument|else|from|properties|implement|Math|document|Hash|Browser|pos|text|Fx|Array|fireEvent|initialize|px|Engine|to|extend|Selectors|Native|result|args|Class|style|color|item|tag|position|push|call|addEvent|filter|bits|rgb|parent|max|attributes|string|retrieve|values|switch|window|empty|opacity||doc|html|array|||Local||cancel|uid|nocash|offset|check|temp|contains|delta|limit|callee|top|prototype|win|script|link|left|className|width|parser|xpath|method|data|body|attribute|load|end|setStyle|step|props|len|height|special|Elements|timer|items|selector|index|context|tween|hsb|mode|list|onComplete|prop|margin|child|delay|String|self|compute|slice|page|Pseudo|replace||bottom||map|include|send|xhr||||iframe|parentNode|inject|image|size|lists|wrapper|trident|base|removeEvent|test|round|Events|Properties|condition|url|typeof|delete|slide|Document|family|keys|EA|version|parsed|klass|getDocument|chk|transition|defined|apply|Extends|merge|time|related|container|getStyle|clone|scroll|while|maxstep|bound|Window|nodeType|chain|default|val|option|toInt|getWindow|right|res||walk|overed|Garbage|Color|results||custom|maxknob|where|drag|try|ddup|remove|methods|JSON|pass|hue|togglers|children|bit|min|indexOf|hex|how|duration|stop|toLowerCase|splat|getPosition|morph|Prototype|100|Swiff|insert|show|onload|pseudo|separator|current|flatten|Cookie|nth|found|droppables|pow|hasOwnProperty|trans|Implements|layout|instance|Short|create|parse|concat|mouse|continue|Number|hide|padding|modifiers|previous||border|getSize|toggler|Request|unit|all|build|javascript|visibility|objects|handle|source|display|onStart|nextSibling|add|alias|UID|capitalize|effects|Options|setOptions|div|fade|anchor|tip|getElementsByTagName|knob|toolTip|store|Inserters|split|old|target|checker|mod|hidden|location|mousemove|styles|join|pick|myTitle|erase|legacy|iterable|number|getScroll|change|notrash|returns|parseInt|returned|rgbToHex|hash|mousedown|hexToRgb|getElements|grid|CSS|sources||count|Object|response|xml|setStyles|headers|secure|periodical|computed|charAt|next|cash|draggedKnob|snap|pseudos|storage|clean|trim|Filter|sibling|path|Drag|classes|partial|uniques|json|ems|complete|childNodes|Event|src|encode|addListener|checkStep|All|cps|Bools|onChange|getComputedStyle|running|serve|domready|code|webkit|queryString|255|offsetHeight|offsetWidth|afterImplement|generics|toggle|wait|status|last|open|unlink|isSuccess|Transitions|mousewheel|getScrollSize|only|opts|rule|onCancel|unlinked|title|href|class|clear|head|addEvents|sortables|over|lambda|collection|reset|documentElement|EAB|onSuccess|post|Property|scrollHeight|Positions|ShortStyles|fixed|myText|dispose|precision|relative|Props|Attributes|substr|hsbToRgb|first|green|times|red|vars|grab|existing|encoding|appendChild|effect|offsets|none|area|every|toQueryString|previousSibling|Date|hasValue|realType|getLast|extended|mix|xparser|removeProperty|links|protect|or|host|readyState|iNow|fevents|iFrom|navigator|mouseup|getChildren|write|pad|tags|selectors|ccoo|getCoordinates|parseFloat|checked|bdw|keyOf|toUpperCase|webkit419|listener|bds|rgbToHsb|steps|disabled|blue|getParent|onTick|saturation|scrollLeft|fixedWidth|fixedHeight|scrollTop|ActiveXObject|Storage|scrollWidth|domain|Styles|fKey|following|bdc|minpos|_method|scripts|floor|regex|maxpos|chr|XMLHttpRequest|Features|node|prepare|attempt|forEach|getProperty|evType|alpha|render|eventStop|selection|tagName|search|lim|kill|Morph|out|iTo|mx|alwaysHide|on|defn|splice|firstChild|param|toStep||op|RegExp|and|removeEvents|mp|images|shift|internal|colors|counter|getElementById|constructor|evalScripts|date|currentStyle|exec|stopPropagation|getElement|client|dual|stopTimer|idle|prev|ownerDocument|camelCase|css|associate|instances|click|application|unload|positioned|percent|success|toString|relatedTarget|preventDefault|scrollTo|update|force|Tween|nativeEvent|Method|constrain|onFrameLoad|transparent|rightClick|XPath|wheel|locate|onDrag|beforeunload||||onAdd|pluginVersion|dim|typize|1000|startTimer|visible|konstructor|ie_domready|fns|genericize|loaded|setProperty|toPosition|serial|vertical|horizontal|inta|detach|not|Parsers|contentWindow|getDroppables|charCodeAt|rules|sheet|offsetParent|bindWithEvent|computePosition|getStyles|preceding|absolute|fromto|encodeURIComponent|after|checkRelatedTarget|gecko|tree|before|mouseout|Camels|addLists|mouseover|DOMMouseScroll|maxPreviousEnd|getXHR|onreadystatechange|caseIn|async|presto|sender|EAC|stripScripts|hasClass|attach|revert|int1|abort|img|previousChange|maxPreviousChange|setProperties|decode|read|velocity|checkDroppables|original|handles|readystatechange|htype|select||previousEnd|NativeEvents|iProps|cont|getOffsetParent|Platform|onStateChange|sin|insertBefore|transitions|cssText|Key|Slider||Transition|sd|PI|cos|coord|prefix|stopper|getItems|toElement|caseOut|moveKnob|getParam|pageX|pageY|xhtml|Chain|maxTitleChars|rpos|Function|innerHTML|random|borderColor|direction|borderWidth|borderStyle|sel|onFailure|getRelativePosition|brightness|callChain|dflt|rr|createElement|separators|getTime|modifier|specialChars|addClass|escapeRegExp|replaceChars|cookie|removeClass|hyphenate|attr|input|pageXOffset|pageYOffset|mouseleave|Width|selected|multiple|clientWidth|clientHeight|mouseenter|half|maxdrag|addEventListener|removeEventListener|zoom|span|keydown|trash|cloneEvents|removeListener|onRemove|ignored|collect|styleFloat|cssFloat|float|novisibility|hasChild|sRegExp|regExp|removeAttribute|even|getCoords|nodeValue|nodeName|odd|done|setAttribute|defer|isBool|Boolean|states|textContent|innerText|evaluate|resolver|clickedElement|substring|getPos|getParents|http|stepMin|hideDelay|showDelay|stepMax|getFirst|DOMContentLoaded|klasses|removeLists|scrollSize|getAllPrevious|wheelStops|HTML|inside|offsetSize|rel|overflow|whitespace|clientX|adopt|clientY|addItems|getClone|cloneOpacity|makeDraggable|elementOpacity|250|selectstart|background|onActive|which|Slide|addSection|wraps|onHide|fullHeight|filterBy|processScripts|lastIndexOf|valueOf|interval|0px|evalResponse|failure|getHeader|emulation|urlEncoded|Content|www|Scroll|math|catch|idx|some|acos|wheelDelta|processHTML|pop|toFloat|fullOpacity|execScript|removeItems|ignore|fullWidth|selectorText|Accept|run|360|6000|600000|destroy|invoke|eval|onerror|description|kp|sp|rs|autoSave|defaultView|IFrame|Constructors|multi|timeout|save||knobheight|Move|parentWindow|newElement|textnode|onProgress|ShockwaveFlash|plugins|error|undefined|fix|distance|contents|values1|Keys|onabort|replaces|sqrt|removeChild|500|clearChain|leave|getClean|useLink|Asset|checkAgainst|fps|Durations|onShow|newTextNode|accessKey|cellSpacing|BackgroundImageCache|cellPadding|noresize|readonly|DOMElement|execCommand|getRandom|2dev|trident4|taintEnabled|CollectGarbage|nix|noshade|clearInterval|getLength|other|clearTimeout|unknown|EMBED|mac|OBJECT|embed|linux|air|runtime|readOnly|rowSpan|maxLength|frameBorder|isFinite|tabIndex||useMap|platform|opera|420|419|getBoxObjectFor|colSpan|getHTML|alt||ctrlKey|control|altKey|meta|up|enter|metaKey|removeProperties|shiftKey|getNext|getAllNext|reverse|button|native|fromElement|getPrevious|down|esc|textarea|frames|IFrame_|getPropertyValue|appendText|replaceChild|cloneNode|toggleClass|getAttribute|getProperties|backspace|space|MooTools|tab|cancelBubble|createTextNode|returnValue|sort|lastChild|atan2|detachEvent|atan|attachEvent|ceil|tan|log|exp|eliminate||asin|compact|nowrap|ismap|htmlFor|setTimeout|abs|setInterval||2b2|unshift|detail|120|checkbox|keyCode|111|menu|fromCharCode|getLastChild|radio|srcElement|setHTML|has|getTag|setText|getKeys|getText|getValues|declare|setSaturation|ffff88|ffffff|onBackground|auto|slideIn|highlight|cssRules|short|resume|normal|long|styleSheets|slideOut|toTop|linear|easeInOut|Pow|Expo|Circ|easeOut|easeIn|toLeft|toRight|toBottom|ease|pause|Group|11cf|AE6D|96B8|444553540000|movie|D27CDB6E|clsid|wMode|always|swLiveConnect|flashVars|classid|shockwave|flash|Flash|Shockwave|dblclick|__flash_unloadHandler|__flash_savedUnloadHandler|mimeTypes|getVersion|remote|CallFunction|returntype|__flash__argumentsToXML|Sine|Back|onSnap|Accordion|makeResizable|drop|emptydrop|onBeforeStart|action|GET|onRequest|POST|PUT|DELETE|4096|Image|tool|Tips|hellip|SmoothScroll|Scroller|serialize|onSort|Sortables|screen|media|stylesheet|onException|setRequestHeader|utf|Quint|Microsoft|XMLHTTP|Requested|Quart|Cubic||618||Bounce|Elastic|Quad|With|responseText|put|getResponseHeader||charset|form|urlencoded|setHeader|java|responseXML|200|300|ecma|allowScriptAccess|GetVariable|getWidth|getHeight|getScrollTop|getScrollLeft|getScrollWidth|getScrollHeight|innerHeight|innerWidth|Style|Left|static|offsetLeft|offsetTop|getTop|getLeft|w3|snapshotItem|org|1999|setMin|setMax|snapshotLength|UNORDERED_NODE_SNAPSHOT_TYPE|namespaceURI|high|starts|with|XPathResult|Bottom|Right|move|resize|xul|hasLayout|getOpacity|setOpacity|submit|blur|selectend|contextmenu|keypress|keyup|focus|rgba|maxWidth|zIndex|rect|fontWeight|textIndent|Top|clip|lineHeight|backgroundColor|maxHeight|backgroundPosition|fontSize|letterSpacing|enabled|zA|setTime|expires|toGMTString|devon|Eaeflnr|x1f|boolean|u00|decodeURIComponent|invert|10000|Swiff_|quality|HEX|HSB|setHue|setBrightness|RGB|x00|toJSON|protocol|https|void'.split('|'),0,{}))
/*
Class: Slider
        Creates a slider with two elements: a knob and a container. Returns the values.
Note:
        The Slider requires an XHTML doctype.
Arguments:
        element - the knob container
        knob - the handle
        options - see Options below
        maxknob - an optional maximum slider handle
Options:
		start - the minimum value for your slider.
		end - the maximum value for your slider.
        mode - either 'horizontal' or 'vertical'. defaults to horizontal.
        offset - relative offset for knob position. default to 0.
        knobheight - positions the max slider knob
		snap - whether the slider will slide in steps 
		numsteps - number of slide steps 
Events:
        onChange - a function to fire when the value changes.
        onComplete - a function to fire when you're done dragging.
        onTick - optionally, you can alter the onTick behavior, for example displaying an effect of the knob moving to the desired position.
                Passes as parameter the new position.
*/
var Slider = new Class({
	options: {
		onChange: Class.empty,
		onComplete: Class.empty,
		onTick: function(pos){
			this.moveKnob.setStyle(this.p, pos);			
		},
		start: 0,
		end: 100,
		offset: 0,
		knobheight: 20,
		knobwidth: 14,
		mode: 'horizontal',
		clip_w:0, 
		clip_l:0,
		isinit:true,
		snap: false,
		range: false,
		numsteps:null
	},
    initialize: function(el, knob,bkg, options, maxknob) {
		this.setOptions(options);
		this.element = $(el);
		this.knob = $(knob);
		this.previousChange = this.previousEnd = this.step = -1;
		this.bkg = $(bkg);
		if(this.options.steps==null){
			this.options.steps = this.options.end - this.options.start;
		}
		if(maxknob!=null)
			this.maxknob = $(maxknob);
		//else
		//	this.element.addEvent('mousedown', this.clickedElement.bindWithEvent(this));
		var mod, offset;
		switch(this.options.mode){
			case 'horizontal':
				this.z = 'x';
				this.p = 'left';
				mod = {'x': 'left', 'y': false};
				offset = 'offsetWidth';
				break;
			case 'vertical':
				this.z = 'y';
				this.p = 'top';
				mod = {'x': false, 'y': 'top'};
				offset = 'offsetHeight';
		}
		this.max = this.element[offset] - this.knob[offset] + (this.options.offset * 2);
		this.half = this.knob[offset]/2;
		this.full = this.element[offset] - this.knob[offset] + (this.options.offset * 2);
		this.min = $chk(this.options.range[0]) ? this.options.range[0] : 0;
		this.getPos = this.element['get' + this.p.capitalize()].bind(this.element);
		this.knob.setStyle('position', 'relative').setStyle(this.p, - this.options.offset);

		this.range = this.max - this.min;
		this.steps = this.options.steps || this.full;
		this.stepSize = Math.abs(this.range) / this.steps;
		this.stepWidth = this.stepSize * this.full / Math.abs(this.range) ;
		

		if(maxknob != null) {
			this.maxPreviousChange = -1;
			this.maxPreviousEnd = -1;
			this.maxstep = this.options.end;
			this.maxknob.setStyle('position', 'relative').setStyle(this.p, + this.max - this.options.offset).setStyle('bottom', this.options.knobheight);
		}
		var lim = {};
		//status = this.z
		lim[this.z] = [- this.options.offset, this.max - this.options.offset];
		//lim[this.z] = [100, this.max - this.options.offset];

		this.drag = new Drag(this.knob, {
			limit: lim,
			modifiers: mod,
			snap: 0,
			onStart: function(){
					this.draggedKnob();
			}.bind(this),
			onDrag: function(){
					this.draggedKnob();
			}.bind(this),
			onComplete: function(){
					this.draggedKnob();
					this.end();
			}.bind(this)
		});
		if(maxknob != null) {  
			this.maxdrag = new Drag(this.maxknob, {
				limit: lim,
				modifiers: mod,
				snap: 0, 
				onStart: function(){
					this.draggedKnob(1);
				}.bind(this),
				onDrag: function(){
					this.draggedKnob(1);
				}.bind(this),
				onComplete: function(){
					this.draggedKnob(1);
					this.end();
				}.bind(this)
			});		
		}

		if (this.options.snap) {
			//this.drag.options.grid = Math.ceil(this.stepWidth);
			this.drag.options.grid = (this.full)/this.options.numsteps ;
			this.drag.options.limit[this.z][1] = this.full;
			//this.drag.options.grid = this.drag.options.grid - (this.knob[offset]/this.options.numsteps);
			status = "GRID - " + this.drag.options.grid  + "  , full = " + this.full// DEBUG

		}
		if (this.options.initialize) this.options.initialize.call(this);
    },
	setMin: function(stepMin){
		this.step = stepMin.limit(this.options.start, this.options.end);
		this.checkStep();
		this.end();
		this.moveKnob = this.knob;
		this.bkg.style.clip = "rect(0px "+  (parseInt(this.toPosition(this.step)) +3) + "px 10px 0px)";
		status =this.bkg.style.clip + "  vl= " + parseInt(this.toPosition(this.step)) ; //Debug
		this.fireEvent('onTick', this.toPosition(this.step));
		return this;
	},
	setMax: function(stepMax){
		this.maxstep = stepMax.limit(this.options.start, this.options.end);
		this.checkStep(1);
		this.end();
		this.moveKnob = this.maxknob;
		var w= Math.abs(this.toPosition(this.step)- this.toPosition(this.maxstep)) + 3 ;
		var r = parseInt(this.clip_l + w); 
		this.bkg.style.clip = "rect(0px "+  r + "px 10px "+ this.clip_l + "px)";

		this.fireEvent('onTick', this.toPosition(this.maxstep));
		// For Init Only 
		if(this.options.isinit){
			var lim = {}; var mi,mx;
			mi = - this.options.offset; 
			mx= parseInt(this.maxknob.getStyle('left')) - this.options.offset-4 ;
			lim[this.z] = [mi, mx];
			this.drag.options.limit = lim;
			this.options.isinit = false;
		}
		return this; 
	},
	clickedElement: function(event){
		var position = event.page[this.z] - this.getPos() - this.half;
		position = position.limit(-this.options.offset, this.max -this.options.offset);

		this.step = this.toStep(position);

		//this.moveKnob = this.knob;
		this.bkg.style.clip = "rect(0px "+  (parseInt(this.toPosition(this.step)) +3) + "px 10px 0px)"  
		//status =this.bkg.style.clip; //Debug
		this.checkStep();
		this.end();
		this.fireEvent('onTick', position);
	},

	draggedKnob: function(mx){
		var lim = {}; var mi,mx;
		if(mx==null) {
			this.step = this.toStep(this.drag.value.now[this.z]);	 
			this.checkStep();
		}else {
			this.maxstep = this.toStep(this.maxdrag.value.now[this.z]); 
			this.checkStep(1);
		}
	},
	checkStep: function(mx){
		var lim = {}; var mi,mx;
		var limm = {};
		if(mx==null) {if (this.previousChange != this.step){this.previousChange = this.step;}}
		else {if (this.maxPreviousChange != this.maxstep){this.maxPreviousChange = this.maxstep;}}

		if(this.maxknob!=null) {

			mi = - this.options.offset; 
			mx= parseInt(this.maxknob.getStyle('left')) - this.options.offset-4 ;
			//mx= parseInt(this.maxknob.getStyle('left')) - this.options.offset ;
			lim[this.z] = [mi, mx];
			this.drag.options.limit = lim;
		

			mi = parseInt(this.knob.getStyle('left'))-this.options.offset+22; 
			//mi = parseInt(this.knob.getStyle('left'))-this.options.offset; 
			
			mx= this.max - this.options.offset;
			limm[this.z] = [mi, mx];
			this.maxdrag.options.limit = limm; 

			if(this.step < this.maxstep){
				this.fireEvent('onChange', { minpos: this.step, maxpos: this.maxstep });
				//this.clip_l = parseInt(this.knob.getStyle('left'));
			}
			else{
				this.fireEvent('onChange', { minpos: this.maxstep, maxpos: this.step });
				//this.clip_l = (parseInt(this.maxknob.getStyle('left')) + 10) ;
			}	
			this.clip_l = parseInt(this.knob.getStyle('left')) + 10;
			//var w = Math.abs(parseInt(this.knob.getStyle('left')) - parseInt(this.maxknob.getStyle('left'))) + 3;	
			var w = Math.abs(parseInt(this.knob.getStyle('left')) - parseInt(this.maxknob.getStyle('left')));
			//if(w > 3) w = w+3;
			
			var r = parseInt(this.clip_l + w); 
			this.bkg.style.clip = "rect(0px "+  r + "px 10px "+ this.clip_l + "px)"  
			//status =this.bkg.style.clip  + " w= " + w //Debug

		}else {  
			this.fireEvent('onChange', this.step);
			this.bkg.style.clip = "rect(0px "+  (parseInt(this.drag.value.now[this.z]) +3)  + "px 10px 0px)"  

		}
	},
	end: function(){
		if (this.previousEnd !== this.step || (this.maxknob != null && this.maxPreviousEnd != this.maxstep)) {
			this.previousEnd = this.step;
			if(this.maxknob != null) {
				this.maxPreviousEnd = this.maxstep;
				if(this.step < this.maxstep)
					this.fireEvent('onComplete', { minpos: this.step + '', maxpos: this.maxstep + '' });
				else    
					this.fireEvent('onComplete', { minpos: this.maxstep + '', maxpos: this.step + '' });
			}else{  
				this.fireEvent('onComplete', this.step + '');
			}
		}
	},
	
	toStep: function(position){
		return Math.round((position + this.options.offset) / this.max * this.options.steps) + this.options.start;
	},

	toPosition: function(step){
		return (this.max * step / this.options.steps) - (this.max * this.options.start / this.options.steps) - this.options.offset;
	}

});

Slider.implement(new Events);
Slider.implement(new Options);                     

var mainURL      = 'proxy.php?sid=o8llfarpe3dsf5hpta872f8uj171fj9u';
var baseURL      = 'proxy.php?sid=o8llfarpe3dsf5hpta872f8uj171fj9u';
var bookURL      = 'http://rv01.roomview.gr/pgresLIVE/pgres.exe';
var mapZoomLevel = 10;
var vCountry = "0";
var vArea = "0";
var vCity = "0";
var vWeatherCityCode = "";
var vWeatherUnitType = 1;
var plsWaitPanel, dlgSearch, dlgRsvData;
var calendar1, calendar2;
var sRooms = 0;
var reRenderCalendar2 = false;
var today       = new Date();
var arrival     = new Date();
var departure   = new Date();
var glSort = '';
var glSortOrder = 'asc';
var glStars = '0';
var glCurrency = 'EUR';
var glCurrencyRate;
var glMinValue = 0;
var glMaxValue = 99999999;
var glHotelCounter = 0;
var gFlickPage = 1;
var gPanoramioPage = 1;
var gYoutubePage = 1;
var mainLayout, mainTabs, daysOfStay, transferReader 
var transferData = {};
var hotelsObjArray = new Array();
var recentSearchesObjArray = new Array();
var hotelsRatingArray = new Array();
var infoGenWindow;
var priceSlider;
var glSelectedHotelIndex;
var preSelectCountryObj;
var gScrollHotelsTimeoutID;
var gl_pnl_lc_Slider; 
var oBtnHtlListPrice, oBtnHtlListName, oBtnHtlListArea, oBtnHtlListCategory, oBtnHtlListDeals;
var oBtnHtlFilterPrice, oBtnHtlFilterCategory;
var middleClickPressed = false;
var cbBedConfig, cbRooms;
//var geoSuggestOptions, resultTpl, ds, oBtnHotelNameFilter, oBtnHotelNameFilter; 
//var oBtnHotelNameFilterClear, oInputhotelNameFilter, map;

var gExpandLeft = true;var gExpandRight = true;

function objPreSelectedHotel (inID, inIDX, inName) {
  this.id         = inID;
  this.idx        = inIDX;
  this.hotelname  = inName;
}
objPreSelectedHotel.id   = -1;
objPreSelectedHotel.idx  = -1;
objPreSelectedHotel.hotelname = '';

function recentSearchesObj (inCountry, inCity, inArea, inGeoname, inWeatherCode, inArrival, inArrivalFormated, inDeparture, inDepartureFormated, inRooms, inAdults, inChildren) {
	this.country = inCountry;
	this.city = inCity;
	this.area = inArea;
	this.geoname = inGeoname;
	this.geoname = inGeoname;
	this.weatherCode = inWeatherCode;
	this.arrival = inArrival;
	this.arrivalFormated = inArrivalFormated;
	this.departure = inDeparture;
	this.departureFormated = inDepartureFormated;
	this.rooms = inRooms;
	this.adults = inAdults;
	this.children = inChildren;
}

function preSelectCountryObj(inCountry, inCity, inArea, inWeatherCode, inGeoName) {
  this.country      = inCountry;
  this.city         = inCity;
  this.area         = inArea;
  this.weathercode  = inWeatherCode;
  this.geoname      = inGeoName;
}
preSelectCountryObj.country = '0';
preSelectCountryObj.city = '0';
preSelectCountryObj.area = '0';
preSelectCountryObj.weathercode = '';
preSelectCountryObj.geoname = '';


function hotelsObj (hotelid, hotelname, hotelcoords, hotelimage, hotelpriceMax, hotelAvailability, 
                    roomdescr, roomid, boarddescr, board, morerooms, hcatdescr, hoteltype,
                    countrydescr, citydescr, areadescr, countrycode, citycode, areacode, session_id, 
                    deal, shown) {
  this.hotelid = hotelid;
  this.hotelname = hotelname;
  this.hotelcoords = hotelcoords;
  this.hotelimage = hotelimage;
  this.hotelpriceMax = hotelpriceMax;
  this.hotelAvailability = hotelAvailability;
  this.roomdescr = roomdescr;
  this.roomid = roomid;
  this.boarddescr = boarddescr; 
  this.board = board;
  this.morerooms = morerooms;
  this.hcatdescr = hcatdescr;
  this.hoteltype = hoteltype;
  this.countrydescr = countrydescr;
  this.citydescr = citydescr;
  this.areadescr = areadescr;
  this.countrycode = countrycode;
  this.citycode = citycode;
  this.areacode = areacode;
  this.session_id = session_id;
  this.deal = deal;
  this.shown = shown;
} 

function hotelsObjToString() {
  switch (glSort) {
    case 'location':
      return '' + this.countrydescr + this.citydescr + this.areadescr + this.hotelpriceMax;
      break;
    case 'stars':
      return '' + this.hcatdescr + this.hotelpriceMax;
      break;
    case 'name':
      return '' + this.hotelname + this.hotelpriceMax;
      break;
    case 'deals':
      return '' + this.deal + this.hotelpriceMax;
    default:
      return PadDigits(this.hotelpriceMax, 10);
      break;
  }
}

Ext.onReady(function() {
   
  
  Ext.BLANK_IMAGE_URL = './cssImages/s.gif';
  Ext.enableGarbageCollector   = true;
  Ext.enableListenerCollection = false;  // Stelios: I'm keepint it false because the garbage collector it's possible to purge event listeners after uncaching an element
   
  /*
  transferReader = new Ext.data.ArrayReader({}, [
    {name: 'trID'},
    {name: 'trFrom'},
    {name: 'trTo'},
    {name: 'trDistance'},
    {name: 'trDuration'},
    {name: 'trIn'},
    {name: 'trOut'},
    {name: 'trBoth'},
    {name: 'trRatePolicy'},
    {name: 'trTypeID'},
    {name: 'trUnits'},
    {name: 'trType'},
    {name: 'trPrice', type: 'float'}
  ]);
  */
  
  list_renderer = {
    render: function(el, response) {
			el.update(response.responseText);
		}
	};

  mainLayout = new Ext.Viewport({
    layout: 'border',
    monitorResize: false,
    id: 'mainLayout',
    items: [
      {
        region: 'north',
        contentEl: 'northDIV',
        id: 'headerRegion',
        split: false,
        height: 120
      }, {
        id: 'filtersRegion',
        region: 'west',
        contentEl: 'col1',
        autoScroll: true,
        split: true,
        width: 220,
        titlebar: true,
        title: 'Filters', 
        collapsed: true,
        titleCollapse: true,
        collapsible: true, 
        collapsedTitle: 'Filters',
        listeners:  { beforeexpand: function() {
                          if (preSelectCountryObj.geoname == '') {
                            alert ('Filters cannot expanded. You must select a destination first.');
                            return false;
                          }
                        } 
                    },
        defaults: {autoScroll:true},
        renderer: list_renderer
      }, {
        region: 'east',
        id: 'accordionREG',
        split: true, 
        width: 200,
        titlebar: true,
        title: 'Destination Information',
        collapsed: true,
        titleCollapse: true,
        collapsible: true, 
        collapsedTitle: 'Destination',
        layout: 'accordion',
        layoutConfig:{ animate:true },
        defaults: {autoScroll:true, closable: false, border: false},
        listeners:  { beforeexpand: function() {
                            if (preSelectCountryObj.geoname == '') {
                              alert ('Destination Info cannot expanded. You must select a destination first.');
                              return false;
                            }
                        }
                    },
        renderer: list_renderer,
        items: [
          {
            id: 'weatherCurrent',
            title: 'Current Weather Conditions',
            html: 'Weather..',
            iconCls: 'weatherCurrentACCO',
            listeners: {beforeexpand: currentWeatherUpdate},
            renderer: list_renderer
          }, {
            id: 'weatherForecast',
            title: '7 Days Weather Forecast',
            html: '7 Days Weather Forecast...',
            iconCls: 'weatherCurrentACCO',
            listeners: {beforeexpand: forecastWeatherUpdate},
            renderer: list_renderer
          }, {
            id: 'flickrPhotos',
            title: 'Flickr Photos for City',
            html: 'Area Photos...',
            iconCls: 'photoACCO',
            listeners: {beforeexpand: flickrPhotosUpdate},
            renderer: list_renderer
          }, {
            id: 'panoramioPhotos',
            title: 'Panoramio Photos for City',
            html: 'Area Photos...',
            iconCls: 'photoACCO',
            listeners: {beforeexpand: panoramioPhotosUpdate},
            renderer: list_renderer
          }, {
            id: 'youtubeVideos',
            title: 'Youtube Videos for City',
            html: 'Area Videos...',
            iconCls: 'videoACCO',
            listeners: {beforeexpand: youtubeVideosUpdate},
            renderer: list_renderer
          }, {
            id: 'additional',
            title: 'Additional Info',
            html: 'Additional Info goes here',
            iconCls: 'infoACCO',
            listeners: {beforeexpand: additionalAreaInfoUpdate},
            renderer: list_renderer
          }
        ]
      }, {
        region: 'south',
        contentEl: 'footerDIV',
        id: 'footerRegion',
        split: true
      }, {
        xtype: 'tabpanel', 
        region: 'center',
        id: 'centerRegion',
        deferredRender: false,
        activeTab: 0,
        enableTabScroll:true,
        defaults: {autoScroll:true},
        plugins:  [new Ext.ux.TabCloseMenu(), new Ext.plugins.TDGi.tabScrollerMenu()], 
        items:[{
          contentEl: 'col3',
          closable: false,
          title: 'Offers'
        }]
      }
    ]
  });

  
  Ext.EventManager.on(window, "beforeunload",  
      function() {
        try {
          Ext.destroy ( mainLayout, ds, resultTpl, plsWaitPanel, dlgSearch, CheckIn, CheckOut, 
                        oBtnHotelNameFilter, oBtnHotelNameFilterClear, oInputhotelNameFilter, oBtnHtlListPrice,
                        oBtnHtlListName, oBtnHtlListArea, oBtnHtlListCategory, oBtnHtlListDeals, Shadowbox, map, 
                        loading, mask, preSelectCountryObj, hotelsObj, priceSlider, 
                        gl_pnl_lc_Slider//, quickTips
                      );
        } catch (e) {
//          alert ('eskasa me ' + e.description);
        }
      }, window, {single:true}
  );
  
  // Capture and disable the middle click except from the doShowHotelInfoPanel function!!
  // Opws panta yparxoun terasties paparies tou IE!!!
  Ext.EventManager.on(document.body, 'mousedown',
    function(e) {
      var middleButtonCode;
           
      if (Ext.isIE) {
        middleButtonCode = 4;
      } else {
        middleButtonCode = 1;
      }
      
      if(e.browserEvent.button == middleButtonCode) {
        e.preventDefault();
        e.stopEvent();
        middleClickPressed = true;
        if (midClickTarget = e.getTarget('a[onclick*=doShowHotelInfoPanel]')) {
          eval (midClickTarget.getAttribute('onclick'));
        }
      }
    }
  );
 
  // Initialize Event Actions
  Ext.get('currencies').on('change', doChangeCurrency);

  // Initialize Dialogs
  initCombos();
  initAutoSuggest();
  initWaitPanel();
  initDlgRsvData();
  initCalendars();
  initButtonsAndInputs();
  initLeftColPanels();
  initDlgSearch();
//  initQuickTips();

  /* Βγήκε γιατί έχει πρόβλημα με το initialize του dlgSearch
  Ext.History.init();
  Ext.History.add('dlgSearch:show');
  Ext.History.on('change', function(token) {
    if (dlgSearch.hidden) {
      dlgSearch.show();
    }      
  });
  */  
  
  /* Initialize the ShadowBox */
  Shadowbox.init( {
                  resizeLgImages: true,
                  initialWidth: 100,
                  initialheight: 100
                  } );	
      
  var mapF5 = new Ext.KeyMap(document, {
    key: Ext.EventObject.F5,
//    stopEvent: true,
    handler: function (keycode, e) {
      try {
        if (hotelsObjArray.length != 0) {
          if (dlgSearch.hidden) {
            dlgSearch.show();
            if (Ext.isIE) {
              // IE doesn't allow cancellation of the F5 key, 
              // so trick it into thinking another key was pressed (backspace in this case)
              e.browserEvent.keyCode = 8;
            }
            e.stopEvent();
          }
        }
      } catch (e) {
        //
      }
    }
  });
  
  
  Ext.getCmp('filtersRegion').hide();
  Ext.getCmp('filtersRegion').ownerCt.doLayout();
  Ext.getCmp('accordionREG').hide();
  Ext.getCmp('accordionREG').ownerCt.doLayout();
  
  var loading = Ext.get('loading');
  var mask = Ext.get('loading-mask');
  mask.setOpacity(.8);
  mask.shift({
    xy:loading.getXY(),
    width:loading.getWidth(),
    height:loading.getHeight(),
    remove:true,
    duration:1,
    opacity:.3,
    easing:'bounceOut',
    callback : function(){
      loading.fadeOut({duration:.2,remove:true});
    }
  });
  
  if (glCurrencyRate == undefined) {
    glCurrencyRate = 1;    
  }
  
  });


  try {
    GBrowserIsCompatible();
  } catch (e) {
    GBrowserIsCompatible = function () {
      return false;
    }
  }

google.load("language", "1");

function flickrPhotosUpdate(pnl) {

  function flickRUpdateCallback() {
    Shadowbox.close();
    Shadowbox.setup();
  }

  var updater = pnl.body.getUpdater();
  updater.setRenderer(pnl.renderer);
  updater.update ( {
    url: baseURL,
    params: 'id=cityPhotos&city=' + preSelectCountryObj.geoname + '&page=' + gFlickPage,
    text: 'Loading Photos...',
    timeout:35,
    nocache: false,
    scripts: false,
    callback: flickRUpdateCallback
  })
}


function panoramioPhotosUpdate(pnl) {
  function panoramioUpdateCallback() {
    Shadowbox.close();
    Shadowbox.setup();
  }
  
  var updater = pnl.body.getUpdater();
  updater.setRenderer(pnl.renderer);
  updater.update ( {
    url: baseURL,
    params: 'id=cityPhotosPanoramio&city=' + preSelectCountryObj.geoname + '&page=' + gPanoramioPage,
    text: 'Loading Photos...',
    timeout:35,
    nocache: false,
    scripts: false,
    callback: panoramioUpdateCallback
  })
}

function youtubeVideosUpdate(pnl) {
  function youtubeUpdateCallback() {
    Shadowbox.close();
    Shadowbox.setup();
  }
  
  var updater = pnl.body.getUpdater();
  updater.setRenderer(pnl.renderer);
  updater.update ( {
    url: baseURL,
    params: 'id=cityVideos&city=' + preSelectCountryObj.geoname  + '&page=' + gYoutubePage,
    text: 'Loading Videos...',
    timeout:35,
    nocache: false,
    scripts: false,
    callback: youtubeUpdateCallback
  })
}

function additionalAreaInfoUpdate(pnl) {
  var updater = pnl.body.getUpdater();
  updater.setRenderer(pnl.renderer);
  updater.update ( {
    url: baseURL,
    params: 'id=additionalAreaInfo&country=' + vCountry + '&city=' + vArea + '&area=' + vCity,
    text: 'Loading Additional Info...',
    timeout:35,
    nocache: false,
    scripts: true
  })
}

function currentWeatherUpdate(pnl) {
  var updater = pnl.body.getUpdater();
  updater.setRenderer(pnl.renderer);
  updater.update ( {
    url: baseURL,
    params: 'id=currentWeather&weathercitycode=' + vWeatherCityCode + '&unittype=' + vWeatherUnitType,
    text: 'Loading Weather Data...',
    timeout:35,
    nocache: false,
    scripts: true
  })
}

function forecastWeatherUpdate(pnl) {
  var updater = pnl.body.getUpdater();
  updater.setRenderer(pnl.renderer);
  updater.update ( {
    url: baseURL,
    params: 'id=forecastWeather&weathercitycode=' + vWeatherCityCode + '&days=' + daysOfStay + '&unittype=' + vWeatherUnitType,
    text: 'Loading Weather Data...',
    timeout:35,
    nocache: false,
    scripts: true
  })
}


function initAutoSuggest() {
  var ds = new Ext.data.Store({
    proxy: new Ext.data.HttpProxy({
        url: mainURL + '&id=getCities',
        method: 'GET' 
    }),
    reader: new Ext.data.XmlReader({ 
        record: 'rs'
    }, [
        {name: 'matchat', mapping: 'matchat'},
        {name: 'areaid', mapping: 'areaid'},
        {name: 'cityid', mapping: 'cityid'},
        {name: 'countryid', mapping: 'countryid'},
        {name: 'weathercode', mapping: 'weathercode'},
        {name: 'geoname', mapping: 'name'}
    ])
  });
  
  // Custom rendering Template
  var resultTpl = new Ext.XTemplate(
      '<tpl for=".">',
      '<div class="search-item" style="padding: 3px; font-size:12px; font-weight:bold; color:#666666;">',
      '{geoname}',
      '</div>',
      '</tpl>'
  );

  var geoSuggestOptions = new Ext.form.ComboBox({
    store: ds,
    typeAhead: false,
    forceSelection: true,
    loadingText: 'Searching...',
    width: 500,
    minChars: 3,
    hideTrigger: true,
    triggerClass: 'x-custom-geoSuggest-trigger-display-none',
    queryParam: 'locstr',
    applyTo: 'acGeoSearch',
    queryDelay: 0,
    itemSelector: 'div.search-item',
    displayField: 'geoname',
    valueField: 'geoname',
    tpl: resultTpl,
    enableKeyEvents: true,
    queryIgnoreCase: true,
    onSelect: function(record){
        preSelectCountryObj.country = record.data.countryid;
        preSelectCountryObj.city = record.data.cityid;
        preSelectCountryObj.area = record.data.areaid;
        preSelectCountryObj.weathercode = record.data.weathercode;
        preSelectCountryObj.geoname = record.data.geoname;
        if (preSelectCountryObj.city == '') {preSelectCountryObj.city = 0;}
        if (preSelectCountryObj.area == '') {preSelectCountryObj.area = 0;}           
				this.setValue(record.data.geoname);
				this.collapse();
    },
    onKeyPress: function(e) {
      var keyPressed = e.getCharCode();
      // allow only backspace, space, del, enter, [arrows], [home-end-pgup-pgdn], [a-z], [A-Z]
      if ( keyPressed == 8 || keyPressed == 32 || keyPressed == 46 || keyPressed == 13 ||   
      (keyPressed >= 37 && keyPressed <= 40) ||
      (keyPressed >= 33 && keyPressed <= 36) || 
      (keyPressed >= 65 && keyPressed <= 90) || 
      (keyPressed >= 97 && keyPressed <= 122) )  {
        return true;
      } else {
        e.stopEvent();
        el = Ext.get('dlgSearchWhereString');
        el.applyStyles('font-weight:bold'); 
        el.syncFx();
        el.highlight();
        el.frame("ff0000", 1, { duration: 1, afterStyle: 'font-weight:normal' });
      }
    }
  });
}


function initWaitPanel() {
	plsWaitPanel = new Ext.Window({
	    layout: 'fit',
	    el: 'wait',
      width: 350, 
			height: 100,
      closable: false,
      collapsible: false,
      resizable: false,
      draggable:false, 
			modal:true,
			shim: true,
			title: "Loading... "
  });	
}


function initButtonsAndInputs() {
  $$$('starRating0').checked = true;
  
  var oBtnHotelNameFilter = new Ext.Button({
    renderTo: 'hotelNameFilterSPAN',
    //iconCls : 'btnHotelFilter',
    text: "Find ",
    tooltip: 'Filter the Hotel List showing only hotels containing somewhere on their name the above entered string The other filters will still be valid.',
    type: 'button'
  });
  oBtnHotelNameFilter.addListener ("click", function() { filterHotelName('filter'); });
  
  var oBtnHotelNameFilterClear = new Ext.Button({
    renderTo: 'hotelNameFilterClearSPAN',
    //iconCls : 'btnHotelFilterClear',
    text: 'Clear',
    tooltip: 'Clear the Hotel Name Filter and show all the hotels. The other filters will still be valid.',
    type: 'button'
  });
  oBtnHotelNameFilterClear.addListener ("click", function() { filterHotelName('reset'); });
  
  var oInputhotelNameFilter = new Ext.form.TextField ({
    renderTo: 'hotelNameInputFilterSPAN',
    id: 'hotelNameFilter',
    width: 170,
    inputType: 'text',
    selectOnFocus: true
  });
  
  oBtnHtlListPrice = new Ext.Button ({
    iconCls : 'btnSortAsc',
    text: 'Price',
    enableToggle: true,
    type: 'button'
  });
  oBtnHtlListPrice.addListener ("click", function() { doSelectSortType('price'); });

  oBtnHtlListName = new Ext.Button ({
    iconCls : 'btnSortNone',
    text: 'Name',
    enableToggle: true,
    type: 'button'
  });
  oBtnHtlListName.addListener ("click", function() { doSelectSortType('name'); });
  
  oBtnHtlListArea = new Ext.Button ({
    iconCls : 'btnSortNone',
    text: 'Location',
    enableToggle: true,
    type: 'button'
  });
  oBtnHtlListArea.addListener ("click", function() { doSelectSortType('location'); });
  
  oBtnHtlListCategory = new Ext.Button ({
    iconCls : 'btnSortNone',
    text: 'Catecory',
    enableToggle: true,
    type: 'button'
  });
  oBtnHtlListCategory.addListener ("click", function() { doSelectSortType('stars'); });
  
  oBtnHtlListDeals = new Ext.Button ({
    iconCls : 'btnSortNone',
    text: 'Offers',
    enableToggle: true,
    type: 'button'
  });
  oBtnHtlListDeals.addListener ("click", function() { doSelectSortType('deals'); });
  
  oBtnHtlFilterPrice = new Ext.Button ({
    iconCls : 'btnFilterRemove',
    text: 'Price range',
    enableToggle: false,
    type: 'button',
    hidden: true
  });
  oBtnHtlFilterPrice.addListener ("click", function() { doResetFilter('price'); });
  
  oBtnHtlFilterCategory = new Ext.Button ({
    iconCls : 'btnFilterRemove',
    text: 'Category',
    enableToggle: false,
    type: 'button',
    hidden: true
  });
  oBtnHtlFilterCategory.addListener ("click", function() { doResetFilter('category'); });
}

function initLeftColPanels() {
  gl_pnl_lc_Slider = new Ext.Panel ({
    renderTo: 'DIVleftColSlider',
    contentEl: 'DIVleftColSliderContent',
    collapsible: true,
    collapsed: false,
    title: 'Price range in: ' + glCurrency
  });

  var pnl_lc_Categories = new Ext.Panel ({
    renderTo: 'DIVleftColCategories',
    contentEl: 'DIVleftColCategoriesContent',
    collapsible: true,
    collapsed: false,
    title: 'Star Rating'
  });
  
  var pnl_lc_Cities = new Ext.Panel ({
    renderTo: 'DIVleftColCities',
    contentEl: 'DIVleftColCitiesContent',
    collapsible: true,
    collapsed: false,
    title: 'Select city area'
  });
  
  pnl_lc_HotelName = new Ext.Panel ({
    renderTo: 'DIVleftColHotelName',
    contentEl: 'DIVleftColHotelNameContent',
    collapsible: true,
    collapsed: false,
    title: 'Hotel Name Contains'
  });  
  
  var pnl_lc_Currencies = new Ext.Panel ({
    renderTo: 'DIVleftColCurrency',
    contentEl: 'DIVleftColCurrencyContent',
    collapsible: true,
    collapsed: false,
    title: 'Change Currency'
  });
  
}


function initQuickTips() {
  new Ext.ToolTip({
    target: 'cityAreaFilter',
    title: 'Filter City\'s Area',
    html: 'Select from the list of available City\'s Areas, the area of your choice. The hotel list will be filtered, showing only the hotels of this area. The other filters will still be valid.'
  });

  new Ext.ToolTip({
    target: 'acGeoSearch',
    title: 'Destination',
    html: 'Enter part of your destination\'s name (minimum 3 characters) to retrieve a list of available destinations containing the entered string'
  });
  
  new Ext.ToolTip({
    target: 'nightsCmb',
    title: 'Nights for your stay',
    html: 'Enter the number of nights for your stay (up to 31). Your departure date is automatically calculated.'
  });
  
  new Ext.ToolTip({
    target: 'calendar1TD',
    title: 'Arrival Date',
    html: 'Enter your arrival date. Your departure date is automatically calculated, according the entered number of nights.'
  });
  
  new Ext.ToolTip({
    target: 'calendar2TD',
    title: 'Departure Date',
    html: 'Enter your departure date. The number of nights for your stay will be automatically calculated.'
  });
  
  new Ext.ToolTip({
    target: 'roomsCmb',
    title: 'Number of Rooms',
    html: 'Enter the number of rooms you\'re requesting. Each room must contain adults and/or children according the bed configuration field.'
  });
  
  new Ext.ToolTip({
    target: 'bedconfigCmb',
    title: 'Bed Configuration',
    html: 'Select the number of adults and/or children you want to host for each room entered at the Number of Room field'
  });

  Ext.QuickTips.init();
  var quickTips = Ext.QuickTips.getQuickTip();
  Ext.apply(quickTips, {
    maxWidth: 300,
    minWidth: 100,
    showDelay: 1000,
    trackMouse: true,
    floating: false
  });
}


function initCombos() {
  var cbBedConfig = new Ext.form.ComboBox({
    displayField: 'bedconfig',
    valueField: 'id',
    editable:false,
    triggerAction: 'all',
    applyTo: 'bedconfigCmb',
    id: 'cbBedConfig',   
    width: 150,
    listWidth: 200,
    autoheight: true,
    lazyInit: true,
    mode: 'local',
    tpl:  new Ext.XTemplate(
        '<tpl for=".">',
        '<div id="{id}" class="x-combo-list-item">',
        '<img src="./images/{image}.gif" border="0">',
        '<div style="position:relative; margin:-15px 0 0 55px;" align="right">',
        '{bedconfig}',
        '</div></div>',
        '</tpl>'),
    store:new Ext.data.SimpleStore({
      fields: ['id', 'bedconfig', 'image'],
        data: [
                ['1_0', '1 adult', 'ico_pax1_0'], 
                ['1_1', '1 adult & 1 child ', 'ico_pax1_1'],
                ['1_2', '1 adult & 2 children ', 'ico_pax1_2'],
                ['2_0', '2 adults', 'ico_pax2_0'],
                ['2_1', '2 adults & 1 child', 'ico_pax2_1'],
                ['2_2', '2 adults & 2 children', 'ico_pax2_2'],
                ['2_3', '2 adults & 3 children', 'ico_pax2_3'],
                ['2_4', '2 adults & 4 children', 'ico_pax2_4'],
                ['3_0', '3 adults', 'ico_pax3_0'],
                ['3_1', '3 adults & 1 child', 'ico_pax3_1'],
                ['3_2', '3 adults & 2 children', 'ico_pax3_2'],
                ['3_3', '3 adults & 3 children', 'ico_pax3_3'],
                ['4_0', '4 adults', 'ico_pax4_0'],
                ['4_1', '4 adults & 1 child', 'ico_pax4_1'],
                ['4_2', '4 adults & 2 children', 'ico_pax4_2'],
                ['5_0', '5 adults', 'ico_pax5_0'],
                ['5_1', '5 adults & 1 child', 'ico_pax5_1'],
                ['6_0', '6 adults', 'ico_pax6_0']
              ]
      })
  });
  cbBedConfig.setValue("2_0");
  $$$("Adults").value = cbBedConfig.value.substring(0,1);
  $$$("Children").value = cbBedConfig.value.substring(2,3);

  cbBedConfig.addListener("select", function() {
    $$$("Adults").value = this.value.substring(0,1);
    $$$("Children").value = this.value.substring(2,3);
  });
  
  var cbRooms = new Ext.form.ComboBox({
    displayField: 'rooms',
    valueField: 'id',
    editable: false,
    triggerAction: 'all',
    applyTo: 'roomsCmb',
    id: 'cbRooms',   
    width: 75,
    listWidth: 75,
    autoheight: true,
    lazyInit: true,
    mode: 'local',
    tpl:  new Ext.XTemplate(
        '<tpl for=".">',
        '<div id="{id}" class="x-combo-list-item">',
        '{rooms}',
        '</div>',
        '</tpl>'),
    store:new Ext.data.SimpleStore({
      fields: ['id', 'rooms'],
        data: [
                ['1', '1 room'], 
                ['2', '2 rooms'],
                ['3', '3 rooms'],
                ['4', '4 rooms']
              ]
      })      
  });
  cbRooms.setValue("1");
  $$$('Rooms').value = cbRooms.value; 
  cbRooms.addListener("select", function() {
    $$$("Rooms").value = this.value;
  });
  
  var cbNights = new Ext.form.ComboBox({
    displayField: 'nights',
    valueField: 'id',
    editable: false,
    triggerAction: 'all',
    applyTo: 'nightsCmb',
    id: 'cbNights',
    width: 75,
    listWidth: 85,
    autoheight: true,
    lazyInit: true,
    mode: 'local',
    tpl:  new Ext.XTemplate(
        '<tpl for=".">',
        '<div id="{id}" class="x-combo-list-item">',
        '{nights}',
        '</div>',
        '</tpl>'),
    store:new Ext.data.SimpleStore({
      fields: ['id', 'nights'],
        data: [
              ['1', '1 Nights'],
['2', '2 Nights'],
['3', '3 Nights'],
['4', '4 Nights'],
['5', '5 Nights'],
['6', '6 Nights'],
['7', '7 Nights'],
['8', '8 Nights'],
['9', '9 Nights'],
['10', '10 Nights'],
['11', '11 Nights'],
['12', '12 Nights'],
['13', '13 Nights'],
['14', '14 Nights'],
['15', '15 Nights'],
['16', '16 Nights'],
['17', '17 Nights'],
['18', '18 Nights'],
['19', '19 Nights'],
['20', '20 Nights'],
['21', '21 Nights'],
['22', '22 Nights'],
['23', '23 Nights'],
['24', '24 Nights'],
['25', '25 Nights'],
['26', '26 Nights'],
['27', '27 Nights'],
['28', '28 Nights'],
['29', '29 Nights'],
['30', '30 Nights'],
['31', '31 Nights']
 
              ]
      })      
  });
  cbNights.setValue("3");
  $$$("s_nights").value = cbNights.value; 
  cbNights.addListener("select", function() {
    $$$("s_nights").value = this.value;
    calcDeparture();
  });
  //Ext.get('s_nights').on('change', calcDeparture);
  //Ext.get('s_nights').on('blur', calcDeparture);
  
  
  var cbLanguages = new Ext.form.ComboBox ({
    displayField: 'languageName',
    valueField: 'languageID',
    editable: false,
    triggerAction: 'all',
    id: 'cbLanguages',
    applyTo: 'languagesCmb',
    maxLength: 150,
    lazyInit: true,
    emptyText: 'Select your language', 
    mode: 'local',
    typeAhead: true, 
    tpl:  new Ext.XTemplate (
            '<tpl for=".">',
            '<div id="{languageID}" class="x-combo-list-item">',
            '<table width="100%"><tbody><tr><td width="50%">{languageNameINT}</td><td>{languageNameLOC}</td></tr></tbody></table>',
            '</div>',
            '</tpl>'),
    store: new Ext.data.SimpleStore ({
            fields: ['languageID', 'languageNameINT', 'languageNameLOC'],
            data: [
                  ['EN', 'English', ''],
                  ['FR', 'French',  'Français'],
                  ['DE', 'German',  'Deutsch'],
                  ['GR', 'Greek',   'Ελληνικά'],
                  ['IT', 'Italian', 'Italiano'],
                  ['ES', 'Spanish', 'Español']
                  ]
          })
  });
  cbLanguages.setValue( 'EN' );
  cbLanguages.addListener("select", function() {
    doChangeLang(this.value);
  });

  /*
  var cbCurrencies = new Ext.form.ComboBox({
    displayField: 'currencyName',
    valueField: 'currencyRate',
    editable: false,
    triggerAction: 'all',
    applyTo: 'currenciesCmb',
    id: 'cbCurrencies',
    width: 150,
    listWidth: 150,
    autoheight: true,
    lazyInit: false,
    mode: 'local',
    tpl:  new Ext.XTemplate(
        '<tpl for=".">',
        '<div id="{currencyAbbr}" title="{currencyRate}" class="x-combo-list-item">',
        '{currencyName}',
        '</div></div>',
        '</tpl>'),
    store:new Ext.data.SimpleStore({
      fields: ['currencyRate', 'currencyAbbr', 'currencyName'],
        data: [
               
              ]
      })      
  });
  */
}


function doShowHotelInfoPanel(arrID, inTabName) {
  var hotelID = hotelsObjArray[arrID].hotelid; 
  var hotelName = hotelsObjArray[arrID].hotelname;
  var hotelPhoto = hotelsObjArray[arrID].hotelimage;
  var hotelMapCoords = hotelsObjArray[arrID].hotelcoords;
  var hotelRoomID = hotelsObjArray[arrID].roomid;
  var hotelRoomDescr = hotelsObjArray[arrID].roomdescr;
  var hotelRoomBoardDescr = hotelsObjArray[arrID].boarddescr;
  var hotelRoomMaxPrice = parseFloat(hotelsObjArray[arrID].hotelpriceMax / 100).toFixed(2);
  
  function roomBookingDetailsDIV() {
    tmpHotelPricing = 'Prices for this hotel starting from  ' +
                      '<span style="font-weight:bold;">' + glCurrency + ' ' + (hotelRoomMaxPrice * glCurrencyRate).toFixed(2) + '</span>' +
                      '  for  ' +
                      '<span style="font-weight:bold;">' + hotelRoomDescr + ' (' + hotelRoomBoardDescr + ')' + '</span>' + 
                      '<ul>' +
                      '<li>- <a href="javascript:void(null)" onclick="getData(\'pricing\', [' + arrID + ', \'0\'])">Book this</a></li>' +
                      '<li>- <a href="javascript:void(null)" onClick="getData(\'pricingAll\', [' + arrID + ', \'' + hotelRoomID + '\'] )">Show me Alternate Room Types</a></li>';
    if (hotelsObjArray[arrID].hotelcoords != '') {                                                  
      tmpHotelPricing += '<li>- <a href="javascript:void(null)" onClick="doShowStaticMap(' + arrID + ');">Hotel Map</a></li>';
    }
    
            //if ( doShowStreetView(arrID, true) ) {
          tmpHotelPricing += '<li>- <a href="javascript:void(null)" onClick="doShowStreetView(' + arrID + ', false)">Street View</a></li>';
        //}
        
    tmpHotelPricing += '</ul>';
    
    return tmpHotelPricing;
  }
  
  function showAndTranslateHotelInfo() {
    $$$('roomBookingDetails' + hotelID).innerHTML = roomBookingDetailsDIV();
    var hotelInfoDescription = $$$('hotelInfoText' + hotelID).innerHTML;
    google.language.translate(hotelInfoDescription, "", "en", function(result) {
      if (!result.error) {
        var hotelInfoDescriptionTranslated = result.translation;
        if ( hotelInfoDescriptionTranslated != hotelInfoDescription ) {
          $$$('hotelInfoText' + hotelID).innerHTML =  '<strong style="font-size:12px; text-decoration:underline;">Description Translated by Google Translation</strong><br/>' + hotelInfoDescriptionTranslated +
                                                      '<br/><br/>' + 
                                                      '<strong style="font-size:12px; text-decoration:underline;">Original Description</strong><br/>' + hotelInfoDescription;
        } else {
          $$$('hotelInfoText' + hotelID).innerHTML = hotelInfoDescription;  
        }                                                   
      }
    });    
  }

  switch (inTabName) {
    case 'Photos':
      inActiveTab = 2;
      break;
    case 'Rooms':
      inActiveTab = 4;
      break;
    default:
      inActiveTab = 0;
      break;
  };
  
  var hotelInfoURL =  
          'id=hotelInfo' +
          '&hotelid=' + hotelID +
          '&cnid=' + vCountry + 
          '&ctid=' + vArea + 
          '&arid=' + vCity + 
          '&dtFrom=' + arrival.format('Ymd') + 
          '&dtTo=' + tmpDepartureDate.format('Ymd') +
          '&bc=' + $$$('Adults').value +
          		 "_" +
          		 $$$('Children').value +
          '&numRooms=' + $$$('Rooms').value +
          '&out=html' +
          '&outDetailed=1' +
          '&roomid=' + hotelRoomID + 
          '&roomdescr=' + hotelRoomDescr +
          '&roomboard=' + hotelRoomBoardDescr +
          '&roomprice=' + hotelRoomMaxPrice;   

  var hotelAmmenitiesURL = 
          'id=hotelAmmenities&hotelid=' + hotelID +
          '&out=html';
          
  var hotePhotosURL = 
          'id=hotelPhotos&hotelid=' + hotelID +
          '&hotelname=' + hotelName +
          '&out=html';


  var newPanel = Ext.getCmp('centerRegion');
  if (! Ext.getCmp('htp' + hotelID)) {
    newPanel.add(new Ext.TabPanel({
      id: 'htp' + hotelID,
      title: hotelName,
      closable: true,
      enableTabScroll:true,
      deferredRender: true,
      defaults: {autoScroll:true, closable: false},
      items: [{
        title: 'Information',
        autoLoad: { url: baseURL, 
                    params: hotelInfoURL,
                    text: "Loading Hotel Info ",
                    callback: function(el, success) {
                      if (success) {
                        showAndTranslateHotelInfo();
                        $$$('roomBookingDetails' + hotelID).style.display = '';
                      }
                    }
                  },
        listeners:  { show: function() {
                        try {
                          $$$('roomBookingDetails' + hotelID).innerHTML = roomBookingDetailsDIV();
                        } catch (e) {
                        }
                      }
                    },
        renderer: list_renderer
      }, {
        title: 'Ammenities',
        autoLoad: { url: baseURL, 
                    params: hotelAmmenitiesURL,
                    text: "Loading Hotel Ammenities " 
                  }
      }, {
        title: 'Photos',
        autoLoad: { url: baseURL, 
                    params: hotePhotosURL, 
                    text: "Loading Hotel Photo Gallery  ",
                    callback: function() {
                                Shadowbox.close();
                                Shadowbox.setup();
                              }
                  }
      }/*, {
        title: 'Map',
        contentEl: 'map' + hotelID,
        listeners:  { show: function() {
                                                  createGoogleMap ('map'+hotelID, hotelID, hotelMapCoords);
                                              }
                    }
      }*/],
      activeTab: inActiveTab,
      listeners:  { show: function() {
                      if (inActiveTab == 0) { 
                        try {
                          $$$('roomBookingDetails' + hotelID).innerHTML = roomBookingDetailsDIV();
                        } catch (e) {
                          //
                        }
                      }
                    },
                    close: function() {
                      this.destroy();
                    }
                  },
      renderer: list_renderer
    }));
   
    if (hotelPhoto == "") Ext.getCmp('htp' + hotelID).items.get(2).disabled = true;
    //if ( hotelMapCoords == "" || ! GBrowserIsCompatible() ) Ext.getCmp('htp' + hotelID).items.get(3).disabled = true;
  }
  
  if (middleClickPressed) {
    middleClickPressed = false;
    newPanel.setActiveTab(Ext.getCmp('hotellist'));
  } else {
    newPanel.setActiveTab(Ext.getCmp('htp' + hotelID));
  } 
}


function createEarthMap (inMapTabElement, inHotelID, inMapCoordinates) {
  var map = new VEMap(inMapTabElement);  
  
  if (inHotelID === null) {
    var haveOnlyOneHotelToShow = false;
  } else {
    var haveOnlyOneHotelToShow = true;
  }
  
  var tmpStartColor = [255, 255, 0];
  var tmpEndColor   = [255,   0, 0]; 
  var tmpColorToSubPerEuro = 255 / (glMaxValue - glMinValue);

  mapLoaded = false;
  for (idx=0, tmpIDX=hotelsObjArray.length; idx<tmpIDX; idx++) {
    if (hotelsObjArray[idx].shown && hotelsObjArray[idx].hotelcoords != '') {
      
      if (! haveOnlyOneHotelToShow && inHotelID === null) {
        inHotelID = hotelsObjArray[idx].hotelid;
        inMapCoordinates = hotelsObjArray[idx].hotelcoords;
      }
      
      if (! mapLoaded) {
        var tmpArray1 = inMapCoordinates.split(',');
        var point = new VELatLong(tmpArray1[0], tmpArray1[1]);
        map.LoadMap(point, mapZoomLevel , VEMapStyle.Hybrid ,false, VEMapMode.Mode2D, true, 0);
        mapLoaded = true;
      }
      
      var tmpArray2 = hotelsObjArray[idx].hotelcoords.split(',');
      var point = new VELatLong(tmpArray2[0], tmpArray2[1]);
      var shape = new VEShape(VEShapeType.Pushpin, point);
      var tmpPrice = /*glCurrency + '|' + */(parseFloat(hotelsObjArray[idx].hotelpriceMax / 100).toFixed(2) * glCurrencyRate).toFixed(2);
      
      if (haveOnlyOneHotelToShow) {
        if (hotelsObjArray[idx].hotelid == inHotelID) {
          shape.SetCustomIcon("<img src='./images/hotel.png'>")
        }
      } else {
          var tmpColor = [255, 255 - Math.round( ((hotelsObjArray[idx].hotelpriceMax / 100) - glMinValue) * tmpColorToSubPerEuro ), 0];
          shape.SetCustomIcon('http://chart.apis.google.com/chart?chst=d_map_spin&chld=1.2|0|' + RGB2HTML(tmpColor[0], tmpColor[1], tmpColor[2]) + '|9|_|' + tmpPrice);
      }
      
      shape.SetTitle(hotelsObjArray[idx].hotelname);
      shape.SetPhotoURL(hotelsObjArray[idx].hotelimage);
      if (hotelsObjArray[idx].hcatdescr.substr(0,1) == '*' ) {
        var hotelRating = hotelsObjArray[idx].hcatdescr.length-1;
        tmpHotelPricing = "Category: <img src='./images/" + hotelRating + "_0_stars.gif' align='absmiddle' /><br/><br/>";
      } else {
        tmpHotelPricing = '';
      }
      tmpHotelPricing +=  'Prices for this hotel starting from  ' +
                          '<span style="font-weight:bold;">' + glCurrency + ' ' + (parseFloat(hotelsObjArray[idx].hotelpriceMax / 100).toFixed(2) * glCurrencyRate).toFixed(2) + '</span>' +
                          '  for  ' +
                          '<span style="font-weight:bold;">' + hotelsObjArray[idx].roomdescr + ' (' + hotelsObjArray[idx].boarddescr + ')' + '</span><br/><br/>' + 
                          '<ul>' +
                          '<li>- <a href="javascript:void(null)" onclick="doShowHotelInfoPanel(' + idx + ', \'Info\')">More Info</a></li>' +
                          '<li>- <a href="javascript:void(null)" onclick="getData(\'pricing\', [' + idx + ', \'0\'])">Book this</a></li>' +
                          '<li>- <a href="javascript:void(null)" onClick="getData(\'pricingAll\', [' + idx + ', \'' + hotelsObjArray[idx].hotelid + '\'] )">Show me Alternate Room Types</a></li>' +
                          '</ul>';
      shape.SetDescription(tmpHotelPricing)
      
      map.AddShape(shape);
    }
  }
}


function doShowStaticMap(inIDX) {
  var mapcoords = hotelsObjArray[inIDX].hotelcoords.split(',');
  
  if (Ext.isIE) {
    mapDimensions = '460x400';
  } else {
    mapDimensions = '470x410';
  }
  mapURL =  'http://maps.google.com/maps/api/staticmap?zoom=14&size=' + mapDimensions + '&maptype=roadmap' +
            '&markers=color:red|' + mapcoords[0] + ',' + mapcoords[1] + '&sensor=false&key=ABQIAAAAJGmiyKMtHKpy1vOwuyJTMhSqPRKG1fmuAw2yyQsjiznJ8Ix9URSj0O6I2pYwHo8J64A_CZ14Cwzgsg';
  doShowURL(mapURL, 'Hotel Map', 'hotelMap'+hotelsObjArray[inIDX].hotelid, 500, 500);
}


function doShowStreetView(inIDX, inCheckOnly) {
  var hotelid   = hotelsObjArray[inIDX].hotelid;
  var mapcoords = hotelsObjArray[inIDX].hotelcoords.split(',');
  var hotelname = hotelsObjArray[inIDX].hotelname;
  
  panoramaClient = new GStreetviewClient(); 
  var hotelStreetViewCoords = new GLatLng(parseFloat(mapcoords[0]), parseFloat(mapcoords[1]));
  panoramaClient.getNearestPanorama(hotelStreetViewCoords, showPanoData);
  
  function showPanoData (panoData) {
    function handleNoFlash(errorCode) {
      if (errorCode == 603) {
        if (! inCheckOnly) {
          alert("Error: Flash doesn't appear to be supported by your browser");
          return;
        } else {
          return false;
        }
      }
    }
    
    if (panoData.code != 200) {
      if (! inCheckOnly) {
        //GLog.write('showPanoData: Server rejected with code: ' + panoData.code);
        alert ( panoData.code + ': No Street View available for selected hotel');
        return;
      } else {
        return false;
      }
    }
  
    if (! inCheckOnly) {  
    	var panwin = new Ext.Window({
        layout: 'fit',
        closeAction: 'hide',
    		title: 'Street View for Hotel ' + hotelname,
    		width:500,
    		height:400,
        items: {
          xtype: 'gmappanel',
        	gmapType: 'panorama',
        	setCenter: {
            lat: parseFloat(mapcoords[0]),
        		lng: parseFloat(mapcoords[1])
        	}   
        }
    	});
    	panwin.show();
    } else {
      return true;
    }
  }
 
}


function createGoogleMap (inMapTabElement, inHotelID, inMapCoordinates) {
  // Initialize the baseIcon class & the Google Maps
  if (GBrowserIsCompatible()) {
    var map;
      
    function createMarker(inPoint, inIcon, inIDX) {
      var hotelInfoParams =  
              'id=hotelInfo' +
              '&hotelid=' + hotelsObjArray[inIDX].hotelid +
              '&cnid=' + vCountry + 
              '&ctid=' + vArea + 
              '&arid=' + vCity + 
              '&dtFrom=' + arrival.format('Ymd') + 
              '&dtTo=' + tmpDepartureDate.format('Ymd') +
              '&bc=' + $$$('Adults').value +
              		 "_" +
              		 $$$('Children').value +
              '&numRooms=' + $$$('Rooms').value +
              '&out=html' +
              '&outDetailed=0' +
              '&roomid=' + hotelsObjArray[inIDX].roomid +  
              '&roomdescr=' + hotelsObjArray[inIDX].roomdescr + 
              '&roomboard=' + hotelsObjArray[inIDX].boarddescr +
              '&roomprice=' + parseFloat(hotelsObjArray[inIDX].hotelpriceMax / 100).toFixed(2);
              
     
      var marker = new GMarker(inPoint, inIcon);
      GEvent.addListener(marker, "click", function() {
        if (hotelsObjArray[inIDX].hcatdescr.substr(0,1) == '*' ) {
          var hotelRating = hotelsObjArray[inIDX].hcatdescr.length;
          tmpHotelPricingImage = "<img src='./images/" + hotelRating + "_0_stars.gif' align='absmiddle' /><br/><br/>"
          tmpHotelPricing = "Category: " + tmpHotelPricingImage;
        } else {
          tmpHotelPricing = '';
        }
      
        tmpHotelPricing +=  'Prices for this hotel starting from  ' +
                            '<span style="font-weight:bold;">' + glCurrency + ' ' + (parseFloat(hotelsObjArray[inIDX].hotelpriceMax / 100).toFixed(2) * glCurrencyRate).toFixed(2) + '</span>' +
                            '  for  ' +
                            '<span style="font-weight:bold;">' + hotelsObjArray[inIDX].roomdescr + ' (' + hotelsObjArray[inIDX].boarddescr + ')' + '</span><br/><br/>' + 
                            '<ul>' +
                            '<li>- <a href="javascript:void(null)" onclick="doShowHotelInfoPanel(' + inIDX + ', \'Info\')">More Info</a></li>' +
                            '<li>- <a href="javascript:void(null)" onclick="getData(\'pricing\', [' + inIDX + ', \'0\'])">Book this</a></li>' +
                            '<li>- <a href="javascript:void(null)" onClick="getData(\'pricingAll\', [' + inIDX + ', \'' + hotelsObjArray[inIDX].hotelid + '\'] )">Show me Alternate Room Types</a></li>' +
                           
                            '<li>- <a href="javascript:void(null)" onClick="doShowStreetView(' + inIDX + ', false)">Street View</a><li>' +
                                                      '</ul>';
                          
        marker.openExtInfoWindow (
          map,
          "custom_info_window_red",
          "<div style='padding:3px'>" +
          "<img src='" + hotelsObjArray[inIDX].hotelimage + "' style='padding:3px;' align='left' width='80' border='0' /> " +
          "<div style='font-size:150%; background-color:#EFEFEF; padding:3px; margin:3px;'>" + hotelsObjArray[inIDX].hotelname + "</div>" + 
          "<div style='margin:3px; padding:3px;'>" + tmpHotelPricing + "</div>" +
          "</div>",
          {
//            ajaxUrl: baseURL + "&" + hotelInfoParams, 
            beakOffset: 12,
            paddingX: 5,
            moreHTML : tmpHotelPricing
          }
        )
      });
      
      return marker;      
    }
     
    // Initialize the map
  	map = new GMap2($$$(inMapTabElement));
  	map.clearOverlays();
  	map.enableScrollWheelZoom();
  	map.addMapType(G_SATELLITE_3D_MAP);
    map.addControl(new GHierarchicalMapTypeControl());
    map.addControl(new GLargeMapControl());
  	//map.enableDoubleClickZoom();
  	//map.addControl(new GLargeMapControl());
  	//map.addControl(new GMapTypeControl());
   
  	baseIcon = new GIcon();
  	//baseIcon.iconSize = new GSize(57, 80);
  	baseIcon.iconSize = new GSize(47, 66);
  	//baseIcon.shadowSize = new GSize(22, 20);
  	baseIcon.iconAnchor = new GPoint(6, 20);
  	baseIcon.infoWindowAnchor = new GPoint(5, 1);
  	 	
    var tmpStartColor = [255, 255, 0];
    var tmpEndColor   = [255,   0, 0]; // Για τα χρώματα (Κίτρινο -> Κόκκινο) αλλάζει τιμές μόνο το πράσινο (από 255 -> 0)
    var tmpColorToSubPerEuro = 255 / (glMaxValue - glMinValue);
    if (inHotelID === null) {
      var haveOnlyOneHotelToShow = false;
    } else {
      var haveOnlyOneHotelToShow = true;
    }

    for (idx=0, tmpIDX=hotelsObjArray.length; idx<tmpIDX; idx++) {
      if (hotelsObjArray[idx].shown && hotelsObjArray[idx].hotelcoords != '') {
        tmpArray1 = hotelsObjArray[idx].hotelcoords.split(',');
        var point = new GLatLng(tmpArray1[0], tmpArray1[1]);
        var tmpPrice = /*glCurrency + '|' + */(parseFloat(hotelsObjArray[idx].hotelpriceMax / 100).toFixed(2) * glCurrencyRate).toFixed(2);

        if (! haveOnlyOneHotelToShow && inHotelID === null) {
          inHotelID = hotelsObjArray[idx].hotelid;
          inMapCoordinates = hotelsObjArray[idx].hotelcoords;
        }
        
        if (! haveOnlyOneHotelToShow) {
          var tmpColor = [255, 255 - Math.round( ((hotelsObjArray[idx].hotelpriceMax / 100) - glMinValue) * tmpColorToSubPerEuro ), 0];
          baseIcon.image = 'http://chart.apis.google.com/chart?chst=d_map_spin&chld=1.2|0|' + RGB2HTML(tmpColor[0], tmpColor[1], tmpColor[2]) + '|9|_|' + tmpPrice;
          map.addOverlay( createMarker(point, baseIcon, idx) );  
        } else {
          if (hotelsObjArray[idx].hotelid != inHotelID ) {
            baseIcon.image = 'http://chart.apis.google.com/chart?chst=d_map_spin&chld=1.2|0|FF0000|10|_|' + tmpPrice;
            map.addOverlay( createMarker(point, baseIcon, idx) );
          }
        }
      }
    }
    
    tmpArray1 = inMapCoordinates.split(',');
    point = new GLatLng(tmpArray1[0], tmpArray1[1]);
    map.setCenter(point, mapZoomLevel, G_HYBRID_MAP);
    
  }
}


function doLazyLoadMapSelection() {
  if (GBrowserIsCompatible()) {
  
    plsWaitPanel.show();
    $$$('plsWaitText').innerHTML = 'Loading world map';
  
    LazyLoad.loadOnce([
      'http://www.spotahotel.com/scripts.jrv?pgid=citycoords&sid=o8llfarpe3dsf5hpta872f8uj171fj9u',
    ], doShowMapSelection, this, false, true);
  }

}

function doShowMapSelection() {
  if (GBrowserIsCompatible()) {
  
    plsWaitPanel.show();
    $$$('plsWaitText').innerHTML = 'Loading world map';
    
 
    function createMarker (inPoint, inIcon, inCityLangLongObj) {
      var retMarker = new GMarker(inPoint, {icon: inIcon, title:inCityLangLongObj.title});
      GEvent.addListener(retMarker, "click", function() {
        mapsWin.hide();
      	$$$('acGeoSearch').value				= inCityLangLongObj.title;
      	preSelectCountryObj.country			= inCityLangLongObj.country;
      	preSelectCountryObj.city				= inCityLangLongObj.city;
      	preSelectCountryObj.geoname			= inCityLangLongObj.title;
      	preSelectCountryObj.weatherCode = inCityLangLongObj.weather;
      });
      
      return retMarker;
    }
      
  	var mapsWin = new Ext.Window({
      layout: 'fit',
      closeAction: 'hide',
  		title: 'Select city from map',
  		width:650,
  		height:400,
      items: {
        id : 'skata',
        xtype: 'gmappanel',
      	gmapType: 'map',
        zoomLevel: 1,
        setCenter: {
          lat: parseFloat(cityLatLongObjArray[0].lat),
          lng: parseFloat(cityLatLongObjArray[0].lng)
        }     
      }
  	});
  	mapsWin.show();

    var mapSelect = Ext.getCmp('skata').getMap();
    mapSelect.enableScrollWheelZoom();
    mapSelect.addControl(new GLargeMapControl());
    mapSelect.clearOverlays(mapSelect);
    GEvent.clearListeners(mapSelect);
    
    var markers = [];
    var icon = new GIcon(G_DEFAULT_ICON);
    for (idx=0; idx<cityLatLongObjArray.length; idx++) {
      var point   = new GLatLng(cityLatLongObjArray[idx].lat, cityLatLongObjArray[idx].lng);
      markers[idx] = createMarker(point, icon, cityLatLongObjArray[idx]);
    }
    var markerCluster = new MarkerClusterer(mapSelect, markers, {gridSize: 60});
    
    if (plsWaitPanel) {
      plsWaitPanel.hide();
    }
  }
}


function doShowURL(inURL, inTitle, inID, inWidth, inHeight) {
  if (! Ext.getCmp(inID) ) {
    if (inWidth == undefined || inHeight == undefined) {
      if (screen.width > 1024) {
        inWidth = 950;
      } else {
        inWidth = 750;
      }
      inHeight = 500;
    }
  	var win = new Ext.Window( {
  	  id: inID,
      layout: 'fit',
      closable: true,
      closeAction: 'close',
      shim: true,
      modal:false,
      collapsible: false,
      width: inWidth,
      height: inHeight,
      autoscroll: true,
      autoHeight: false,
      resizable: true,
      html: '<iframe src="' + inURL + '" width="100%" height="100%" frameborder="0"></iframe>',
      title: inTitle,
      buttons: [ {
        text: 'Close Window',
        handler: function() {
          Ext.getCmp(inID).html = '';       
          Ext.getCmp(inID).close();
        }
      }]
    });
  }
  Ext.getCmp(inID).show();
}

function showHideDealsHotelDescription(inDescrDIV, inLabelDIV, inHotelName) {
  if (! Ext.getCmp('winHotelDescr' + inLabelDIV) ) {
  	var win = new Ext.Window( {
  	  id: 'winHotelDescr' + inLabelDIV,
      layout: 'fit',
      closable: true,
      closeAction: 'close',
      shim: true,
      modal:false,
      collapsible: false,
      width: 400,
      height: 250,
      autoscroll: false,
      autoHeight: true,
      resizable: true,
      html: $$$(inDescrDIV).innerHTML,
      title: 'Description for Hotel ' + inHotelName,
      buttons: [ {
        text: 'Close Window',
        handler: function() {
          Ext.getCmp('winHotelDescr' + inLabelDIV).html = '';       
          Ext.getCmp('winHotelDescr' + inLabelDIV).close();
        }
      }]
    }); 
  }
  Ext.getCmp('winHotelDescr' + inLabelDIV).show();
  
  /*
  if ($$$(inDescrDIV).style.display == '') {
   $$$(inDescrDIV).style.display = 'none';
    $$$(inLabelDIV).innerHTML = 'Show Hotel Description'; 
  } else {
    $$$(inDescrDIV).style.display = '';
    $$$(inLabelDIV).innerHTML = 'Hide Hotel Description';
  }
  */
}

function doChangeLang(inLang) {
  if (inLang != 'EN') {
    window.location = '?lang=' + inLang + '&currency=' + glCurrency;
  }
}