/*
 * knwebstd-tools-dialog: JavaScript library for handling modal dialogs and Ajax
 *
 * Copyright (c) 2008 Kuehne+Nagel
 *
 * This code is not freeware. It may not be used for any (private or commercial) purposes except by
 * Kuehne+Nagel and its associated companies.
 */

if (!window.knwebstd) {
    window.knwebstd = new Object();
}
knwebstd.dialog = new Object();

/*
 * PRIVATE: Determines the range of the selection of the given field. Works browser independently.
 * @field a text input field
 * @return an selection object, containing the start and end positions as well as the length
 */
knwebstd.dialog.getSelection = function(field) {
  var result = {start:-1,end:-1,length:-1};
  if (document.selection) {
    // IE Support
    field.focus();
    var Sel = document.selection.createRange();
    result.length = document.selection.createRange().text.length;
    Sel.moveStart('character', -field.value.length);
    result.end = Sel.text.length;
    result.start = result.end - result.length;
  } else if ((field.selectionStart || field.selectionStart == 0)
      &&(field.selectionEnd || field.selectionEnd == 0)){
    // Firefox support
    result.start = field.selectionStart;
    result.end = field.selectionEnd;
    result.length = result.end - result.start;
  }
  return result;
}

/**
 * PUBLIC: Validates the value that would be created by the given event against the given constraints using the specified separator.
 * @param event a keyPress event containing the pressed key and the target text input field
 * @param precision the maximal number of overall digits, may be null
 * @param scale the maximal number of decimal digits, may be null
 * @param separator the decimal separator character
 * @return true, if the pressed key leads to a valid value in the field, else false
 */
knwebstd.dialog.validateDecimal = function(event, precision, scale, separator, eventHandler){
  if(eventHandler && typeof eventHandler == 'function'){
    // if the eventHandler returns false, the whole process is canceled
    var result = eventHandler(event);
    if(result == false){
    	return false;
    }
  }
  var code = event.charCode;
  var field = event.target;
  if(window.event){
    // in internet explorer the event is handled in a proprietary way
    event = window.event;
    field = window.event.srcElement;
    code = window.event.keyCode;
  }
  // if no printable character was pressed, proceed the event
  if(code == 0){
    return true;
  }
  if(event.ctrlKey){
  	return true;
  }
  var input = String.fromCharCode(code);
  // check, if the new character is permitted at all
  if ('0123456789-,.'.indexOf(input) == -1){
    return false;
  }
  // if no constraints are defined, the value is always valid
  if(precision == null && scale == null){
    return true;
  }
  var delimiter;
  var regSeparator;
  if(separator == ','){
    delimiter = '\\.';
    regSeparator = separator;
  } else {
    delimiter = ',';
    regSeparator = '\\'+separator;
  }

  var selection = knwebstd.dialog.getSelection(field);
  var old_value = field.value;
  var value = old_value.substring(0,selection.start) + input + old_value.substring(selection.end);

  // remove the sign and eventual delimiters
  var pattern = '[-'+delimiter+']';
  var regexp = new RegExp(pattern,'g');
  value = value.replace(regexp,'');
  var pos = value.indexOf(separator);
  var decimals = 0;
  var integerDigits = precision;
  if(pos > -1){
    // position of separator defines the scale
    decimals = value.length - (pos + 1);
    // reduce value to integer part
    value = value.substring(0, pos);
  }
  // check number of decimals
  if(scale != null){
    if(decimals > scale){
      return false;
    }
    integerDigits = precision - scale;
  }

  // check number of digits
  if(precision != null && value.length > integerDigits){
    return false;
  }
  return true;
}



/*
 * Goes to upmost parent frame and loads the given url. If url is empty, the parent frame is reloaded.
 */
knwebstd.dialog.loadInUpmostParent = function(url) {
  var currentWindow = window;
  while(currentWindow.parent && (currentWindow.parent != currentWindow)){
    currentWindow = currentWindow.parent;
  }
  if(!url){
    url = currentWindow.location;
  }
  currentWindow.location.href = url;
}

