// Content: js core
//
// Wrapper class which is used to implement rudimentary inheritance.
// Derived class obtains a base pointer after inheritance, which when
// called in its constructor initializes the base class.
//
var Class = {
    extend: function(derivedClass, baseClass)
    {
        derivedClass.prototype.base = function()
        {
            return baseClass.prototype.constructor.apply(this, arguments);
        }
    }   
}


//
// This class is used for all custom DOM and JavaScript event handling
//
function Event() 
{
    ///////////////////////////////////
    // Event Definition
    ///////////////////////////////////

    //
    // Private data members
    //  
    var listeners       = new Array();      // Holds all listeners attached to this Event instance, when fire() is called
                                            // they will all be called sequentially with as argument(s) the argument(s) with
                                            // which the event listener was created as well as the argument(s) which are passed
                                            // with the call to the fire() method.


    //
    // Public methods
    //
    this.addListener    = null;             // Used to add an event listener to this Event instance's listener array.
    this.removeListener = null;             // Used to remove an event listener with a certain signature from this Event 
                                            // instance's listener array, because listeners created using the EventHandler
                                            // class all have the same signature, this method can not be used to remove them.
    this.fire           = null;             // Method used to notify all listeners that an event has occured, it will call
                                            // all listeners attached to this Event instance sequentially.
    

    ///////////////////////////////////
    // Event Implementation
    ///////////////////////////////////
    
    //
    // Public methods
    //    
    
    //
    // listener: A valid EventHandler instance (core.js)
    //
    this.addListener = function(listener) 
    { 
        listeners[listeners.length] = listener;
    }   

    //
    // listener: A valid signature of the listener to remove from the listener queue
    //
    this.removeListener = function(listener)
    {
        for (var i=0; i<listeners.length; i++)
        {
            if (listeners[i] == listener)
            {
                for (var j=i; j<listeners.length; j++)
                    listener[j] = listener[j+1];
                listeners.length--
                break;
            }
        }
    }
    
    //
    // data: An argument object, can be used to pass event related data from the Event instance to all its listeners (OPTIONAL)
    //
    this.fire = function(data) 
    {
        for (var i=0; i<listeners.length; i++)
            listeners[i](data);
    }
}


//
// Use this class to create event handlers which are executed in any given scope.
// By using a 'closure' on the scope variable, it is possible to have event handling
// where 'this' points to the correct class instead of the classic 'this' where it points
// to the element on which the event was triggered.
//
function EventHandler(scope, handler) 
{
    ///////////////////////////////////
    // EventHandler Definition
    ///////////////////////////////////

    //
    // Private data members
    //
    var handlerArgs = null;
    
    
    ///////////////////////////////////
    // EventHandler Constructor
    ///////////////////////////////////
    var handlerArgs = arguments;
    
    //
    // When an EventHandler instance is created, a function is returned in which the scope and the handler specified in the
    // constructor arguments are embedded (closure). When called (mostly by Event.fire()) that function will get executed
    // which in turn executes the specified handler in the provided scope.
    //
    return function() {
        var eventArgs = new Array();
        
        // Concatenate any arguments provided by the call to the handler
        // to the eventArgs array
        for (var i=2; i<handlerArgs.length; i++)
            eventArgs.push(handlerArgs[i]);

        // Concatenate any arguments provided during handler creation to
        // the eventArgs array
        for (var i=0; i<arguments.length; i++)
            eventArgs.push(arguments[i]);

        // Call the method wrapped inside this handler (i.e. execute the 'closure')
        handler.apply(scope, eventArgs);
    }
}


//
// DOM namespace, in here any DOM wrapper methods can be found
//
var DOM = {};


//
// Browser independent method used to add event listeners to DOM element events
//
// element      : The DOM element in question (object)
// eventName    : The name of the event to which to subscribe (string without 'on')
// listener     : The signature of the callback function which should be called when the event is fired
//
DOM.addEventListener = function(element, eventName, listener)
{
    try {
        if (element.addEventListener)
            element.addEventListener(eventName, listener, false);
        else if (element.attachEvent)
            element.attachEvent('on'+eventName, listener);
        else
            element['on'+eventName] = listener;
    }
    catch (e) {
        throw (e.message);  
    }
}


//
// Browser independent method used to remove event listeners from DOM element events.
// Cannot be used on listeners created using the EventHandler class, since these instances
// all share the same signature.
//
// element      : The DOM element in question (object)
// eventName    : The name of the event from which to unsubscribe (string without 'on')
// listener     : The signature of the callback function which should be detached
//
DOM.removeEventListener = function(element, eventName, listener)
{
    try {
        if (element.removeEventListener)
            element.removeEventListener(eventName, listener, true);
        else if (element.detachEvent)
            element.detachEvent('on'+eventName, listener);
        else
            element['on'+eventName] = null;
    }
    catch (e) {
        throw (e.message);  
    }
}


//
// SystemEventArgs is the base class for all custom event argument objects (event data).
// Instances of this class provide easy-to-use wrapped access to event targets and a way
// to determine if the event took place within a certain parent (isChildOf method)
//
// e:   A native event arguments object, if none is natively provided by the browser the constructor will get it in an 
//      alternative way (IE)
//
function SystemEventArgs(e)
{
    if (!e) e = window.event;   
    this.target = e.target ? e.target : e.srcElement;
    this.target = this.target && this.target.nodeType == 3 ? this.target.parentNode : this.target;

    //
    // This method can be used to check if an event was triggered within one of the DOMElement's
    // child elements
    //
    // DOMElement:  The parent element for which to check
    //
    this.target.isChildOf = function(DOMElement) {
        var node = this;
        while (node && node != DOMElement && node.tagName != "BODY")
            node = node.parentNode;
        return (node.tagName != "BODY");    
    }
}


//
// Event argument object used for keyboard events, extends SystemEventArgs
//
// e:   A native event arguments object, necessary for base class construction, if none is provided the base class constructor 
//      will get it.
//
function KeyPressEventArgs(e) 
{
    //
    // Create an instance of the SystemEventArgs class (base class)
    //
    this.base(e);
    
    //
    // Contains the key code of the key which was pressed, if used in comparisons compare against
    // the virtual keys (VK_xxx) mapped at the bottom of this file (core.js) for enhanced code readability.
    //
    this.keyPressed = e.charCode ? e.charCode : e.keyCode;
}

//
//  KeyPressEventArgs should derive from SystemEventArgs, this call supplies KeyPressEventArgs with the base class contructor 
//  (this.base() method)
//
Class.extend(KeyPressEventArgs, SystemEventArgs);


//
// Event argument object used for mouse click events, extends SystemEventArgs
//
// e:   A native event arguments object, necessary for base class construction, if none is provided the base class constructor 
//      will get it.
//
function ClickEventArgs(e) 
{
    //
    // Create an instance of the SystemEventArgs class (base class)
    //
    this.base(e);
    
    //
    // Find out in a browser independent way which mouse button was pressed, if used in comparisons compare against
    // the virtual mouse buttons (VM_xxx) mapped at the bottom of this file (core.js) for enhanced code readability.
    //
    this.buttonPressed = e.which ? e.which : e.button;
    if (this.buttonPressed == 0 || this.buttonPressed == 1)
        this.buttonPressed = 1;
    else if (this.buttonPressed == 2 || this.buttonPressed == 3)
        this.buttonPressed = 2;
    else if (this.buttonPressed != 0)
        this.buttonPressed == 4
}

//
//  ClickEventArgs should derive from SystemEventArgs, this call supplies ClickEventArgs with the base class contructor 
//  (this.base() method)
//
Class.extend(ClickEventArgs, SystemEventArgs);


//
// Event argument object used for the MouseOver event, extends SystemEventArgs
//
// e:   A native event arguments object, necessary for base class construction, if none is provided the base class constructor 
//      will get it.
//
function MouseOverEventArgs(e)
{
    //
    // Create an instance of the SystemEventArgs class (base class)
    //
    this.base(e);
}

//
//  MouseOverEventArgs should derive from SystemEventArgs, this call supplies MouseOverEventArgs with the base class contructor 
//  (this.base() method)
//
Class.extend(MouseOverEventArgs, SystemEventArgs);


//
// Wrapper method to reduce code when checking if a variable is uninitialized.
//
// object: The object instance (can be any kind of object) which to check
//
function IsNull(object)
{
    return (object == 'undefined' || object == null);   
}


//
// This method provides 'printf-like' functionality, natively in the String class meaning it is available on any string
//
// str              : The parameterized string on which to perform the formatting
// extra arguments  : Used to replace a parameter ({n}, where n >=0 and {0} matches 2nd argument {1} the 3rd and so on).
//                    If there is not a matching argument for a placeholder, the placeholder will show up in the result string
//                    unmodified.
//
// Usage:             String.format("User {0} has logged in as {1} ...", username, role, ....)
// Example:           For username = 'admin' and role='administrator' => 'User admin has logged in as administrator'
//
String.format = function(str)
{
    //
    // Method works only with string objects
    //
    if (str && (typeof(str) == "string" || str instanceof String))
    {
        //
        // Loop over the extra arguments (replacement values)
        //
        for (var i=1; i<arguments.length; i++)
        {
            //
            // If the value is a string object the replacement value is used as is
            //
            if (typeof(arguments[i]) == "string" || arguments[i] instanceof String)
                str = str.replace("{"+(i-1)+"}", arguments[i]);
            
            //
            // else if the value is for example an object, we need to make it a valid string
            // in order to be allowed to execute a regular expression, so the toString() method,
            // which all object in JavaScript have is called, in most cases this will result in
            // the class name of the object, but at least the format will not fail.
            //
            else
                str = str.replace("{"+(i-1)+"}", arguments[i].toString());
        }
    }
    return str;
}

//
// If input string is not UTF16 it will be return unmodified
//
String.UTF16toUTF8 = function(inStr)
{
	function _to_utf8(s) {
	  var c, d = "";
	  for (var i = 0; i < s.length; i++) {
	    c = s.charCodeAt(i);
	    if (c <= 0x7f) {
	      d += s.charAt(i);
	    } else if (c >= 0x80 && c <= 0x7ff) {
	      d += String.fromCharCode(((c >> 6) & 0x1f) | 0xc0);
	      d += String.fromCharCode((c & 0x3f) | 0x80);
	    } else {
	      d += String.fromCharCode((c >> 12) | 0xe0);
	      d += String.fromCharCode(((c >> 6) & 0x3f) | 0x80);
	      d += String.fromCharCode((c & 0x3f) | 0x80);
	    }
	  }
	  return d;
	}
	
	function _from_utf8(s) {
	  var c, d = "", flag = 0, tmp;
	  for (var i = 0; i < s.length; i++) {
	    c = s.charCodeAt(i);
	    if (flag == 0) {
	      if ((c & 0xe0) == 0xe0) {
	        flag = 2;
	        tmp = (c & 0x0f) << 12;
	      } else if ((c & 0xc0) == 0xc0) {
	        flag = 1;
	        tmp = (c & 0x1f) << 6;
	      } else if ((c & 0x80) == 0) {
	        d += s.charAt(i);
	      } else {
	        flag = 0;
	      }
	    } else if (flag == 1) {
	      flag = 0;
	      d += String.fromCharCode(tmp | (c & 0x3f));
	    } else if (flag == 2) {
	      flag = 3;
	      tmp |= (c & 0x3f) << 6;
	    } else if (flag == 3) {
	      flag = 0;
	      d += String.fromCharCode(tmp | (c & 0x3f));
	    } else {
	      flag = 0;
	    }
	  }
	  return d;
	}
	
	var _attempted_utf8 = _to_utf8(inStr);
	var _attempted_utf16 = _from_utf8(_attempted_utf8);
	if (inStr == _attempted_utf16)
		return _attempted_utf8;
	else
		return inStr;
}

//
// This method can be used to left pad strings to a given length with a given character.
// It is added to the String objects prototype and can therefore be called on any valid
// string instance.
//
// char             : String representing the character with which to pad this string
// maxPaddingLength : Integer, maxPaddingLength - string length == the amount of padding characters
//
String.prototype.pad = function(char, maxPaddingLength)
{
    var ret = this;
    var paddingCount = maxPaddingLength - this.length;
    for (var i=0; i<paddingCount; i++)
        ret = "0" + this;
    return ret;
}


//
// GLOBAL DEFINES
//
var ASCENDING = 1;      // Used for sorting (i.e. in generic sort method and timetable.js)
var DESCENDING = -1;    // Used for sorting (i.e. in generic sort method and timetable.js)
var BACK = -1;          // Used for navigation (i.e. in timetable.js and resultpage.js when scrolling through weeks)
var FORWARD = 1;        // Used for navigation (i.e. in timetable.js and resultpage.js when scrolling through weeks)


//
// This sort method can be used to extend normal sort method with a direction parameter.
//
// a            : sort value
// b            : sort value
// direction    : sort direction, use one of the global defines i.e. ASCENDING (default when none provided) or DESCENDING
//
function sort(a, b, direction)
{
    // The default direction is ASCENDING when none has been provided 
    direction = IsNull(direction) ? ASCENDING : direction;
    
    // a should precede b
    if (a < b)
        return (direction == ASCENDING ? -1 : 1);
    
    // a equals b no sort nescessary, order between the two remains unchanged
    else if (a == b)
        return 0;

    // b should precede a
    else
        return (direction == ASCENDING ? 1 : -1);
}


//
// This class wraps urls and exposes all parts of the url through properties
//
// rawUri: valid uri like document.location, this uri will be parsed into an easy to access instance
//
function Uri(rawUri)
{
    ///////////////////////////////////
    // Uri Definition
    ///////////////////////////////////
    
    //
    // Private data members
    //
    var url             = null;     // original uri is stored here

    
    //
    // Public properties
    //
    this.protocol       = null;
    this.host           = null;
    this.port           = null;
    this.pathname       = null;
    this.search         = null;     // The original querystring without "?" character
    this.hash           = null;     // contains any '#' anchor references

    
    //
    // Public methods
    //
    this.toString       = null;     // Overwrites default toString, instead of outputting the class name it outputs the 
                                    // complete URI as string

    
    //
    // Public objects
    //
    this.querystring    = null;     // Wrapper object for the query string


    ///////////////////////////////////
    // Uri Implementation
    ///////////////////////////////////

    //
    // Public methods
    //

    this.toString = function()
    {
        return (this.protocol+ "://" + this.host + (this.port.length ? ":"+this.port : "") + this.pathname + this.querystring.toString() + this.hash);
    }    
   

    //
    // Public objects
    //   
    
    this.querystring = new function()
    {
        ///////////////////////////////////
        // Querystring Definition
        ///////////////////////////////////
        
        //
        // Private data members
        //
        var params          = {};       // Object which will contain all query string parameter objects (key-value pairs)
        
        
        //
        // Public methods
        //
        this.contains       = null;     // This method indicates, by parameter name, if a certain parameter is present 
                                        // the query string
        this.addParam       = null;     // Adds a parameter to this query string's parameter collection
        this.removeParam    = null;     // Removes a parameter from this query string's parameter collection
        this.getParam       = null;     // Returns, if it exists in the parameter collection, the parameter with the 
                                        // specified name (key)
        this.toString       = null;     // Overwrites the native toString and instead of returning the class name it will now
                                        // return the entire query string and all its parameters as string
        
        
        ///////////////////////////////////
        // Querystring Implementation
        ///////////////////////////////////
        
        //
        // Public methods
        //
        
        //
        // name     : name of the parameter checked
        //
        this.contains = function(name)
        {
            return (typeof(params[name]) != null && typeof(params[name]) != 'undefined')
        }
        
        
        //
        // name     : name of the parameter to add to the parameter collection
        // value    : value of the same parameter
        //
        this.addParam = function(name, value)
        {
            params[name] = value;
        }
        

        //
        // name     : name of the parameter to remove from the parameter collection, 
        //            the parameter will be removed if it exists, if not the method will just exit
        //
        this.removeParam = function(name)
        {
            if (this.contains(name))
                delete params[name];
        }


        //
        // name     : name of the parameter to retrieve, if it exists the parameter object (key-value) will be returned, if not
        //            null is returned
        //
        this.getParam = function(name)
        {
            if (this.contains(name))
                return params[name];
            else
                return null;
        }
                
        
        this.toString = function()
        {
            var string = "";
            for (var key in params)
            {
                if (key.length)
                    string += "&"+escape(key)+"="+escape(params[key]);
            }
            if (string.length)
                string = string.replace(/&/i, "?");  // replace the leading '&' with '?' to create valid query string
            return string;
        }
    };


    ///////////////////////////////////
    // Uri Constructor
    ///////////////////////////////////
    url = rawUri;

    this.protocol = url.protocol.substring(0, url.protocol.length-1);   // strip ":" character
    this.host = url.hostname;
    this.port = url.port;
    this.pathname = url.pathname;
    this.search = url.search.replace(/\?/i, "");                        // strip "?" character
    this.hash = url.hash;

    //
    // Parse the querystring into escaped key-value pairs
    //
    var keyValuePairs = this.search.split('&');
    for (var i=0; i<keyValuePairs.length; i++)
    {
        var keyValuePair = keyValuePairs[i].split('=');
        var key = unescape(keyValuePair[0]);
        var value = unescape(keyValuePair[1]);
        this.querystring.addParam(key, value);
    }   
}


//
// Use this class to wrap generic functionality around any DOM element
//
// DOMElement: The element which to augment with the extra functionality provided by this class
//
var GenericContainer = function(DOMElement)
{
    ///////////////////////////////////
    // GenericContainer Definition
    ///////////////////////////////////    

    //
    // Public properties
    //
    this.isVisible      = null;


    //
    // Public methods
    //
    this.show           = null;     // Used to show the element through its display attribute and if necessary its parent element
    this.hide           = null;     // Used to hide the element through its display attribute and if necessary its parent element
    this.setInnerHTML   = null;
    this.setClassName   = null;
    
    
    ///////////////////////////////////
    // GenericContainer Implementation
    ///////////////////////////////////
    
    //
    // Public methods
    //
    
    //
    // applyToParent: boolean, indicates if the parent element should also be shown
    //
    this.show = function(applyToParent)
    {
        this.isVisible = true;
        DOMElement.style.display = "";
        if (applyToParent && DOMElement.parentNode)
            DOMElement.parentNode.style.display = "";
    }
    
    //
    // applyToParent: boolean, indicates if the parent element should also be hidden
    //    
    this.hide = function(applyToParent)
    {
        this.isVisible = false;
        DOMElement.style.display = "none";
        if (applyToParent && DOMElement.parentNode)
            DOMElement.parentNode.style.display = "none";
    }
    
    
    //
    // value : string which to assign to the embedded DOM element's innerHTML
    //
    this.setInnerHTML = function(value)
    {
        DOMElement.innerHTML = value.toString();
    }    
    
    //
    // value : string, class name which to assign to the embedded DOM element's className
    //
    this.setClassName = function(value)
    {
        DOMElement.className = value.toString();
    }
    
    
    ///////////////////////////////////
    // GenericContainer Constructor
    ///////////////////////////////////    
    this.isVisible = true;

    if (!DOMElement.style || (DOMElement.style.display && DOMElement.style.display == "none"))
        this.isVisible = false;
}


//
// DateLib namespace, in here any Date manipulation methods can be found, also some extensions on the 
// native Data class can be found here
//
var DateLib = {};


//
// Checks if a given value represents a valid date
//
// date : a valid Date instance or a string formatted as: "yyyymmdd"
//
DateLib.isDate = function(value) 
{ 
    return !IsNull(value) && (value instanceof Date || DateLib.parse(value)); 
}


//
// Parse a value representing a date to a valid Date instance.
//
// date : a valid Date instance or a string formatted as: "yyyymmdd".
//
DateLib.parse = function(value)
{
    if (value instanceof Date)
    {
        return value;
    }
    else if ((typeof(value) == "string" || typeof(value) == "number") && value.toString().length == 8)
    {
        value = value.toString();
    
        var date = new Date();
        date.setFullYear(parseInt(value.substring(0,4), 10), parseInt(value.substring(4,6), 10), parseInt(value.substring(6,8), 10));
        date.setHours(0,0,0,0);

        return date;
    }
    return null;
}


//
// This method returns the day of the week for a given, where 0 represents Monday and 6 represents Sunday
// 
// date : Date instance or string formatted as "yyyymmdd"
//
DateLib.getDayOfWeek = function(date)
{
    if (DateLib.isDate(date))
    {
        date = DateLib.parse(date);
        return date.getDay() - 1 >= 0 ? date.getDay() - 1 : 6;
    }
    else
        return null;
}


