
/******************************************************************************/
/**                                                                          **/
/**                        Configuration variables                           **/
/**                                                                          **/

jQuery.configuration = {

    // BUG: The min-width and max-width css properties are not valid for
    //      the window resizing heuristic, as they are automatically changed
    //      by Chrome and Safari when changing the width.
    maxWidth: 1200, // px, required to adjust the large image in item display
//    minWidth: 900, // px

    maxWidthLargeImage: '500px',

    tooltipTopOffset: -4,
    tooltipLeftOffset: 5,

    dialogTopMin: 20,
    dialogTopMax: 150,
    dialogLeftMin: 20,
    dialogLeftMax: 1000,

    highlightColor: '#CFE1CF',

    // NOTE: If the thousands separator changes to other than '.', ',' or ' ',
    //       check the sanitizeNumber() function.
    decimalSeparator: ',',
    thousandsSeparator: '.',
    currencySymbol: '&euro;',
    breadcrumbSeparator: ' > ',
    
    // the urls are set below
    url: {},

    dialog: {
        buttons: {
            ok: {'Aceptar': function() {$.globalFunctions.dialogClose(this);}},
            cancel: {'Cancelar': function() {$.globalFunctions.dialogClose(this);}},
            close: {'Cerrar': function() {$.globalFunctions.dialogClose(this);}},
//            edit: {'Editar': function() { $('#formEdit').submit(); }},
            edit: {'Editar': function() {$.itemForm.edit();}},
            sendToAFriend: {'Enviar': function() {$.globalFunctions.sendToAFriend();}},
            report: {'Enviar': function() {$.globalFunctions.report();}},
            // the property cannot be named just "delete"
            deleteButton: {'Eliminar': function() {$.itemForm.deleteItem();}},
            categoryMenuAccept: {'Aceptar': function() {
                $.categoryMenu.accept();
            }},
            categoryMenuCancel: {'Cancelar': function() {
                $.categoryMenu.close();
            }}
        },
        events: {
            redirect: function() {
//                $('*').css('cursor', 'wait');
                location.replace($.configuration.url.redirection);
            },
            closeCategoryMenuTasks: function() {
                $.categoryMenu.closeCategoryMenuTasks();
            }
        }
    },

    allowedImageTypes: new Array('png', 'jpg', 'jpeg', 'gif'),
    bannedClass: 'vetado',

    largeContactMessageHeight: 170,
    largeContactMessageHeightParent: 200,
    largeContactMessageWidth: 300,
    smallContactMessageHeight: 50,
    smallContactMessageHeightParent: 80,
    // the contact message textarea sets the contact panel width.
    // Less than 182 makes a breakline in the the title
    smallContactMessageWidth: 182

};

// recursive object definitions cannot be declared in one declaration, because
// the referred properties are "undefined" until the object declaration is finished
// PATHS
// the host name must be taken dinamically, as it may include "www." or not
jQuery.configuration.url.domain = 'http://' + location.host + '/';
jQuery.configuration.url.ads = jQuery.configuration.url.domain + 'ads/';
jQuery.configuration.url.insert = $.configuration.url.domain + 'insert';
jQuery.configuration.url.edit = $.configuration.url.domain + 'edit';
jQuery.configuration.url.uploadsThumbDir = $.configuration.url.domain + 'uploads/thumb/';
jQuery.configuration.url.redirection = $.configuration.url.domain;

jQuery.configuration.url.images = {
    directory: $.configuration.url.domain + 'images/',
//    markerUser: 'markerUser',
//    markerSportCenter: 'marker',
    marker: 'marker',
    markerAprox: 'markerAprox',
    markerAproxLarge: 'markerAproxLarge',
    sprite: 'sprite'
};

jQuery.configuration.url.ajax = {
    directory: $.configuration.url.domain + 'ajax/',
    contact: 'contact',
    contactAdmin: 'contact-admin',
    insert: 'insert-item',
    update: 'update-item',
    deleteItem: 'delete-item',
    sendToAFriend: 'send-item',
    report: 'report-item',
    uploadImage: 'upload-image',
    removeImage: 'remove-image',
    getLargeImage: 'get-large-image',
    email: 'get-email',
    categoryMenuSearchPanel: 'category-menu-search-panel',
    categoryMenuItemForm: 'category-menu-item-form',
    helpPopup: 'help-popup'
};

/**                                                                          **/
/**                        Configuration variables                           **/
/**                                                                          **/
/******************************************************************************/

/******************************************************************************/
/**                                                                          **/
/**                          Initialize system                               **/
/**                                                                          **/

(function($) {

//------------------------------------------------------------------------------

$(document).ready(function() {

    // window
    $.globalFunctions.initializeWindowAndDocument();

    // logos
//    $.globalFunctions.initializeLogos();

    // navigation bar
    $.globalFunctions.initializeNavigationBar();

    // drop down lists
    $.globalFunctions.initializeSelects();

    // provinces drop down lists and corresponding address fields
//    $.globalFunctions.initializeProvincesAndAddresses();
    $.globalFunctions.initializeItemForm();

    // search panel
    $.searchPanel.initializeSearchPanel();

    // element appearing effects
    $.globalFunctions.launchAppearingEffects();
    
    // images
    $.globalFunctions.initializeImages();

    // forms
    $.globalFunctions.initializeForms();

    // map
//    $('div.map').initializeMap();
    // a little while is waited before the map is initialized, so that the
    // other elements of the page have time to load, and the page doesn't 
    // block blank, or even worse, the previous page remains in screen while
    // the map is loading
    setTimeout("$('div.map').initializeMap()", 1500);

    // buttons
    $('button').initializeButton();

    // click events of buttons and other elements
    $.globalFunctions.setClickEvents();

    // help popup
    // last initilization, because other initialized elements may need it
    $('.popupHelpPoint').initializeHelpPopup();

    // tooltip
    // last initilization, because other initialized elements may need it
    $('.tooltip').initializeTooltip();

});

//------------------------------------------------------------------------------

})(jQuery);

/**                                                                          **/
/**                          Initialize system                               **/
/**                                                                          **/
/******************************************************************************/

/******************************************************************************/
/**                                                                          **/
/**                      jQuery object functions plugin                      **/
/**                                                                          **/