/* Starts a dialog. Content is loaded from dialogUrl. Display options and callbacks are passed via options argument.
*  Parameters:
*  dialogUrl - the URL to show in dialog.
*  options - an option object
*  urlParams - Params to attach to URL.
*/
knwebstd.dialog.startDialog = function(dialogUrl, options, urlParams) {
    var _options = {name: 'knwebstd_dialog_iframe', title: 'Dialog', zIndex: 1000, overlay: 35, closeImg: 'static/images/knwebstd/cancel16.gif',
                    width: 600, height: 450, ajax: false, waitpage: null, callbackUrl: null, callbackFunction: null, afterCloseFunction: null};
    _options = $.extend(_options, options);

    knwebstd.dialog.currentDialog = new Object();
    knwebstd.dialog.currentDialog.zIndex = _options.zIndex;
    knwebstd.dialog.currentDialog.ajax = _options.ajax;
    knwebstd.dialog.currentDialog.waitpage = _options.waitpage;
    knwebstd.dialog.currentDialog.callbackUrl = _options.callbackUrl;
    knwebstd.dialog.currentDialog.callbackFunction = _options.callbackFunction;
    knwebstd.dialog.currentDialog.afterCloseFunction = _options.afterCloseFunction;
    var iframeId = _options.name;
    if (window.name && window.name.length >= iframeId.length) {
    	var base = window.name.substr(0,iframeId.length);
    	if (base == iframeId) {
        	var index = (window.name.substr(iframeId.length,window.name.length-iframeId.length))*1;
        	iframeId = base.concat(index+1);
    	}
    }
    knwebstd.dialog.currentDialog.iframeId = iframeId;

    if ($("#knwebstd_dialog").size()!=1) {
        // append div-section for dialog
        $("<div id='knwebstd_dialog' class='knwebstd_overlay_centered_fixed'></div>").appendTo("body");
    }

    // append decoration elements to dialog DIV
    $("<div class='knwebstd_overlay_title'>" + _options.title + "</div><input type='image' id='knwebstd_dialog_close_button' src='" + _options.closeImg +
      "' class='knwebstd_overlay_close_button' onclick='knwebstd.dialog.closeDialog(null, false);'/>" +
      "<div id='knwebstd_dialog_body' class='knwebstd_overlay_content' style='height:100%'></div>").appendTo("#knwebstd_dialog");

    // Dimensions and position
    if (_options.width) {
        $("#knwebstd_dialog").width(_options.width);
    }
    if (_options.height) {
        $("#knwebstd_dialog").height(_options.height);
    }
    knwebstd.centerOnPage('#knwebstd_dialog');

    // Make visible
    $("#knwebstd_dialog").jqm({modal:true, overlay: _options.overlay, trigger:false, toTop: true, zIndex: _options.zIndex}).jqmShow();
    //knwebstd.drawIframe("knwebstd_dialog", _options.zIndex); // not required, dialog already is in an iframe

    if (urlParams) {
        dialogUrl += (dialogUrl.match(/\?/) ? "&" : "?") + knwebstd.paramStr(urlParams);
    }
    // finally append an iframe for the actual content. The browser will immediately start loading the content.
    $("#knwebstd_dialog_body").append("<iframe class='knwebstd_overlay_iframe' id='" + knwebstd.dialog.currentDialog.iframeId + "' name='" + knwebstd.dialog.currentDialog.iframeId +
           "' scrolling='auto' frameborder='0' width='100%' height='100%' src='" + dialogUrl +
           "' onload='knwebstd.dialog.processIframeErrorResponse();'>");
}


/** Process error response to be displayed in popup iframe. Extract the actual error message and replace link target by
*   proper link to remove error message.
*/
knwebstd.dialog.processIframeErrorResponse = function() {
    var doc;
    if (knwebstd.isIE()) {
      // for some reason the onload event is called after closing a modal dialog in IE, so this may in fact be null
      doc = window.knwebstd_dialog_iframe?knwebstd_dialog_iframe.document:null;
    } else {
      doc = frames[frames.length-1].document;
    }
    if (doc) {
      $('#knwebstd_error_nav a', doc).each(function() {
          $(this).attr("href", "javascript:knwebstd.closeError();");
          $(this).html("Close");
      });
      if ($('#knwebstd_error_message', doc).size() == 1) {
          var errorMessage = $('#knwebstd_error_message', doc).html();
          $('body', doc).empty().html(errorMessage);
      }
    }
}


/*
* Adds an event handler that will be called when a modal dialog is closed.
* This method should be called from within the dialog that needs to react on its own closing.
*
* Methods thus registered need to return true, otherwise closing of the dialog will be aborted.
*
* Parameters:
* handler A function object to be called before the dialog is closed.
*/
knwebstd.dialog.addBeforeCloseHandler = function(handler) {
  if (typeof(handler) != "function") {
    alert("Not a function! " + handler); // programming error
  }
  if (window.document.beforeCloseHandlers == undefined) {
    window.document.beforeCloseHandlers = new Array();
  }
  window.document.beforeCloseHandlers[window.document.beforeCloseHandlers.length]=handler;
}

/*
* Removes all potentially registered beforClose event handlers that might have been registered with knwebstd.dialog.addBeforeCloseHandler.
*/
knwebstd.dialog.clearBeforeCloseHandlers = function(handler) {
  if (window.document.beforeCloseHandlers) {
    window.document.beforeCloseHandlers = undefined;
  }
}

/*
* Removes all potentially registered beforClose event handlers and closes the dialog.
* This corresponds to Clicking the 'X' in the top-right-corner with omitting the call to the beforeCloseHandlers.
*/
knwebstd.dialog.closeThisDialogImmediately = function(handler) {
  knwebstd.dialog.clearBeforeCloseHandlers();
  knwebstd.dialog.closeMe(null,false);
}