//
// This method return the number of days for a given month in a given year, taking leap years into account.
//
// year     : integer representing the year
// month    : integer representing the month (>= 1 and <= 12) for which the number of days should be returned
//
DateLib.getNrDaysInMonth = function(year, month)
{
    var months = { 1:31, 2:undefined, 3:31, 4:30, 5:31, 6:30, 7:31, 8:31, 9:30, 10:31, 11:30, 12:31 };
    if (IsNull(year) || IsNull(month) || month < 1 || month > 12) 
        return 0;
    months["2"] = ((year % 4 == 0) ^ (year % 100 == 0) ^ (year % 400 == 0)) ? 29 : 28;
    return months[parseInt(month, 10)];
}


//
// This array ensures all the months (short name and long name) are localized per language upon publication. Used in calendar.js 
// and datepicker.js
//
DateLib.monthNames = [
    ['Jan','January'],
    ['Feb','February'],
    ['Mar','March'],
    ['Apr','April'],
    ['May','May'],
    ['Jun','June'],
    ['Jul','July'],
    ['Aug','August'],
    ['Sep','September'],
    ['Oct','October'],
    ['Nov','November'],
    ['Dec','December']
];


//
// Array with localized shortNames for the days in the week. Used in calendar.js and datepicker.js
//
DateLib.dayNames = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];


//
// Returns the index of the week day of the first day for the given month in the given year where 0 = Monday and 6 = Sunday
//
// year     : integer representing the year
// month    : integer representing the month (>= 1 and <= 12) for which the day index of the first day should be returned
//
DateLib.getFirstDayInMonth = function(year, month)
{   
    if (IsNull(year) || IsNull(month) || month < 1 || month > 12)
        return null;
    var date = new Date(year, month-1, 1);
    date.setHours(12);
    return (DateLib.getDayOfWeek(date));
}


//
// By adding this method to the Date prototype, any Date instance can return only its date part where the time is normalized.
// Used when date comparision is nescessary and two equal dates with different timestamps should be considered to be equal.
//
Date.prototype.toDate = function()
{
    return new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0, 0);
}


//
// Can be called on any valid Date instance to see if it equals a provided Date instance or valid date string formatted like "yyyymmdd"
//
// date : a valid Date instance or a string formatted as: "yyyymmdd".
//
Date.prototype.equals = function(date)
{
    if (!DateLib.isDate(date))
        return false;
    
    date = DateLib.parse(date);
    
    return (this.getTime() == date.getTime());
}

//
// Used to add an amount of years, months, days, hours, minutes, seconds or milliseconds to a Date instance's value.
//
// type :   "Y"  = add years,
//          "M"  = add months,
//          "D"  = add days,
//          "h"  = add hours,
//          "m"  = add minutes,
//          "s"  = add seconds,
//          "ms" = add milliseconds
// value:   the amount of 'type' to add to this date
//
// NOTE:    when adding or subtracting months only the month index is changed, meaning month lengths are disregarded
//
Date.prototype.add = function(type, value)
{
    var types = {"Y":1, "M":1, "D":1, "h":1, "m":1, "s":1, "ms":1}
    
    if (IsNull(type) || IsNull(types[type]) || IsNull(value) || typeof(value) != "number" || value == 0)
        return this.copy();

    var result = this.copy();
        
    switch (type)
    {
        case "Y":
            var nrDays = DateLib.isLeapYear(this.getFullYear()) ? 366 : 365;
            result.setTime(this.getTime() + (value*nrDays*24*60*60*1000));
            break;
        case "M":
            result.setFullYear(this.getFullYear()+Math.floor(value/12));
            var month = this.getMonth() + (value % 12);
            if (month < 0)
                result.setMonth(12 - Math.abs(month));
            else if (month > 11)
                result.setMonth(month % 12);
            break;
        case "D":
            result.setTime(this.getTime() + (value*24*60*60*1000));
            break;      
        case "h":
            result.setTime(this.getTime() + (value*60*60*1000));
            break;          
        case "m":
            result.setTime(this.getTime() + (value*60*1000));
            break;          
        case "s":
            result.setTime(this.getTime() + (value*1000));
            break;          
        case "ms":
            result.setTime(this.getTime() + value);     
            break;      
    }
    return result;
}


//
// This methods returns a deep copy of the Date instance
//
Date.prototype.copy = function()
{
    var copy = new Date();
    copy.setTime(this.getTime());
    return copy;
}


//
// This array is used to map carrier codes to images representing their logo. 
// Used in timetable.js
//
var carrierLogos = {
    "9W": {src: "/travel/ie_en/images/9W-Jet-Airways_25x25_tcm106-117147.gif", alt: "Jet Airways logo"},
    "AF": {src: "/travel/ie_en/images/AF-Air-France_25x25_tcm106-117148.gif", alt: "Air France logo"},
    "AM": {src: "/travel/ie_en/images/AM-Aeromexico_25x25_tcm106-117149.gif", alt: "Airomexico logo"},
    "AZ": {src: "/travel/ie_en/images/AZ-Alitalia_25x25_tcm106-117150.gif", alt: "Alitalia logo"},
    "CM": {src: "/travel/ie_en/images/CM-COPA-Airlines_25x25_tcm106-117151.gif", alt: "COPA Airlines logo"},
    "CO": {src: "/travel/ie_en/images/CO-Continental-Airlines_25x_tcm106-117152.gif", alt: "Continental Airlines logo"},
    "CZ": {src: "/travel/ie_en/images/CZ-China-Southern-Airlines__tcm106-117153.gif", alt: "China Southern Airlines logo"},
    "DL": {src: "/travel/ie_en/images/DL-Delta-Air-Lines_25x25_tcm106-117154.gif", alt: "Delta Airlines logo"},
    "KE": {src: "/travel/ie_en/images/KE-Korean-Air_25x25_tcm106-117155.gif", alt: "Korean Air logo"},
    "KL": {src: "/travel/ie_en/images/KL-Royal-Dutch-Airlines_25x_tcm106-117156.gif", alt: "Royal Dutch Airlines logo"},
    "KQ": {src: "/travel/ie_en/images/KQ-Kenya-Airways_25x25_tcm106-117157.gif", alt: "Kenya Airways logo"},
    "MP": {src: "/travel/ie_en/images/MP-Martinair-Holland_25x25_tcm106-117158.gif", alt: "Martinair Holland logo"},
    "NW": {src: "/travel/ie_en/images/NW-Northwest-Airlines_25x25_tcm106-117159.gif", alt: "Northwest Airlines logo"},
    "OK": {src: "/travel/ie_en/images/OK-Czech-Airlines_25x25_tcm106-117160.gif", alt: "Czech Airlines logo"},
    "SU": {src: "/travel/ie_en/images/SU-Aeroflot_25x25_tcm106-117161.gif", alt: "Aeroflot logo"},
    "UX": {src: "/travel/ie_en/images/UX-Air-Europa_25x25_tcm106-117162.gif", alt: "Air Europa logo"},
    "DEFAULT": {src: "/travel/ie_en/images/no-logo_tcm106-118251.gif", alt: ""}
}


//
// Keyboard defines. All keycodes are mapped here to virtual keys.
// Use these virtual keys to make keyboard readout code more readable.
//
var VM_BUTTONLEFT = 1;
var VM_BUTTONMIDDLE = 4;
var VM_BUTTONRIGHT = 2;
var VK_BACKSPACE = 8;
var VK_TAB = 9;
var VK_ENTER = 13;
var VK_SHIFT = 16;
var VK_CTRL = 17;
var VK_ALT = 18;
var VK_PAUSEBREAK = 19;
var VK_CAPSLOCK = 20;
var VK_ESCAPE = 27;
var VK_SPACE = 32;
var VK_PAGEUP = 33;
var VK_PAGEDOWN = 34;
var VK_END = 35;
var VK_HOME = 36;
var VK_ARROWLEFT = 37;
var VK_ARROWUP = 38;
var VK_ARROWRIGHT = 39;
var VK_ARROWDOWN = 40;
var VK_INSERT = 45;
var VK_DELETE = 46;
var VK_0 = 48;
var VK_1 = 49;
var VK_2 = 50;
var VK_3 = 51;
var VK_4 = 52;
var VK_5 = 53;
var VK_6 = 54;
var VK_7 = 55;
var VK_8 = 56;
var VK_9 = 57;
var VK_A = 65;
var VK_B = 66;
var VK_C = 67;
var VK_D = 68;
var VK_E = 69;
var VK_F = 70;
var VK_G = 71;
var VK_H = 72;
var VK_I = 73;
var VK_J = 74;
var VK_K = 75;
var VK_L = 76;
var VK_M = 77;
var VK_N = 78;
var VK_O = 79;
var VK_P = 80;
var VK_Q = 81;
var VK_R = 82;
var VK_S = 83;
var VK_T = 84;
var VK_U = 85;
var VK_V = 86;
var VK_W = 87;
var VK_X = 88;
var VK_Y = 89;
var VK_Z = 90;
var VK_LEFTWINDOWKEY = 91;
var VK_RIGHTWINDOWKEY = 92;
var VK_SELECTKEY = 93;
var VK_NUMPAD0 = 96;
var VK_NUMPAD1 = 97;
var VK_NUMPAD2 = 98;
var VK_NUMPAD3 = 99;
var VK_NUMPAD4 = 100;
var VK_NUMPAD5 = 101;
var VK_NUMPAD6 = 102;
var VK_NUMPAD7 = 103;
var VK_NUMPAD8 = 104;
var VK_NUMPAD9 = 105;
var VK_MULTIPLY = 106;
var VK_ADD = 107;
var VK_SUBTRACT = 109;
var VK_DECIMALPOINT = 110;
var VK_DIVIDE = 111;
var VK_F1 = 112;
var VK_F2 = 113;
var VK_F3 = 114;
var VK_F4 = 115;
var VK_F5 = 116;
var VK_F6 = 117;
var VK_F7 = 118;
var VK_F8 = 119;
var VK_F9 = 120;
var VK_F10 = 121;
var VK_F11 = 122;
var VK_F12 = 123;
var VK_NUMLOCK = 144;
var VK_SCROLLLOCK = 145;
var VK_SEMICOLON = 186;
var VK_EQUALSIGN = 187;
var VK_COMMA = 188;
var VK_DASH = 189;
var VK_PERIOD = 190;
var VK_FORWARDSLASH = 191;
var VK_GRAVEACCENT = 192;
var VK_OPENBRACKET = 219;
var VK_BACKSLASH = 220;
var VK_CLOSEBRAKET = 221;
var VK_SINGLEQUOTE = 222;