(function($) {

//------------------------------------------------------------------------------

jQuery.fn.initializeTooltip = function() {

    return this
        .hover($.globalFunctions.displayTooltip, $.globalFunctions.removeTooltip)
        .focus($.globalFunctions.displayTooltip)
        .blur($.globalFunctions.removeTooltip);
};

//------------------------------------------------------------------------------

jQuery.fn.initializeHelpPopup = function() {
    
    return this.click($.globalFunctions.displayPopupHelp);
};

//------------------------------------------------------------------------------

jQuery.fn.initializeButton = function() {

    return this
//        .hover(
//            function() {
//                var backgroundImage = $(this).css('background-image');
//                if (backgroundImage) {
//                    backgroundImage = backgroundImage.replace('Inactive', 'Active');
//                    $(this).css('background-image', backgroundImage);
//                }
//            },
//            function() {
//                var backgroundImage = $(this).css('background-image');
//                if (backgroundImage) {
//                    backgroundImage = backgroundImage.replace('Active', 'Inactive');
//                    $(this).css('background-image', backgroundImage);
//                }
//        })
        .mouseup(function() {
            $(this).blur()
        });
};

//------------------------------------------------------------------------------

jQuery.fn.initializeMap = function() {

  // item form page
  if (this.is('#divMapForm')) {
        var $this = $(this);
        if ($this.attr('initializeMap')) {
            
            $.mapFunctions.initializeMapItemForm(
                $('#divMapForm').get(0),
                $('#latitude').sanitizedValue(),
                $('#longitude').sanitizedValue(),
                $('#address').sanitizedValue()
            );

            $this.removeAttr('initializeMap').show();
        }
        else {
            $this.attr('initializeMap', 'true');
        }
  }
  // item display page
  else if (this.is('#divMapDisplay')) {

    var $seeMap = $('button.seeMap');

    $seeMap.click(function() {

        $(this).hide();

        var $address = $('.itemDisplay .address');

//        $('.itemDisplay .address .mapWrapper').show();
        $address.find('.mapWrapper').show();

        if ($address.attr('geolocate')) {
            $.mapFunctions.getLatLngForDirectionsMap(
                    $address.find('.completeAddress').sanitizedValue()
            );
        }
        else {
            $.mapFunctions.initializeDirectionsMap(
                $("#divMapDisplay").get(0),
                $("#divDirections").get(0),
    //            $('#latitudeDisplay').sanitizedValue(),
    //            $('#longitudeDisplay').sanitizedValue(),
    //            $('#markerDisplay').sanitizedValue()
                $address.find('.latitude').sanitizedValue(),
                $address.find('.longitude').sanitizedValue(),
                $address.find('.marker').sanitizedValue()
            );
        }
    });

    if ($seeMap.attr('visible')) {
        $seeMap.click();
    }

//    var emptyText = unescape(decodeURI($.messages.altTitleCaption.calculateRoute));
    var emptyText = unescape($.messages.altTitleCaption.calculateRoute);

    $('#btnCalculateRoute').click(function() {
        var calculateRouteFrom = $('.itemDisplay #calculateRouteFrom').sanitizedValue();
        if (calculateRouteFrom && (calculateRouteFrom != emptyText)) {
            $.mapFunctions.setDirections(
                calculateRouteFrom,
//                $('#address').sanitizedValue(),
                $('.itemDisplay .address .completeAddress').sanitizedValue(),
                'es'
            );
        }
        this.blur();
    });
    
    $('#calculateRouteFrom').displayEmptyFieldText(emptyText);
  }
  // item list page
  else if (this.is('#divMapList')) {

    var mapZoom = $.mapFunctions.zoom.country;
    
    if ($('#address').sanitizedValue() || $('#city').sanitizedValue()
            || $('#postcode').sanitizedValue())
    {
        mapZoom = $.mapFunctions.zoom.zone;
    }
    else if ($('#cmbProvinces').sanitizedValue()) {
        mapZoom = $.mapFunctions.zoom.province;
    }
    // initialize the map
    $.mapFunctions.initializeMapItemList(
        $('#divMapList').get(0),
        $('#latitude').sanitizedValue(),
        $('#longitude').sanitizedValue(),
        mapZoom
    );
    
    // set the item markers in the map
    $('.itemList li').each(function() {
        var $marker = $('.marker', this);
        $.mapFunctions.setMarkerLatLng(
            $marker.attr('latitude'),
            $marker.attr('longitude'),
            $marker.attr('aprox'),
            $.trim($('h2 a', this).text()),
            $.globalFunctions.getMarkerInformation(this)
        );
    });
  }
  
  return this;
};

//------------------------------------------------------------------------------
/**
 * Display a text in an input field whenever it is empty.
 *
 * The class "displayAndNoDisplayEmptyFieldText" is assigned to the input field
 * permanently.
 *
 * The class "displayEmptyFieldText" is assigned to the input field only when
 * it is empty.
 *
 * @param textToDisplay string
 * @return jQuery
 */
jQuery.fn.displayEmptyFieldText = function(textToDisplay) {

    this
        .addClass('displayAndNoDisplayEmptyFieldText')
        .focus(function() {
            var $this = $(this);
            if ($this.is('.displayEmptyFieldText')) {
                $this.removeClass('displayEmptyFieldText').val('');
            }
        })
        .blur(function() {
            var $this = $(this);
            
            if (( ! $this.sanitizedValue()) || ($this.val() == textToDisplay)) {
                $this.addClass('displayEmptyFieldText').val(textToDisplay);
//                $this.addClass('displayEmptyFieldText');
//                if ($this.is('select')) {
//                    $this.html('<option>' + textToDisplay + '</option>');
//                }
//                else {
//                    $this.val(textToDisplay);
//                }
            }
        })
        .blur();
    return this;
};

//------------------------------------------------------------------------------
/**
 * Display a text in a html element whenever it is empty.
 *
 * The class "displayEmptyElementText" is assigned to the html element when
 * it is empty.
 *
 * NOTE: The change() event is limited to <input>, <textarea> and <select>
 *       elements, so it must be fired manually when another html element
 *       changes.
 *
 * @param textToDisplay string
 * @return jQuery
 */
jQuery.fn.displayEmptyElementText = function(textToDisplay) {

    return this
        .change(function() {
            var $this = $(this);
//            var content = $.globalFunctions.sanitize($this.html());
//            if (content && (decodeURI(content) != textToDisplay)) {
            // must be retrieved the text() and not the html() because there
            // could be empty html elements, like <ul></ul>
            var content = $.trim($this.text());
            if (content && (content != textToDisplay)) {
                $this.removeClass('displayEmptyElementText');
            }
            else {
                if ($this.is('select')) {
                    textToDisplay = '<option>' + textToDisplay + '</option>';
                }
                $this.addClass('displayEmptyElementText').html(textToDisplay);
            }
        })
        .change();
};

//------------------------------------------------------------------------------

jQuery.fn.sanitizedValue = function() {
    return $.globalFunctions.sanitize(this.val());
};

//------------------------------------------------------------------------------
/**
 * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 */
jQuery.fn.standarizeNumber = function() {
    return $.globalFunctions.sanitizeNumber(this.val());
};

//------------------------------------------------------------------------------
/**
 * Write an error message within a jQuery 'ui-state-error' class div.
 * 
 * @param options = { <br />
 *          message: string - default: 'Error', <br />
 *          scrollToParent: boolean - default: false <br />
 *        }
 *        XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 */
//jQuery.fn.errorMessage = function(options) {
jQuery.fn.errorMessage = function(message, scrollToParent) {

//    var settings = {
//        message: 'Error',
//        scrollToParent: false
//    };
//
//    $.extend(settings, options);
//
//    // check if the message was not previously inserted, to avoid inserting
//    // it twice
//    if ( ! this.find('.ui-state-error').length) {
//        var errorDiv =
//            '<div class="ui-widget">'
//          + '    <div class="ui-state-error ui-corner-all">'
//          + '        <div class="ui-icon ui-icon-alert"></div>'
//          + '        <div class="errorMessage"></div>'
//          + '    </div>'
//          + '</div>';
//        this.prepend(errorDiv);
//    }
//
//    this.find('.ui-state-error')
//        .show()
//        .find('.errorMessage')
//        .html(settings.message);
//
//    if (settings.scrollToParent) {
//        this.scrollWindowAnimated();
//        // better focus() than select() because it is valid for both text
//        // and checkbox inputs
//        this.find('input, textarea').focus();
//    }



//    var settings = {
//        message: 'Error',
//        scrollToParent: false
//    };
//
//    $.extend(settings, options);

    // check if the message was previously inserted, to avoid inserting
    // it twice
    if ( ! this.find('.message.error').length) {
        this.prepend('<div class="message error"></div>');
    }

    // if the message is empty, the .html() function fails
    if ( ! message) {
        message = $.messages.error.defaultMessage;
    }
    
    this
        .find('.message.error')
//        .html($.messages.error[messageKey])
        .html(message)
        .show();
//console.info(scrollToParent);
    if (scrollToParent) {
        this.scrollWindowAnimated();
        // better focus() than select() because it is valid for both text
        // and checkbox inputs
//        this.find('input, textarea').focus();
        this.find('input, textarea').filter(':first').focus();
    }
    
    return this;
};

//------------------------------------------------------------------------------
/**
 * Remove the 'ui-state-error' class div from the calling element.
 * XXXXXXXXXXXXXXXXXXXXXXXXXX
 */
jQuery.fn.hideErrorMessage = function() {
    this.find('.message.error').hide();
    return this;
};

//------------------------------------------------------------------------------
/**
 * Scroll the window to the calling element at once, with no effect.
 */
jQuery.fn.scrollWindow = function() {
    var pointToScroll = this.offset().top - 30;
    if (pointToScroll < 0) {
        pointToScroll = 0;
    }
    $('html, body').attr( {scrollTop: pointToScroll} );
    return this;
};

//------------------------------------------------------------------------------
/**
 * Scroll the window to the calling element with moving effect.
 */
jQuery.fn.scrollWindowAnimated = function() {
    var pointToScroll = this.offset().top - 30;
    if (pointToScroll < 0) {
        pointToScroll = 0;
    }
    $('html, body').animate( {scrollTop: pointToScroll} , 2000);
    return this;
};

//------------------------------------------------------------------------------
/**
 * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 */
jQuery.fn.initializeRequireFields = function() {
//        $('label.required').each(function() {
////            var $this= $(this);
////            $this.html($this.html() + ' <span class="highlight">*</span>')
//            $(this).append(' <span class="highlight tooltip" tooltipkey="required">*</span>');
//        });
    this.find('label.required')
        .append(' <span class="highlight tooltip" tooltipkey="required">*</span>')
        .find('.tooltip')
        .initializeTooltip();
    return this;
};

//------------------------------------------------------------------------------
/**
 * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 */
jQuery.fn.disabledMessage = function(disabledMessage) {

    return this.focus(function() {
        var $this = $(this);
        if ($this.is('.disabled')) {

            $this.blur();

            $.globalFunctions.dialogOpen({
                scrollToTop: false,
                title: $.messages.dialog.titles[disabledMessage],
                message: $.messages.dialog.messages[disabledMessage],
                buttons: $.configuration.dialog.buttons.ok
            });
        }
    });
};

//------------------------------------------------------------------------------

})(jQuery);

/**                                                                          **/
/**                      jQuery object functions plugin                      **/
/**                                                                          **/
/******************************************************************************/

/******************************************************************************/
/**                                                                          **/
/**                        Global functions plugin                           **/
/**                                                                          **/