/*
*  Closes an opened sub dialog (to be called from the parent frame).
*  Parameters:
*  callbackOptions - option object to override current dialog callback options with
*  data - Data passed from the caller
*/
knwebstd.dialog.closeDialog = function(callbackOptions, data) {

  // call event handlers that might have been registered with knwebstd.dialog.addBeforeCloseHandler
  if (knwebstd.dialog.currentDialog) {
    var beforeCloseHandlers;
    var iframe = $('#'+knwebstd.dialog.currentDialog.iframeId);
    if (iframe.size()>0) {
      if (iframe[0].contentDocument) { // DOM
        beforeCloseHandlers = iframe[0].contentDocument.beforeCloseHandlers;
      } else if (iframe[0].contentWindow) { // IE
        beforeCloseHandlers = iframe[0].contentWindow.document.beforeCloseHandlers;
      }
    }
    if (beforeCloseHandlers != undefined) {
      var result = true;
      for (i=0;i<beforeCloseHandlers.length;i++) {
        result = result && beforeCloseHandlers[i].call();
      }
      if (!result) {
        return; // abort close
      }
    }
  }

  // eval callback function
    if (callbackOptions && callbackOptions.callbackFunction && (typeof callbackOptions.callbackFunction == 'string')) {
        // This trick is needed when the name of a callbackFunction id passed from a sub dialog (Cannot pass the function
        // itself because it lies in the name space of the parent dialog).
        eval("callbackOptions.callbackFunction=" + callbackOptions.callbackFunction);
    }
  // eval afterCose function
    if (callbackOptions && callbackOptions.afterCloseFunction && (typeof callbackOptions.afterCloseFunction == 'string')) {
        // This trick is needed when the name of a callbackFunction id passed from a sub dialog (Cannot pass the function
        // itself because it lies in the name space of the parent dialog).
        eval("callbackOptions.afterCloseFunction=" + callbackOptions.afterCloseFunction);
    }
    var callback = $.extend(knwebstd.dialog.currentDialog, callbackOptions);

    // Call close handler
    if (!callback.ajax && callback.callbackFunction) {
        callback.callbackFunction(data);
    }

    // remove html content from DOM only after callback execution (callback function may need to access DOM,
    // e.g. options dialog)
    var dialogToRemove = $("#knwebstd_dialog");
    // remove iframes first, to avoid their content being reloaded by move operation of jqModal
    $('iframe',dialogToRemove).remove();
    dialogToRemove.jqmHide();
    dialogToRemove.remove();

    // remove iframe if necessary (should happen after jqmHide() and remove() to avoid problem with IE
    knwebstd.removeIframe(knwebstd.dialog.currentDialog.zIndex);

    var afterCloseData = data;
    if (knwebstd.dialog.currentDialog.afterCloseData) {
        afterCloseData = knwebstd.dialog.currentDialog.afterCloseData;
    }
    // clear values from current dialog
    knwebstd.dialog.currentDialog = null;
    if (callback.afterCloseFunction) {
        callback.afterCloseFunction(afterCloseData);
    }
    knwebstd.dialog.forwardAfterClose(callback, data);
}


// Go forward after close. This is aware of URL/Ajax JS callbacks.
knwebstd.dialog.forwardAfterClose = function(callback, data) {
    // Fix for strange Firefox behaviour with relative URLs
    if (!knwebstd.isIE() && callback.callbackUrl) {
       if (callback.callbackUrl.charAt(0) == '/') {
           callback.callbackUrl = location.protocol + "//" + location.host + callback.callbackUrl;
       }
    }

    if (callback.ajax) {
        if (!callback.callbackUrl || !callback.callbackFunction) {
            // Programming error
            alert("URL and callback function must be provided for Ajax callback handler!");
        }
        knwebstd.dialog.ajaxGet(callback.callbackUrl, callback.callbackFunction, {waitpage: callback.waitpage});
    } else {
        if (callback.callbackUrl) {
            location.href = callback.callbackUrl;
        }
    }
}


/* Closes an opened sub dialog from within itsself (i.e. the iframe). If there is no parent frame this will merely
*  execute a provided callback. That way, a close dialog link is enabled to transparently 'jump' to its target inline
*  if the enclosing dialog is not opened in an iframe.
*  Parameters:
*  callbackOptions - option object to override current dialog options with
*  data - Data passed from the caller - may either be an object or a function that returns an object. In the latter case
*         the function will be called and the data it returns is passed on.
*  Returns true if a parent frame was found and the child dialog could be closed from there.
*/
knwebstd.dialog.closeMe = function(callbackOptions, data) {
  // for Firefox it is necessary to set a (very small) timeout to give the calling iframe a chance to get finished
  setTimeout(function() {
    if (typeof data == 'function') {
      data = data();
    }
    if (window.parent && window.parent.knwebstd.dialog.currentDialog) {
      window.parent.knwebstd.dialog.closeDialog(callbackOptions, data);
      return true;
    } else {
      if (callbackOptions) {
        if (!callbackOptions.ajax && callbackOptions.callbackFunction) {
          callbackOptions.callbackFunction(data);
        }
        knwebstd.dialog.forwardAfterClose(callbackOptions, data);
      }
      return false;
    }
   },0);
}