//
// MAKE DWR ACCEPT UTF16 BY OVERRIDING BATCH FUNCTIONALITY IN THE ENGINE BY CORRECT CODE
//
if (dwr && dwr.engine && dwr.engine._createBatch)
{
	dwr.engine._createBatch = function() {
		var batch = {
			map:{
				callCount:0,
				page:String.UTF16toUTF8(unescape(window.location.pathname + window.location.search)),
				httpSessionId:dwr.engine._getJSessionId(),
				scriptSessionId:dwr.engine._getScriptSessionId()
			},
			charsProcessed:0, paramCount:0,
			headers:[], parameters:[],
			isPoll:false, headers:{}, handlers:{}, preHooks:[], postHooks:[],
			rpcType:dwr.engine._rpcType,
			httpMethod:dwr.engine._httpMethod,
			async:dwr.engine._async,
			timeout:dwr.engine._timeout,
			errorHandler:dwr.engine._errorHandler,
			warningHandler:dwr.engine._warningHandler,
			textHtmlHandler:dwr.engine._textHtmlHandler
		};
		if (dwr.engine._preHook) 
			batch.preHooks.push(dwr.engine._preHook);
		if (dwr.engine._postHook) 
			batch.postHooks.push(dwr.engine._postHook);
		var propname, data;
		if (dwr.engine._headers) {
			for (propname in dwr.engine._headers) {
				data = dwr.engine._headers[propname];
				if (typeof data != "function")
					batch.headers[propname] = data;
			}
		}
		if (dwr.engine._parameters) {
			for (propname in dwr.engine._parameters) {
				data = dwr.engine._parameters[propname];
				if (typeof data != "function") 
					batch.parameters[propname] = data;
			}
		}
		return batch;
	}
}
// Content: js input
//
// Class used to wrap <INPUT> and <TEXTAREA> elements for easier use in JavaScript
//
// DOMInputElement  : <INPUT> or <TEXTAREA> element 
//
// Dependencies     : core.js
//
function Input(DOMInputElement)
{
    ///////////////////////////////////
    // Input Definition
    /////////////////////////////////// 

    //
    // Private data members
    //
    var previousValue       = "";           // Store last entered value to check if there are changes
    var hasChanged          = false;        // Boolean indicating if there are changes in the input value (case insensitive)
    var hasFocus            = false;        // Boolean indicating if the Input instance has focus
    var hasNativeLostFocus  = false;        // Boolean indicating if native onlostfocus (DOM) event has been 
                                            // implemented on the element, if not onclick is used


    //
    // Events
    //
    this.click              = new Event();  // Event fired when one of the mouse buttons has been pressed
    this.keyUp              = new Event();  // Event fired when a key has been released
    this.keyDown            = new Event();  // Event fired when a key has been pressed
    this.lostFocus          = new Event();  // Event fired when this instance loses focus


    //
    // Event handlers
    //
    var onClick             = null;         // Event handler called when DOM element registers an click event
    var onKeyUp             = null;         // Event handler called when DOM element registers a keyup event
    var onKeyDown           = null;         // Event handler called when DOM element registers a keydown event
    var onLostFocus         = null;         // Event handler called when DOM element registers a native lostfocus 
                                            // event if implemented by DOM or a native click in any place other 
                                            // than this control which is implemented by hooking the click event 
                                            // of the document object
    
    
    //
    // Properties
    //
    this.getLength          = null;         // Returns the length of the text input 
    this.getValue           = null;         // Returns the text input
    this.setValue           = null;         // Sets the text input of this instance
    this.setClassName       = null;         // Sets the class name to reflect the provided value
    
    
    //
    // Public methods
    //
    this.hasChanged         = null;         // Returns true if the current text input differs from the previous, 
                                            // if not returns false (case insensitive)
    this.hasFocus           = null;         // Returns true if this instance has focus, if not returns false
    this.isEmpty            = null;         // Returns true if there is no text input or the text input length is 0, 
                                            // else returns false
    this.select             = null;         // Use this method to text select all text input
    this.focus              = null;         // Use this method to apply focus to this instance
    
    
    ///////////////////////////////////
    // Input Implementation
    /////////////////////////////////// 
    
    //
    // Event handlers
    //

    //
    // e: DOM click event data if provided by event, if not provided (i.e. IE) 
    //    the ClickEventArgs (core.js) instance will get it from the window.event
    //
    var onClick = function(e)
    {
        //
        // Using the framework's ClickEventArgs class (core.js) wraps button clicks
        // in a browser independent way, use the VM_xxx mappings (core.js) in comparisons
        //
        this.click.fire(new ClickEventArgs(e));
    }   
    
    
    //
    // e: DOM keyup event data if provided by event, if not provided (i.e. IE) 
    //    the KeyPressEventArgs (core.js) instance will get it from the window.event
    //
    var onKeyUp = function(e) 
    {
        //
        // Determine if anything in the text input has changed (case insensitive)
        //
        hasChanged = (DOMInputElement.value.toLowerCase() != previousValue.toLowerCase())
        
        //
        // Store the current value as the previous value for later change determination
        //
        previousValue = DOMInputElement.value;
        
        //
        // Using the framework's KeyPressEventArgs class (core.js) wraps keyboard keys
        // in a browser independent way, use the VK_xxx mappings (core.js) in comparisons
        //
        this.keyUp.fire(new KeyPressEventArgs(e));
    }   


    //
    // e: DOM keydown event data if provided by event, if not provided (i.e. IE) 
    //    the KeyPressEventArgs (core.js) instance will get it from the window.event
    //
    var onKeyDown = function(e)
    {
        //
        // Using the framework's KeyPressEventArgs class (core.js) wraps keyboard keys
        // in a browser independent way, use the VK_xxx mappings (core.js) in comparisons
        //
        var eventArgs = new KeyPressEventArgs(e);
        
        //
        // Determine if anything in the text input has changed (case insensitive)
        //
        hasChanged = (DOMInputElement.value.toLowerCase() != previousValue.toLowerCase())
        
        //
        // Store the current value as the previous value for later change determination
        //
        previousValue = DOMInputElement.value;
        
        //
        // Always fire keyDown event
        //
        this.keyDown.fire(eventArgs);

        //
        // If there is no native support in DOM for lostfocus event and the
        // tab key was pressed we have to assume the control lost focus and
        // fire the lostFocus event manually
        //
        if (!hasNativeLostFocus && eventArgs.keyPressed == VK_TAB)
        {
            hasFocus = false;
            this.lostFocus.fire(eventArgs);
        }
    }   


    //
    // e: DOM lostfocus event data if provided by event, if not provided (i.e. IE) 
    //    the SystemEventArgs (core.js) instance will get it from the window.event
    //
    var onLostFocus = function(e)
    {
        var eventArgs = new SystemEventArgs(e);
        
        //
        // If the DOM has native lostfocus support no further processing is 
        // needed to determine this instance lost focus, fire the lostFocus event
        //
        if (hasNativeLostFocus)
        {
                hasFocus = false;
                this.lostFocus.fire(eventArgs);     
        }
        
        //
        // No native support of lostfocus event means this code is reached anytime
        // the user clicks anywhere in the document, so first check for an early out
        // by checking if we have focus, if not we are done, else process further.
        //
        else if (this.hasFocus())
        {
            //
            // Instance has focus, if the DOM element triggering this event handler
            // is the same as this instance's DOM element, it is this instance which
            // lost focus, so fire the lostFocus event manually, else we are done
            //
            if (eventArgs.target && eventArgs.target.id != DOMInputElement.id)
            {
                hasFocus = false;
                this.lostFocus.fire(eventArgs);
            }
        }
    }


    //
    // Properties
    //

    this.getLength = function() 
    { 
        return parseInt(DOMInputElement.value.length); 
    }
    
    this.getValue = function() 
    { 
        return DOMInputElement.value; 
    }

    
    //
    // value: the text to use as text input
    //
    this.setValue = function(value) 
    { 
        DOMInputElement.value = value.toString();
        
        // Determine if anything in the text input has changed (case insensitive)
        hasChanged = (previousValue.toLowerCase() != value.toString().toLowerCase());
    }

    
    //
    // value: string representation of the class name to assign to the DOM element's ccs className property 
    //        (should not start with a number).
    //        
    //
    this.setClassName = function(value) 
    { 
        DOMInputElement.className = value.toString(); 
    }
    
    
    //
    // Public methods
    //  
    
    this.hasChanged = function() 
    { 
        return hasChanged; 
    }
        
    this.hasFocus = function() 
    { 
        return hasFocus; 
    }
    
    this.isEmpty = function()
    {
        return (IsNull(DOMInputElement.value) || DOMInputElement.value.length <= 0)
    }

    this.select = function()
    {
        DOMInputElement.select();
    }
    
    this.focus = function() 
    { 
        DOMInputElement.focus(); 
        hasFocus = true; 
    }   
    
    
    ///////////////////////////////////
    // Input Constructor
    ///////////////////////////////////
    
    //
    // Hook all DOM events needed for Input control functionality
    //
    DOM.addEventListener(DOMInputElement, "click", new EventHandler(this, onClick));
    DOM.addEventListener(DOMInputElement, "keyup", new EventHandler(this, onKeyUp));
    DOM.addEventListener(DOMInputElement, "keydown", new EventHandler(this, onKeyDown));
    DOM.addEventListener(DOMInputElement, "focus", new EventHandler(this, this.focus));
    
    //
    // Determine if native DOM lostfocus can be used or if it has to be simulated using document.click event
    //
    if (!IsNull(DOMInputElement.lostfocus) || !IsNull(DOMInputElement.onlostfocus))
    {
        hasNativeLostFocus = true;
        DOM.addEventListener(DOMInputElement, "lostfocus", new EventHandler(this, onLostFocus));
    }
    else
    {
        hasNativeLostFocus = false;
        DOM.addEventListener(document, "click", new EventHandler(this, onLostFocus));
    }
}
// Content: js list
//
// Class used to add list functionality (clicks, mouseovers and keypresses, navigation) 
// to a given DOM element, it converts an array of item data into ListItem instances.
// The layout and rendering of a ListItem can be customized by overriding the ListItem
// class found underneath the List class
//
// DOMListElement   : Any valid DOM container
//
// Dependencies     : core.js
//
function List(DOMListElement)
{
    ///////////////////////////////////
    // List Definition
    ///////////////////////////////////

    //
    // Private data members
    //
    var items               = new Array();  // Array which will contain all ListItem instances
    var itemIndexByValue    = new Object(); // Contains the index into the items array for each ListItem value
                                            // to enable easy search on value
    var selectedItem        = null;         // The currently selected ListItem instance
    var isEmpty             = true;         // Indicates if the List instance contains any ListItem instances
    var isVisible           = false;        // Indicates if the List instance is visible
    var valueField          = "value";      // Default name of the property by which an item's value can be found
    var textField           = "text";       // Default name of the property by which an item's text can be found


    //
    // Events
    //
    this.click              = new Event();  // Event fired by onClick whenever user clicks on the List or its ListItems
    this.listItemSelected   = new Event();  // Event fired by onListItemSelected whenever user selects a ListItem
    this.listItemCreated    = new Event();  // Event fired for each ListItem created
    this.lostFocus          = new Event();  // Event fired by onLostFocus whenever user clicks outside of the list


    //
    // Event handlers
    //
    var onClick             = null;         // Handler called whenever user clicks on the List or its ListItems
    var onListItemSelected  = null;         // Handler called whenever a ListItem is selected
    var onLostFocus         = null;         // Handler called whenever the List loses focus, since the DOMListElement
                                            // does not have to have a native lostfocus implemented, it is simulated
                                            // using a hook on the document.click event with a check to see if the area
                                            // where the click took place is within the DOMListElement
    
    
    //
    // Properties
    //
    this.setValueField      = null;         // Method which can be used to change the name of the property where an item's
                                            // value can be found by the setDatasource method
    this.setTextField       = null;         // Method which can be used to change the name of the property where an item's
                                            // text can be found by the setDatasource method
    this.getSelectedItem    = null;         // Returns the currently selected ListItem instance
    this.getItemByValue     = null;         // Returns if available the ListItem instance whose value property matches the 
                                            // requested value
    this.getItemCount       = null;         // Returns the number of ListItem instances belonging to this List instance
    
    
    //
    // Public methods
    //
    this.createItem         = null;         // This method returns a new ListItem instance which contains the way it should
                                            // be rendered inside the list's DOM element, overwrite to return differently 
                                            // rendered item instances (class derives from ListItem class)
    this.setDatasource      = null;         // Method which consumes an array of objects where each object has a property
                                            // from which to create a ListItem value field and one from which a text field
                                            // can be created. ListItems are created here and indexed by order and by
                                            // value
    this.selectPreviousItem = null;         // Use this method to select the first ListItem instance preceding the currently
                                            // selected one, if none is selected it wraps to the end of the list
    this.selectNextItem     = null;         // Use this method to select the first ListItem instance following the currently
                                            // selected one, if none is selected the first ListItem instance in the list
                                            // will be selected
    this.show               = null;         // Method which makes the List instance visible
    this.hide               = null;         // Method which hides the List instance
    this.clear              = null;         // Use this method to reset/empty the List instance
    this.isEmpty            = null;         // Returns true if the List has no ListItems, false if there are ListItems 
                                            // present in the List
    this.isVisible          = null;         // Returns true if the list is visible, false if not
    this.getItemAt          = null;         // Returns the Item at the specified position
    
    
    ///////////////////////////////////
    // List Implementation
    /////////////////////////////////// 
    
    //
    // Event handlers
    //
    
    //
    // e: DOM click event data if provided by event, if not provided (i.e. IE) 
    //    the ClickEventArgs (core.js) instance will get it from the window.event
    //
    var onClick = function(e)
    {
        var eventArgs = new ClickEventArgs(e);
        this.click.fire(eventArgs);
        
        if (eventArgs.buttonPressed == VM_BUTTONLEFT && (eventArgs.target == DOMListElement || eventArgs.target.isChildOf(DOMListElement))) {
            //
            // Grab the item index from the target DOM element, which is captured in the event arguments
            // and select the item which corresponds with this index, after which the list hides
            //
            var index = eventArgs.target.getAttribute("index");
            if (index && index >= 0 || index < items.length)
            {
                var item = items[index];
                if (item)
                    item.select();
            }
            this.hide();
        }
    }
    

    //
    // item: the item which is being selected, as provided by the ListItem's selected event
    //  
    var onListItemSelected = function(item) 
    {
        //
        // Deselect previously selected item, if there is one
        //
        if (!IsNull(selectedItem))
            selectedItem.deselect();
        
        //
        // Make sure the selection cursor is moved to the right scroll position in the List
        //
        DOMListElement.scrollTop = item.getTop();
        
        //
        // Make this item the currently selected item
        //
        selectedItem = item;
    }

    
    //
    // e: DOM click event data (because onlostfocus is simulated using document.click) 
    //    if provided by event, if not provided (i.e. IE) the SystemEventArgs (core.js) 
    //    instance (this class provides enough information, therefore using the ClickEventArgs 
    //    class is not nescessary) will get it from the window.event
    //
    var onLostFocus = function(e) 
    {
        var eventArgs = new SystemEventArgs(e);
    
        //
        // Since a document.click triggered this event handler determine if the click originated from within 
        // the List's DOM element, if so the List instance has focus, or if it originated from outside of it 
        // (when visible), in which case the List no longer has focus
        //
        if (this.isVisible() && eventArgs.target != DOMListElement && eventArgs.target.parentNode != DOMListElement)
            this.lostFocus.fire(eventArgs);
    }


    //
    // Properties
    //

    //
    // fieldName: name of the property where the List can find the values for its ListItems
    //  
    this.setValueField = function(fieldName) 
    { 
        if (fieldName && fieldName.length)
            valueField = fieldName; 
    }
    
    
    //
    // fieldName: name of the property where the List can find the display texts for its ListItems
    //  
    this.setTextField = function(fieldName) 
    { 
        if (fieldName && fieldName.length)
            textField = fieldName; 
    }
    
        
    this.getSelectedItem = function()
    {
        return selectedItem;
    }
    
    
    //
    // value: value which is matched against the value field of the ListItems, if a match is found the item is returned,
    //        else null will be returned
    //  
    this.getItemByValue = function(value)
    {
        //
        // Check if a valid item exists, if not return null
        //
        if (IsNull(value) || IsNull(itemIndexByValue[value]))
            return null;
        
        //
        // If there is a match, return the item
        //
        return items[itemIndexByValue[value]];
    }
    
    
    this.getItemCount = function() 
    { 
        return items.length; 
    }   


    //
    // Public methods
    //

    //
    // itemIndex: the 0 based index (position) of the item in the List, this value is stored on the ListItem's element for easy
    //            retrieval during event handling
    // value    : the value (=key) which needs to be applied to the ListItem
    // text     : the text (=value) which needs to be displayed for the ListItem
    //
    this.createItem = function(itemIndex, value, text)
    {
        return new ListItem(itemIndex, value, text);
    }


    //
    // datasource: array of objects from which to create ListItem instances
    //
    this.setDatasource = function(datasource)
    {
        //
        // Reset the list
        //
        this.clear();
        
        //
        // Check if datasource is not null and if it is an array (arrays have length property)
        //
        if (!IsNull(datasource) && datasource.length) 
        {
            //
            // Loop over the objects in the array
            //
            for (var i=0; i<datasource.length; i++)
            {
                isEmpty = false;

                //
                // default property for the ListItem's value field is object.value, this property is used
                // in case there is no other property set through setValueField or if the value set does
                // not exist as a property within the object
                //
                var value = datasource[i][valueField] ? datasource[i][valueField] : datasource[i].value;

                //
                // default property for the ListItem's text field is object.text, this property is used
                // in case there is no other property set through setTextField or if the value set does
                // not exist as a property within the object
                //
                var text = datasource[i][textField] ? datasource[i][textField] : datasource[i].text;

                //
                // Create the item
                //
                var item = this.createItem(i, value, text);
                
                //
                // Hook List's listItemSelected event handler to the item's selected event
                //
                item.selected.addListener(new EventHandler(this, this.listItemSelected.fire, item));

                //
                // Add the item's node as a child to the List's DOM element
                //
                DOMListElement.appendChild(item.node);

                //
                // Store this item's value-index pair for easy access by value
                //
                itemIndexByValue[item.value] = items.length;
                
                //
                // Add the item to the List's item array
                //
                items.push(item);
                
                //
                // Tell all interested event handlers that an item has been created, pass the item as event data
                //
                this.listItemCreated.fire(item);
            }
        }
    }


    this.selectPreviousItem = function() {
        if (this.getItemCount() > 0) {
            if (selectedItem && selectedItem.index-1 >= 0)
                //
                // If there was already a selected item and it is not the first item in the list,
                // select the item preceding the selected item
                //
                items[selectedItem.index-1].select();
            else
                //
                // In case there is no previously selected item, or the previously selected item is the
                // first item in the list wrap around and select the last item in the list
                //
                items[items.length-1].select();
        }
    }   
    
    
    this.selectNextItem = function() {
        if (this.getItemCount() > 0) {
            if (selectedItem && selectedItem.index+1 < this.getItemCount())
                //
                // If there was already a selected item and it is not the last item in the list,
                // select the item following the selected item
                //          
                items[selectedItem.index+1].select();
            else
                //
                // In case there is no previously selected item, or the previously selected item is the
                // last item in the list wrap around and select the first item in the list
                //          
                items[0].select();
        }       
    }   
    
    
    this.show = function() 
    {
        //
        // Reset the scroll position
        //
        DOMListElement.scrollTop = 0;
        
        //
        // Make sure the List's container is visible
        //
        DOMListElement.parentNode.style.display = "block";

        //
        // Make the List visible
        //
        DOMListElement.style.display = "block"; 
        isVisible = true;   
    }
    
    
    this.hide = function()
    {
        //
        // Make sure the List's container is hidden
        //  
        DOMListElement.parentNode.style.display = "none";

        //
        // Make the List hidden
        //  
        DOMListElement.style.display = "none";
        isVisible = false;
    }


    this.clear = function() 
    {
        //
        // The pop method is used to remove and return the last element of an array, 
        // it also shortens the length of the array by 1
        //
        var item = items.pop();
        
        //
        // Loop until there no longer are any items
        //
        while (item)
        {
            //
            // Remove the item from the DOM
            //
            DOMListElement.removeChild(item.node);
            
            //
            // Clear the reference to the item, so it can be garbage collected
            //
            delete item;
            
            //
            // Get the new last item, if there is one
            //
            item = items.pop();
        }
        
        //
        // Reset the array to empty by setteing its length to 0 manually, now we can be sure it is really emtpy
        //
        items.length = 0;
        
        //
        // If there is a selected item, delete it and set the selectedItem variable to null to reset it
        //
        if (!IsNull(selectedItem))
        {
            delete selectedItem;
            selectedItem = null;
        }
        isEmpty = true;
    }
        
    
    this.isEmpty = function()
    {
        return isEmpty;
    }
    
    
    this.isVisible = function() 
    { 
        return isVisible; 
    }   

    this.getItemAt = function(nr) 
    { 
	if ( nr >= 0 && nr < this.getItemCount() )
	{
		return items[nr]; 
	}
	return null;
    }   
    
    ///////////////////////////////////
    // List Constructor
    /////////////////////////////////// 

    //
    // Hook all DOM events needed for Input control functionality
    //
    DOM.addEventListener(DOMListElement, "click", new EventHandler(this, onClick));
    DOM.addEventListener(document, "click", new EventHandler(this, onLostFocus));
    
    //
    // The listItemSelected event is fired when the selected event on an item is fired, when this happens
    // we want to execute the onListItemSelected event handler
    //
    this.listItemSelected.addListener(new EventHandler(this, onListItemSelected));  
}


//
// This class represents a single ListItem, which is used by the List class. Instances created by this class are represented in the
// DOM by <A> elements, derive from this class if you want to render different kinds of ListItems, i.e. <LI>
//
// itemIndex    : the 0 based index (position) of the item in the List, this value is stored in the ListItem's element attribute
//                list for easy ListItem instance retrieval during event handling
// value        : the value (=key) which needs to be applied to the ListItem
// text         : the text (=value) which needs to be displayed for the ListItem
//
// Dependencies : core.js
//
function ListItem(itemIndex, value, text) 
{
    ///////////////////////////////////
    // ListItem Definition
    /////////////////////////////////// 
    
    //
    // Public data members
    //
    this.node       = null;         // Reference to <A> element representing this ListItem instance in the DOM
    this.index      = null;         // Index of the instance within the List's item array, is stored as attribute on the <A> 
                                    // element so the instance can easily be found in the array during event handling
    this.value      = null;         // Value field of the ListItem
    this.text       = null;         // Text field of the ListItem, is represented in the DOM by the <A> element's innerHTML

    
    //
    // Events
    //
    this.selected   = new Event();  // Event triggered each time this ListItem instance is selected
    
    //
    // Public methods
    //
    this.select     = null;         // Method used to select this instance (highlight it) and fire the selected event
    this.deselect   = null;         // Method used to deselect this instance (remove the highlight)
    this.getTop     = null;         // Method which returns the top of the ListItem's node in the list for proper scrolling
                                    // and highlighting, might have to be overwritten in derived classes which use different
                                    // DOM element to render an item
    
    
    ///////////////////////////////////
    // ListItem Implementation
    /////////////////////////////////// 
    
    //
    // Public methods
    //
    
    this.select = function() 
    {
        this.node.className = "selected";
        this.selected.fire(this);
    }


    this.deselect = function()
    {
        this.node.className = null;
    }
    

    this.getTop = function()
    {
        //
        // Return the proper top position of the DOM element for use in highlighting and scrolling
        //
        return this.node.offsetTop - 20;
    }
    

    ///////////////////////////////////
    // ListItem Constructor
    ///////////////////////////////////
    this.index = itemIndex;
    this.value = value;
    this.text = text;

    //
    // Default ListItem is represented by <A> element in the DOM, create it here
    //
    this.node = document.createElement("a");
    
    //
    // Add the itemIndex as an attribute to the <A> element, so that the ListItem instance
    // can easily be found in the List's item array when events occur
    //
    this.node.setAttribute("index", itemIndex);
    
    //
    // Display the text field
    //
    this.node.innerHTML = text;
}
// Content: js button
//
// Class used to add button functionality (clicks, mouseovers and keypresses) 
// to a given DOM element
//
// DOMButtonElement : Any valid DOM element
//
// Dependencies     : core.js
//
function Button(DOMButtonElement)
{   
    ///////////////////////////////////
    // Button Definition
    /////////////////////////////////// 

    //
    // Events
    //
    this.press      = new Event();  // Event fired by onClick if mouse button pressed was VM_BUTTONLEFT and 
                                    // by onKeyUp if the key pressed was VK_ENTER
    this.mouseover  = new Event();  // Event fired by onMouseOver after the attempt to change mouse pointer 
                                    // into a hand to indicate the button can be pressed
    
    
    //
    // Event handlers
    //
    var onClick     = null;         // Handler called whenever an onclick event is triggered on the DOM element. 
                                    // Only fires press event if the mouse button pressed was a VM_BUTTONLEFT
    var onKeyUp     = null;         // Handler called whenever an onkeyup event is triggered on the DOM element.
                                    // Only fires press event if the key pressed was a VK_ENTER
    var onMouseOver = null;         // Handler called whenever an onmouseover event is triggered on the DOM element.
                                    // It attempts to turn mouse pointer into a hand to indicate the button can be pressed
    
    
    //
    // Public methods
    //
    this.show       = null;
    this.hide       = null;


    ///////////////////////////////////
    // Button Implementation
    /////////////////////////////////// 

    //
    // Event handlers
    //
    
    //
    // e: DOM click event data if provided by event, if not provided (i.e. IE) 
    //    the ClickEventArgs (core.js) instance will get it from the window.event
    //  
    var onClick = function(e)
    {
        var eventArgs = new ClickEventArgs(e);
        if (eventArgs.buttonPressed == VM_BUTTONLEFT)
            this.press.fire(eventArgs);
    }

    
    //
    // e: DOM keyup event data if provided by event, if not provided (i.e. IE) 
    //    the KeyPressEventArgs (core.js) instance will get it from the window.event
    //
    var onKeyUp = function(e)
    {
        var eventArgs = new KeyPressEventArgs(e);
        if (eventArgs.keyPressed == VK_ENTER)
            this.press.fire(eventArgs);
    }
        
    
    //
    // e: DOM mouseover event data if provided by event, if not provided (i.e. IE) 
    //    the MouseOverEventArgs (core.js) instance will get it from the window.event
    //
    var onMouseOver = function(e)
    {
        //
        // Attempt to change cursor into hand to indicate the button can be pressed,
        // this is done using try/catch to avoid having to use UA testing for which
        // an extra library has to be included
        //
        try {
            DOMButtonElement.style.cursor = "pointer";
        }
        catch (e) {
            DOMButtonElement.style.cursor = "hand";
        }                       
        
        this.mouseover.fire(new MouseOverEventArgs(e));
    }   
    

    //
    // Public methods
    //

    this.show = function()
    {
        DOMButtonElement.style.display = "block";
    }

    this.hide = function()
    {
        DOMButtonElement.style.display = "none";
    }


    ///////////////////////////////////
    // Button Constructor
    ///////////////////////////////////

    //
    // Hook all DOM events needed for Input control functionality
    //
    DOM.addEventListener(DOMButtonElement, "click", new EventHandler(this, onClick));
    DOM.addEventListener(DOMButtonElement, "keyup", new EventHandler(this, onKeyUp));
    DOM.addEventListener(DOMButtonElement, "mouseover", new EventHandler(this, onMouseOver));
}
// Content: js imagebutton
//
// Class used to add image button functionality (clicks, mouseovers and keypresses, swapping of images) 
// to a given DOM element. This control parses all the child nodes within the provided DOMButtonElement
// and if a child is an <IMG> it adds it to an array used for image swapping. This class extends the Button
// class
//
// DOMButtonElement : Any valid DOM element
// defaultIdx       : Index of the default image within the array of available swap images
//
// Dependencies     : core.js,
//                    button.js (ImageButton class extends Button class)
//
function ImageButton(DOMButtonElement, defaultIdx)
{
    this.base(DOMButtonElement);    // Initialize the base class (Button), this always has to be done first
    
    ///////////////////////////////////
    // ImageButton Definition
    ///////////////////////////////////

    //
    // Private data members
    //
    var images      = new Array();  // Array containing all swap images, all <IMG> elements converted to 
                                    // Image instances by the parse method
    var activeImage = null;         // Active Image instance
    

    //
    // Private members
    //
    var parse       = null;         // Recursively parses DOMButtonElement's child nodes to create Image instances 
                                    // for each <IMG> element found which it then adds to the images array used for
                                    // swapping


    //
    // Public members
    //
    this.showImage  = null;         // Use this method to show a particular Image instance by index from the images 
                                    // array
    this.show       = null;         // Use this method to make this ImageButton instance visible
    this.hide       = null;         // Use this method to hide this ImageButton instance


    ///////////////////////////////////
    // ImageButton Implementation
    ///////////////////////////////////

    //
    // Private members
    //

    //
    // node: a valid DOM element
    //
    var parse = function(node)
    {
        //
        // Check if this node is an <IMG> element, if so create an Image instance for it,
        // hide it by default and add it to the images array for swapping
        //
        if (node.tagName && node.tagName == "IMG")
        {
            var image = new Image(node);
            image.hide();
            images.push(image); 
        }

        //
        // Recursively parse this node's child nodes to check for <IMG> elements
        //
        for (var i=0; i<node.childNodes.length; i++)
        {
            parse(node.childNodes[i]);
        }
    }   
    

    //
    // Public members
    //
        
    this.show = function()
    {
        DOMButtonElement.style.display = "";
    }

    this.hide = function()
    {
        DOMButtonElement.style.display = "none";
    }

    //
    // idx: integer representing the index of the image to be shown (0 based)
    //
    this.showImage = function(idx) 
    {
        //
        // If the value for idx is not valid return without doing anything
        //
        if (idx < 0 || idx > images.length-1)
            return;

        //
        // If there already wa an active image, hide it
        //
        if (activeImage)
            activeImage.hide();
        
        //
        // Show the requested image
        //
        images[idx].show();
        
        //
        // Set the activeImage to the newly shown image
        //
        activeImage = images[idx];
    }

    
    ///////////////////////////////////
    // ImageButton Constructor
    /////////////////////////////////// 
    
    //
    // Collect all <IMG> elements which can be used for swapping
    //
    parse(DOMButtonElement);

    //
    // If any <IMG> elements where found during parsing, show the
    // default image, or if that index is out-of-range the first
    // image
    //
    if (images.length)
        this.showImage(defaultIdx || 0);


    ///////////////////////////////////
    // Private classes
    ///////////////////////////////////

    //
    // This private class wraps show/hide functionality around <IMG> elements
    //
    // DOMImageElement: A valid <IMG> element
    //
    function Image(DOMImageElement)
    {
        this.show = function()
        {
            DOMImageElement.style.display = "";
        }
        
        this.hide = function()
        {
            DOMImageElement.style.display = "none";
        }
    }
}