(function($) {

//------------------------------------------------------------------------------
// public functions ------------------------------------------------------------
//------------------------------------------------------------------------------

jQuery.globalFunctions = {

    //--------------------------------------------------------------------------
    /**
     * Set the click events to the elements, generally buttons.
     */
    setClickEvents: function() {

        $('.insertButton button').click(function() {
            window.location.assign($.configuration.url.insert);
        });

        $('.searchPanel button.search').click(function() {
//            $.globalFunctions.handleSubmitSearch();
            $.searchPanel.handleSubmitSearch();
//            $.searchPanel.submitSearch();
        });

        $('.searchPanel button.clean').click($.searchPanel.cleanSearch);

        $('#buttonShowEmail').click(function() {
            $($(this).parent()).load(
                $.globalFunctions.getAjaxUrl('email'),
                {id: $(this).attr('itemId')}
            );
        });

        $('#buttonSave').click(function() {
//            var itemId = $(this).attr('itemId');
//            if (itemId) {
            if ($(this).attr('itemId')) {
//                $.itemForm.update(itemId);
                $.itemForm.update();
            }
            else {
                $.itemForm.insert();
            }
        });

        $('#buttonCancel').click(function() {

            $.globalFunctions.goBack();

        });

        $('.itemDisplay .images img.thumb').click(function() {
            $.post(
                $.globalFunctions.getAjaxUrl('getLargeImage'),
                {imageName: escape($(this).attr('imageName'))},
                $.globalFunctions.largeImageLoaded
            );
        });

        $('#buttonContact').click(function() {

            $.globalFunctions.contact();
        });

        $('#buttonContactAdmin').click(function() {

            $.globalFunctions.contactAdmin();
        });

        $('#buttonHighlight').click(function() {

            $.globalFunctions.dialogOpen({
                title: $.messages.dialog.titles.highlight,
                message: $.messages.dialog.messages.highlightDisabled,
//                buttons: $.extend(
//                    {},
//                    $.configuration.dialog.buttons.sendToAFriend,
//                    $.configuration.dialog.buttons.cancel
//                ),
                buttons: $.configuration.dialog.buttons.close
//                width: 400
            });
        });

        $('#buttonSendToAFriend').click(function() {

            var message = $.messages.dialog.messages.sendToAFriend
                + '<label class="required">' + $.messages.altTitleCaption.emailFriend
                + '</label>'
                + ' <input id="friendEmail" type="text" />'
                + '<label>' + $.messages.altTitleCaption.emailSender + '</label>'
                + ' <input id="senderEmail" type="text" />'
                + '<label>' + $.messages.altTitleCaption.nameSender + '</label>'
                + ' <input id="senderName" type="text" />'
                + '<label>' + $.messages.altTitleCaption.comment + '</label>'
                + ' <textarea id="senderComment"></textarea>';
            
            $.globalFunctions.dialogOpen({
                classes: 'inputForm',
                title: $.messages.dialog.titles.sendToAFriend,
                message: message,
                buttons: $.extend(
                    {},
                    $.configuration.dialog.buttons.sendToAFriend,
                    $.configuration.dialog.buttons.cancel
                ),
                width: 400
            })
            .initializeRequireFields();
        });

        $('#buttonReport').click(function() {

            var message = $.messages.dialog.messages.report
                        + '<label>' + $.messages.altTitleCaption.email + '</label>'
                        + ' <input id="reportEmail" type="text" />'
                        + '<label class="required">' + $.messages.altTitleCaption.reason + '</label>'
                        + ' <textarea id="reportReason"></textarea>';
            $.globalFunctions.dialogOpen({
                classes: 'inputForm',
                title: $.messages.dialog.titles.report,
                message: message,
                buttons: $.extend(
                    {},
                    $.configuration.dialog.buttons.report,
                    $.configuration.dialog.buttons.cancel
                ),
                width: 400
            })
            .initializeRequireFields();
        });

        $('#buttonEdit').click(function() {

            var message = $.messages.dialog.messages.confirmUpdate
                        + '<label>' + $.messages.altTitleCaption.keycode + '</label>'
                        + ' <input id="typedKeycode" type="text" />';
            $.globalFunctions.dialogOpen({
                title: $.messages.dialog.titles.confirmUpdate,
                message: message,
                buttons: $.extend(
                    {},
                    $.configuration.dialog.buttons.edit,
                    $.configuration.dialog.buttons.cancel
                ),
                width: 300
            });
        });

        $('#buttonDelete').click(function() {
            var message = $.messages.dialog.messages.confirmDelete
                        + '<label>' + $.messages.altTitleCaption.keycode + '</label>'
                        + ' <input id="typedKeycode" type="text" />';
            $.globalFunctions.dialogOpen({
                title: $.messages.dialog.titles.confirmDelete,
                message: message,
                buttons: $.extend(
                    {},
                    $.configuration.dialog.buttons.deleteButton,
                    $.configuration.dialog.buttons.cancel
                ),
                width: 300
            });
        });

        $('.searchPanel .selectCategory button, .itemForm .selectCategory')
            .click(function() {
                $.categoryMenu.selectCategoriesEvent(this);
            });

        $('ul.itemList li').click(function(event) {
            // the event is only triggered if the control key was not
            // pressed, thus can the user open the link in a new tab in
            // background by clicking the new window link while pressing
            // the control key
            if ( ! event.ctrlKey) {
                location.assign($(this).attr('url'));
            }
        });

//        $('button.newWindow').click(function() {
//            window.open($(this).attr('href'));
//        });

        $('ul.itemList li .newWindow a').click(function(event) {
            // the event propagation is not stopped if the control key was
            // pressed, thus can the user open the link in a new tab in
            // background
            if ( ! event.ctrlKey) {
                event.stopPropagation();
            }
        });

        $('ul.itemList button.locate').click(function(event) {
            event.stopPropagation();
            $('body').scrollWindowAnimated();
            var $marker = $(this).parent();
            $.mapFunctions.centerMap($marker.attr('latitude'), $marker.attr('longitude'));
        });

        $('button.toggle')
            .click(function() {
                var $this = $(this);
                $this.find('span').toggle();
                $('#' + $this.attr('toggleElement')).toggle();
            })
            // BUG: Chrome needs the toggling spans to be display=block, in
            //      order to display the arrow icon properly. So their display
            //      css property is set to block, and hidden with jQuery. The
            //      block property will be restored with the toggle() function.
            //      If block is not set, the toggle() function will restore
            //      the display property to inline, what fails in Chrome.
            .find('.initiallyHidden').hide();

        $('#buttonAdultYes').click(function() {

            $('.adultCategory .message').hide();

            $('.adultCategory .itemListContent').show();
        });

        $('#buttonAdultNo').click(function() {

            $.globalFunctions.goBack();

        });

    }, // end of setClickEvents()

    //--------------------------------------------------------------------------
    /**
     * Set the resize event of the window.
     * 
     * The .bodyPanel element should have minWidth and maxWidth
     * css properties.
     *
     * It should also be css initialized with
     * the width of maxWidth.
     */
    initializeWindowAndDocument: function() {

        $(window).resize(function(){
            // if the '.bodyPanel' element has padding, this heuristic will
            // fail, as the width doesn't include the side padding. If side
            // padding is required, it must be set in the 'body' tag
            var windowWidth = $('body').width();
            var $bodyPanel = $('.bodyPanel');

            // BUG: The min-width and max-width css properties are not valid
            //      for this heuristic, as they are automatically changed by
            //      Chrome and Safari when changing the width.
            if (windowWidth > $.configuration.maxWidth) {
                windowWidth = $.configuration.maxWidth;
            }
            else if (windowWidth < $.configuration.minWidth) {
                windowWidth = $.configuration.minWidth;
            }

            if (windowWidth != $bodyPanel.width()) {
                $bodyPanel.width(windowWidth);
            }
        })
        .resize();

        // keydown and keyup provide a code indicating which key is pressed,
        // while keypress indicates which character was entered. If pressed
        // the esc key, keypressed returns 0 and keyup 27
        $(document).bind('keyup', $.globalFunctions.dialogKeyup);
    },

    //--------------------------------------------------------------------------
    /**
     * Toggle a hover class for all page logos when the pointer hover a logo.
     *
     * This function is useful when there are several kinds of logos and
     * everyone must display an effect at the same time when one of them is
     * hovered.
     */
    initializeLogos: function() {

        $('.logo').hover(
            function() {
                $('.logo img').addClass('hover');
            },
            function() {
                $('.logo img').removeClass('hover');
            }
        );
    },

    //--------------------------------------------------------------------------
    /**
     * Set the events of the navigation bar.
     */
    initializeNavigationBar: function() {

        $('.navigationBar .categoryMenu > ul > li').hover(function() {
            $('.categories', this).show();
        }, function() {
            $('.categories', this).hide();
        });

        $('.navigationBar .childCategoryGroup').hover(function() {
            var $this = $(this);
            var $childCategoryList = $this.find('ul');
            $childCategoryList
                .css('display', 'inline-block')
                .width($childCategoryList.width())
                .css('position', 'absolute')
                .css('left', $this.outerWidth(true));
            if (($childCategoryList.offset().left + $childCategoryList.width())
                    > window.innerWidth)
            {
                $childCategoryList.css(
                    'left',
                    '-' + $childCategoryList.outerWidth(true) + 'px'
                );
            }
        }, function() {
            $('ul', this).hide();
        });

        $('.navigationBar .categoryMenu a').click(function() {
            var provinceId = $('#cmbProvincesNavigationBar').val();
            if (provinceId) {
                var $this = $(this);
                var href = $this.attr('href') + '/' + provinceId + '/'
                         + $('#cmbProvincesNavigationBar :selected').text().toLowerCase();
                $this.attr('href', href);
            }
            // if the drop down menu is not hidden and the user returns to
            // this page with the history button, the menu remains visible
            $('.navigationBar .categories').hide();
        });
    },

    //--------------------------------------------------------------------------

    initializeSelects: function() {

        $('select').each(function() {
            var $this = $(this);

            $this.val($this.attr('selectedValue'));
            $this.removeAttr('selectedValue');
        });
    },

    //--------------------------------------------------------------------------

//    initializeProvincesAndAddresses: function() {
    initializeItemForm: function() {

//        $('.cmbProvinces').each(function() {
//        $('#province').each(function() {
//            var $this = $(this);
//
////            $this.val($this.attr('selectedValue'));
////            $this.removeAttr('selectedValue');
//
////            var $addressField = '';
////            var $addressField = $('#address');
//            var $cityField = $('#city');
//
////            if ($this.is('#cmbProvincesSearch')) {
////                $addressField = $('#addressSearch');
////            }
////            else if ($this.is('#cmbProvincesItemForm')) {
////                $addressField = $('#address');
////            }
//
        // NOTE: This initialization must be the first one, or the latitude
        //       and longitude fields will be wrongly emptied.
//        $.globalFunctions.disablingPair($('#provinces'), $('#city'), 'provinceNotSelected');
//        $.globalFunctions.disablingPair($('#city'), $('#postcode'), 'cityNotTyped');
//        $.globalFunctions.disablingPair($('#postcode'), $('#address'), 'postcodeNotTyped');
        $.globalFunctions.disablingPair(
            $('.itemForm #provinces'), $('.itemForm #city'), 'provinceNotSelected'
        );
        $.globalFunctions.disablingPair(
            $('.itemForm #city'), $('.itemForm #postcode'), 'cityNotTyped'
        );
        $.globalFunctions.disablingPair(
            $('.itemForm #postcode'), $('.itemForm #address'), 'postcodeNotTyped'
        );
//        $.globalFunctions.disablingPair(
//            $('.searchPanel #city, .searchPanel #postcode'),
//            $('.searchPanel #address'),
//            'cityAndPostcodeNotTyped'
//        );
//
//        // the search latitude and longitude are cleaned if a new address is
//        // set, so it can be renognised when the previous searched address
//        // has not been changed, and it is not geolocated again
//        $('#addressSearch, #cmbProvincesSearch').change(function() {
//            $('#latitudeSearch').val('');
//            $('#longitudeSearch').val('');
//        });

//        $('#cmbProvincesItemForm').change(function() {
        $('.itemForm #provinces').change(function() {
//              $("#address").val('');
              $("#latitude").val('');
              $("#longitude").val('');
              $.mapFunctions.countryZoomMapItemForm();
        });

        // due to the disablingPair() function, the change events of #postcode
        // and #address are launched whenever the #provinces, #city or #postcode
        // are changed
        $('.itemForm #address').change(function() {
//        $('.itemForm #postcode, .itemForm #address').change(function() {
            if ($('#divMapForm').is(':visible')) {

                var $postcode = $('.itemForm #postcode');
                var marker = false;
                if (( ! $postcode.is('.disabled')) && $postcode.sanitizedValue()) {
                    var completeAddress = $postcode.sanitizedValue()
                                        + ',' + $('#city').text()
                                        + ',' + $('#provinces:selected').text()
                                        + ',' + $('#provinces').attr('country');

                    var $address = $('.itemForm #address');
//                    if ($address.is(':visible') && ( ! $address.is('.disabled'))
//                            && $address.sanitizedValue())
                    if (( ! $address.is('.disabled')) && $address.sanitizedValue()) {
                        completeAddress = $address.sanitizedValue() + ',' + completeAddress;
                        marker = true;
                    }

                    $.mapFunctions.setMarkerMapForm(completeAddress, marker);
                }
                else {
                  $('#latitude').val('');
                  $('#longitude').val('');
                  $.mapFunctions.countryZoomMapItemForm();
                }
            }
        });

//        $('.itemForm #provinces, .itemForm #city, .itemForm #postcode, .itemForm #address')
//        $('.itemForm #postcode, .itemForm #address')
//            .change(function() {
//                $.mapFunction.setMarkerMapForm(
//                    $('.itemForm #provinces').sanitizedValue(),
//                    $('.itemForm #city').sanitizedValue(),
//                    $('.itemForm #postcode').sanitizedValue(),
//                    $('.itemForm #address').sanitizedValue()
//                );
//            });

//        $('#city, #postcode, #address').focus(function() {
//
//            var $this = $(this);
//
//            if ($this.is('.disabled')) {
//
//                $this.blur();
//
//                $.globalFunctions.dialogOpen({
//                    scrollToTop: false,
//                    title: $.messages.dialog.titles.provinceNotSelected,
//                    message: $.messages.dialog.messages.provinceNotSelected,
//                    buttons: $.configuration.dialog.buttons.ok
//                });
//            }
//        });

//        return this;
    },

    //--------------------------------------------------------------------------
    /**
     * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
     */
    disablingPair: function($disabler, $disabled, disabledMessage) {

//        $disabler
//            .change(function() {
//                if (( ! $disabler.is('.disabled')) && $disabler.sanitizedValue()) {
//                    // the change event is launched, so that if the disabled
//                    // element is also a disabling one, its related disabled
//                    // element is sequentially disabled
//                    $disabled.removeClass('disabled').change();
//                }
//                else {
//                    $disabled.addClass('disabled').change();
//                }
//            })
//            .change();

//        $disabler.each(function() {
//            var $this = $(this)
//
//            $this
//                .change(function() {
//
//                    var disable = true;
//
//                    if ($disabler.length == 1) {
//                        disable = ( ! $disabler.is('.disabled')) && $disabler.sanitizedValue();
//                    }
//                    else {
//                        var totalValue = '';
//
//                        $disabler.each(function() {
//                            totalValue += $(this).sanitizedValue();
//                        });
//                        
//                        disable = ( ! $this.is('.disabled')) && totalValue;
//                    }
//
////                    if (( ! $this.is('.disabled')) && $this.sanitizedValue()) {
////                    if (( ! $this.is('.disabled')) && totalValue) {
//                    if (disable) {
//                        $disabled.addClass('disabled').change();
//                    }
//                    else {
//                        // the change event is launched, so that if the disabled
//                        // element is also a disabling one, its related disabled
//                        // element is sequentially disabled
//                        $disabled.removeClass('disabled').change();
//                    }
//                })
//                .change();
//        });


        if ($disabler.length == 1) {
            
            $disabler
                .change(function() {
                    if (( ! $disabler.is('.disabled')) && $disabler.sanitizedValue()) {
                        // the change event is launched, so that if the disabled
                        // element is also a disabling one, its related disabled
                        // element is sequentially disabled
                        $disabled.removeClass('disabled').change();
                    }
                    else {
                        $disabled.addClass('disabled').change();
                    }
                })
                .change();
        }
        else {

            $disabler.each(function() {

                $(this)
                    .change(function() {

                        var disablersValue = '';
                        var disablerDisabled = true;

                        // NOTE: A semaphore could be added to the disabled
                        // element to count the disabling disabler elements
                        // instead of everytime checking every disabler element,
                        // but in order to keep it simple and due to that currently
                        // there are no long disabler collections (maximum 2),
                        // this loop solution is implemented
                        $disabler.each(function() {
                            var $this = $(this);
                            disablersValue += $this.sanitizedValue();
                            disablerDisabled = disablerDisabled && $this.is('.disabled');
                        });

                        if (( ! disablerDisabled) && disablersValue) {
                            // the change event is launched, so that if the disabled
                            // element is also a disabling one, its related disabled
                            // element is sequentially disabled
                            $disabled.removeClass('disabled').change();
                        }
                        else {
                            $disabled.addClass('disabled').change();
                        }
                    })
                    .change();
            });
        }

        $disabled.disabledMessage(disabledMessage);
//        $disabled.focus(function() {
//
//            if ($disabled.is('.disabled')) {
//
//                $disabled.blur();
//
//                $.globalFunctions.dialogOpen({
//                    scrollToTop: false,
//                    title: $.messages.dialog.titles[disabledMessage],
//                    message: $.messages.dialog.messages[disabledMessage],
//                    buttons: $.configuration.dialog.buttons.ok
//                });
//            }
//        });
    },

    //--------------------------------------------------------------------------
    /**
     * Initialize the images in the item display and item form pages.
     */
    initializeImages: function() {

        // if the window is narrower than the maximum width allowed and
        // the large image has it maximum width allowed, it might exceed
        // the center panel dimensions and overlap the right panel
        if ($('body').width() < $.configuration.maxWidth) {
            $('.itemDisplay #divLargeImage img').css(
                'maxWidth', $.configuration.maxWidthLargeImage
            );
        }

        // the current page is item form
        // it must be checked if there are div.imageForms because the
        // uploaderPreviewer javascript may be not included and produce an error
        if ($('div.imageForms').length) {

            $('div.imageForms').append($.uploaderPreviewer.createImageForms());

            // the images are populated if the item form is to edit, and not
            // to insert
            if ($('div.imageForms[images]').length) {
                var imageFilenames = $('div.imageForms[images]').attr('images').split(',');
                $.uploaderPreviewer.populateImages(imageFilenames);
                $('div.imageForms[images]').removeAttr('images');
            }
        }

    },

    //--------------------------------------------------------------------------
    /**
     * 
     */
    initializeForms: function() {

//        $.globalFunctions.initializeRequireFields();
        $('body').initializeRequireFields();
        
        if ($('#category').val()) {
            $('.selectCategory button').hide();
        }
        else {
            $('#category').hide();
        }

        $('.itemDisplay .contact .message textarea')
            .focus(function() {
                $(this)
                    .height($.configuration.largeContactMessageHeight)
                    .width($.configuration.largeContactMessageWidth)
                    .parent().height($.configuration.largeContactMessageHeightParent);
            })
            .blur(function() {
                $(this)
                    .height($.configuration.smallContactMessageHeight)
                    .width($.configuration.smallContactMessageWidth)
                    .parent().height($.configuration.smallContactMessageHeightParent);
            });

//        $('#private')
        $('input:checkbox.private')
            .change(function() {
                var $this = $(this);
                // the toggle function is not used, because the private checkbox
                // change event may be called with no state change, and thus the
                // synchronisation with the agency name input is broken
//                if ($('#private').is(':checked')) {
                if ($this.is(':checked')) {
//                    $('#agencyName, .agencyName').hide();
                    $('#' + $this.attr('toggleElement')).hide();
                }
                else {
//                    $('#agencyName, .agencyName').show();
                    $('#' + $this.attr('toggleElement')).show();
                }
            })
            .change();

        // NOTE: In the item form, the ad type drop down list is named "adType",
        //       in the search panel "cmbAdType".
        $('.adType select:first').disabledMessage('categoryNotSelected');
//        $.globalFunctions.disablingPair(
//            $(), $('.adType select:first'), 'categoryNotSelected'
//        );

        // must go after the category initialization, or the price may be
        // visible when should not
                // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
        $('#adType')
            .change(function() {

                var $this = $(this);
                var adType = $(this).sanitizedValue();
                
                // ad type 15 -> swap
                // ad type 16 -> given
                // ad type 17 -> search given
                if ((adType == 15) || (adType == 16) || (adType == 17)) {
                    $('.price').hide();
                    $this.attr('price', 'false');
                }
                else {
                    $('.price').show();
                    $this.attr('price', 'true');
                }
            })
            .change();

        var $category = $('.itemFormBody #category');
        if ($category.attr('categoryGroupId')) {
//            $.categoryMenu.toggleFeaturesAndAdTypes(
//                $category.attr('categoryGroupId'), $category.attr('categoryId')
//            );
            $.categoryMenu.adjustFormToCategory({
                containerElementClass: 'itemForm',
                categoryGroupId: $category.attr('categoryGroupId'),
                categoryId: $category.attr('categoryId')
            });
        }

    },

    //--------------------------------------------------------------------------
    /**
     * Display hidden elements on page load.
     */
    launchAppearingEffects: function() {
        
        $('#buttonInsert').fadeIn(5000);
        $('.informationPoint').fadeIn(5000);
        $('.breadcrumbs ul').slideDown();
//        $('.resultCountAndPagination')
//            .effect('highlight', { color: $.configuration.highlightColor }, 4000);
//        $('.heading hr')
//            .effect('highlight', { color: '#fff' }, 1500);

    },

    //--------------------------------------------------------------------------
    /*
     * Get the value of a specified parameter teken from the url with
     * regular expressions.
     *
     * @param parameterName string
     * @return string Parameter value
     *
     */
    getUrlParameter: function(parameterName) {

      var unescapedParameters = unescape(window.location.search);
      var regexPattern = new RegExp(parameterName + "=(.+?)(&|$)");

      // the first array position is the whole matching string, with the parameter
      // name. The second position is the searched substring. It is returned null
      // if there is no match.
      var matches = unescapedParameters.match(regexPattern);

      var parameter = "";

      if(matches != null) {
        // the first array position is the whole matching string, with the parameter
        // name. The second position is the searched substring.
        parameter = unescape($.globalFunctions.sanitize(matches[1]));
      }
      return parameter;
    },

    //--------------------------------------------------------------------------
    /**
     * Strip tags and blank spaces of a given string using regular expressions.
     *
     * @param string The string to sanitize.
     * @return string The sanitized string.
     * 
     * @uses stripScripts() -> remove javascripts
     * @uses stripTags() -> remove html tags
     * @uses strip() -> remove leading and trailing whitespace
     * @uses encodeURIComponent() -> encode for url special characters, tildes, line
     *       breaks, spaces, etc., including the characters: , / ? : @ & = + $ #
     */
    sanitize: function(string) {
        
        var result = '';
        if (string) {
            result = encodeURIComponent(this.strip(this.stripTags(this.stripScripts(string))));
        }
        return result;
    },

    //--------------------------------------------------------------------------
    /**
     * Strip tags and blank spaces of a given string using regular expressions.
     *
     * @param string The string to sanitize.
     * @return string The sanitized string.
     *
     * @uses stripScripts() -> remove javascripts
     * @uses stripTags() -> remove html tags
     * @uses strip() -> remove leading and trailing whitespace
     * @uses encodeURIComponent() -> encode for url special characters, tildes, line
     *       breaks, spaces, etc., including the characters: , / ? : @ & = + $ #
     *       XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
     */
    sanitizeNumber: function(string) {

//        return $.globalFunctions.sanitize(string)
//                .replace($.globalFunctions.thousandsSeparator, '')
//                .replace($.globalFunctions.decimalSeparator, '.');
//        var result = '';

//        var regex = /^((\d{1,3}(\.\d{3})*)|(\d+))(,\d+)?$/;
        // regular expression = /^((\d{1,3}(\,\d{3})*)|(\d+))(.\d+)?$/
        // examples:
        // 1
        // 1111
        // 1,111
        // 1,111.111
        // NOTE: The double backslash works for chars like '.', ',', ' '.
        //       For other chars, test the statement.
        var regex = '^((\\d{1,3}(\\' + $.configuration.thousandsSeparator
                  + '\\d{3})*)|(\\d+))(' + $.configuration.decimalSeparator
                  + '\\d+)?$';
//        var regex = new RegExp('^((\\d{1,3}(\\' + $.configuration.thousandsSeparator
//                  + '\\d{3})*)|(\\d+))(' + $.configuration.decimalSeparator
//                  + '\\d+)?$');

        // if the match is in the first position
        if (string.search(regex) == 0) {
            // NOTE: The double backslash works for chars like '.', ',', ' '.
            //       For other chars, test the statement.
            regex = new RegExp('\\' + $.configuration.thousandsSeparator, 'g');

            string = string.replace(regex, '');

            string = string.replace($.configuration.decimalSeparator, '.');

            string = encodeURIComponent(string);
        }
        else {
            string = '';
        }

        return string;

//        return string.replace(/<\/?[^>]+>/gi, '');
//
//        var scriptFragment = '<script[^>]*>([\\S\\s]*?)<\/script>';
//        return string.replace(new RegExp(scriptFragment, 'img'), '');
//        var scriptFragment = 'ddd,dd';
//        return string.replace(scriptFragment, '');
    },

    //--------------------------------------------------------------------------
    /**
     * Strip the 'script' tags of a given string using regular expressions.
     * <br />
     * Function taken from the prototype framework.
     * 
     * @param string The string to sanitize.
     * @return string The given string without 'script' tags.
     */
    stripScripts: function(string) {
        var scriptFragment = '<script[^>]*>([\\S\\s]*?)<\/script>';
        return string.replace(new RegExp(scriptFragment, 'img'), '');
    },

    //--------------------------------------------------------------------------
    /**
     * Strip the xml like tags of a given string using regular expressions.
     * <br />
     * Function taken from the prototype framework.
     *
     * @param string The string to sanitize.
     * @return string The given string without xml like tags.
     */
    stripTags: function(string) {
        return string.replace(/<\/?[^>]+>/gi, '');
    },

    //--------------------------------------------------------------------------
    /**
     * Strip leading and trailing blank spaces of a given string using regular
     * expressions.
     * <br />
     * Function taken from the prototype framework.
     *
     * @param string The string to sanitize.
     * @return string The given string without trailing spaces.
     */
    strip: function(string) {
        return string.replace(/^\s+/, '').replace(/\s+$/, '');
    },

    //--------------------------------------------------------------------------
    /**
     * Open a dialog box with the given parameters.
     * <br />
     * It is checked if the dialog box was already created in order to reuse it
     * and to not create another one again.
     * 
     * @param options = { <br />
     *      dialogBoxId: string - default: 'dialog', <br />
     *      classes: string - default: '', <br />
     *      scrollToTop: boolean - default: true, <br />
     *      modal: boolean - default: true, <br />
     *      width: string|int - default: '300px', <br />
     *      height: string|int - default: 'auto', <br />
     *      minWidth: int|string - default: 'auto', <br />
     *      // BUG: If minHeight is 'auto', IE crashes <br />
     *      minHeight: int|string - default: 0, <br />
     *      buttons: object - default: {}, <br />
     *      title: string - default: '', <br />
     *      message: string - default: '', <br />
     *      close: function - no default. The event "dialogclose" is unbound
     *             if no event "closed" is passed. <br />
     * }
     * @return jQuery $dialog
     * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
     */
    dialogOpen: function(options) {

        var settings = {
            dialogBoxId: 'dialog',
            classes: '',
            scrollToTop: true,
            modal: true,
            width: '300px',
            height: 'auto',
//            minWidth: 'auto',
            minWidth: 'none',
            // BUG: If minHeight is 'auto', IE crashes
//            minHeight: 0,
            minHeight: 'none',
//            buttons: {},
            buttons: { OK: function() { $.globalFunctions.dialogClose(this); } },
            title: '',
            message: '',
            closeButtonCaption: 'X',
            beforeCloseEvent: ''
        };
        $.extend(settings, options);

        var $body = $('body');

        if (settings.scrollToTop) {
            $body.scrollWindow();
        }

        var $dialog = $.globalFunctions.dialogFactory(settings);

//        $body
//            .prepend($dialog)
//            .prepend($.globalFunctions.setOverlay(settings, $body));
//        $body
////            .append($.globalFunctions.setOverlay(settings, $body))
//            .append($.globalFunctions.setOverlay(settings))
//            .append($dialog);
        $body.append($dialog);
//            .append($.globalFunctions.setOverlay(settings, $body))
        if (settings.modal) {
            $dialog.before($.globalFunctions.setOverlay(settings));
        }
            
//$('body').prepend('<br />' + $dialog.css('top'));

        // the dialog must be assigned to a DOM element before being positioned
        $.globalFunctions.setDialogPositionAndFocus($dialog);
//$('body').prepend('<br />' + $dialog.css('top'));

        // set the focus on the first button of the dialog
        // the dialog cannot get the focus, as it is not tab stoppable. It
        // could be done tab stoppable with the tabindex attribute, but the
        // desired effect is not reached, as the elements within the dialog
        // are not followed sequentially
        $dialog.find('button:first').focus();

        if (settings.beforeCloseEvent) {
            // the closing event is launched once and unbound
            $dialog.one('beforeClose', settings.beforeCloseEvent);
        }
        else {
            $dialog.unbind( 'beforeClose');
        }
        
        // keydown and keyup provide a code indicating which key is pressed, 
        // while keypress indicates which character was entered. If pressed
        // the esc key, keypressed returns 0 and keyup 27
//        $(document).bind('keyup', {$dialog: $dialog}, $.globalFunctions.dialogKeyup);

        // BUG: IE6 displays the drop down lists on the top of all elements,
        //      ignoring the "z-index" property.
        if ($.browser.msie && ($.browser.version < 7)) {
            $('select').hide();
        }

        return $dialog;
    },
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    // this event is launched just once per key up, not once per open dialog,
    // because it is attached to the document, not to the dialogs
    dialogKeyup: function(event) {
        // next statement checks if the dialog is in the top of the layout
        // If it has any element after it, probably another dialog, the event
        // is not performed.
        // The event cannot be stopped, as the relevant element is the last
        // one, not the first one
        // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
//        if ( ! event.data.$dialog.next().length) {
        var $dialog = $('.dialog:last');

        if ($dialog.length) {
        
//        console.info(event.data.$dialog.next().length);
            // esc key
            if (event.which == 27) {
//                $.globalFunctions.dialogClose(event.data.$dialog);
                $.globalFunctions.dialogClose($dialog);
            }
            // NOTE: The enter key is not captured as event, because if it is
            //       pressed when a button has the focus, the button event is
            //       executed and after the one of the enter key, wich may
            //       lead to wrong behaviour.
            // 
            // enter key
            // if the enter key is pressed in a textarea, no event is launched
//            else if ((event.which == 13) && ( ! $(event.target).is('textarea'))) {
////                event.data.$dialog.find('.buttons button:first').click();
//                $dialog.find('.buttons button:first').click();
//            }
        }
    },

// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    /**
     * The dialog must be assigned to a DOM element before being positioned.
     */
    setDialogPositionAndFocus: function($dialog) {

        var $document = $(document);

        // BUG: innerHeight and innerWidth not supported by IE6.
//        var top = window.innerHeight / 2 - $dialog.height() / 2;
        var top = $(window).height() / 2 - $dialog.height() / 2;
//        var top = window.innerHeight / 2 - $dialog.height() / 2 + $(document).scrollTop();
//        var left = window.innerWidth / 2 - $dialog.width() / 2;
        var left = $(window).width() / 2 - $dialog.width() / 2;

        if (top < $.configuration.dialogTopMin) {
//        if (top < ($.configuration.dialogTopMin + $(document).scrollTop())) {
            top = $.configuration.dialogTopMin;
        }
        else if (top > $.configuration.dialogTopMax) {
//        else if (top > ($.configuration.dialogTopMax + $(document).scrollTop())) {
            top = $.configuration.dialogTopMax;
        }

        if (left < $.configuration.dialogLeftMin) {
            left = $.configuration.dialogLeftMin;
        }
        else if (left > $.configuration.dialogLeftMax) {
            left = $.configuration.dialogLeftMax;
        }

        top += $document.scrollTop();
        left += $document.scrollLeft();

        // BUG: The top property of an element cannot be assigned in Chrome
        //      when it is "auto". It must be changed to a numeric value
        //      before assigning the new value.
        $dialog.offset( {top: 0, left: 0} );

//        return $dialog.offset( { top: top, left: left } ).focus();
//        return $dialog.offset( { top: top, left: left } ).attr("tabindex",-1).focus();
//        return $dialog.offset( { top: top, left: left } ).attr("tabindex", 1);
        return $dialog.offset( {top: top, left: left} );
//        return $dialog.offset( {top: newTop, left: left} );
//        return $dialog;
    },

// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
//    setOverlay: function(settings, $body) {
    setOverlay: function(settings) {
        // document must be used instead of $('body') because if the body was
        // enlarged due to the dialog, the new dimension will not be recognised
        var $document = $(document);
        var $overlay = $('<div id="' + settings.dialogBoxId + 'Overlay" class="overlay"></div>')
            .attr('elementId', settings.dialogBoxId)
//            .width($body.outerWidth(true))
//            .height($body.outerHeight(true));
            .width($document.width())
            .height($document.height());
        if ( ! settings.modal) {
            $overlay.click(function() {
                $.globalFunctions.dialogClose(this);
            });
        }
        return $overlay;
    },

// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    dialogFactory: function(settings) {

        var $dialog = $('#' + settings.dialogBoxId);

//        if ( ! settings.classes) {}

        // dialog div factory
        if ($dialog.length) {
            $.globalFunctions.dialogClose($dialog);
        }

        $dialog = $(
'<div id="' + settings.dialogBoxId + '" class="drag dialog ' + settings.classes + '">'
    + '<button class="close"></button>'
    + '<div class="dragHandler title"></div>'
    + '<div class="content"></div>'
    + '<div class="buttons"></div>'
+ '</div>'
);
        $.globalFunctions.setDrag($dialog);
        
        $dialog.find('.close').html(settings.closeButtonCaption).click(function() {
            $.globalFunctions.dialogClose(this);
        });
        $dialog.find('.title').html(settings.title);
        $dialog.find('.content').html(settings.message);
        $dialog.find('.buttons').html($.globalFunctions.generateButtons(settings.buttons));

        return $dialog.width(settings.width).height(settings.height);
    },

// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    generateButtons: function(buttons) {

        var $buttons = $();
        
        for (button in buttons) {
            $buttons = $buttons.add($('<button>' + button + '</button>').click(buttons[button]));
        }
        
        return $buttons;
    },

// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    setDrag: function($element) {
        $element.find('.dragHandler')
            .mousedown(function(event) {
                var $this = $(this);
                $.globalFunctions.dragOffsetTop = event.clientY - $this.offset().top;
                $.globalFunctions.dragOffsetLeft = event.clientX - $this.offset().left;
                $(document)
                    .bind('mousemove', {$dragElement: $element}, $.globalFunctions.drag)
                    .one('mouseup', function() {
                        $(document).unbind('mousemove', $.globalFunctions.drag);
                    });
            });

        return $element;
    },
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    drag: function(event) {
        return event.data.$dragElement.offset({
            top: event.clientY - $.globalFunctions.dragOffsetTop,
            left: event.clientX - $.globalFunctions.dragOffsetLeft
        });
    },

    //--------------------------------------------------------------------------
    /**
     * Close the dialog box opened with dialogOpen().
     * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
     */