/*
* Start inline dialog, i.e. content comes from inline HTML
* Parameters:
* contentHTML - the HTML content to be included
* options - an option object
*/
knwebstd.dialog.startInlineDialog = function(contentHTML, options) {
    var _options = {title: 'Dialog', width: false, maxWidth: false, minWidth: false, height: false, maxHeight:false,
                    minHeight:false, zIndex: 1000, overlay: 35, closeImg: 'static/images/knwebstd/cancel16.gif'};
    _options = $.extend(_options, options);

    if (!knwebstd.dialog.currentDialog) {
        knwebstd.dialog.currentDialog = new Object();
    }
    knwebstd.dialog.currentDialog.zIndex = _options.zIndex;

    if ($("#knwebstd_dialog").size()!=1) {
        // append div-section for dialog
        $("<div id='knwebstd_dialog' class='knwebstd_overlay_centered_fixed'></div>").appendTo("body");
    }

    // append decoration elements to dialog DIV
    $("<div class='knwebstd_overlay_title'>" + _options.title + "</div><input type='image' id='knwebstd_dialog_close_button' src='" + _options.closeImg +
        "' class='knwebstd_overlay_close_button' onclick='knwebstd.dialog.closeDialog(null, false);'/>" +
        "<div id='knwebstd_dialog_body' class='knwebstd_overlay_content'></div>").appendTo("#knwebstd_dialog");

    // append the actual content
    $("#knwebstd_dialog_body").append(contentHTML);

    // Dimensions and position
    if (_options.width) {
        $("#knwebstd_dialog").width(_options.width);
    }
    if (_options.height) {
        $("#knwebstd_dialog").height(_options.height);
    }
    if (_options.maxWidth) {
        $("#knwebstd_dialog").css('max-width' , _options.maxWidth);
    }
    if (_options.maxHeight) {
        $("#knwebstd_dialog").css('max-height', _options.maxHeight);
    }
    if (_options.minWidth) {
        $("#knwebstd_dialog").css('min-width' , _options.minWidth);
    }
    if (_options.minHeight) {
        $("#knwebstd_dialog").css('min-height', _options.minHeight);
    }
    knwebstd.centerOnPage('#knwebstd_dialog');

    // Make visible
    $("#knwebstd_dialog").jqm({modal:true, overlay: _options.overlay, trigger:false, toTop: true, zIndex: _options.zIndex}).jqmShow();
    knwebstd.drawIframe("knwebstd_dialog", _options.zIndex);
}