//
//  ImageButton should derive from Button, this call supplies ImageButton with the base class contructor 
//  (this.base() method)
//
Class.extend(ImageButton, Button);
// Content: js radiobutton
//
// Class used to add radio button functionality (only one selected at a time, clickable, etc) to a given DOM element 
// for easy scripting access from JavaScript. This class extends the Button
// class
//
// DOMRadioButtonElement: <INPUT type="radio"> element 
//
// Dependencies         : core.js,
//                        button.js
//
function RadioButton(DOMRadioButtonElement)
{
    this.base(DOMRadioButtonElement);   // Initialize the base class (Button), this always has to be done first

    ///////////////////////////////////
    // RadioButton Definition
    ///////////////////////////////////
    
    //
    // Public data members
    //
    this.node       = null;             // Storage for the original reference to the DOM element
    this.value      = null;             // Storage for the radio button's value attribute
    
    
    //
    // Events
    //
    this.selected   = new Event();      // Event fired when the radio button is selected by the user
    
    
    //
    // Properties
    //
    this.getValue   = null;             // Returns the value of the DOM element's value attribute
    this.setValue   = null;             // Sets the value of the DOM element's value attribute
    
    
    //
    // Public methods
    //
    this.select     = null;             // Use this method to select this radio button
    this.enable     = null;             // Enables this radio button
    this.disable    = null;             // Disables this radion button
    this.isSelected = null;             // Returns true if this radio button is selected, if not returns false
    
    
    ///////////////////////////////////
    // RadioButton Implementation
    ///////////////////////////////////

    //
    // Properties
    //

    this.getValue = function()
    {
        return DOMRadioButtonElement.value;
    }
    
    
    //
    // value: any value which can be converted to a string, this is the value which classically would be posted
    //
    this.setValue = function(value)
    {
        if (!IsNull(value))
            DOMRadioButtonElement.value = value.toString();
    }
    
    
    //
    // Public methods
    //
    
    this.select = function()
    {
        DOMRadioButtonElement.checked = true;
        
        //
        // Notify interested parties that this radio button has been selected by firing the selected event
        //
        this.selected.fire(this);       
    }
    
    
    this.enable = function()
    {
        //
        // Enabling a radio button equals removing the disabled attribute, since just the presence of this attribute
        // regardless of its value disables the radio button
        //
        DOMRadioButtonElement.removeAttribute("disabled");
    }   
    
    
    this.disable = function()
    {
        //
        // Add "disabled" attribute to the DOM element to disable it. The only value for this attribute which can be 
        // W3C XHTML validated without any problems is "disabled"
        //
        DOMRadioButtonElement.setAttribute("disabled", "disabled");
    }   
    
    
    this.isSelected = function()
    {
        return DOMRadioButtonElement.checked;
    }
    
    
    ///////////////////////////////////
    // RadioButton Constructor
    ///////////////////////////////////
    this.node = DOMRadioButtonElement;
    this.value = DOMRadioButtonElement.value;

    //
    // Hook the Button's (base class) press event to the select method, so anytime this RadioButton instance is pressed it will
    // be selected
    //
    this.press.addListener(new EventHandler(this, this.select));
}

//
//  RadioButton should derive from Button, this call supplies RadioButton with the base class contructor 
//  (this.base() method)
//
Class.extend(RadioButton, Button);


//
// Class used to represent and manage a group of RadioButton instances
//
// group        : OPTIONAL  If provided should be the string value of the DOM radio button group's name attribute, in which case
//                the class constructor will collect all radio buttons from the DOM that have a matching value for their
//                name attribute and create RadioButton instances based on them
//
// Dependencies : core.js,
//                button.js
//
function RadioButtonGroup(group)
{
    ///////////////////////////////////
    // RadioButtonGroup Definition
    ///////////////////////////////////
    
    //
    // Private data members
    //
    var radioButtons        = new Array();      // Storage for all the RadioButton instances grouped by this RadioButtonGroup
                                                // instance
    var selectedRadioButton = null;             // Reference to the currently selected RadioButton instance


    //
    // Public data members
    //
    this.items              = null;             // Public interface to the RadioButton instances


    //
    // Events
    //
    this.selectionChanged   = new Event();      // Event fired each time a different RadioButton instance in this group is selected


    //
    // Event handlers
    //
    var onSelectionChanged  = null;             // Event handler which fires the selectionChanged event, called by the 
                                                // RadioButton's selected event
    
    
    //
    // Properties
    //
    this.getValue           = null;             // Returns the value of the selected RadioButton's value attribute
    this.setValue           = null;             // Attempts to select the RadioButton that has the provided value as value for its
                                                // value attribute
    this.getByIndex         = null;             // Returns, if it exists, the RadioButton at the specified index
    this.getCount           = null;             // Returns the number of RadioButton instances assigned to this group
    
    
    //
    // Public methods
    //
    this.add                = null;             // Adds a valid RadioButton instance to this group
    this.dispose            = null;             // Clears and resets this instance
    
    
    ///////////////////////////////////
    // RadioButtonGroup Implementation
    /////////////////////////////////// 

    //
    // Event handlers
    //

    //
    // radioButton: The RadioButton instance being selected. This argument is provided by the RadioButton instance's selected event 
    //
    var onSelectionChanged = function(radioButton)
    {
        //
        // Store the currently selected RadioButton instance reference as the previously selected radio button
        //
        var prevSelectedRadioButton = selectedRadioButton;
        
        //
        // Now the selectedRadioButton variable can safely be changed to reference the RadioButton instance being selected
        //
        selectedRadioButton = radioButton;
        
        //
        // Notify all interested parties that a new selection is made, use the custom SelectionChangedEventArgs class
        // as event data, since this class can send both the previous and the currently selected RadioButton instances
        //
        this.selectionChanged.fire(new SelectionChangedEventArgs(selectedRadioButton, prevSelectedRadioButton));
    }
    
    
    //
    // Properties
    //
    
    this.getValue = function()
    {       
        //
        // If there is a RadioButton instance selected return its value else return null
        //
        return selectedRadioButton ? selectedRadioButton.getValue() : null;
    }
    
    
    //
    // value: a string representing the value you want selected. This value is than matched against the values of 
    //        the RadioButton instances in this group. If a match is found (case sensitive), the RadioButton it 
    //        belongs to is selected
    //
    this.setValue = function(value)
    {
        //
        // Loop over all this group's RadioButton instances trying to match their value against the requested value
        //
        for (var i=0; i<radioButtons.length; i++)
            if (radioButtons[i].getValue().toString() == value.toString())
                //
                // Match found, select the RadioButton instance
                //
                radioButtons[i].select();
    }
    
    
    //
    // idx: index within this group of the RadioButton which needs to be returned
    //
    this.getByIndex = function(idx)
    {
        //
        // If the provided idx is out-of-range return null
        //
        if (!idx || idx < 0 || idx >= radioButtons.length)
            return null;
        
        //
        // Else return the RadioButton instance the index belongs to
        //
        return radioButtons[idx];
    }

    
    this.getCount = function()
    {
        return radioButtons.length;
    }
    
    
    //
    // Public methods
    //
    
    //
    // radioButton: a valid RadioButton instance to add to this group
    //
    this.add = function(radioButton)
    {
        if (radioButton instanceof RadioButton)
        {
            //
            // If the provided RadioButton instance is selected let the group know
            //
            if (radioButton.isSelected()) 
                selectedRadioButton = radioButton;
            
            //
            // Make sure the group gets notified when the RadioButton instance is selected
            //
            radioButton.selected.addListener(new EventHandler(this, onSelectionChanged));
            
            //
            // Add the provided RadioButton instance to this group's array of items
            //
            radioButtons[radioButtons.length] = radioButton;
        }
    }


    this.dispose = function()
    {
        //
        // If there is a selected RadioButton instance, delete it, freeing its reference so it can be garbage collected
        //
        if (!IsNull(selectedRadioButton))
            delete selectedRadioButton;
        
        //
        // Delete all references to RadioButton instances so their references are freed and they can be garbage collected
        //
        for (var i=0; i<radioButtons.length; i++)
            delete radioButtons[i];
            
        //
        // Now we can be sure that when the length of the array is zero (=effectively resetting the array) there won't 
        // be any memory leaks due to items which are still reference counted
        //
        radioButtons.length = 0;
    }


    ///////////////////////////////////
    // RadioButtonGroup Constructor
    ///////////////////////////////////
    
    //
    // Public accessor into the items array
    //
    this.items = radioButtons
    
    //
    // If a group name was provided collect all corresponding DOM elements and create a valid group
    //
    if (group && typeof(group) == "string")
    {
        //
        // Since radio buttons have no unique element collect all <INPUT> elements
        //
        var inputs = document.getElementsByTagName("INPUT");
        for (var i=0; i<inputs.length; i++)
        {
            //
            // If an <INPUT type="radio"> is found with its name attribute equal to the provided group name,
            // create a RadioButton instance for it and add it to the group
            //
            if (inputs[i].type == "radio" && inputs[i].name == group) {
                this.add(new RadioButton(inputs[i]));
            }
        }

        //
        // If the provided group name results in no RadioButton instances the name did not correspond with a valid
        // radio button group, so throw an error
        //
        if (!radioButtons.length)
            throw("Error: RadioButtonGroup(group), radio button group '["+group+"]' does not exist.");

        //
        // A RadioButtonGroup instance should always have one and only one RadioButton instance selected, 
        // so if at this point one has not been selected yet, select the first in the group
        //
        if (IsNull(selectedRadioButton))
            selectedRadioButton = radioButtons[0];
    }       
}

//
// Class used to pass multi-valued event data when a RadioButtonGroup's selected RadioButton instance changes. When this event
// is fired the consuming event handlers might be interested in both the newly selected item as well as the previously selected
// item.
//
function SelectionChangedEventArgs(selected, deselected)
{
    this.selectedItem = selected;
    this.deselectedItem = (deselected != selected) ? deselected : null;
}
// Content: js datepicker
//
// Class used to create a date picker control based on two <SELECT> elements, one for day and one for month + year
//
// DOMDayElement        : Any valid DOM <SELECT> element representing the days of the month
// DOMMonthYearElement  : Any valid DOM <SELECT> element representing the the months of the year
//
// Dependencies         : core.js
//
function DatePicker(DOMDayElement, DOMMonthYearElement)
{
    ///////////////////////////////////
    // DatePicker Definition
    ///////////////////////////////////
    
    //
    // Private data members
    //
    var today               = new Date();   // Today's date
    var daySelector         = null;         // Alias for the DOMDayElement argument used for better code readability
    var monthYearSelector   = null;         // Alias for the DOMMonthYearElement argument used for better code readability
    var selectedDate        = new Date();   // The DatePicker instance's currently selected date
    
    
    //
    // Events
    //
    this.dateSelected       = new Event();  // Event fired by onDateSelected when a day or a month has been selected
    
    
    //
    // Event handlers
    //
    var onDateSelected      = null;         // Event handler called by native DOM onchange event on both <SELECT> elements


    //
    // Properties
    //
    this.setDate            = null;         // Method to be used to set this DatePicker instance's selected date
    this.getDate            = null;         // Method which can be used to read out the currently selected date within this
                                            // DatePicker instance


    //
    // Private methods
    //
    var updateDaySelector   = null;         // Internal method used to synchronize the day selector to the selected date month/year
    var updateMonthSelector = null;         // Internal method used to synchronize the month+year selector to the selected date

    
    //
    // Public methods
    //
    this.toString           = null;         // returns the selected date as a string formatted like "yyyymmdd"


    ///////////////////////////////////
    // DatePicker Implementation
    ///////////////////////////////////
    
    //
    // Event handlers
    //
    
    //
    // e: DOM change event data if provided by event. Not yet used
    //
    var onDateSelected = function(e)
    {
        //
        // Year is represented by the first four characters in monthYearSelector.value
        //
        var year = parseInt(monthYearSelector.value.substring(0,4));
        
        //
        // Month is represented by the last two of the six characters in monthYearSelector.value
        //
        var month = parseInt(monthYearSelector.value.substring(4,6), 10);
        
        //
        // Get the selected day
        //
        var day = parseInt(daySelector.value);
        
        //
        // If the month has changed start over from first day (month-1 because month is 1 based and getMonth() is 0 based)
        //
        //if (selectedDate && selectedDate.getMonth() != month-1)
        //    day = 1;

        //
        // Set the new date to be the selected date
        //
        this.setDate(new Date(year, month-1, day));
    }   


    //
    // Properties
    //
    
    //
    // value: a valid Date instance or a date string formatted as "yyyymmdd"
    //
    this.setDate = function(value)
    {
        if (DateLib.isDate(value))
        {
            var today = new Date();
            var date = DateLib.parse(value);
            
            //
            // If the requested date is in the past the selected date equals today, 
            // else use the requested date
            //
            if (today > date)
                selectedDate = today;
            else
                selectedDate = date;
            
            //
            // Selected date has changed so update the <SELECT> elements' option lists to reflect the new date
            //
            updateMonthSelector();
            updateDaySelector();

            //
            // Tell all interested parties (objects listening to this event) that a date has been selected
            //
            this.dateSelected.fire(date.copy());            
        }
    }   


    this.getDate = function()
    {
        return selectedDate;    
    }


    //
    // Private methods
    //  

    var updateDaySelector = function()
    {
        //
        // Make sure the daySelector element is a <SELECT> element before we try to make changes to its list of options
        //
        if (daySelector.tagName == "SELECT")
        {   
            //
            // Empty the list, deleting previous options
            //
            daySelector.options.length = 0;
            
            var selectedIndex = 0;
            var nrDaysInMonth = DateLib.getNrDaysInMonth(selectedDate.getFullYear(), selectedDate.getMonth()+1);
            var startDay = 1;

            //
            // If the selected month is the current month clip the start day of the month to today and adjust
            // the nrDaysInMonth to reflect the number of remaining days in this month
            //
            if (selectedDate.getFullYear() == today.getFullYear() && selectedDate.getMonth() == today.getMonth())
            {
                startDay = today.getDate();
                nrDaysInMonth = nrDaysInMonth + (1 - startDay);
            }
            
            //
            // For each day create an option
            //
            for (var i=0; i<nrDaysInMonth; i++) {
                daySelector.options[i] = new Option(i+startDay, i+startDay);
            
                //
                // If this day equals the day part of the selected date, adjust the selectedIndex used for the list
                //
                if (i+startDay == selectedDate.getDate())
                    selectedIndex = i;
            }
            
            //
            // Set the selectedIndex of the list to the one found in the previous loop or else 0
            //
            daySelector.options.selectedIndex = selectedIndex;
        }
    }
    
    
    var updateMonthSelector = function()
    {
        //
        // Make sure the monthYearSelector element is a <SELECT> element before we try to make changes to its list of options
        //
        if (monthYearSelector.tagName == "SELECT")
        {
            var today = new Date();
            
            //
            // Empty the list, deleting previous options
            //          
            monthYearSelector.options.length = 0;

            var year = today.getFullYear();
            var month = today.getMonth();

            //
            // For each month create an option, months in different years could occur within the list, 
            // since the first month is always the current month
            //
            for (var i=month; i<month+12; i++)
            {
                //
                // Detect year transition, if found add 1 to the year variable
                //
                if (i%12 < month && year == today.getFullYear())
                    year++;
                
                //
                // Look up the month name in the localized monthNames array defined in core.js use i mod 12 to
                // wrap months every 12 months
                //
                var text = DateLib.monthNames[i%12][1]+" "+year.toString();
                
                //
                // value is formatted as "yyyymm" since month is '0' based we need to add 1 to it and then pad
                // it with a '0' in case it has only one digit
                //
                var value = year.toString()+((i%12)+1).toString().pad('0',2);
                
                //
                // Create the new option based on the found text and value
                //
                monthYearSelector.options[i-month] = new Option(text, value);
            }
            
            //
            // Calculate the new selectedIndex. selectedDate.getMonth() >= this month and selectedDate.getFullYear() >= this year
            //
            var selectedIndex = selectedDate.getMonth()-today.getMonth()+(selectedDate.getFullYear()-today.getFullYear())*12;
            
            //
            // Select the option represented by the calculated selectedIndex
            //
            monthYearSelector.options.selectedIndex = selectedIndex;
        }       
    }
    
    
    //
    // Public methods
    //  

    this.toString = function()
    {
        //
        // Return the selected date as a string formatted like "yyyymmdd", use the pad method to make sure single digit values
        // get padded by a '0'
        //
        return selectedDate.getFullYear().toString()+(selectedDate.getMonth()+1).toString().pad('0',2)+selectedDate.getDate().toString().pad('0',2);
    }   

    ///////////////////////////////////
    // DatePicker Constructor
    ///////////////////////////////////
    
    //
    // Create the aliases for code readability
    //  
    daySelector = DOMDayElement;
    monthYearSelector = DOMMonthYearElement;

    //
    // Synchronize both lists
    //
    updateDaySelector();
    updateMonthSelector();
    
    //
    // Hook all DOM events needed for DatePicker control functionality to child controls
    //
    DOM.addEventListener(daySelector, "change", new EventHandler(this, onDateSelected));
    DOM.addEventListener(monthYearSelector, "change", new EventHandler(this, onDateSelected));
}
// Content: js calendar
//
// Class used to create a Calendar from a DOM element. Calendar child object are parsed by css class name starting 
// from DOMCalendarElement. 
//
// DOMCalendarElement   : Any valid DOM element, but for the Calendar class to operate the child elements mentioned below 
//                        have to be present
// title                : if provided this string will be shown in the header
//
// Dependencies: core.js
//
// Parsed child nodes' class names are:
//
//  header              : title is written to this DOM node's innerHTML
//  btnClose            : 'X' button in the left corner of the header
//  monthselector       : DOM <SELECT> element for month selection
//  dayselector         : The container element for the <TABLE> element which is used to pick a day
//
function Calendar(DOMCalendarElement, title)
{
    ///////////////////////////////////
    // Calendar Definition
    ///////////////////////////////////

    //
    // Private data members
    //
    var today           = new Date();           // Today's date
    var year            = today.getFullYear();  // Current year
    var month           = today.getMonth();     // Current month
    var selectedDate    = null;                 // Selected date, also used to preselect a date before showing the calendar
    var header          = null;                 // Reference to the DOM element which represents the header of the calendar
    var btnClose        = null;                 // The 'X' button in the header of the calendar
    var monthSelector   = null;                 // Reference to the DOM element representing the month selector (<SELECT>)
    var daySelector     = null;                 // Reference to the DOM container to which the days' <TABLE> will be written

    
    //
    // Events
    //
    this.dateSelected   = new Event();          // Event fired by onDateSelected when a user has selected a valid day in the 
                                                // calendar


    //
    // Event handlers
    //
    var onMonthChanged  = null;                 // Event handler called whenever the selected value of the month selector 
                                                // changes.
    var onDateMouseOver = null;                 // Event handler called whenever the user moves the mouse pointer over a
                                                // day in the calendar
    var onDateSelected  = null;                 // Event handler called whenever the user left clicks on a day to select it
    
    
    //
    // Properties
    //
    this.setTitle       = null;                 // Method used to set the title text in the calendar's header
    this.setDate        = null;                 // Method used to set the selected/preselected date
    this.getDate        = null;                 // Method used to get the selected date
    
    
    //
    // Private methods
    //
    var updateDays      = null;                 // Internal method used to update the table containing the days of the month
                                                // whenever the selected month changes
    var parse           = null;                 // Internal method called upon initialization of the class, used to parse
                                                // all childnodes by class name needed to create all nescessary child controls 
                                                // for a valid Calendar instance
    
    
    //
    // Public methods
    //
    this.show           = null;                 // Shows the Calendar instance
    this.hide           = null;                 // Hides the Calendar instance
    

    ///////////////////////////////////
    // Calendar Implementation
    ///////////////////////////////////

    //
    // Event handlers
    //

    //
    // e: DOM change event data if provided by event. 
    //    Not yet used
    //  
    var onMonthChanged = function(e)
    {
        //
        // Update the selected year and month in the selected date object
        //
        selectedDate.setFullYear(parseInt(monthSelector.value.substring(0,4)));
        selectedDate.setMonth(parseInt(monthSelector.value.substring(4,6), 10)-1);
        
        //
        // Update the day display without selecting a day, the method is called using
        // the call method and the 'this' scope so it will execute in the class instance's 
        // scope instead of in the scope of the DOM element.which triggered the original event
        //
        updateDays.call(this, false);   
    }


    //
    // e: DOM mouseover event data if provided by event, if not provided (i.e. IE) 
    //    the MouseOverEventArgs (core.js) instance will get it from the window.event
    //  
    var onDateMouseOver = function(e)
    {
        var eventArgs = new MouseOverEventArgs(e);
        
        //
        // Attempt to change cursor into hand to indicate that the day pointed to by 
        // the mouse pointer can be selected by pressing it, this is done using try/catch 
        // to avoid having to use UA testing for which an extra library has to be included
        //
        try {
            eventArgs.target.style.cursor = "pointer";
        }
        catch (e) {
            eventArgs.target.style.cursor = "hand";
        }       
    }

    
    //
    // e: DOM click event data if provided by event, if not provided (i.e. IE) 
    //    the ClickEventArgs (core.js) instance will get it from the window.event
    //  
    var onDateSelected = function(e)
    {
        var eventArgs = new ClickEventArgs(e);
        
        //
        // Update the year and month of the selected date object
        //
        selectedDate.setFullYear(parseInt(monthSelector.value.substring(0,4)));
        selectedDate.setMonth(parseInt(monthSelector.value.substring(4,6), 10)-1);
        
        //
        // Retrieve the day of the month by reading out the day attribute of the day element
        // on which the event was triggered
        //
        selectedDate.setDate(parseInt(eventArgs.target.getAttribute("day")));

        //
        // Fire the dateSelected event, pass a copy of the selected date as event argument.
        // It has to be a copy so changes to it in consuming code won't affect the selected
        // date in this instance
        //
        this.dateSelected.fire(selectedDate.copy().toDate());
    }   

    
    //
    // Properties
    //  
    
    //
    // title: valid string representing the text which should be display in the Calendar instance's header bar
    //
    this.setTitle = function(title)
    {
        header.innerHTML = title;
    }
    
    
    //
    // date: valid Date instance or date string formatted like "yyyymmdd" 
    //
    this.setDate = function(date)
    {
        if (DateLib.isDate(date))
        {
            //
            // Parse date, so date strings formatted like "yyyymmdd" will also yield valid Date instances
            //
            selectedDate = DateLib.parse(date);
            
            //
            // Select the month of the selected date in the month selector
            //
            monthSelector.options[selectedDate.getMonth()-today.getMonth()+(selectedDate.getFullYear()-today.getFullYear())*12].selected = true;
        }
    }
    

    this.getDate = function()
    {
        return selectedDate;
    }
    
    
    //
    // Private methods
    //  
    
    //
    // blnSelectDay: true if the selected date should be highlighted, false if not
    //
    var updateDays = function(blnSelectDay)
    {
        var nrDays = DateLib.getNrDaysInMonth(selectedDate.getFullYear(), selectedDate.getMonth()+1);
        var firstDay = DateLib.getFirstDayInMonth(selectedDate.getFullYear(), selectedDate.getMonth()+1);   

        //
        // Remove all previous day elements
        //
        daySelector.innerHTML = "";

        var table = document.createElement("TABLE");
        daySelector.appendChild(table);
        table.setAttribute("cellspacing", "0");
        
        var header = document.createElement("TR");
        header.className = "head";
        
        //
        // Loop over the days of the week to create the table header cells containing each day's short name
        //
        for (var i=0; i<DateLib.dayNames.length; i++)
        {
            var day = document.createElement("TD");
            day.innerHTML = DateLib.dayNames[i];
            header.appendChild(day);
        }
        table.appendChild(header);

        //
        // Empty cell template, is cloned whenever an empty cell needs to be rendered
        //
        var emptyCell = document.createElement("TD");
        emptyCell.innerHTML = "&nbsp;";
        
        var weekIdx = 1;
        var dayIdx = 1;
        
        //
        // Loop while last day of the month has not been reached yet
        //
        while (dayIdx<=nrDays)
        {
            //
            // Add a new row (week) to the table
            //
            var weekrow = document.createElement("TR");
            table.appendChild(weekrow);
            
            //
            // Loop over the days in the week
            //
            for (var j=0; j<7; j++)
            {
                if ((weekIdx==1 && j<firstDay) || dayIdx>nrDays)
                {
                    //
                    // Render an empty cell in case:
                    // 1) The current day is of the previous month (can only occur in the first week) or 
                    // 2) The current day is of the next month (can only occur in the last week)
                    //
                    weekrow.appendChild(emptyCell.cloneNode(true)); 
                }
                else
                {
                    //
                    // If the day is before today render day as greyed out and unclickeable
                    //
                    if (selectedDate.getMonth() == today.getMonth() && dayIdx<today.getDate())
                    {
                        var day = document.createElement("TD");
                        day.innerHTML = dayIdx;
                        weekrow.appendChild(day);                       
                    }

                    //
                    // else render normal clickeable day
                    //
                    else
                    {
                        var day = document.createElement("TD");
                        var button = document.createElement("A");

                        //
                        // Store the day index in the day attribute of the element, used in onDateSelected
                        //
                        button.setAttribute("day", dayIdx);
                        button.innerHTML = dayIdx;

                        //
                        // If this day equals the day of the selected date and blnSelectDay is set to true,
                        // highlight this day
                        //
                        if (dayIdx == selectedDate.getDate() && blnSelectDay)
                            button.className = "selected";

                        //
                        // Append the link to the cell (day)
                        //
                        day.appendChild(button);

                        //
                        // Append the day to the week (cell to the row)
                        //
                        weekrow.appendChild(day);                       
                    }
                    dayIdx++;
                }
            }
            weekIdx++
        }       
        
        //
        // Assign the daySelector's new innerHTML to the daySelector's innerHTML to circumvent IE bug
        // (this is the only way to get IE to re-evaluate and re-render the HTML fragment)
        //
        daySelector.innerHTML = daySelector.innerHTML;
        
        //
        // Now that the innerHTML does not need to be modified any more which would cause previously 
        // assigned event handlers to be invalidated, we can now safely add event handlers to any elements.
        // We collect all daySelectors (A elements) and make sure that each of them gets its native
        // onclick and onmouseover events hooked by this class's event handlers for those events, which 
        // when called will be executed with this instance as scope (this).
        //
        var buttons = daySelector.getElementsByTagName("A");
        for (var i=0; i<buttons.length; i++)
        {
            DOM.addEventListener(buttons[i], "click", new EventHandler(this, onDateSelected));
            DOM.addEventListener(buttons[i], "mouseover", new EventHandler(this, onDateMouseOver));
        }
    }
    

    //
    // node: a valid DOM element
    //  
    var parse = function(node)
    {
        //
        // Check node's class name  to see if it should be parsed into a child control for the calendar
        //
        if (node.className)
        {
            switch (node.className)
            {
                case "header": 
                    header = node; 
                    break;
                case "btnClose": 
                    btnClose = new Button(node);
                    break;
                case "monthselector":
                    monthSelector = node;                   
                    break;
                case "dayselector":
                    daySelector = node;
                    break;
                default:
                    break;
            }
        }
        
        //
        // Recursively do the same for this node's childnodes
        //
        for (var i=0; i<node.childNodes.length; i++)
        {
            parse(node.childNodes[i]);
        }
    }


    //
    // Public methods
    //
    
    this.show = function()
    {       
        //
        // Update the days to reflect currently selected month and preselect the selected date
        //
        updateDays.call(this, true);

        DOMCalendarElement.style.display = 'block';     
        monthSelector.focus();
    }
    
    
    this.hide = function()
    {
        DOMCalendarElement.style.display = 'none';
    }   


    ///////////////////////////////////
    // Calendar Constructor
    ///////////////////////////////////

    //
    // Recursively create all the nescessary child objects based on 
    // the DOM element's child nodes (by class name)
    //
    parse(DOMCalendarElement);
    
    //
    // Clear the month selector's option list   
    //
    monthSelector.options.length = 0;

    //
    // Create the month selector's list of months using the localized month names array as defined in core.js
    // The list always starts at the current month and show 12 months starting from this month
    //
    for (var i=month; i<month+12; i++)
    {
        //
        // If a year boundary is crossed add 1 to the year
        //
        if (i%12 < month && year == today.getFullYear())
            year++;

        //
        // Get the long name of the month (2nd element in the localized month names array) and concatenate
        // the year to it to get the display text for this option. i mod 12 makes months wrap every 12 months
        //
        var text = DateLib.monthNames[i%12][1]+" "+year.toString();
        
        //
        // Create the value for this option which is a string formatted as "yyyymm", pad makes sure a single 
        // digit month gets padded with a '0'. Once again i mod 12 makes months wrap every 12 months
        //
        var value = year.toString()+((i%12)+1).toString().pad('0',2);
        
        //
        // Add a new option to the month selector's list based on the new text and value
        //
        monthSelector.options[i-month] = new Option(text, value);
    }   

    // 
    // Set the title as provided by the matching constructor argument
    //
    this.setTitle(title);
    
    //
    // Set the default selected date to be today's date
    //
    this.setDate(new Date(today.getFullYear(), today.getMonth(), today.getDate()));
    
    //
    // Hook select's native onchange event to onMonthChanged event handler and tell it to execute in the
    // scope of this instance (this scope)
    //
    DOM.addEventListener(monthSelector, "change", new EventHandler(this, onMonthChanged));

    //
    // When a date is selected call the hide method to close the calendar
    //
    this.dateSelected.addListener(new EventHandler(this, this.hide));

    //
    // The "X" button (btnClose) in the header, when pressed, should also hide the calendar
    //
    btnClose.press.addListener(new EventHandler(this, this.hide));
}