//    dialogClose: function(trigger) {
    dialogClose: function(trigger) {

        if (( ! trigger) || (trigger == 'undefined')) {
            trigger = '#dialog';
        }

//        trigger.preventDefault();

        var $dialog = $(trigger);

//        if ($dialog.is('button')) {
//            $.configuration.dialogButton
//        }

        if ( ! $dialog.is('.dialog')) {

            if ($dialog.attr('elementId')) {
                $dialog = $('#' + $dialog.attr('elementId'));
            }
            else {
                $dialog = $dialog.parents('.dialog:first');
            }
        }

        // the beforeClose event is launched before the dialog is detached
        // from the document. It is not removed, so that it may be reused
        // later. Anyway, the remove() function doesn't remove the element
        // from the document, it remains
//        $dialog.trigger('beforeClose').remove();
//        $('#' + $dialog.attr('id') + 'Overlay').remove();
        $dialog.trigger('beforeClose').detach();
        $('#' + $dialog.attr('id') + 'Overlay').detach();
        
//        $(document).unbind('keypress', $.globalFunctions.dialogKeypress);
//        $(document).unbind('keyup', $.globalFunctions.dialogKeyup);

        // BUG: IE6 displays the drop down lists on the top of all elements,
        //      ignoring the "z-index" property.
        if ($.browser.msie && ($.browser.version < 7)) {
            $('select').show();
            $('#adTypeHidden').hide();
        }
    },

    //--------------------------------------------------------------------------

    dialogErrorOpen: function(title, message) {

            $.globalFunctions.dialogOpen({
                dialogBoxId: 'dialogError',
                scrollToTop: false,
                title: title,
                message: message,
                buttons: $.configuration.dialog.buttons.ok
            });
    },

    //--------------------------------------------------------------------------
    
    getImageUrl: function(imageKey) {

        var imageUrl = $.configuration.url.images.directory
                  + $.configuration.url.images[imageKey]
                  + '.png';

        return imageUrl;
    },

    //--------------------------------------------------------------------------

    getImageHtml: function(imageKey, altText) {

        var imageHtml = '<img src="' + $.globalFunctions.getImageUrl(imageKey)
                      + '" alt="' + altText + '">';

        return imageHtml;
    },

    //--------------------------------------------------------------------------

    getAjaxUrl: function(filenameKey) {
        
        $ajaxUrl = $.configuration.url.ajax.directory 
                 + $.configuration.url.ajax[filenameKey];
        
        return $ajaxUrl;
    },

    //--------------------------------------------------------------------------
    /**
     * Display a large image or an error dialog box.
     *
     * @param result Ajax result.
     */
    largeImageLoaded: function(result) {

        var $result = $(result);

        if ($result.is('.image')) {
            // if returned a complete html image, errors may appear with
            // special characters in the filename, like tildes. So it must
            // be returned only the image pathname
            $('#divLargeImage')
                .hideErrorMessage()
                .find('img')
                    .attr('src', (unescape($result.text())))
                    .show();
        }
        else {
            $('#divLargeImage')
                .errorMessage($result.html())
                .find('img').hide();
        }
    },

    //--------------------------------------------------------------------------
    /**
     * @param element DOM element
     * @return string
     */
    getMarkerInformation: function(element) {

        var $element = $(element);

        var $markerInformation = $('<div><div class="markerInformation">'
                                 + '<div class="image"></div>'
                                 + '<div class="data"></div>'
                                 + '</div></div>');

        var $newWindowLink = $element.find('.newWindow');

        var noConcreteAddress = '';
        if ($element.find('.marker').attr('aprox')) {
            noConcreteAddress = '<p class="notes">'
                              + $.messages.altTitleCaption.aproxMarker
                              + '</p>';
        }
        
        $markerInformation.find('.data')
            .append('<h4><a href="' + $newWindowLink.find('a').attr('href') + '">'
                   + $element.find('h2').html() + '</a></h4>')
            .append('<p>' + $element.find('.features input').val() + '</p>')
            .append('<p>' + $element.find('.marker').attr('completeAddress') + '</p>'
                   + noConcreteAddress)
            .append('<p class="newWindow">' + $newWindowLink.html() + '</p>');

        if ($element.find('img:not(.itemNoPicture)').length) {
            $markerInformation.find('.image').append(
                $element.find('img:not(.itemNoPicture)').parent().clone()
            );
        }

        return $markerInformation.html();
    },

    //--------------------------------------------------------------------------

    submitIfEnter: function(event) {

        // the change event must be launched or it will not be executed before
        // submitting and may lead to errors
        $(event.target).change();

        if(event.which == 13) {
            $('.searchPanel button.search').click();
        }
    },

    //--------------------------------------------------------------------------
    /**
     * NOTE: The cache system only works when the popup window is already
     *       open. It doesn't work when the help popup is closed, because
     *       the dialog is destroyed on closing. Thus, the popup window is
     *       created everytime that is opened. It is however not a big issue,
     *       because this functionality is expected not to be used often.
     *       XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
     */
    displayPopupHelp: function(event) {

        var $helpPopupDiv = $('#helpPopupDiv');

        var currentHelpTextKey = $(event.target).attr('helpTextKey');

        var content = '';

        if ( ! $helpPopupDiv.length) {

            var settings = {
                dialogBoxId: 'helpPopupDiv',
                classes: 'helpPopup',
                scrollToTop: false,
                modal: false,
                title: $.messages.dialog.titles.help,
                message: content,
                buttons: $.configuration.dialog.buttons.close,
                width: parseInt($(event.target).attr('helpPopupWidth')),
                position: [(event.clientX + 25), (event.clientY - 50)]
            };

            $.globalFunctions.dialogOpen(settings);
                $helpPopupDiv = $('#helpPopupDiv');

            // if it is the first time the dialog opens, it must be assigned
            // to the $helpPopupDiv variable, as it was created after the
            // first assignment
//            $helpPopupDiv = $('#helpPopupDiv');

            $helpPopupDiv
                .find('.content')
                .click( function() { $.globalFunctions.dialogClose(this) } );
        }
        else if ($helpPopupDiv.attr('helpTextKey') == currentHelpTextKey) {

            content = $helpPopupDiv.find('.content').html();
        }
//console.info(content);
//                    console.info($helpPopupDiv.attr('helpTextKey'));
//                    console.info(currentHelpTextKey);
        if ( ! content) {

            // if it is the first time the dialog opens, it must be assigned
            // to the $helpPopupDiv variable, as it was created after the
            // first assignment
//            $helpPopupDiv = $('#helpPopupDiv');
//
//            $helpPopupDiv.attr('helpTextKey', currentHelpTextKey);
//
//            $helpPopupDiv.find('.content').load(
//                    $.globalFunctions.getAjaxUrl('helpPopup'),
//                    { helpTextKey: currentHelpTextKey }
//            )
            $helpPopupDiv
                .attr('helpTextKey', currentHelpTextKey)
                .find('.content')
                .load(
                    $.globalFunctions.getAjaxUrl('helpPopup'),
                    { helpTextKey: currentHelpTextKey },
                    function() { $.globalFunctions.setDialogPositionAndFocus($helpPopupDiv); }
                );
//                .load(
//                    $.globalFunctions.getAjaxUrl('helpPopup'),
//                    { helpTextKey: currentHelpTextKey }
//                );
        }

//        $helpPopupDiv.find('.content')
//            .css('cursor', 'crosshair')
            // the dialog box is closed when it is clicked anywhere on the text
            // (not on the title or the button bar)
//            .click( function() { $.globalFunctions.dialogClose(this) } );
    },

    //--------------------------------------------------------------------------
    /**
     * Display a tooltip for an element.
     *
     * This function is supposed to be called by two events: hover and focus.
     *
     * To avoid conflicts between different events of a same element which may
     * call this function, if there is any existing tooltip displayed, it is
     * removed before creating a new one. Thus, no tooltip factory is implemented,
     * always is created a new tooltip. Otherwise, the method grows unnecessary
     * complicated, as creating a new one everytime is fast and not memory
     * consuming.
     *
     * @param event
     * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
     */
    displayTooltip: function(event) {

        $.globalFunctions.removeTooltip();

        // if the element is taken with event.target, the "a" html elements
        // may fail and take their child elements instead themselves
        var $element = $(event.currentTarget);
        
        var $tooltipDiv = $('<div id="tooltipDiv"><div class="sprite"></div>'
                          + '<div class="text"></div></div>');

        $tooltipDiv.find('.text').html($.messages.tooltip[$element.attr('tooltipKey')]);
        
        // the element must be assigned to the DOM to be able to get its width.
        // If its left is not set to 0 and the tooltip width excedes the right
        // window border, the width will be automatically reduced by the browser,
        // in order to fit the tooltip in the window, which will give a wrong
        // tooltip width value and a misbehaviour will occur
        $tooltipDiv.insertAfter($element).css('left', 0);

        var left = $element.position().left
                 + $element.outerWidth(true)
                 + $.configuration.tooltipLeftOffset;

        // the default tooptip position is left
        var tooltipClass = 'left';

        if ((left + $tooltipDiv.outerWidth(true)) > $(document).width()) {
            left = $element.position().left
                 - $tooltipDiv.outerWidth(true)
                 - $.configuration.tooltipLeftOffset;

            tooltipClass = 'right';
        }
        
        $tooltipDiv
            .addClass(tooltipClass)
            .css({
                left: left,
                top: $element.position().top + $.configuration.tooltipTopOffset
            })
            .show();
    },

    //--------------------------------------------------------------------------

    removeTooltip: function() {

        $('#tooltipDiv').detach();

    },

    //--------------------------------------------------------------------------

    goBack: function() {

        // NOTE: Internet Explorer and Opera start the history length at 0,
        //       while Firefox, Chrome, and Safari start at 1.
        var firstHistoryElement = 1;

        if ($.browser.msie || $.browser.opera) {
            firstHistoryElement = 0;
        }

        if (history.length > firstHistoryElement) {
            history.back();
        }
        else {
            location.replace($.configuration.url.domain);
        }
    },

    //--------------------------------------------------------------------------
    /**
     * Remove the 'ui-state-error' class div from the calling element.
     * XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
     */
    hideErrorMessages: function() {
        $('.message.error').hide();
    },

    //--------------------------------------------------------------------------

    contact: function() {

        // the code field must be read before it is destroyed with the next
        // dialog
        var parameters = {
            senderEmail: $('#contactEmail').sanitizedValue(),
            senderPhone: $('#contactPhone').sanitizedValue(),
            senderMessage: $('#contactMessage').sanitizedValue(),
            itemId: $('#itemId').sanitizedValue(),
            title: $('h1').text(),
            url: location.href
        };

        if ( ! parameters.senderEmail && ! parameters.senderPhone) {

            $.globalFunctions.dialogOpen({
                dialogBoxId: 'dialogError',
                scrollToTop: false,
                title: $.messages.dialog.titles.noEmailNoPhone,
                message: $.messages.dialog.messages.noEmailNoPhone,
                buttons: $.configuration.dialog.buttons.ok
            });
        }
        else if ( ! parameters.senderMessage) {

            $.globalFunctions.dialogOpen({
                dialogBoxId: 'dialogError',
                scrollToTop: false,
                title: $.messages.dialog.titles.noMessage,
                message: $.messages.dialog.messages.noMessage,
                buttons: $.configuration.dialog.buttons.ok
            });
        }
        else {
            $.globalFunctions.dialogOpen({
                title: $.messages.dialog.titles.sending,
                message: $.messages.dialog.messages.sendingMessage,
                buttons: {}
            });

//            $.post($.globalFunctions.getAjaxUrl('contact'), parameters, contacted);
//            $.post($.globalFunctions.getAjaxUrl('contact'), parameters, displayResultInDialog);
            $.post($.globalFunctions.getAjaxUrl('contact'), parameters, displayContactResult);
        }
    },

    //--------------------------------------------------------------------------

    contactAdmin: function() {

        // the code field must be read before it is destroyed with the next
        // dialog
        var parameters = {
            email: $('#contactEmail').sanitizedValue(),
            subject: $('#contactSubject').sanitizedValue(),
            message: $('#contactMessage').sanitizedValue()
        };

        if ( ! parameters.email) {

            $.globalFunctions.dialogErrorOpen(
                $.messages.dialog.titles.noEmail,
                $.messages.dialog.messages.noEmail
            );
        }
        else if ( ! parameters.subject) {

            $.globalFunctions.dialogErrorOpen(
                $.messages.dialog.titles.noSubject,
                $.messages.dialog.messages.noSubject
            );
        }
        else if ( ! parameters.message) {

            $.globalFunctions.dialogErrorOpen(
                $.messages.dialog.titles.noMessage,
                $.messages.dialog.messages.noMessageAdmin
            );
        }
        else {
            $.globalFunctions.dialogOpen({
                title: $.messages.dialog.titles.sending,
                message: $.messages.dialog.messages.sendingMessage,
                buttons: {}
            });

            $.post(
                $.globalFunctions.getAjaxUrl('contactAdmin'),
                parameters,
                displayResultInCenterPanel
//                contactedAdmin
            );
        }
    },

    //--------------------------------------------------------------------------

    sendToAFriend: function() {

        // the code field must be read before it is destroyed with the next
        // dialog
        var parameters = {
            friendEmail: $('#friendEmail').sanitizedValue(),
            senderEmail: $('#senderEmail').sanitizedValue(),
            senderName: $('#senderName').sanitizedValue(),
            senderComment: $('#senderComment').sanitizedValue(),
            title: $('h1').text(),
            url: location.href
        };

        if ( ! parameters.friendEmail) {

            $.globalFunctions.dialogOpen({
                dialogBoxId: 'dialogError',
                title: $.messages.dialog.titles.noEmailFriend,
                message: $.messages.dialog.messages.noEmailFriend,
                buttons: $.configuration.dialog.buttons.ok
            });
        }
        else {
            $.globalFunctions.dialogOpen({
                title: $.messages.dialog.titles.sending,
                message: $.messages.dialog.messages.sendingAd,
                buttons: {}
            });

//            $.post($.globalFunctions.getAjaxUrl('sendToAFriend'), parameters, sentToAFriend);
            $.post(
                $.globalFunctions.getAjaxUrl('sendToAFriend'),
                parameters,
                displayResultInDialog
            );
        }
    },

    //--------------------------------------------------------------------------

    report: function() {

        // the code field must be read before it is destroyed with the next
        // dialog
        var parameters = {
            email: $('#reportEmail').sanitizedValue(),
            reason: $('#reportReason').sanitizedValue(),
            title: $('h1').text(),
            url: location.href
        };

        if ( ! parameters.reason) {

            $.globalFunctions.dialogOpen({
                dialogBoxId: 'dialogError',
                title: $.messages.dialog.titles.noReason,
                message: $.messages.dialog.messages.noReason,
                buttons: $.configuration.dialog.buttons.ok
            });
        }
        else {
            $.globalFunctions.dialogOpen({
                title: $.messages.dialog.titles.sending,
                message: $.messages.dialog.messages.sendingMessage,
                buttons: {}
            });

//            $.post($.globalFunctions.getAjaxUrl('report'), parameters, reported(result, 'aaa'));
//            $.post($.globalFunctions.getAjaxUrl('report'), parameters, displayResultInCenterPanel);
            $.post($.globalFunctions.getAjaxUrl('report'), parameters, displayResultInDialog);
        }
    }

}; // end of public functions of jQuery.globalFunctions