/* Start message dialog (dynamically sized)
*  Parameters:
*  message - is displayed in the text area of the dialog
*  actionMap - an array of {button:'Caption', callbackUrl:'URL', ajax:true|false, waitpage:'center'|id, callbackFunction:'Callback'} objects.
               Caution: callback functions (callbackFunction, afterCloseFunction) need to be put in quotes!
*  options - either the title or an options object, see startInlineDialog()
*/
knwebstd.dialog.messageDialog = function(message, actionMap, options) {

  // create buttons
    var buttonsHTML = "";
    for (var i = 0; i < actionMap.length; i++) {
        // Reuse Ajax aware behaviour of closeDialog() by this kind of "JS reflection"
        var callbackOptions = "{";
        if (actionMap[i].callbackUrl) {
            actionMap[i].callbackUrl = actionMap[i].callbackUrl.replace(/'/g, "\\'");
            callbackOptions = callbackOptions + "callbackUrl: '" + actionMap[i].callbackUrl + "', ";
        }
        if (actionMap[i].ajax) {
            callbackOptions = callbackOptions + "ajax: " + actionMap[i].ajax + ", ";
        }
        if (actionMap[i].callbackFunction) {
            callbackFunctionString = "" + actionMap[i].callbackFunction;
            // replace all " with ', because the build string is opened and closed with "
            callbackFunctionString = callbackFunctionString.replace(/"/g, "\'");
            callbackOptions = callbackOptions + "callbackFunction: " + callbackFunctionString + ", ";
        }
        if (actionMap[i].afterCloseFunction) {
            afterCloseFunctionString = "" + actionMap[i].afterCloseFunction;
            // replace all " with ', because the build string is opened and closed with "
            afterCloseFunctionString = afterCloseFunctionString.replace(/"/g, "\'");
            callbackOptions = callbackOptions + "afterCloseFunction: " + afterCloseFunctionString + ", ";
        }
        if (actionMap[i].waitpage) {
            callbackOptions = callbackOptions + "waitpage: '" + actionMap[i].waitpage + "', ";
        }
        if (callbackOptions != '{') {
            callbackOptions = callbackOptions.substring(0, callbackOptions.length-2);
        }
        callbackOptions = callbackOptions + '}';

        var actionHandler = "knwebstd.dialog.closeDialog(" + callbackOptions + ", '" + actionMap[i].button + "');";
        buttonsHTML = buttonsHTML + "<input type=\"button\" id=\"knwebstd_dialog_button_" + i + "\" value=\"" +
                            actionMap[i].button + "\" onclick=\"" + actionHandler + "\"/>";
        if (i < actionMap.length-1) {
          buttonsHTML = buttonsHTML + "&nbsp;&nbsp;";
        }
    }

    // construct options
    var _options = {maxWidth:500, minWidth:250, useContentArea: false};
    if (options) {
        if (typeof options == 'string') {
          _options = $.extend(_options, {title: options});
        } else {
          _options = $.extend(_options, options)
        }
    }

    // render "naked" or in content area?
    var contentHTML;
    if (_options.useContentArea) {
        contentHTML = '<table cellSpacing="0" cellPadding="0" border="0" width="100%"><tr valign="top"><td class="knwebstd_app_margin_l"><IMG class="knwebstd_app_margin_l" height="1" alt="" src="images/knwebstd/transdot.gif" border="0"></td><td align="center">';
        contentHTML += '<table class="knwebstd_content_table" width="100%"><tr><td class="knwebstd_content_title">';
        contentHTML += _options.contentAreaTitle ? _options.contentAreaTitle : '&nbsp';
        contentHTML += '</td></tr><tr><td class="knwebstd_content_body"';
        if (message) { contentHTML += '<div style="padding:10px;">'+message+'</div>'; }
        contentHTML += '</td></tr><tr><td class="knwebstd_content_nav">';
        contentHTML += buttonsHTML;
        contentHTML += '</td></tr></table>';
        contentHTML += '</td><td class="knwebstd_app_margin_r"><IMG class="knwebstd_app_margin_r" height="1" alt="" src="images/knwebstd/transdot.gif" border="0"></td></tr></table>';
    } else {
        contentHTML = "<p>" + message + "</p><div style='text-align:center; padding:20px;'>";
        contentHTML += buttonsHTML
        contentHTML += "</div>";
    }

    knwebstd.dialog.startInlineDialog(contentHTML, _options);
}

/** Escapes html content by replacing the crucial characters.
 *
 * This implementation is motivated by
 * "http://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content"
*/
knwebstd.dialog.filterHtml = function(content){
	return content.replace(/&/g,'&amp;').
                replace(/</g,'&lt;').
                replace(/>/g,'&gt;').
                replace(/"/g,'&quot;').
                replace(/'/g,'&#x27;').
                replace(/\//g,'&#x2F;');
}

/** Start options dialog
*   Parameters:
*   message - The message text to show above the options
*   items - An array of {key:'Key', value:'Value'} objects
*   options - An option object (see below and startInlineDialog()
*   Caution: callback function afterCloseFunction needs to be put in quotes (see messageDialog())!
*/
knwebstd.dialog.optionsDialog = function(message, items, options) {
    var _options = {ok: 'OK', cancel: 'Cancel', list: false, multiSelect: false, targetField: null, maxSize: null, afterCloseFunction: null, useContentArea: false, useMessageAsContentAreaTitle: true};
    _options = $.extend(_options, options);

    var optionsHTML = "<div style='margin:5px'><form id='knwebstd_select_form'>";
    if (_options.list) {
        optionsHTML += "<select name='knwebstd_options_select'";
        if (_options.maxSize && items.length > _options.maxSize) {
            optionsHTML += " size='" + _options.maxSize;
        }
        if (_options.multiSelect) {
            optionsHTML += "' multiple='multiple'";
        }
        optionsHTML += "'>";
        for (var i = 0; i < items.length; i++) {
            optionsHTML += "<option value='" + items[i].key + "'>" + items[i].value + "</option>";
        }
        optionsHTML += "</select>";
    } else {
        for (var i = 0; i < items.length; i++) {
            optionsHTML += "<input name='knwebstd_options_select' value='" + items[i].key + "' type='";
            if (_options.multiSelect) {
                optionsHTML += "checkbox";
            } else {
                optionsHTML += "radio";
            }
            optionsHTML += "'>" + items[i].value + "</input><br/>";
        }
    }
    optionsHTML += '</form></div>';

    // create content
    var content = "";
    if (_options.useContentArea) {
      if (message) {
        if (!_options.contentAreaTitle && _options.useMessageAsContentAreaTitle) {
          _options.contentAreaTitle = "&nbsp;"+message+"&nbsp;";
        } else {
          content = "<div>"+message+"</div>";
        }
      }
    } else {
      if (message) {
          content = "<div style='margin:-10px 5px 10px'>" + message + "</div>";
      }
    }
    content += optionsHTML;

    var actionMap = [{button: _options.ok, callbackFunction: 'knwebstd.dialog.onCloseOptionsDialog', afterCloseFunction: _options.afterCloseFunction}, {button: _options.cancel}];

    knwebstd.dialog.currentDialog = {targetField: _options.targetField, list: _options.list};
    knwebstd.dialog.messageDialog(content, actionMap, _options);
    // Support double click for single select
    if (_options.list && !_options.multiSelect) {
        $('#knwebstd_dialog_body select[name=knwebstd_options_select]').dblclick(function() {
            $('#knwebstd_dialog_body input[value=' + _options.ok + ']').click();
        });
    }
}


// Private callback function to handle options dialog's OK button.
knwebstd.dialog.onCloseOptionsDialog = function() {
    var result = "";
    // Get comma-separated result string
    if (knwebstd.dialog.currentDialog.list) {
       var select = $('#knwebstd_dialog_body select[name=knwebstd_options_select]').get(0);
       for (var i = 0; i < select.options.length; i++) {
           if (select.options[i].selected) {
               if (result != "") {
                   result = result + ",";
               }
               result = result + select.options[i].value;
           }
       }
    } else {
        $('#knwebstd_dialog_body input[name=knwebstd_options_select]').each(function appendChecked() {
            if (this.checked) {
                if (result != "") {
                    result = result + ",";
                }
                result = result + this.value;
             }
        });
    }
    // Pass the result to both target field and callback
    if (knwebstd.dialog.currentDialog.targetField) {
        knwebstd.JQueryFromIdString(knwebstd.dialog.currentDialog.targetField).attr('value', result);
    }
    knwebstd.dialog.currentDialog.afterCloseData = result;
}


/** Show waitpage. Depending on target parameter this is either a blocking overlay or merely an image shown as content of a DIV.
*   Parameters:
*   target - either 'center' for blocking overlay or the id of an arbitrary DIV.
*/
knwebstd.dialog.showWaitpage = function(target) {
    var data = "<div style='display:block;width:150px;text-align:center;padding-top:20px;padding-bottom:20px;'>" +
               "<img src='static/images/knwebstd/waiting.gif' alt='waiting...'/></div>";
    if (target == 'center') {
        $("<div id='knwebstd_waitpage' class='knwebstd_overlay_centered_fixed'></div>").appendTo("body");
        $("#knwebstd_waitpage").html(data);
        knwebstd.centerOnPage("#knwebstd_waitpage");
        var zIndex = 1000;
        $("#knwebstd_waitpage").jqm({modal:true, overlay:35, trigger:false, toTop: true, zIndex: zIndex}).jqmShow();
        knwebstd.drawIframe("knwebstd_waitpage", 1000);
    } else {
        knwebstd.JQueryFromIdString(target).html(data);
    }
}


/** Reset waitpage. Depending on target parameter this is either a blocking overlay or merely an image shown as content of a DIV.
*   Parameters:
*   target - either 'center' for blocking overlay or the id of an arbitrary DIV.
*/
knwebstd.dialog.resetWaitpage = function(target) {
    if (target == 'center') {
        $("#knwebstd_waitpage").jqmHide();
        $("#knwebstd_waitpage").remove();
        knwebstd.removeIframe(1000);
    } else {
        knwebstd.JQueryFromIdString(target).empty();
    }
}


/* Submit form programmatically. Aware of submit monitor.
*  Parameters:
*  formSelector - Identifies the form.
*  options - either the event or an options object { event: 'Event', ajaxCallback: 'Callback', waitpage:'center'|id, errorPopup:true|false,
*  												 	 beforeSubmit: callback function to be called before carrying out the submit }
*/
knwebstd.dialog.formSubmit = function(formSelector, options) {
    var form = $(formSelector);
    if (form.size() != 1) {
        alert("No unique form exists for " + formSelector);
        return false;
    }

    if (typeof options == 'string') {
        options = {event: options};
    }

    var originalEvent = null;
    if (options.event) {
        // Make sure there is a hidden field event - this is required as name=value of the pressed submit button is
        // not transmitted when submit is triggered programmatically.
        if ($(formSelector + ' :input[name=event]').size() == 0) {
           form.append('<input type="hidden" name="event"/>');
        } else {
           originalEvent = $(formSelector + ' :input[name=event]').attr('value');
        }
        $(formSelector + ' :input[name=event]').attr('value', options.event);
    }

    if(options.dynamicParams){
      // set values of dynamic parameter elements
      var params = options.dynamicParams.valueOf();
      for (var param in params) {
        set(form[0].id, param, params[param]);
      }
    }

    if (submitMonitor()) {
        if (options.ajaxCallback) { // make an ajax submit
            // Pass Ajax standard URL parameter
            var addedAjaxField = false;
            if ($(formSelector + ' :input[name=ajax]').size() == 0) {
               form.append('<input type="hidden" name="ajax" value="true"/>');
               addedAjaxField = true;
            }
            if (options.waitpage) {
                knwebstd.dialog.showWaitpage(options.waitpage);
            }

            var measurement = new Object();
            var ajaxSubmitOptions = {
            	beforeSubmit: function(data, jq_obj, opt){
	              // start response time measurement
	              if(knwebstd.responseTimeMeasurement){
	              	measurement.submitted = knwebstd.responseTimeMeasurement.getTimestamp();
	              }
	              if (options.beforeSubmit) {
	                options.beforeSubmit(data, jq_obj, opt);
	              }
	            },
            	success: function(responseText) {
                // finish response time measurement
                if(knwebstd.responseTimeMeasurement){
                	measurement.duration1 = knwebstd.responseTimeMeasurement.getTimestamp() - measurement.submitted;
                }
                options.ajaxCallback(responseText, options);
                if(knwebstd.responseTimeMeasurement){
                	measurement.duration2 = knwebstd.responseTimeMeasurement.getTimestamp() - measurement.submitted;
                }
                resetSubmitMonitor();
            	},
            	error: function(responseText) {
                // finish response time measurement
                if(knwebstd.responseTimeMeasurement){
                	measurement.duration1 = knwebstd.responseTimeMeasurement.getTimestamp() - measurement.submitted;
                }
            		knwebstd.dialog.processAjaxErrorResponse(responseText, options);
                if(knwebstd.responseTimeMeasurement){
                	measurement.duration2 = knwebstd.responseTimeMeasurement.getTimestamp() - measurement.submitted;
                }
            		resetSubmitMonitor();
            	},
            	complete: function(request, textStatus){
	              // save measured response time
	              if(knwebstd.responseTimeMeasurement){
	              	var correlationId = request.getResponseHeader('kn.webstd.tools.corrId');
                  if(!correlationId){
                    correlationId = 0;
                  }
	                measurement.correlationId = correlationId;
	                knwebstd.responseTimeMeasurement.addAjaxMeasurement(measurement);
	              }
              }
            };

            form.ajaxSubmit(ajaxSubmitOptions);
            if (addedAjaxField) {
                $(formSelector + ' :input[name=ajax]').remove();
            }
        } else { // make a non-ajax submit
            if (options.beforeSubmit) {
            	if (options.beforeSubmit() == true) {
                    form.submit();
            	}
            } else {
                form.submit();
            }
        }
    }

    if(originalEvent === null){
    	$(formSelector + ' :input[name=event]').remove();
    } else {
    	if(!originalEvent){
    		originalEvent = '';
    	}
    	$(formSelector + ' :input[name=event]').attr('value', originalEvent);
    }
    return false;
}


/* Confirm and submit form programmatically. Aware of submit monitor.
*  Parameters:
*  formSelector - Identifies the form.
*  options - see formSubmit() Caution: ajaxCallback needs to be put in quotes.
*  title - The confirmation dialog title
*  message - the confirmation message
*  ok - The positive confirmation button text
*  cancel - The negative confirmation button text
*/
knwebstd.dialog.confirmedSubmit = function(formSelector, options, title, message, ok, cancel) {
    if (!maySubmit()) {
        return false;
    }
    var submitOptions = '{'
    if (options.event) {
        submitOptions = submitOptions + "event: '" + options.event + "', ";
    }
    if (options.ajaxCallback) {
        submitOptions = submitOptions + "ajaxCallback: " + options.ajaxCallback + ", ";
    }
    if (submitOptions != '{') {
        submitOptions = submitOptions.substring(0, submitOptions.length-2);
    }
    submitOptions = submitOptions + '}';

    knwebstd.dialog.messageDialog(message, [{button: ok, callbackFunction:
        'function() {knwebstd.dialog.formSubmit(\'' + formSelector + '\', ' + submitOptions + ');}'},
        {button: cancel}], title);
    return false;
}


/* Wrap an URL into an Ajax handler. This will retrieve the URL asynchronously and passes the result into
*  a callback function
*  Parameters:
*  url - The URL to wrap.
*  callbackFunction - The callback function to execute for the response.
*  options - an options object {waitpage:'center'|id, errorPopup:true|false}
*  errorCallback - The error callback function to execute when the request returned an error.
*/
knwebstd.dialog.ajaxGet = function(url, callbackFunction, options, errorCallback) {
    if (options && options.waitpage) {
        knwebstd.dialog.showWaitpage(options.waitpage);
    }
    if (!callbackFunction) {
        callbackFunction = knwebstd.dialog.processAjaxResponse;
    }
    if (!errorCallback) {
        errorCallback = knwebstd.dialog.processAjaxErrorResponse;
    }

    var measurement = new Object();
    // start response time measurement
    if(knwebstd.responseTimeMeasurement){
      measurement.submitted = knwebstd.responseTimeMeasurement.getTimestamp();
    }
    $.ajax({type: 'GET', url: url, data: {ajax: 'true'},
            success: function(responseText) {
              // finish response time measurement
              if(knwebstd.responseTimeMeasurement){
                measurement.duration1 = knwebstd.responseTimeMeasurement.getTimestamp() - measurement.submitted;
              }
              callbackFunction(responseText, options);
              if(knwebstd.responseTimeMeasurement){
                measurement.duration2 = knwebstd.responseTimeMeasurement.getTimestamp() - measurement.submitted;
              }
           	},
            error: function(request, textStatus, errorThrown) {
              // finish response time measurement
              if(knwebstd.responseTimeMeasurement){
                measurement.duration1 = knwebstd.responseTimeMeasurement.getTimestamp() - measurement.submitted;
              }
              errorCallback(request, textStatus, errorThrown, options);
              if(knwebstd.responseTimeMeasurement){
                measurement.duration2 = knwebstd.responseTimeMeasurement.getTimestamp() - measurement.submitted;
              }           	},
            complete: function(request, textStatus){
              // save measured response time
              if(knwebstd.responseTimeMeasurement){
                var correlationId = request.getResponseHeader('kn.webstd.tools.corrId');
                if(!correlationId){
                  correlationId = 0;
                }
                measurement.correlationId = correlationId;
                knwebstd.responseTimeMeasurement.addAjaxMeasurement(measurement);
              }
            }
           });
}


/** Standard Ajax callback function that replaces all elements tagged with the 'ajaxzone' class
*   in the current page by their counterparts from the response received upon the Ajax request.
*   Parameters:
*   responseText - The Ajax response text
*   context - Context information passed from requestor. If a property 'zones' in form of an
*     array of string is present, only those ajax zones from the response will be processed,
*     whose id is contained in this list.
*/
knwebstd.dialog.processAjaxResponse = function(responseText, context) {

    // Need to do some extra work as JQuery traversal does not detect top level DIVs and does not work with body tag at all.
  var responseBodyContent = knwebstd.extractBodyContent(responseText);
  var responseDom = knwebstd.JQueryFromHtmlString(responseBodyContent);

  // handling of IVersionable forms
  var updatedVersion = $('input[name=updatedVersion]', responseDom)
  if(updatedVersion.size() > 0){
    $('input[name=version]').val(updatedVersion.val());
  }

  var relevantZones;
  if(context){
    relevantZones = context.zones;
  }
    var replaceDom = knwebstd.dialog.testError(responseDom, context);
    if (replaceDom) {
      $('.ajaxzone', replaceDom).each(function findAndReplace() {
        var process = !relevantZones;
        if(!process){
           for(zone in relevantZones){
            if(zone && this.id == relevantZones[zone]){
              process = true;
              break;
            }
           }
        }
        if(this.id == "errorMessages" || this.id == "infoMessages"){
          process = true;
        }
        if(process){
            var target = knwebstd.JQueryFromIdString(this.id)
            target.html(this.innerHTML);
        }
      });
  }
}


/** Ajax error callback function (for technical errors).
*/
knwebstd.dialog.processAjaxErrorResponse = function(request, textStatus, errorThrown, context) {
    var target;
    if (context && context.waitpage && context.waitpage != 'center') {
        target = context.waitpage;
    }
    var errorMessage = knwebstd.createError(textStatus || errorThrown, true, target);

    // Need to do some extra work as JQuery traversal does not detect top level DIVs and does not work with body tag at all.
  var errorBodyContent = knwebstd.extractBodyContent(errorMessage);
  var errorDom = knwebstd.JQueryFromHtmlString(errorBodyContent);

    knwebstd.dialog.testError(errorDom, context);
}


/** Test response DOM for whether it contains an error. If it does, the error is displayed and the function returns false.
*   Otherwise the response DOM readily prepared for JQuery traversal is returned.
*/
knwebstd.dialog.testError = function(responseDom, context) {
    if (context && context.waitpage) {
        knwebstd.dialog.resetWaitpage(context.waitpage);
    }
    if (!responseDom) {
        return false;
    }
    // Consider errors in response (i.e. functional error pages as opposed to technical errors which are treated in a separate
    // error callback)
    var target;
    if (context && context.waitpage && context.waitpage != 'center') {
        target = context.waitpage;
    }

    responseDom = knwebstd.processInlineErrorResponse(responseDom, target);
    var errorMessage = $('#knwebstd_error_message', responseDom);
    if (errorMessage.size() == 1) {
        if (target && ((context && !context.errorPopup) || knwebstd.dialog.currentDialog != null)) {
            knwebstd.JQueryFromIdString(target).html(errorMessage.html());
        } else {
            $('#knwebstd_error_nav', errorMessage).empty();
            // Basic concurrency handling. Suppress error message if there is yet another popup open. Consider another kind
            // of message dialog for this error message.
            if (knwebstd.dialog.currentDialog == null) {
                knwebstd.dialog.messageDialog(errorMessage.html(), [{button: 'OK'}], {title: 'Error'});
            }
        }
        return false;
    } else {
        return responseDom;
    }
}


/** Toggle folding state of a foldable block. Lazily reload content via ajax request if necessary.
*   Precondition: The block must have been rendered with two alternating fold/unfold buttons, and a placeholder DIV for
*   the actual block content.
*   Parameters:
*   foldBtnId - The id of the fold button
*   unfoldBtnId - The id of the unfold button
*   contentId - The id of the block content element.
*   stateField - form input field to store the folding state to.
*   beforeFold - callback function. Returns 'false' to cancel the folding action.
*   afterFold - callback function. Is called after the folding action.
*   beforeUnfold - callback function. Returns 'false' to cancel the folding action.
*   afterUnfold - callback function. Is called after the unfolding action.
*   url - The Ajax URL to call for lazy reload (optional).
*   callback - The Ajax callback function to process the response (optional).
*/
knwebstd.dialog.toggleBlock = function(foldBtnId, unfoldBtnId, contentId, stateField, beforeFold, afterFold, beforeUnfold, afterUnfold, url, callback) {
    var foldBtn = knwebstd.JQueryFromIdString(foldBtnId);
    var isUnfold = foldBtn.css('display') == 'none';
  var allowed = false;
  if(isUnfold){
    allowed = (beforeUnfold) ? Function(beforeUnfold)() : true;
  } else {
    allowed = (beforeFold) ? Function(beforeFold)() : true;
  }
  if(allowed != false){
    foldBtn.css('display', isUnfold ? '' : 'none');
    var unfoldBtn = knwebstd.JQueryFromIdString(unfoldBtnId);
    unfoldBtn.css('display', unfoldBtn.css('display') == 'none' ? '' : 'none');
    var block = knwebstd.JQueryFromIdString(contentId);
    block.css('display', isUnfold ? '' : 'none');
    if(stateField){
      stateField.val(!isUnfold);
    }

    if (isUnfold){
      if(url && block.html() == '') {
        var afterCallback;
        var callbackHandler = callback;
        if(afterUnfold){
          callbackHandler = function(response, context){
      	    if(!callback){
              callback = knwebstd.dialog.processAjaxResponse;
      	    }
      	    callback(response, context);
            eval(afterUnfold);
          }
        }
        knwebstd.dialog.reloadBlock(foldBtnId, unfoldBtnId, contentId, url, callbackHandler);
      } else {
        if(afterUnfold){
          eval(afterUnfold);
        }
      }
    } else {
      if(afterFold){
        eval(afterFold);
      }
    }
  }
    // TODO: For persistent block state another Ajax request should be triggered here that sends the blockId (the blockId can
    // be different from the contentId and needs therefore to be passed as an additional parameter!) and the folding state.
    // The response needs not to be processed.
}


/** Force reload of foldable block. Will disable all fold buttons until request returns. Does execute
*   independent of folding state.
*   Parameters: see toggleBlock()
*/
knwebstd.dialog.reloadBlock = function(foldBtnId, unfoldBtnId, contentId, url, callback) {
    var btn = knwebstd.JQueryFromIdString(foldBtnId);
    if (btn.css('display') == 'none') {
        btn = knwebstd.JQueryFromIdString(unfoldBtnId);
    }
    btn.css('display', 'none');
    if (!callback) {
        callback = knwebstd.dialog.processAjaxResponse;
    }
    knwebstd.dialog.ajaxGet(url, function(responseText, context) {
            callback(responseText, context);
            btn.css('display', '');
        }, {waitpage: contentId, zones:[contentId]}, function(request, textStatus, errorThrown, context) {
            knwebstd.dialog.processAjaxErrorResponse(request, textStatus, errorThrown, context);
            btn.css('display', '');
        });
}