//
// Broker class used when one Calendar instance needs to serve multiple controls with there own scope (values).
//
// DOMCalendarElement   : Any valid DOM element, but for the Calendar class to operate the child elements mentioned below 
//                        have to be present
// defaultTitle         : if provided this string will be shown in the header, if no title is provided when calling the show method
//
// Dependencies         : core.js
//
// NOTE                 : the consumers need to implement the following two public methods: getDate() which returns a valid Date 
//                        instance and setDate(value) which takes a valid Date instance as an argument. These are called by the 
//                        show method and onDateSelected method. An exception is thrown in case one or both are missing in 
//                        the target object
//
function CalendarBroker(DOMCalendarElement, defaultTitle)
{
    ///////////////////////////////////
    // CalendarBroker Definition
    ///////////////////////////////////
    
    //
    // Private data members
    //
    var calendar        = null;     // Reference to the single Calendar instance used by the broker to serve 
                                    // multiple target objects
    var target          = null;     // Reference to the object from which to read the selected date and to which
                                    // to output the date selected in the Calendar control
    
    
    //
    // Event handlers
    //
    var onDateSelected  = null;     // Event handler called when a date has been selected in the Calendar control
    
    
    //
    // Public methods
    //
    this.show           = null;     // Shows the Calendar instance for a given target object with a given title
    this.hide           = null;     // Hides the Calendar instance
    
    
    ///////////////////////////////////
    // CalendarBroker Implementation
    ///////////////////////////////////

    //
    // Event handlers
    //

    //
    // date: the selected date as provided by the Calendar's dateSelected event
    //
    var onDateSelected = function(date)
    {
        //
        // Call the setDate method on the target object with the selected date.
        // Since the method only functions correctly in the target object's scope
        // call it using this scope (target) as the scope in which to execute the
        // method
        //
        target.setDate.call(target, date);
    }
    
    
    //
    // Public methods
    //
    
    //
    // oTarget  : a valid target object to/from which the CalendarBroker instance will read/write the selected date
    //            by using getDate and setDate(date) methods which have to be implemented in the target object's class
    // title    : the title to display in the Calendar instance's header when showing it, will default to the defaultTitle
    //            if provided while constructing the CalendarBroker instance, if not an empty string is used as title.
    //
    this.show = function(oTarget, title)
    {
        //
        // Throw an exception in case oTarget is null or does not implement the two methods mentioned above
        //
        if (IsNull(oTarget) || IsNull(oTarget.getDate) || IsNull(oTarget.setDate))
            throw("Error: CalendarBroker.show(oTarget [,title]) 'oTarget' is not a valid target object or it is missing a 'getDate' method which returns a 'Date' object and/or a 'setDate(value)' method which takes a 'Date' object as an argument.")
    
        //
        // If a title was provided, display it, else check if there is a default title to display, if not
        // display an empty string
        //
        calendar.setTitle(!IsNull(title) ? title.toString() : (!IsNull(defaultTitle) ? defaultTitle.toString() : ""));

        //
        // Get the date from the target object and set the calendar's selected date to be equal to this date
        //
        calendar.setDate(oTarget.getDate());
        
        //
        // Store a reference to the target object for use in any other class methods
        //
        target = oTarget;
        
        //
        // Show the Calendar instance managed by this CalendarBroker instance
        //
        calendar.show();
    }


    this.hide = function()
    {
        //
        // Hide the Calendar instance managed by this CalendarBroker instance
        //
        calendar.hide();
    }   
    
    
    ///////////////////////////////////
    // CalendarBroker Constructor
    /////////////////////////////////// 
    var calendar    = new Calendar(DOMCalendarElement, defaultTitle);
    var target      = null;

    //  
    // Hook the onDateSelected event handler to the Calendar instance's dateSelected event
    //
    calendar.dateSelected.addListener(new EventHandler(this, onDateSelected));
}
// Content: js stationfinder
//
// This class is used to show/hide a popup by which the user can browse countries in order to find airports belonging to these
// countries, both these lists are retrieved using AJAX and are based on <SELECT> elements. To provide better ease-of-use only
// the root element of the popup has to be passed to the constructor after which this class parses its necessary elements from
// the DOM by looking for child elements whose class names match certain class names needed to successfully create this control.
// If an item is selected by the user it (station object) can be passed to any object which has a setStation(station) method
// implemented, the past station object has a 'code' and a 'name' property.
//
// DOMStationFinderElement  : A valid DOM element which acts as a valid container for this class, check the parse(...) method to
//                            see which child elements are needed to be present for instances of this class to function properly
// title                    : The title which should be shown in the title bar of the popup, defaults to a empty string.
//
// Dependencies             : core.js,
//                            button.js,
//                            CountryRequest.js,
//                            CountryStationRequest.js,
//                            OttAjaxService.js,
//                            [path to dwr]\engine.js
//
// NOTE                     : A global object 'Page.locale' MUST be present and have a property named 'languageCode' and
//                            one named 'countryCode' set in accordance with their normal usage with KLM.com
//                            (as of yet dashboardpage.js and resultpage.js both provide this functionality)
//
function StationFinder(DOMStationFinderElement, title)
{
    ///////////////////////////////////
    // StationFinder Definition
    ///////////////////////////////////

    //
    // Private data members
    //    
    var selectedCountry         = null;             // Storage for the Country instance being currently selected
    var selectedStation         = null;             // Storage for the Station instance being currently selected
    var header                  = null;             // Reference to this StationFinder's DOM header element
    var body                    = null;             // Reference to this StationFinder's DOM body element
    var footer                  = null;             // Reference to this StationFinder's DOM footer element


    //
    // Controls
    //
    var countrySelector         = null;             // Reference to the DOM select element used to pick a country 
    var stationSelector         = null;             // Reference to the DOM select element used to pick a station belonging to the
                                                    // selected country
    var btnClose                = null;             // Button instance (button.js) used to close the StationFinder popup
    var btnSelect               = null;             // Button instance (button.js) used to confirm the StationFinder's selection


    //
    // Events
    //
    this.countrySelected        = new Event();      // Event fired whenever a country has been selected in the country selector                                                    
    this.stationSelected        = new Event();      // Event fired whenever a station has been selected in the station selector


    //
    // Private event handlers
    //
    var onCountrySelected       = null;             // This event handler ensures the countrySelected event is fired with the 
                                                    // selected country as event data
    var onStationSelected       = null;             // This event handler ensures the stationSelected event is fired once the 
                                                    // user confirms his/her selection using the select button
    
    
    //
    // Private methods
    //
    var getCountries            = null;             // This method is responsible for retrieving all countries through AJAX
    var getStations             = null;             // This method will retrieve all stations belonging to the selected country
                                                    // through AJAX
    var updateCountrySelector   = null;             // This is the callback method for getCountries and adds all countries found
                                                    // as options to the country selector
    var updateStationSelector   = null;             // This is the callback method for getStations and adds all stations found
                                                    // as options to the station selector
    var parse                   = null;             // Recursively parses the DOM starting at the StationFinder container element
                                                    // in search of elements required to successfully instantiate this class,
                                                    // it does this by inspecting the class name of each child node
    
    
    //
    // Public methods
    //
    this.setTitle               = null;             // Sets the title in the header element
    this.show                   = null;             // Shows the StationFinder instance
    this.hide                   = null;             // Hides the StationFinder instance
    
    
    ///////////////////////////////////
    // StationFinder Implementation
    ///////////////////////////////////    
    
    //
    // Private event handlers
    //
    
    var onCountrySelected = function()
    {
        //
        // Do nothing if there are no countries in the list
        //
        if (countrySelector.options.selectedIndex == 0)
            return;
        
        //
        // Get the code and the name of the selected country
        //
        var code = countrySelector.options[countrySelector.options.selectedIndex].value;
        var name = countrySelector.options[countrySelector.options.selectedIndex].text;
        
        //
        // Create a 'country' object, since almost all used simple objects have a code and
        // a name property and an instance of type Country can not be created, an anonymous object
        // with those properties suffices
        //
        selectedCountry = {code: code, name: name};
        
        //
        // Notify all event handlers registered to the countrySelected event that a country has been selected and 
        // let them know which one
        //
        this.countrySelected.fire(selectedCountry);
    }


    var onStationSelected = function()
    {   
        //
        // Do nothing if there are no countries in the list, because no valid selection can be made in this case
        //    
        if (countrySelector.options.selectedIndex == 0)
            return;
        
        //
        // Get the code and the name of the selected station
        //        
        var code = stationSelector.options[stationSelector.options.selectedIndex].value;
        var name = stationSelector.options[stationSelector.options.selectedIndex].text;
        
        //
        // Create a 'station' object, since almost all used simple objects have a code and
        // a name property and an instance of type Station can not be created, an anonymous object
        // with those properties suffices
        //        
        selectedStation = {code: code, name: name};
        
        //
        // Notify all event handlers registered to the stationSelected event that a station has been selected and 
        // let them know which one
        //        
        this.stationSelected.fire(selectedStation);
    }


    //
    // Private methods
    //
    
    var getCountries = function()
    {
        //
        // Reset the list and show a localized "loading" message, so the user knows the countries are being retrieved
        //
        countrySelector.options.length = 0;     
        countrySelector.options[0] = new Option("Loading",  "");
        
        //
        // Get the locale to use from the Page object to build a proper CountryRequest for use with the AJAX call
        //
        CountryRequest.language = Page.locale.languageCode;
        
        //
        // Get the countries using the CountryRequest, tell AJAX to use updateCountrySelector(...)  as a callback
        //
        OttAjaxService.getCountries(CountryRequest, new EventHandler(this, updateCountrySelector));
    }


    //
    // country: 
    //
    var getStations = function(country)
    {
        //
        // Reset the list and show a localized "loading" message, so the user knows the station for the selected counrty 
        // are being retrieved
        //    
        stationSelector.options.length = 0;     
        stationSelector.options[0] = new Option("Loading",  "");
        
        //
        // Get the locale to use from the Page object to build a proper CountryStationRequest for use with the AJAX call, also
        // put the code of the selected country in this Request object so the server knows for which country to return all stations
        //        
        CountryStationRequest.language = Page.locale.languageCode;
        CountryStationRequest.selectedCountry = country.code;
        
        //
        // Get the station for the selected country using the CountryStationRequest, tell AJAX to use updateStationSelector(...)  
        // as a callback
        //        
        OttAjaxService.getStationsInCountry(CountryStationRequest, new EventHandler(this, updateStationSelector));
    }
    

    //
    // response: The CountryResponse object returned by the AJAX call in the getCountries method
    //
    var updateCountrySelector = function(response)
    {
        //
        // Reset the list and manually add the first option which tell the user what to do with this control, the message shown
        // is a localized string telling him to select a country
        //      
        countrySelector.options.length = 0;
        countrySelector.options[0] = new Option("Select a country", "");
        
        //
        // Sort the result, since it is unsorted to begin with, by calling the native array.sort method with the custom 
        // sort(a,b,direction) method (core.js) as an argument so that a sort direction can be provided
        //
        response.countries.sort(function(a, b) { return sort(a.name, b.name, ASCENDING) });
        
        //
        // Create a new option in the select for each country in the result set, the name is displayed the code is used as
        // the value for the value attribute and is used for all communication purposes
        //
        for (var i=0; i<response.countries.length; i++)
            countrySelector.options[i+1] = new Option(response.countries[i].name, response.countries[i].code);
    }
    

    //
    // response: The CountryStationResponse object returned by the AJAX call in the getStations method
    //
    var updateStationSelector = function(response) 
    {
        //
        // Reset the list
        // 
        stationSelector.options.length = 0;
        
        //
        // If there are no errors and station(s) were returned, continue
        //
        if (!response.errors.length && response.stations.length)
        {
            //
            // Sort the result, since it is unsorted to begin with, by calling the native array.sort method with the custom 
            // sort(a,b,direction) method (core.js) as an argument so that a sort direction can be provided
            //        
            response.stations.sort(function(a, b) { return sort(a.name, b.name, ASCENDING) });
            
            //
            // Create a new option in the select for each station in the result set, the name is displayed the code is used as
            // the value for the value attribute and is used for all communication purposes
            //            
            for (var i=0; i<response.stations.length; i++)
            {
                stationSelector.options[i] = new Option(response.stations[i].name, response.stations[i].code);
            }
            
            //
            // Enable the station selector
            //
            stationSelector.removeAttribute("disabled");
        }
        
        //
        // Errors occured or no stations were found, so notify the user there are no stations by adding a localized message 
        // as the only option in the selector and disabling it for further use
        //
        else
        {
            stationSelector.options[0] = new Option("No stations found", "");
            stationSelector.setAttribute("disabled", "disabled");
        }
    }
    
    
    //
    // node: DOMStationFinderElement (constructor argument) and all its child nodes, since this method is recursive
    //
    var parse = function(node)
    {
        //
        // If there is no node found, continue to the next
        //
        if (IsNull(node)) 
            return;
        
        //
        // Needed nodes are identified by class name
        //
        if (node.className)
        {
            switch (node.className)
            {
                case "header": 
                    header = node; 
                    break;
                case "body":
                    body = node;
                    break;
                case "footer":
                    footer = node;
                    break;
                case "btnClose": 
                    btnClose = new Button(node);
                    break;
                case "countryselector":
                    countrySelector = node;
                    break;
                case "stationselector":
                    stationSelector = node;
                    break;
                case "btnSelect":
                    btnSelect = new Button(node);
                    break;
                default:
                    break;
            }
        }
        
        //
        // Iterate through this node's child nodes, this way the node tree get processed top-down processing an entire branch before
        // moving from left to the right to the next branch, this is the recursive part
        //
        for (var i=0; i<node.childNodes.length; i++)
        {
            parse(node.childNodes[i]);
        }
    }
    
    
    //
    // Public methods
    //    
    
    //
    // title: string representing the requested title
    //
    this.setTitle = function(title)
    {
        header.innerHTML = title;
    }
    
    
    this.show = function()
    {   
         if (countrySelector.options.length < 2) {
	getCountries();
         }
        //
        // Reset any previous selection(s)
        //
        selectedCountry = null;
        selectedStation = null;
        
        //
        // Reset the option list of both selectors
        //        
        countrySelector.options.selectedIndex = 0;
        stationSelector.options.length = 0;
        
        //
        // Disable the station selector, so the user first will have to select a country for which to show stations
        //
        stationSelector.setAttribute("disabled", "disabled");
        
        //
        // Make the StationFinder visible
        //
        DOMStationFinderElement.style.display = 'block';
        
        //
        // Default the focus to the country selector
        //
        countrySelector.focus();
    }
    
    
    this.hide = function()
    {
        //
        // Hide the StationFinder
        //
        DOMStationFinderElement.style.display = 'none';
    }
    

    ///////////////////////////////////
    // StationFinder Constructor
    ///////////////////////////////////        
    parse(DOMStationFinderElement);
    
    this.setTitle(title);
    //getCountries();
    
    //
    // Hook the onCountrySelected event handler to the country select control's native DOM event "onchange", so our custom event 
    // chain can be run each time the value of the country selector changes (=update the station list, etc..)
    //
    DOM.addEventListener(countrySelector, "change", new EventHandler(this, onCountrySelected));
    
    //
    // Make sure when a country is selected, a new list of stations for the selected country will be requested
    //
    this.countrySelected.addListener(new EventHandler(this, getStations));
    
    //
    // When the user confirms his/her selection with the select button, the StationFinder should auto-close
    //
    btnSelect.press.addListener(new EventHandler(this, onStationSelected));
    
    //
    // The previous line of code fires the event in this event hook which will auto-close the StationFinder when a selection has 
    // been confirmed by the user
    //
    this.stationSelected.addListener(new EventHandler(this, this.hide));
    
    //
    // Pressing the close button (marked by "X") should also close the StationFinder
    //
    btnClose.press.addListener(new EventHandler(this, this.hide));
}