//------------------------------------------------------------------------------
// private functions -----------------------------------------------------------
//------------------------------------------------------------------------------

function displayResultInCenterPanel(result) {

    displayResult(result, 'centerPanel');
}

//------------------------------------------------------------------------------

function displayResultInDialog(result) {

    displayResult(result, 'dialog');
}

//------------------------------------------------------------------------------

function displayContactResult(result) {

    $('.contact .contactForm').html(result);

    displayResult(result, 'dialog');
}

//------------------------------------------------------------------------------
// this function displays messages of type "message" and "error", but not
// "warning"
function displayResult(result, okResultDisplay) {
    // the result html elements must be enclosed in a parent html element, to
    // be able to read all of them. The wrap function doesn't work in this case,
    // with several separated html elements
    var $result = $('<div>' + result + '</div>');

    if ($result.find('.message').length) {
//        $('.centerPanel').html($result.find('.message'));
//        $.globalFunctions.dialogClose();
//        $.globalFunctions.initializeLogos();
        if (okResultDisplay == 'centerPanel') {
            $('.centerPanel').html($result.find('.message'));
            $.globalFunctions.dialogClose();
//            $.globalFunctions.initializeLogos();
        }
        else {
            $.globalFunctions.dialogOpen({
                title: $.messages.dialog.titles.completed,
                message: $result.find('.message'),
                buttons: $.configuration.dialog.buttons.close
            });
        }
    }
    else {
        var settings = {
            title: $.messages.dialog.titles.error,
            message: $result,
            buttons: $.configuration.dialog.buttons.close
        };
        if ($result.find('.error').length) {
            settings.message = $result.find('.error');
        }
        $.globalFunctions.dialogOpen(settings);
    }
};

//------------------------------------------------------------------------------

})(jQuery);

/**                                                                          **/
/**                        Global functions plugin                           **/
/**                                                                          **/
/******************************************************************************/