//
// This is a class which can by used when multiple controls need to use a StationFinder, by using this class only one instance
// of the StationFinder has to reside within the page, the context is maintained by the consuming controls (through their event 
// handler setup in regards to this broker's show method. Consuming classes MUST IMPLEMENT a setStation(station) method where 
// station must be an object with at least a 'name' property and a 'code' property.
//
// DOMStationFinderElement  : A valid DOM element which acts as a valid container for the StationFinder class, check the parse(...) 
//                            method to see which child elements are needed to be present for instances of this class to function 
//                            properly
// defaultTitle             : The title which should be shown by default in the title bar of the popup, defaults to a empty string.
//
function StationFinderBroker(DOMStationFinderElement, defaultTitle)
{
    ///////////////////////////////////
    // StationFinderBroker Definition
    ///////////////////////////////////
    
    //
    // Private data members
    //
    var stationFinder       = null;             // Storage for the shared StationFinder instance this StationFinderBroker brokers
    var target              = null;             // Storage for the reference to the object which is currently using the 
                                                // StationFinder
    
    
    //
    // Private event handlers
    //
    var onStationSelected   = null;             // This event handler is called whenever a station is selected and will notify
                                                // the current target object of the change by calling the object's setStation 
                                                // method with the selected station as argument
    
    
    //
    // Public methods
    //
    this.show               = null;             // Shows the embedded StationFinder instance for a provided target object
    this.hide               = null;             // Hides the embedded StationFinder instance
    
    
    ///////////////////////////////////
    // StationFinderBroker Implementation
    ///////////////////////////////////    
    
    //
    // Private event handlers
    //

    //
    // station: object provided by the stationSelected event (object containing a 'name' property and a 'code' property
    //
    var onStationSelected = function(station)
    {
        //
        // Call the target object's setStation method within the context of that target object
        //
        target.setStation.call(target, station);
    }    
    
    
    //
    // oTarget  : reference, this is the object to which the selection should be pushed
    // title    : string, text shown in the header when the StationFinder is opened for the specified target object
    //
    this.show = function(oTarget, title)
    {
        //
        // Check if the passed object reference is valid and if the instance it refers to has the setStation method implemented
        //
        if (IsNull(oTarget))
            throw("Error: StationFinderBroker.show(oTarget [,title]), 'oTarget' is not a valid target object.");
        else if (IsNull(oTarget.setStation))
            throw("Error: StationFinderBroker.show(oTarget [,title]), 'oTarget' must implement a public 'setStation(station)' method.");
        
        //
        // A valid target object was found, set the header title, rollback in case of missing title is as follows:
        // title -> defaultTitle -> empty string
        //
        stationFinder.setTitle(!IsNull(title) ? title.toString() : (!IsNull(defaultTitle) ? defaultTitle.toString() : ""));
        
        //
        // Store the reference to the target object in case it needs to be used in other class methods
        //
        target = oTarget;
        
        //
        // Show the StationFinder instance embedded in this StationFinderBroker
        //
        stationFinder.show();
    }
    

    this.hide = function()
    {
        stationFinder.hide();
    }
 
 
    ///////////////////////////////////
    // StationFinderBroker Constructor
    ///////////////////////////////////
    
    //
    // Create the single persistent StationFinder instance which this broker will 
    // broker between the different registered consumers
    //
    stationFinder = new StationFinder(DOMStationFinderElement, defaultTitle);
    
    //
    // Always execute our internal event handler first when a station is selected
    //
    stationFinder.stationSelected.addListener(new EventHandler(this, onStationSelected));
}
// Content: js autocompleter
//
// This class represents a basic autocompleter
//
// input: A valid instance of the Input class or any class which extends it
// list: A valid instance of the List class or any class which extends it
//
// Dependencies : core.js,
//                input.js,
//                list.js
//
// NOTE         : Always create an instance of a class which extends this class, this class is meant to be an 
//                'abstract base' class, which in this case means it does not operate by itself, since the autocomplete 
//                and onResponse methods have no implementation (and should never get one in this class!).
//
function Autocompleter(input, list) 
{   
    ///////////////////////////////////
    // Autocompleter Definition
    /////////////////////////////////// 

    //
    // Public data members
    //
    this.input              = null;     // Reference to a valid Input class or any derived class instance
    this.list               = null;     // Reference to a valid List class or any derived class instance

    //
    // Private event handlers
    //
    var onKeyUp             = null;     // Handler called after a key has been pressed (keyup) in the input control
    var onListItemSelected  = null;     // Handler called when an item has been selected from the list control,
                                        // either by clicking with the mouse, pressing enter or navigating with the 
                                        // arrow keys
    var onLostFocus         = null;     // Handler called when either the input or the list control loses focus by 
                                        // either pressing VK_TAB to move focus to next control or by clicking outside
                                        // of this list and the input control

    //
    // Public event handlers
    //
    this.autocomplete       = null;     // HAS TO BE IMPLEMENTED IN DERIVED CLASS !!!
                                        // Handler called when a key other than VK_ARROWUP, VK_ARROWDOWN, VK_SHIFT
                                        // VK_CTRL, VK_ALT, VK_CAPSLOCK, VK_ENTER or VK_ESCAPE is pressed.
    
    this.onResponse         = null;     // HAS TO BE IMPLEMENTED IN DERIVED CLASS !!!
                                        // Callback handler which should be called when data is received to process it
    
    //
    // Properties
    //
    this.getValue           = null;     // Returns the text of the input control
    this.getItemByValue     = null;     // Returns the list item whose value matches the provided value
    
    
    //////////////////////////////////////
    // Autocompleter Implementation
    //////////////////////////////////////  

    //
    // e: valid KeyPressEventArgs instance (core.js) passed by input control when its keyup event is fired.
    //
    var onKeyUp = function(e) {
        switch (e.keyPressed) {
            case VK_ARROWUP: 
                if (this.list.isVisible())
                    return this.list.selectPreviousItem();
            case VK_ARROWDOWN: 
                if (this.list.isVisible())
                    return this.list.selectNextItem();
            case VK_SHIFT: 
            case VK_CTRL:
            case VK_ALT: 
            case VK_CAPSLOCK: 
                return;
            case VK_ENTER:
            case VK_ESCAPE: 
                 this.list.hide();
                return;
            default: 
                return this.autocomplete();
        }
    }   
    
    //
    // item: valid ListItem instance (list.js) passed by the list when its listItemSelected event is fired.
    //
    var onListItemSelected = function(item)
    {
        if (item)
        {
            // A valid ListItem has a text property and a value property, 
            // put the selected text in the input control
            this.input.setValue(item.text);
        }
        this.input.focus();
    }   

    //
    // e: not used
    //
    var onLostFocus = function(e) {
        if (!this.input.hasFocus() && this.list.isVisible())
        {
            this.list.hide();
        }
    }   
    

    //
    // Public event handlers
    //

    this.autocomplete = function()
    {
        alert("Autocompleter: 'autocomplete' has to be implemented in derived class");
    }
    
    this.onResponse = function() 
    { 
        alert("Autocompleter: 'onResponse' method has to be implemented in derived class"); 
    }
    

    //
    // Properties
    //

    this.getValue = function()
    {
        return this.input.getValue();
    }   

    //
    // value: valid list item's value
    //
    // returns null if no ListItem instance in the list has a matching value else the matching ListItem instance
    //
    this.getItemByValue = function(value)
    {
        return this.list.getItemByValue(value);
    }


    ///////////////////////////////////
    // Autocompleter Constructor
    ///////////////////////////////////
    this.input = input;
    this.list = list;   

    //  
    // Setup our event handlers so they are called when the targetted event is fired
    //
    this.input.keyUp.addListener(new EventHandler(this, onKeyUp));
    this.input.lostFocus.addListener(new EventHandler(this, onLostFocus));
    this.list.lostFocus.addListener(new EventHandler(this, onLostFocus));
    this.list.listItemSelected.addListener(new EventHandler(this, onListItemSelected));
}
// Content: js stationlist
//
// Class used to add list functionality (clicks, mouseovers and keypresses, navigation) 
// to a given DOM element, it converts an array of item data into StationListItem instances
// by overriding the createItem method of the List class (=base class)
//
// DOMListElement   : Any valid DOM container
//
// Dependencies     : core.js,
//                  : list.js                  
//
function StationList(DOMListElement)
{
    this.base(DOMListElement);      // Initialize the base class (Autocompleter), this always has to be done first

    ///////////////////////////////////
    // StationList Definition
    ///////////////////////////////////
    
    // See List class (list.js) definition. Add any StationList specifics here
    

    ///////////////////////////////////
    // StationList Implementation
    ///////////////////////////////////
    
    //
    // Overrides createItem in the List class (=base class) in order to write out a more customized item
    //
    // itemIndex: the 0 based index (position) of the item in the StationList, this value is stored on the StationListItem's 
    //            element for easy retrieval during event handling
    // code     : the station code (=value in the base class) which needs to be applied to the StationListItem
    // name     : the name of the station (=text in the base class) which needs to be displayed for the StationListItem
    //    
    this.createItem = function(itemIndex, code, name)
    {
        return new StationListItem(itemIndex, code, name);
    }
    

    ///////////////////////////////////
    // StationList Constructor
    ///////////////////////////////////
    
    //
    // Tell the base class which fields should be used for the value and the text of a ListItem, used when parsing the datasource
    // array
    //
    this.setValueField("code");
    this.setTextField("name");
    

}

//
//  StationList should derive from List, this call supplies StationList with the base class contructor 
//  (this.base() method)
//
Class.extend(StationList, List);


//
// This class represents a single StationListItem, which inherits from ListItem (list.js). Instances created by this class are 
// represented in the DOM by <A> elements, this class extends the default ListItem with tabindex attribute and a href attribute
//
// itemIndex    : the 0 based index (position) of the item in the StationList, this value is stored in the StationListItem's 
//                element attribute list for easy ListItem instance retrieval during event handling
// code         : the station code (=value in the base class) which needs to be applied to the StationListItem
// name         : the name of the station (=text in the base class) which needs to be displayed for the StationListItem
//
// Dependencies : core.js,
//                list.js
//
function StationListItem(itemIndex, code, name) 
{
    this.base(itemIndex, code, name);           // Initialize the base class (Autocompleter), this always has to be done first
    
    //
    // Set the custom attributes
    //
    this.node.setAttribute("href", "#");
    this.node.setAttribute("tabindex", itemIndex+7);
}

//
//  StationListItem should derive from ListItem, this call supplies StationListItem with the base class contructor 
//  (this.base() method)
//
Class.extend(StationListItem, ListItem);
// Content: js stationautocompleter
//
// This class, which derives from the Autocompleter class, offers specialized autocompletion and caching
// for station/airport business objects. When text input is given by the user in the related textbox 
// a list with possible matches (name-value pairs) will be shown. Autocompletion can be done by name as 
// well as by code. When not called manually autocompletion through Ajax takes place only for the first 
// letter, if this letter has not been used before, and on the third character to check if a valid airport 
// code has been entered (these are always 3 character codes)
//
// DOMInputElement  : A valid DOM element which can provide user input
// DOMListElement   : Any valid DOM container which will contain the suggestion list 
//
// Dependencies     : core.js,
//                    input.js,
//                    autocompleter.js,
//                    stationlist.js,
//                    StationRequest.js,
//                    OttAjaxService.js,
//                    [path to dwr]\engine.js
//
// NOTE             : A global object 'Page.locale' MUST be present and have a property named 'languageCode' and
//                    one named 'countryCode' set in accordance with their normal usage with KLM.com
//                    (as of yet dashboardpage.js and resultpage.js both provide this functionality)
//
function StationAutocompleter(DOMInputElement, DOMListElement) 
{ 
    this.base(new Input(DOMInputElement), new StationList(DOMListElement)); // Initialize the base class (Autocompleter), 
                                                                            // this always has to be done first

    ///////////////////////////////////
    // StationAutocompleter Definition
    ///////////////////////////////////
    
    //
    // Private data members
    //
    var hideList            = false;        // Does the list have to be hidden


    //
    // Private event handlers
    //
    var onListItemCreated   = null;         // Event handler which is called each time an item is created and added by the list
    

    //
    // Public event handlers
    //
    this.autocomplete       = null;         // This event handler is used to trigger autocompletion internally, but can also be
                                            // called by consuming code. It is public in order to overwrite the autocomplete method
                                            // defined in the Autocompleter base class. If more than one possible match is found
                                            // these matches will be shown in the list
    this.onResponse         = null;         // This event handler is called by the autocomplete event handler in case an Ajax call
                                            // returned (callback) from the Java application in order to autocomplete a non-cached 
                                            // item. It is public in order to overwrite the autocomplete method defined in the 
                                            // Autocompleter base class.

    
    //
    // Properties
    //
    this.setInitialValue    = null;         // Used only by the Dashboard on both the dashboard page (when parsing and checking
                                            // values set in the cookie) and the result page (when parsing and validating the values
                                            // past by the URL from the dashboard page), it ensures the list will not be shown when
                                            // initial values are set
    this.getValue           = null;         // Either returns the value part (text) of the selected list item or if there is no
                                            // selected item, the literal text as set in the textbox
    this.setValue           = null;         // Tries to set the provided value through autocompletion
    this.setStation         = null;         // This property is implemented to be able to set station/airport as selected in the
                                            // StationFinder instances (these look for this method in consuming objects)
                                            // as the current value in the StationAutocompleter.
    
    
    //
    // Public classes
    //
    this.cache              = null;         
    
    
    //////////////////////////////////////
    // StationAutocompleter Implementation
    //////////////////////////////////////  

    //
    // Private event handlers
    //

    //
    // item: The reference to the item created by the StationList instance, this reference is passed in the event arguments
    //       of the ListItemCreated event in the list
    //    
    var onListItemCreated = function(item)
    {
        if (!this.cache.contains(item.text, "text"))
            this.cache.add(item);
    }    
    
    
    //
    // Public event handlers
    //
    
    //
    // value: The name, partial name or code of the station/airport which should be autocompleted
    //
    this.autocomplete = function(value)
    {
        //
        // keyword contains the text to autocomplete, if none was provided through this method's arguments
        // the text input from the user provided through the textbox is used
        //
        var keyword = value || this.input.getValue();
    
        //
        // If there is no text in the textbox, hide the list (early out)
        //
        if (this.input.isEmpty())
            this.list.hide();
        
        //
        // Only if the text input is different from the previous text input either a cached item is returned or an Ajax call
        // will be made to try and autocomplete
        //
        else if (this.input.hasChanged())
        {
            //
            // Retrieve any cached items which match the requested keyword, sort them alphabetically on their text component in
            // ascending order. The first sort is the native array sort, this sort can be passed a custom function, in this case 
            // the generic sort method provided by the core library (core.js), which you can pass a sort direction.
            //
            var cachedItems = this.cache.get(keyword).sort(function(a, b) { return sort(a.text, b.text, ASCENDING); });
            
            //
            // Matches found in the cache, provide them as datasource to the list so they can be created in the list and shown
            //
            if (cachedItems.length)
            {
                this.list.setDatasource(cachedItems);
                if (cachedItems.length)
                    this.list.show();           
            }

            //
            // No matches found in the cache, so it is either a new first letter being requested or a three letter code, either
            // way connect with the Java application through the OttAjaxService business object to request items matching the 
            // requested keyword
            //
            else
            {
                StationRequest.language = Page.locale.languageCode;
            
                //
                // If the keyword is not a three letter airport/station code, make sure only the first letter of the keyword is
                // send to be queried, this because the application will match any non-single character queries on airport/station
                // codes and thus not return any results if more than one character is send for a non-code query
                //
                StationRequest.firstLetter = (keyword.length != 3 ? keyword.substr(0,1) : keyword);
                
                //
                // Execute the search, and pass any results to the onRepsonse event handler, the 3rd and 4th argument in the 
                // EventHandler constructor are passed to the onResponse handler as well, they are the requested keyword and 
                // flushCache arguments of the onResponse event handler. The cache needs to be flushed in case a requested code
                // is also part of the name of the aiport/station it belongs to, because otherwise the list would contain a 
                // duplicate. For example when searching for the code 'AMS' it will already present in the list since the user
                // searched for 'AM' since it is the code belonging to 'Amsterdam - Schiphol'.
                //
                OttAjaxService.getStationsByLetter(StationRequest, new EventHandler(this, this.onResponse, keyword, (value && value.length == 3)));
            }
        }
        
        //
        // In any other case something unexpected happened, since this is not critical enough to throw exceptions, it is handled
        // gracefully by hiding the list
        //
        else
            this.list.hide();   
    }     
    
    
    //
    // keyword      : string containing the full query text (in this case set up during the creation of the EventHandler 
    //                  encapsulating this method which can be found in the autocomplete event handler above.
    // flushCache   : boolean indicating if the cache needs to be flushed. This is necessary  a requested code
    //                is also part of the name of the aiport/station it belongs to, because otherwise the list would contain a 
    //                duplicate. For example when searching for the code 'AMS' it will already present in the list since the user
    //                searched for 'AM' since it is the code belonging to 'Amsterdam - Schiphol'.
    // response     : The StationResponse object as returned by the call to OttAjaxService.getStationsByLetter(..). For its 
    //                available properties check out the application's Java docs
    //
    this.onResponse = function(keyword, flushCache, response)
    {
        if (!response.errors.length && response.stations.length)
        {
            //
            // Copy the reference to the array of found matches, for code readability
            //
            var datasource = response.stations;
            
            //
            // Pass the results to the list so it can create the appropriate items in the cache
            //
            this.list.setDatasource(datasource);

            //
            // Query the cache for items, so in case of code search the items with text matching the keyword are also
            // retrieved and vice versa
            //
            datasource = this.cache.get(keyword);
            
            //
            // Alphabetically sort the final result array in ascending order
            //
            datasource = datasource.sort(function(a,b) { return sort(a.text, b.text, ASCENDING); });
            
            //
            // Recreate the list so it reflects the result array and shows all the items found by code and by name
            //
            this.list.setDatasource(datasource);
            
            //
            // If the list should be hidden manually (in case of cookie value or url value) set the cache to flush
            // this will force the list to have one item which will also be selected (business logic dictates this
            // may happen only at initialization)
            //
            if (this.hideList)
            {
                flushCache = true;
                this.hideList = false;
            }

            //
            // One item and cache set to flush (only happens as of yet when initializing) so select the item and hide 
            // the list
            //
            if (datasource.length == 1 && flushCache)
            {
                this.list.selectNextItem();
                this.list.hide();
            }
            
            //
            // More than one item found so display the list to the user
            //
            else if (datasource.length)
            {
                this.list.show();
            }

            //
            // Gracefull failure by hiding the list
            //
            else
            {
                this.list.hide();
            }
            
            //
            // If the cache was set to flush, we are now done with it so flush it (only happens at initialization 
            // with cookie value or url value when one item should automatically select and no list of options should
            // be displayed)
            //
            if (flushCache)
                this.cache.flush();
        }
    }     
    
    
    //
    // Properties
    //
    
    //
    // value: Any text string, represents the value against which to match the available options
    //
    this.setInitialValue = function(value)
    {
        //
        // Display the query in the textbox
        //
        this.input.setValue(value);
        
        //
        // Attempt to autocomplete it
        //
        this.autocomplete(value);
        
        //
        // Since this method is only used during initialization and this is the only time we want a pre-select
        // the list needs to be manually forced to hide
        //
        this.hideList = true;
    }    
    
    
    this.getValue = function()
    {
        //
        // If there is a list item currently selected, return its text component
        //
        if (this.list.getSelectedItem())
            return this.list.getSelectedItem().value;
            
        //
        // If not return the raw input of the textbox
        //
        else
            return this.input.getValue();
    }

    
    //
    // value: Any text string, represents the value against which to match the available options
    //
    this.setValue = function(value)
    {
        //
        // Attempt to autocomplete the query
        //    
        this.autocomplete(value);
    }
    
    
    //
    // station: A valid Station instance as defined in the Java docs
    //
    this.setStation = function(station)
    {
        //
        // Use the code part of the station, since this garantees only one unique result will be selected by the 
        // StationAutocompleter instance
        //
        //this.setValue(station.code);
        this.setInitialValue(station.code);
    }


    ///////////////////////////////////
    // StationAutocompleter Constructor
    ///////////////////////////////////

    //
    // Listen to the list's ListItemCreated event so items created can, if necessary be added to the cache
    //
    this.list.listItemCreated.addListener(new EventHandler(this, onListItemCreated));


    ///////////////////////////////////
    // Public classes
    ///////////////////////////////////

    //
    // Singleton instance used internally by the StationAutocompleter to store retrieved station code (key) / station name (value)
    // for fast access later. This caching takes place inside the StationAutocompleter's onResponse event handler, cache requests 
    // are made by its autocomplete event handler
    //
    // Example flow of item being added:
    //
    // 'Amsterdam - Schiphol' (keyword), 'AMS' (value)
    //
    // 1) Create an object with these as keyword and value and add it to the objects array
    // 2) Add code to values dictionary and let it point to the newly created object (by index)
    // 3) Add 'A' to keywords dictionary and let it also point to the newly created object (by index)
    // 4) Repeat 3) for 'Am', 'Ams', 'Amst', etc. until the full text has been parsed
    //
    this.cache = new function()
    {
        ///////////////////////////////////
        // Cache Definition
        ///////////////////////////////////
        
        //
        // Private data members
        //
        var objects     = null;     // Contains the actual cached item with value property and text property (entire string), 
        var values      = null;     // Contains all cached station/airport codes and for each the index into the objects array
                                    // where the actual object used for display can be found
        var keywords    = null;     // Contains all airport/station names (entire string and substrings) and for each the index 
                                    // into the objects array where the actual object used for display can be found


        //
        // Public methods
        //
        this.add        = null;     // Adds an item to the cache
        this.get        = null;     // Gets an item from the cache by text and by value, by default duplicates are eliminated, but 
                                    // if necessary they can be allowed. This method is case insensitive.
        this.getByText  = null;     // Gets an item by text (station/airport name, partial or full), an exclusion filter can be
                                    // specified to ignore certain items (= array of objects with value property containing the code)
                                    // This method is case insensitive.
        this.getByValue = null;     // Gets an item by value (station/airport code). This method is case insensitive.
        this.contains   = null;     // Checks if the cache contains a specific item. This method is case insensitive.
        this.flush      = null;     // Resets the cache to its uninitialized state


        ///////////////////////////////////
        // Cache Implementation
        ///////////////////////////////////
        
        //
        // item: Object to add to the cache, it must have a 'value' and a 'text' property since it is a list item
        //
        this.add = function(item) 
        {
            var idx = objects.length;

            //
            // Create a new object based on the provided one (deep copy), this to prevent alterations on the passed item
            // to influence the cached item (would otherwise be possible since item is passed by reference)
            //            
            objects[idx] = new Object();
            objects[idx].value = item.value;
            objects[idx].text = item.text;

            //
            // Create an entry for this item by code in the values dictionary (codes linked to object index)
            //
            values[item.value.toLowerCase()] = idx;

            //
            // Create keywords in the keywords dictionary for the text this is done as follows:
            //
            // 1) Add the first character to the dictionary and let it point to the newly created object (by index)
            // 2) Repeat 1) for a substring which grows by 1 character each iteration always starting at the first charcter 
            //    till finally the full text is added as an entry
            //
            for (var i=1; i<=item.text.length; i++) 
            {
                var subkey = item.text.substring(0, i).toLowerCase();

                //
                // Each keyword gets an array of indices into the objects array, since one keyword may be viable for more than one
                // object
                //
                if (!this.contains(subkey, "text")) 
                {
                    keywords[subkey] = new Array();
                }
                keywords[subkey][keywords[subkey].length] = idx;
            }
        }
        
        
        //
        // value            : string, either code or text of the object(s) to retrieve, search will be performed on code and text
        // allowDuplicates  : boolean, indicating if duplicates should be allowed in the result array. This can occur in cases
        //                    where the code is also the first part of the text. By default this argument is set to false.
        //
        this.get = function(value, allowDuplicates)
        {
            //
            // Use default value 'false' in case the argument was not provided
            //
            allowDuplicates = allowDuplicates ? allowDuplicates : false;
            
            value = value.toLowerCase();

            var result = new Array();

            //
            // Try to get the requested 'text' in the values dictionary (by code), this always returns 0 or 1 result
            //
            result = result.concat(this.getByValue(value));
            
            //
            // In case duplicate results are allowed, any results found in the keywords dictionary (by text) can just be
            // concatenated to the previously found results
            //
            if (allowDuplicates)
            {
                result = result.concat(this.getByText(value));
            }
            
            //
            // If no duplicates are allowed, remove doubles
            //
            else
            {
                //
                // Try to retrieve objects matching the requested 'text' in the keywords dictionary using getByText and put
                // them in a temporary array, so they as of yet remain seperate from the rest of the results found so far, this
                // for de-doubling purposes
                //
                var result2 = this.getByText(value, result);
                var bValueExists = false;
                
                //
                // In the values dictionary there are only entries consisting of 3 characters (codes) and each is unique, therefore
                // if anything was found, there will only be one result
                //
                if ( result.length == 1 )
                {
                    //
                    // Loop over the object(s) which were found in the keywords dictionary (text), the result of that search can 
                    // contain more than one object, since texts do not have to be unique and can partially match as well. 
                    //
                    for (var ii = 0; ii < result2.length; ii++ )
                    {
                        var item = result2[ii];
                        
                        //
                        // Compare each entry here against the text of the object found by code, if it matches a double has been found
                        //
                        if ( item.text.toLowerCase() == result[0].text.toLowerCase() )
                        {
                            bValueExists = true;
                        }
                    }
                }
                
                //
                // A duplicate has been found, since there is only one item in the results found by code in the values dictionary and
                // this item is also present in the results of the text search in the keywords dictionary, the former can be ditched
                //
                if ( bValueExists )
                {
                    result = result2;
                }
                
                //
                // No doubles, so both result sets are needed to be returned, concatenate them and return the final result
                //
                else
                {
                    result = result.concat(result2);
                }
            }
            return result;
        }


        //
        // keyword      : string, the keyword to search for (case insensitive)
        // excludeList  : array of strings to exclude from the results (optional)
        //
        this.getByText = function(keyword, excludeList) 
        {
            var result = new Array();
            
            //
            // If a exclude list has been provided, convert it in an object (dictionary) for easier matching
            //
            var exclude = new Object();
            if (excludeList && excludeList.constructor.name == "Array")
            {
                for (var i=0; i<excludeList.length; i++)
                {
                    if (!IsNull(excludeList[i].value))
                        exclude[excludeList[i].value.toLowerCase()] = excludeList[i].value.toLowerCase();
                }
            }
            
            //
            // Check if there is an entry for the requested keyword
            //
            if (this.contains(keyword, "text")) 
            {
                keyword = keyword.toLowerCase();
              
                //
                // The requested keyword exists in the keywords dictionary, so get all objects pointed to by the keyword.
                // If the text component of any of them exists in the exclude list, leave it out of the final result. This match
                // is case insensitive
                //
                var result = new Array();
                for (var i=0; i<keywords[keyword].length; i++) {
                    var object = objects[keywords[keyword][i]];
                    if (IsNull(exclude[object.value.toLowerCase()]))
                        result[result.length] = object;
                }
            }
            return result;
        }


        //
        // value: The value (code) for which to return a match if there is one (is always 0 or 1). The matching is performed case
        //        insensitive.
        //        
        this.getByValue = function(value)
        {
            var result = new Array();
            if (this.contains(value, "value")) 
            {
                result.push(objects[values[value.toLowerCase()]]);
            }
            return result;
        }


        //
        // value : string, value (code) or text (name) for which to check if it exists within one or more of the dictionaries
        // type  : 'text' (only search by name), 'value' (only search by code) and 'both' (by name and code)
        //
        this.contains = function(value, type) {
            type = type.toLowerCase() || "text";
            if (value && value.length)
            {
                value = value.toLowerCase();
                if (type == "text")
                    return !IsNull(keywords[value]);
                if (type == "value")
                    return !IsNull(values[value]);
                else if (type == "both")
                    return (!IsNull(values[value]) || !IsNull(keywords[value]));
            }
            return false;
        }

        
        this.flush = function()
        {
            delete keywords;
            delete values;
            
            for (var i=0; i<objects.length; i++)
                delete objects[i];
                
            delete objects;
            
            objects = new Array();
            values = new Object();
            keywords = new Object();
        }
        

        ///////////////////////////////////
        // Cache Constructor
        ///////////////////////////////////    
        var objects = new Array();                                      
        var values = new Object();                                      
        var keywords = new Object();
    }   
}

//
//  StationAutocompleter should derive from Autocompleter, this call supplies StationAutocompleter with the base class contructor 
//  (this.base() method)
//
Class.extend(StationAutocompleter, Autocompleter);
// Content: js dashboard_ott
//
// Class used to instance an OTT Dashboard for both the dashboard page as well as the result page
//
// blnUsePostback   : set to true to submit search using postback (dashboard page), to false to use AJAX (result page)
//
// Dependencies     : core.js,
//                    resultpage.js,
//                    stationfinder.js, 
//                    calendar.js, 
//                    stationautocompleter.js,
//                    button.js,
//                    radiobutton.js,
//                    cookies.js,
//                    [dwr interfaces path]/SearchRequest.js,
//                    [dwr interfaces path]/OttAjaxService.js
//
var searchTable = {BOTH: 0, DEPARTURES: 1, ARRIVALS: 2} // Enumeration which list the possible tables to search in.

function Dashboard_OTT(blnUsePostback) 
{
    ///////////////////////////////////
    // Dashboard_OTT Definition
    ///////////////////////////////////

    //
    // Private data members
    //
    var stationFinderBroker         = null;         // Station finder popup broker control
    var btnOriginStationFinder      = null;         // Link which shows the station finder for origin
    var btnDestinationStationFinder = null;         // Link which shows the same station finder for destination
    var calendarBroker              = null;         // Calendar popup broker control
    var btnDepartureCalendar        = null;         // Button which shows the calendar to pick a departure date
    var btnReturnCalendar           = null;         // Button which shows the same calendar to pick a return date
    var originAutocompleter         = null;         // StationAutocompleter used to fill in an origin airport (by name or code)
    var destinationAutocompleter    = null;         // StationAutocompleter used to fill in an destination airport (by name or code)
    var btnSearch                   = null;         // Button shown in the dashboard used to 'submit' search criteria
    // 20080717 WvdH: added Mark's fix to prevent an empty resultpage from search for results
    var searchAllowed               = true;

    //
    // Public data members
    //
    this.departureDatePicker        = null;         // Two dropdownlists (day, month+year) used to pick a departure date
    this.returnDatePicker           = null;         // Two dropdownlists (day, month+year) used to pick a return date
    this.tripTypeSelector           = null;         // Radiobutton group (one for one way and one for round trip)
    this.btnNewSearch               = null;         // Button shown below any results can also be used to 'submit' search criteria
                                                    // OPTIONAL: only there when blnUsePostback is set to false (result page)

    //
    // Events
    //
    this.searching                  = new Event();  // This event is fired immediately when search is called
    this.results                    = new Event();  // This event is fired when dashboard gets a response to a search request

    
    //
    // Private event handlers
    //
    var onResponse                  = null;         // Callback handler for search requests, this handler fires the results event
    var onDepartureDateChanged      = null;         // Handler called when the departure date changes, makes sure it can not be 
                                                    // further in the future than the return date, if it is this handler sets the 
                                                    // return date to be equal to the new departure date
    var onReturnDateChanged         = null;         // Handler called when the return date changes, makes sure it can not be 
                                                    // further in the past than the departure date, if it is this handler sets the 
                                                    // departure date to be equal to the new return date
    var onTripTypeChanged           = null;         // Handler called whenever the user clicks a different radio button in the 
                                                    // group, when changed to 'one way' all return controls are hidden

    
    //
    // Properties
    //
    this.getOrigin                  = null;         // Returns a reference to the origin autocompleter
    this.getDestination             = null;         // Returns a reference to the destination autocompleter

    
    //
    // Public methods
    //
    this.search                     = null;         // Method called by 'search' and 'new search' buttons to 'submit' the search 
                                                    // criteria and execute the search. Can also be called externally to initiate
                                                    // a search request. If blnUsePostback is set to true (dashboard page) the 
                                                    // search criteria are set in the url's querystring after which a redirect to
                                                    // the result page takes place, if blnUsePostback is false an AJAX search 
                                                    // request is created and executed
    this.focus                      = null;         // Method used by "new search" to scroll to the top of the page giving focus 
                                                    // to the dashboard

    
    ///////////////////////////////////
    // Dashboard_OTT Implementation
    /////////////////////////////////// 

    //
    // Private event handlers
    //
    
    //
    // response: SearchResponse object passed by the remote procedure
    //
    var onResponse = function(response)
    {
        if (!response.errors.length)
        {
                //
                // Store valid station codes in a cookie, so when user returns to the site next time he/she gets
                // the last searched stations prefilled
                //
                // 20080717 WvdH: added Mark's fix to prevent an empty resultpage to do a search
                // setCookie("fromairport", String.UTF16toUTF8(originAutocompleter.getValue()), 10);
                // setCookie("toairport", String.UTF16toUTF8(destinationAutocompleter.getValue()), 10);
        }
        this.results.fire(response);    
    }   
    
    //
    // date: valid Date instance passed by the DatePicker instance used for the 
    //       departure date once its selection is changed
    //
    var onDepartureDateChanged = function(date)
    {
        var returnDate = this.returnDatePicker.getDate();

        //
        // If the new date is further in the future than the return date, synchronize them
        //
        if (date > returnDate)
            this.returnDatePicker.setDate(date);
    }

    //
    // date: valid Date instance passed by the DatePicker instance used for the 
    //       return date once its selection is changed
    //
    var onReturnDateChanged = function(date)
    {
        var departureDate = this.departureDatePicker.getDate();

        //
        // If the new date is further in the future than the departure date, synchronize them
        //      
        if (date < departureDate)
            this.departureDatePicker.setDate(date);
    }   

    //
    // e: instance of SelectionChangedEventArgs (found in radiobutton.js) passed by
    //    the radio button group when a radio button is selected. This event arguments
    //    object contains a reference to the selected item and to the previously 
    //    selected item
    //
    var onTripTypeChanged = function(e)
    {
        switch (parseInt(e.selectedItem.getValue()))
        {
            // Trip type is 'one way'
            case 1:
                $("returndate").style.visibility = "hidden"; // Hide all controls for the return journey
                break;
                
            // Trip type is 'round trip' or other type
            default:
                $("returndate").style.visibility = "visible"; // Show all controls for the return journey
                break;
        }
    }


    //
    // Properties
    //  
    this.getOrigin = function()
    {
        return originAutocompleter;
    }   
    
    this.getDestination = function()
    {
        return destinationAutocompleter;
    } 
    
    
    //
    // Public methods
    //  

    //
    //Public method added for resetting the show partners checkbox
    //
    this.searchNew = function(withPartners) 
    {
        //on searchbutton pressed uncheck partners box
        if ($("chxFlightsPartners")) {
            if (!withPartners)
                $("chxFlightsPartners").checked = false;
            //make sure that on new search sortcolumn and direction are reset;
            //this.btnSearchPressed = true;
            //make sure that on new search sortcolumn and direction are reset for both timetables
            Page.departTimetable.resetActiveSortColumn();
            Page.returnTimetable.resetActiveSortColumn();
        }
        //initiate search
        this.search();
    }

    //
    // whichTable: value from the searchTable enumeration i.e. searchTable.BOTH to search for both departure as well 
    //             as return flights
    //
    this.search = function(whichTable) 
    { 
        // 20080717 WvdH: added Mark's fix to prevent an empty resultpage to do a search  
        if (!searchAllowed)
        {
            searchAllowed = true;
            return;
        }

        //
        // Notify interested objects that a search is in progress
        //
        this.searching.fire();

        //
        // By default search for both departure and return flights
        //
        whichTable = !IsNull(searchTable[whichTable]) ? whichTable : searchTable.BOTH;      
       
        // 20070717 WvdH: added Mark's fix to prevent an empty resultpage to search        
        //
        // Store valid station codes in a cookie, so when user returns to the site next time he/she gets
        // the last searched stations prefilled
        //
        setCookie("fromairport", originAutocompleter.getValue(), 10);
        setCookie("toairport", destinationAutocompleter.getValue(), 10);
        

        //
        // Dashboard page
        //
        // Using postback to result page in order to search
        //
        if (blnUsePostback)
        {
            //
            // Grab the url
            //
            var url = new Uri(document.location);
            
            //
            // Add search criteria to the query string
            //
            url.querystring.addParam("origin", originAutocompleter.getValue());
            url.querystring.addParam("dest", destinationAutocompleter.getValue());
            url.querystring.addParam("depDate", this.departureDatePicker.toString());
            url.querystring.addParam("retDate", this.returnDatePicker.toString());
            url.querystring.addParam("triptype", this.tripTypeSelector.getValue());
            
            // Now redirect to the result page using the same protocol and the query string.
            //
            // NOTE: Do not change $vars unless you are absolutely sure what you are doing,
            //       since these are localized by Tridion during publishing
            //
            document.location.href = url.protocol+"://www.klm.com/travel/ie_en/travel_information/travel_planning/timetable/timetable_result.htm"+url.querystring.toString();
        }
        
        //
        // Result page
        //
        // Using AJAX to search
        //
        else
        {
            //
            // Fill out the global SearchRequest object provided by DWR with the search criteria
            //
            var isOneWay = (this.tripTypeSelector.getValue().toString() == '1');
            SearchRequest.timetableType = $("chxFlightsPartners").checked ? "SkyteamTimetable" : "Timetable";

            //
            // If one of the following is not enabled its corresponding array in the SearchResponse will have 0 elements
            //
            SearchRequest.enableOutbound = (whichTable == searchTable.BOTH || whichTable == searchTable.DEPARTURES);
            SearchRequest.enableInbound = ((whichTable == searchTable.BOTH || whichTable == searchTable.ARRIVALS) && !isOneWay);

            //
            // Global settings can be grabbed from the global Page object (in this case defined in resultpage.js)
            //
            SearchRequest.preferredLanguage = Page.locale.languageCode;

            SearchRequest.originCode = originAutocompleter.getValue();
            var departureDate = this.departureDatePicker.getDate();
            SearchRequest.departureDate = String.format("{0}-{1}-{2}", departureDate.getFullYear(), departureDate.getMonth().toString().pad('0',2), departureDate.getDate().toString().pad('0', 2));

            SearchRequest.destinationCode = destinationAutocompleter.getValue();
            var returnDate = this.returnDatePicker.getDate();
            SearchRequest.returnDate = String.format("{0}-{1}-{2}", returnDate.getFullYear(), returnDate.getMonth().toString().pad('0',2), returnDate.getDate().toString().pad('0', 2));

            SearchRequest.oneWayOrReturn = this.tripTypeSelector.getValue();    

            //
            // Execute the search remotely on the server, and when results are returned process these in the onResponse 
            // event handler using this Dashboard as the 'this' scope in the handler
            //
            OttAjaxService.getConnections(SearchRequest, new EventHandler(this, onResponse));
        }
    }
    
    this.focus = function() 
    {
        window.scrollTo(0,0);
    }


    ///////////////////////////////////
    // Dashboard_OTT Constructor
    /////////////////////////////////// 
    // Make sure dashboard is properly aligned cthis hack is added cause of really terrible css work
    // Added on 16-08-08 by Cor
    if($("heightcheck"))
       $("departuredate").style.height = $("heightcheck").offsetHeight + "px";

    stationFinderBroker         = new StationFinderBroker(document.getElementById("stationfinderbroker"));
    btnOriginStationFinder      = new Button($("btnStationFinder1"));
    btnDestinationStationFinder = new Button($("btnStationFinder2"));   
    calendarBroker              = new CalendarBroker($("calendarbroker"));
    btnDepartureCalendar        = new Button($("btnCalendar1"));
    btnReturnCalendar           = new Button($("btnCalendar2"));    
    originAutocompleter         = new StationAutocompleter($("origin_ott"), $("originlist"));
    destinationAutocompleter    = new StationAutocompleter($("destination_ott"), $("destinationlist"));
    btnSearch                   = new Button($("btnSearch"));

    this.btnNewSearch           = !blnUsePostback ? new Button($("btnNewSearch")) : null;
    this.departureDatePicker    = new DatePicker($("dep_day_other"), $("dep_monthyear_other"));
    this.returnDatePicker       = new DatePicker($("ret_day_other"), $("ret_monthyear_other")); 
    this.tripTypeSelector       = new RadioButtonGroup("triptype");
 
    //
    // When the trip type changes the return date controls should be shown/hidden
    //
    this.tripTypeSelector.selectionChanged.addListener(new EventHandler(this, onTripTypeChanged));
   
    //
    // Prefill of last searched station codes (cookies) is only done on the dashboard page (blnUsePostback is set to true)
    //
    if (blnUsePostback)
    {
        try 
        {
            originAutocompleter.setInitialValue(getCookie("fromairport"));
        } 
        catch (e) {
            originAutocompleter.setInitialValue(originAutocompleter.getValue());
        }

        try 
        {
            destinationAutocompleter.setInitialValue(getCookie("toairport"));
        } 
        catch (e) {
            destinationAutocompleter.setInitialValue(destinationAutocompleter.getValue());
        }
    }
    
    //
    // Prefill from query string on the result page
    //
    else
    {
        //
        // NOTE: Please keep in mind that because of Java or Websphere url parameters ARE case sensitive as opposed
        //       to what is specified in the regular url specs
    
        //
        // Grab the current url and create an Uri instance based on it 
        // for easy access to the query string
        //
        var url = new Uri(document.location);
        
        // 20070717 WvdH: added Mark's fix for empty resultpage
        //
        // Grab the url params
        //
        var origin          = url.querystring.getParam("origin");
        var destination     = url.querystring.getParam("dest");
        var depdate         = url.querystring.getParam("depDate");        
        var retdate         = url.querystring.getParam("retDate");          
        var triptype        = url.querystring.getParam("triptype");
        var showpartners    = url.querystring.getParam("showpartners");
        var partners    = url.querystring.getParam("flightPartner");

        try 
        {
            originAutocompleter.setInitialValue(origin); // was: url.querystring.getParam("origin"));
            destinationAutocompleter.setInitialValue(destination); // was: url.querystring.getParam("dest"));
        }
        catch (e) {}    // Just continue in case prefill fails, see if anything else can be prefilled
        
        //
        // Grab the dates in the url, they are formatted as: "yyyymmdd"
        //
        try
        {
            // var depdate = url.querystring.getParam("depDate");
            if (depdate.length == 8) 
            {
                var date = new Date();          
                date.setFullYear(parseInt(depdate.substring(0,4), 10), parseInt(depdate.substring(4,6), 10)-1, parseInt(depdate.substring(6,8), 10));
                date.setHours(0,0,0,0);
                this.departureDatePicker.setDate(date);
            }
        }
        catch (e) {}    // Just continue in case prefill fails, see if anything else can be prefilled
        
        try
        {
            // var retdate = url.querystring.getParam("retDate");  
            if (retdate.length == 8)
            {   
                var date = new Date();          
                date.setFullYear(parseInt(retdate.substring(0,4), 10), parseInt(retdate.substring(4,6), 10)-1, parseInt(retdate.substring(6,8), 10));
                date.setHours(0,0,0,0);
                this.returnDatePicker.setDate(date);
            }
        }
        catch (e) {}    // Just continue in case prefill fails, see if anything else can be prefilled

        try
        {
            this.tripTypeSelector.setValue(triptype); // was: url.querystring.getParam("triptype"));
        }
        catch (e) {}    // Just continue in case prefill fails
        
        try
        {
           var param = partners; // was: url.querystring.getParam("partners"));
           if (param.toUpperCase() == "TRUE")
                $("chxFlightsPartners").setAttribute("checked", "checked");
            else
                $("chxFlightsPartners").removeAttribute("checked");
        }
        catch (e) {}    // Just continue in case prefill fails

    if (!origin && !destination && !depdate && !retdate && !triptype && !showpartners)
        searchAllowed = false;

    }

    
    //
    // Controller setup: Controls are hooked to eachother to perform complex business logic between them.
    //
    
    //
    // When one of the station finder buttons is pressed, the station finder broker needs to be shown in the appropriate state.
    // Explanation of EventHandler arguments:
    //
    // scope            : Since the second argument is the method to call and this is a method within the 
    //                    stationFinderBroker scope ('this' pointer in the method points to members 
    //                    in stationFinderBroker instance NOT the dashboard instance) the scope of the 
    //                    event handler needs to be set to stationFinderBroker instead of the usual 'this' scope
    //                    in order for the method to function correctly
    // callback method  : The callback method to execute when the press event is fired
    // target object    : The stationFinderBroker needs a target object to which it can output its results by calling
    //                    the setStation(station) which has to be implemented in the target object.
    // title            : The title which will be shown once the StationFinder instance is shown
    //
    btnOriginStationFinder.press.addListener(new EventHandler(stationFinderBroker, stationFinderBroker.show, originAutocompleter, "Find departure airport"));
    btnDestinationStationFinder.press.addListener(new EventHandler(stationFinderBroker, stationFinderBroker.show, destinationAutocompleter, "Find destination airport"));

    //
    // Each time a date is selected in one of the DatePicker instances, this date has to be boundary checked i.e. departure date
    // must be before or equal to return date and therefore return date has to after or equal to departure date
    //
    this.departureDatePicker.dateSelected.addListener(new EventHandler(this, onDepartureDateChanged));
    this.returnDatePicker.dateSelected.addListener(new EventHandler(this, onReturnDateChanged));
    
    //
    // When one of the calendar buttons is pressed, the calendar broker needs to be shown in the appropriate state.
    // Explanation of EventHandler arguments:
    //
    // scope            : Since the second argument is the method to call and it resides in the calendarBroker scope
    //                    ('this' pointer in the method points to members in calendarBroker instance NOT the 
    //                    dashboard instance) the scope of the event handler needs to be set to calendarBroker 
    //                    instead of the usual 'this' scope in order for the method to function correctly
    // callback method  : The callback method to execute when the press event is fired
    // target object    : The calendarBroker needs a target object from which it can read its date to select using
    //                    the getDate() method and to which it can write its selected date using the setDate(date)
    //                    method (where date has to be a valid Date instance) to be implemented in the target object
    // title            : The title which will be shown once the Calendar instance is shown
    //
    btnDepartureCalendar.press.addListener(new EventHandler(calendarBroker, calendarBroker.show, this.departureDatePicker, "Departure date"));
    btnReturnCalendar.press.addListener(new EventHandler(calendarBroker, calendarBroker.show, this.returnDatePicker, "Return date"));    

    //
    // When the search button is pressed execute a search request
    //
    btnSearch.press.addListener(new EventHandler(this, this.searchNew, false));

    //
    // If the new search button is present (result page) execute a search request when it is pressed
    //
    if (this.btnNewSearch)
        this.btnNewSearch.press.addListener(new EventHandler(this, this.focus));    

    //hide the printbutton until print this page is fixed
    hidePrintButton();
}

function hidePrintButton() 
{
    if ($("mailprint1") && $("mailprint2")) 
    {
        //hide top mailprint buttons
        var mailPrintTags1 = $("mailprint1").getElementsByTagName("a");
        mailPrintTags1[0].style.display ="none";
        mailPrintTags1[1].style.display ="none";
        //hide bottom mailprint buttons
        var mailPrintTags2 = $("mailprint2").getElementsByTagName("a");
        mailPrintTags2[0].style.display ="none";
        mailPrintTags2[1].style.display ="none";
        return;
    }
    else 
    {
        return;
    }
}
// Content: js sendtoafriend
var Overlay2 = function () {
    var bug = [];
    var doHideSelects = false;

    function getViewPortHeight() {
        return self.innerHeight ? self.innerHeight : document.documentElement &&
            document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body ? document.body.clientHeight : null;
    }


    function getDocumentHeight() {
        return document.documentElement &&
            document.documentElement.scrollHeight ? document.documentElement.scrollHeight : document.body ? document.body.scrollHeight : null;
    }


    function fix() {
        if (doHideSelects) {
            var selects = document.getElementsByTagName("SELECT");
            for (var idx = 0; idx < selects.length; idx++) {
                bug.push(selects[idx]);
            }
        }
    }

    return {
        overlay:null, 
        element:null, 
        firstRun:true, 
        init:function () {
            if (this.overlay == null) {
                this.overlay = document.createElement("div");
                this.overlay.className = "overlay2";
                this.overlay.id = "overlay2";
                this.overlay.style.display = "none";
                document.body.insertBefore(this.overlay, document.body.firstChild);
                doHideSelects = is_ie5_5up && !is_ie7up && typeof document.body.filters != "undefined";fix();
            }
        }, 
        show:function (id) {
            this.element = document.getElementById(id);
            if (this.element == null) {return;}
            if (this.firstRun) {
                this.element.innerHTML = "<div class=\"popup\"><div class=\"right\">" + "<a href=\"#" + id + "\" onclick=\"Overlay2.hideonhitclose('" + id + "');return false;\">" + "<img src='/ie_en/images/mailafriend_close_tcm106-116956.gif' alt=\"Close layover\" /></a></div>" + this.element.innerHTML + "</div>";
            }
            this.overlay.style.height = getDocumentHeight() + "px";
            window.onresize = function () {Overlay2.overlay.style.height = getDocumentHeight() + "px";};
            if (doHideSelects) {
                for (var idx = 0; idx < bug.length; idx++) {bug[idx].style.visibility = "hidden";}
            }
                this.overlay.style.display = "block";
                this.element.style.display = "block";
                var newTop = (document.documentElement.scrollTop || document.body.scrollTop) + getViewPortHeight() / 2 + "px";
                this.element.style.top = newTop;
                this.firstRun = false;
                setCaptcha();
         },
         hide:function () {
            this.overlay.style.display = "none";
            this.element.style.display = "none";
            if (doHideSelects) {for (var idx = 0; idx < bug.length; idx++) {bug[idx].style.visibility = "visible";}}
         },
    hideonhitclose:function () {
            this.overlay.style.display = "none";
            this.element.style.display = "none";
            Page.errorHandler.reset();
        $("emailto1").value = "";
        $("emailto2").value = "";
        $("emailto3").value = "";
        $("emailfrom").value = "";
        $("emailname").value = "";
        $("emailmsg").value = "";
        $("emailcaptcha").value = "";
            if (doHideSelects) {for (var idx = 0; idx < bug.length; idx++) {bug[idx].style.visibility = "visible";}}
        }
    };
}()

function MailaFriend(obj, bool, id) {
    var r;
    if (r = document.getElementById(id)) {
        r.style.display = ('no' == obj.value) ? 'none' : 'block' ;
    }
}

currentCC = 0;
function addCC() {
    if (currentCC <= 2) {
        currentCC = currentCC + 1;
        var ccRow = document.getElementById("cc" + currentCC);
        ccRow.style.display = "block";
        if (currentCC == 2) {
            document.getElementById("addCCButton").style.display = "none";
        }
    }
}

function sendForm() {
  Page.errorHandler.reset();  
  //Setup the EmailRequest object
  EmailRequest.toEmail = new Array();
  EmailRequest.toEmail[0] = $("emailto1").value;
  if ($("emailto2") && $("emailto2").value) EmailRequest.toEmail[1] = $("emailto2").value;
  if ($("emailto3") && $("emailto3").value) EmailRequest.toEmail[2] = $("emailto3").value;
  EmailRequest.fromEmail = $("emailfrom").value;
  EmailRequest.fromName  = $("emailname").value;
  EmailRequest.bodyText  = $("emailmsg").value;
  EmailRequest.captchaText = $("emailcaptcha").value;
  EmailRequest.flightPartner = $("chxFlightsPartners").checked;
  
  //should be obtained from url rather than hardcoded
  EmailRequest.locale    = Page.locale.countryCode + "_" + Page.locale.languageCode;    

  // Setup details of outbound flight
  var outboundDetails = {};
  Page.departTimetable.getSelectedConnection().getSegmentDetails(outboundDetails);
  EmailRequest.outboundDetailRequest = outboundDetails;

  // Setup details of inbound flight if appropriate
  var roundtrip = Page.dashboard.tripTypeSelector.getValue();
  if (roundtrip==2) {
     var inboundDetails = {};
     Page.returnTimetable.getSelectedConnection().getSegmentDetails(inboundDetails);
     EmailRequest.inboundDetailRequest = inboundDetails;
  }

  OttAjaxService.emailResultsToFriend(EmailRequest, sendResultsToAFriendCallback);      
}

//CALLBACK function
function sendResultsToAFriendCallback(emailResponse) {
    if (!emailResponse.errors.length)
    {
        Page.errorHandler.add(new EmailSentMessage(null, emailResponse.text));

        $("emailto1").value = "";
        $("emailto2").value = "";
        $("emailto3").value = "";
        $("emailfrom").value = "";
        $("emailname").value = "";
        $("emailmsg").value = "";
        $("emailcaptcha").value = "";

        Overlay2.hide();
        return false;
    }
    else
    {
        Page.errorHandler.add(new EmailError(emailResponse.errors[0].code, emailResponse.errors[0].text));
    }


    //var text;
    //if (emailResponse.errors.length > 0) {
    //       text = emailResponse.errors[0].text;
    //}
    //else {
    //       text = emailResponse.text;
    //}
    //$("addCCButton").innerHTML = text;
    
    //reload captcha
    setCaptcha();
}

function setCaptcha() {
    OttAjaxService.generateCaptchaId(setCaptchaCallback);
}
//CALLBACK function
function setCaptchaCallback(generatedId) {
    $("captchaImage").src = "/commercial/ott/jcaptcha.do?id="+generatedId;
}

function EmailError(errorCode, errorText) 
{
    var container = $("timetableErrorBox");
    var error = $("timetableErrorBody");

    error.className = "errorContent";

    if (error.innerHTML.indexOf(errorText.toString()) == -1)
        error.innerHTML += (error.innerHTML.length > 0 ? "<br />" : "") + errorText.toString();
    
    container.className = "errorBox";
    container.style.display = "block";

    this.dispose = function()
    {
        container.style.display = "none";
        error.innerHTML = "";
    }
}

function EmailSentMessage(errorCode, errorText) 
{
    var container = $("timetableErrorBox");
    var error = $("timetableErrorBody");

    error.className = "alertContent";       
    
    if (error.innerHTML.indexOf(errorText.toString()) == -1)
        error.innerHTML += (error.innerHTML.length > 0 ? "<br />" : "") + errorText.toString();

    container.className = "alertBox success";
    container.style.display = "block";

    this.dispose = function()
    {
        container.style.display = "none";
        error.innerHTML = "";
    }
}
// Content: js cookies
// Content: js cookies
function setCookie(inName, inValue, inNumberOfDays)
{
	var expDate = new Date ();
	var cookieString;
	
	expDate.setTime (expDate.getTime() + (86400000 * inNumberOfDays));
	var gmtExpDate = expDate.toGMTString();
	
	cookieString = inName + "=" + inValue;
	if (inNumberOfDays != 0)
		cookieString += ";expires=" + gmtExpDate;	
	
	cookieString += ";path=/";	
	document.cookie = cookieString;
}

//this function gets the value of a cookie given it's name
function getCookie (inName)	{
	var dCookie = document.cookie; 
	var cName = inName + "=";
	var cLen = dCookie.length;
	var cBegin = 0;
	while (cBegin < cLen) 	{
		var vBegin = cBegin + cName.length;
		if (dCookie.substring(cBegin, vBegin) == cName) { 
			var vEnd = dCookie.indexOf (";", vBegin);
		    if (vEnd == -1) 
				vEnd = cLen;
			return unescape(dCookie.substring(vBegin, vEnd));
		}
		cBegin = dCookie.indexOf(" ", cBegin) + 1;
		if (cBegin == 0)
			 break;
	}
	return null;
}

//this function will delete a cookie by setting the expiration date in the past
function deleteCookie(inName)	{
	document.cookie = inName + "=; expires=Thu, 01-Jan-70 00:00:01 GMT";
}