
if (typeof KuferTools == 'undefined') KuferTools = {};
if (typeof KuferTools.Utils == 'undefined') KuferTools.Utils = {};

/**
 * Fuegt einem jquery Objekt (ziel) ein select mit den angegebenen Optionen hinzu
 * BeispielAufruf:
 *   var options=[{"val": "wert1", "text": "text1","farbe": '#00FFFF'}, {"val": "wert2", "text": "text2", "farbe": "yellow"}];
 *   KuferTools.Utils.addSelect($('#testTabelle'), 'name', 'selectId', 'selectClass', 'wert2', options);
 */
KuferTools.Utils.addSelect = function (ziel, name, id, classname, selectedValue, options, disabled) {
  //Die neue jQuery-Version (1.9) wirft bei $('#') fehler
  if (id != '')
    ziel.find('#' + id).remove();
  var $select = $('<select name="' + name + '" id="' + id + '" class="' + classname + '"' + (disabled ? ' disabled' : '') + '></select>');
  ziel.append($select);
  jQuery.each(options, function (val, item) {
    //*DS: umgebaut auf den String, weil ansonsten das select mit showMessage nicht(!!!) funktioniert hat
    var option = '<option value="' + item.val + '"';

    if (item.val == selectedValue) {
      option += ' selected';
      if (item.farbe != '')
        $select.css('background-color', item.farbe);
    }
    if (item.farbe != '') {
      option += ' style="background-color: ' + item.farbe + '"';
    }

    option += '>' + item.text + '</option>';

    $select.append($(option));
  });
}

/**
 * Liefert false, wenn nicht alle Selects mit der Klasse pKlasse den gleichen Wert
 * gewählt haben, ansonsten den bei allen gewählten Wert.
 */
KuferTools.Utils.getValueAllerSelectsMitKlasse = function (pKlasse) {
  var selektierteOptionen = jQuery('select.' + pKlasse + ' option:selected');

  // sind alle selektierten Optionen gleich ?
  for (var i = 1; i < selektierteOptionen.length; ++i) {
    if ($(selektierteOptionen[i]).val() != $(selektierteOptionen[i - 1]).val()) {
      return false; //nein => false zurückgeben
    }
  }

  return jQuery(selektierteOptionen[0]).val();
}

/**
 * Speichert das Formular des Inputbuttons per AJAX Request!
 * Ruft mit dem Rückgabewert von Ajax die angegebene callBackFunction auf
 * 
 * event: submit des Formulars
 * callBackFunction(data): wird mit der Rückgabe von jQuery.post aufgerufen.
 */
KuferTools.Utils.ajaxPost = function (event, callBackFunction) {
  event.preventDefault();
  var form = $(event.currentTarget);
  var link = form.prop('action');
  KuferTools.Utils.doAjaxPost(form, link, callBackFunction);
}

KuferTools.Utils.doAjaxPost = function (form, link, callBackFunction) {
  var dataString = 'ajax=content';

  // Versteckte Eingabefelder
  form.find('input[type=hidden]').each(function () {
    dataString = dataString + '&' + jQuery(this).prop('name') + '=' + (jQuery(this).val());
  });
  // Eingabefelder
  form.find('input:text').each(function () {
    dataString = dataString + '&' + jQuery(this).prop('name') + '=' + (jQuery(this).val());
  });
  // Passwortfelder
  form.find('input[type=password]').each(function () {
    dataString = dataString + '&' + jQuery(this).prop('name') + '=' + (jQuery(this).val());
  });
  // Absendebutton
  form.find('input[type=submit]').each(function () {
    dataString = dataString + '&' + jQuery(this).prop('name') + '=' + (jQuery(this).val());
  });
  // Angewählte Checkboxen
  form.find('input[type=radio]').each(function () {
    if (this.checked == true) {
      dataString = dataString + '&' + jQuery(this).prop('name') + '=' + (jQuery(this).val());
    }
  });
  // Angewählte Radio-Groups
  form.find('input[type=checkbox]').each(function () {
    if (this.checked == true) {
      dataString = dataString + '&' + jQuery(this).prop('name') + '=' + (jQuery(this).val());
    }
  });
  // Textarea
  form.find('textarea').each(function () {
    if (typeof (jQuery(this).prop('name')) != 'undefined' && jQuery(this).prop('name') != '') {
      jQuery(this).css('display', 'block');
      dataString = dataString + '&' + jQuery(this).prop('name') + '=' + (jQuery(this).val());
    }
  });
  // Select
  form.find('select').each(function () {
    if (typeof (jQuery(this).prop('name')) != 'undefined' && jQuery(this).prop('name') != '') {
      dataString = dataString + '&' + jQuery(this).prop('name') + '=' + (jQuery(this).val());
    }
  });

  // Speichern von + Zeichn in den Sichten
  dataString = dataString.replace(/\+/g, '%2B');

  //Falls der ReloadSchutz aktiv ist, muss die PostID hochgesetzt werden, da ein zweiter Submit
  //vom Reloadschutz abgefangen wird.
  if (typeof (KuferTools.ReloadSchutz) != 'undefined' && typeof (KuferTools.ReloadSchutz.PostID) != 'undefined')
    KuferTools.ReloadSchutz.PostID++;

  KuferTools.Utils.AjaxLadebildschirm.Start();

  jQuery.post(link, dataString, function (data) {

    KuferTools.Utils.AjaxLadebildschirm.Stop();

    callBackFunction(data);
  });
}

/**
 * Schickt ein Post-Request ab. Kein AJAX, es wird nach dem Absenden eine neue Seite angezeigt.
 * 
 * @param string pAction Zieladresse
 * @param object pDataObj Attributnamen werden Key, Werte werden Value für Post
 *                        Beispiel: {"schlüssel1": 1, "schlüssel2": 2}
 */
KuferTools.Utils.Post = function (pAction, pDataObj) {
  var formStr = '<form method="post" action="' + pAction + '">';

  jQuery.each(pDataObj, function (key, value) {
    formStr += '<input type="hidden" name="' + key + '" value="">';
  });
  formStr += '</form>';

  var $form = jQuery(formStr);

  jQuery('body').append($form);

  //*TH: Hier per jQuery bei den Hidden-Inputs die Werte setzen, anstatt es oben direkt im HTML zusammenzubauen,
  //damit die Characters escaped werden. Falls wer eine bessere Lösung weiß, gerne umsetzen.
  jQuery.each(pDataObj, function(key, value) {
    jQuery('input[name="' + key + '"]').val(value);
  });

  $form.trigger('submit');
};

/**
 * Gibt einen Fehlermeldungsdialog aus, falls vom Server fehlerhafte Daten empfangen wurden
 * 
 * @param {Object} pLoadedObj
 * @param {string} pLink
 * @param {Object|undefined} [pAbbruchButton=undefined]
 * @returns Null
 */
KuferTools.Utils.showAjaxErrorDialog = function (pLoadedObj, pLink, pAbbruchButton) {
  // Falls kein oder ein fehlerhaftes Objekt vom Server zurückgegeben wird (z.B. wegen einer 
  // Exception in PHP), wird eine Fehlermeldung ausgegeben.
  if (!KuferTools.Utils.isAjaxObjectCorrect(pLoadedObj)) {

    var abbruchButton;
    if (typeof pAbbruchButton !== 'undefined') {
      abbruchButton = pAbbruchButton;
    }
    else {
      abbruchButton = {
        "Abbrechen": function () { $(this).dialog("close"); }
      };
    }

    var meldungsText = 'Fehlerhafte Daten vom Server empfangen. ';
    if (typeof pLoadedObj === 'string' && $(pLoadedObj).hasClass('accessDenied')) {
      // Meldung: Sie haben keinen Zugriff.
      meldungsText = $(pLoadedObj).html();
    }
    meldungsText += 'Bitte setzen Sie sich mit der Schulungseinrichtung in Verbindung. ';
    meldungsText += 'Dies kann passieren, wenn Sie sich ausgeloggt haben oder ausgeloggt wurden. ';
    meldungsText += '';
    meldungsText += '<p style="color: red">Änderungen die gespeichert werden sollten, konnten nicht übernommen werden.</p>';
    console.log('Falsche Daten in der Ajax-Response zum Ajax-Request mit dem Link ' + pLink + '. ');

    if (typeof KuferTools.showMessage !== 'undefined') {
      KuferTools.showMessage({ meldung: meldungsText, title: 'Achtung', buttons: abbruchButton });
    } else { //in den alten Mitarbeitertools gibt es die Funktion KuferTools.showMessage nicht
      showMessage(meldungsText, 'Achtung', abbruchButton); //TODO: entfernen, wenn die alten KuferTools gelöscht werden
    }
    KuferTools.Utils.AjaxLadebildschirm.Stop();
    // Achtung: wird ausgewertet, um aus der aufrufenden Funktion auszusteigen;  daher ist hier true 
    // für die Rückgabe nötig
    return true;
  }
  return;
}


/**
 * Prüft ob das vom Server empfangene Ajax Response Objekt korrekt ist
 * 
 * Falls der Inhalt des Ajax Response Objekts auf Serverseite leer ist, wird am Client ein Objekt der Form
 * {page: ''} erzeugt, das für eine Fehlerausgabe im Dialog benutzt wird.
 * 
 * @param {Object} pLoadedObj
 * @returns {Boolean} true, falls das Objekt ein korrekte Form hat.
 */
KuferTools.Utils.isAjaxObjectCorrect = function (pLoadedObj) {
  if (typeof pLoadedObj !== 'object' || isempty(pLoadedObj) ||
    (typeof pLoadedObj.page !== 'undefined' && isempty(pLoadedObj.page)))
    return false;
  return true;
}

/**
 * Gibt einen Dialog für Zugriff verweigert aus
 * 
 * Wird ähnlich zu KuferTools.Utils.showAjaxErrorDialog verwendet; Allerdings wird hier kein
 * Objekt sondern nur ein String mit dem Sichteninhalt vom Server empfangen und nur der Fall einer
 * Zugriffsverletzung ausgegeben
 * 
 * @param string pData
 * @returns {undefined|Boolean}
 */
KuferTools.Utils.showAccessDeniedDialog = function (pData) {
  if (typeof pData === 'string' && $(pData).find('.accessDenied').length > 0) {
    // Meldung: Sie haben keinen Zugriff.
    var meldungsText = $(pData).html();
    var abbruchButton = {
      "Abbrechen": function () { $(this).dialog("close"); }
    };
    if (typeof KuferTools.showMessage !== 'undefined') {
      KuferTools.showMessage({ meldung: meldungsText, title: 'Hinweis', buttons: abbruchButton });
    } else { //in den alten Mitarbeitertools gibt es die Funktion KuferTools.showMessage nicht
      showMessage(meldungsText, 'Hinweis', abbruchButton); //TODO: entfernen, wenn die alten KuferTools gelöscht werden
    }
    KuferTools.Utils.AjaxLadebildschirm.Stop();
    // Achtung: wird ausgewertet, um aus der aufrufenden Funktion auszusteigen;  daher ist hier true 
    // für die Rückgabe nötig
    return true;
  }
  return;
};

/**
 * Gibt einen Dialog für nicht eingeloggt aus
 * 
 * Wird ähnlich zu KuferTools.Utils.showAjaxErrorDialog verwendet; Allerdings wird hier kein
 * Objekt sondern nur ein String mit dem Sichteninhalt vom Server empfangen.
 * 
 * @param string pData
 * @returns {undefined|Boolean}
 */
KuferTools.Utils.showNotLoggedInDialog = function (pData) {
  if (typeof pData === 'string' && $(pData).find('.notLoggedIn').length > 0) {
    var meldungsText = $(pData).html();
    var abbruchButton = {
      "Abbrechen": function () { $(this).dialog("close"); }
    };
    if (typeof KuferTools.showMessage !== 'undefined') {
      KuferTools.showMessage({ meldung: meldungsText, title: 'Hinweis', buttons: abbruchButton });
    } else { //in den alten Mitarbeitertools gibt es die Funktion KuferTools.showMessage nicht
      showMessage(meldungsText, 'Hinweis', abbruchButton); //TODO: entfernen, wenn die alten KuferTools gelöscht werden
    }
    KuferTools.Utils.AjaxLadebildschirm.Stop();
    // Achtung: wird ausgewertet, um aus der aufrufenden Funktion auszusteigen;  daher ist hier true 
    // für die Rückgabe nötig
    return true;
  }
  return;
};


/**
 * Allgemeine Funktionen zum Senden eine Ajax Requests und Handeln der AjaxResponse vom Server (im Responsive Stand)
 * 
 * Die Ajax Response wird auf Serverseite von der Funktion getAjaxObject($pView), die im entsprechenden Presenter
 * (z.B. VerhaltensListeAuswahlPresenter) aufgerufen wird, aufgebaut. Ein solcher spezieller Presenter zum Bearbeiten eines
 * Ajax Requests gibt die für eine  Ajax Response notwendigen Informationen in einem Std Objekt zurück (also u.a. zusätzlich zur
 * Sicht noch benötigte .js und .css Files)
 * 
 * @param string pActionLink
 * @param button pButtons
 * @param string pWidth
 * @param array(function) callBackFunctionAry  ermöglicht ggf. spezielle Handlings beim Bearbeiten der Ajax response
 * @param function postHookFunction
 * Folgende Parameter werden direkt an showMessage weitergeleitet:  pButtons, pWidth
 */
KuferTools.Utils.loadAndDisplayPage4Ajax = function (pActionLink, pButtons, pWidth, callBackFunctionAry, postHookFunction) {

  KuferTools.Utils.AjaxLadebildschirm.Start();


  //schickt ein Ajax Request an den ActionLink und  bearbeitet die Ajax Response vom Server; empfängt hierbei ein Datenobjekt (data), 
  //das die HTMML Daten der Seite (bzw. Sicht) und zusätzl.Informationen (wie css u. js Dateien u. Deklarationen)  enthält

  jQuery.post(pActionLink, function (data) {
    KuferTools.Utils.AjaxLadebildschirm.Stop();
    //wird ggf. in Fehlerfällen gebraucht
    var abbruchButton = {
      "Abbrechen": function () { $(this).dialog("close"); }
    };

    var loadedObject = KuferTools.Utils.getObjectFromJson(data);

    // Falls kein oder ein fehlerhaftes Objekt zurückgegeben wird, wird eine Fehlermeldung ausgegeben.
    if (KuferTools.Utils.showAjaxErrorDialog(loadedObject, pActionLink, abbruchButton))
      return;

    //CSS Dateien für die gerade geladene Page einbinden
    if (jQuery.isArray(loadedObject.css)) {
      KuferTools.Utils.loadCssFiles(loadedObject.css);
    }
    //JS Dateien für die gerade geladene Page einbinden
    if (jQuery.isArray(loadedObject.js)) {
      //noch nicht geladene JS Dateien einbinden
      KuferTools.Utils.loadJsFiles(loadedObject.js);
    }
    if (jQuery.trim(loadedObject.jsdeclarations) !== '') {
      eval(loadedObject.jsdeclarations);
    }
    registerNamespace("KuferTools.Inhalt");

    //Achtung: die Seite loadedObject.inhalt enthält den Seiteninhalt
    KuferTools.Inhalt = $(loadedObject.inhalt);

    //Callback Funktionen, die vor der Ausgabe ausgeführt werden sollen, z.B. zum verändern von Eingabefeldern
    if (typeof (callBackFunctionAry) != 'undefined') {
      var callbackLen = callBackFunctionAry.length;
      for (var i = 0; i < callbackLen; i++) {
        var callBackFunction = callBackFunctionAry[i];
        callBackFunction(); //ist hier z.B. spezielles Handling für Fach- und  Teilnehmeroptionen
      }
    }

    // Hier keine Validierung von Formularelementen (Input-, Selectfeldern etc.) einbauen; Formular hat hier nämlich nichts mit dem 
    // Sightbuilder zu tun; daher gibt es  z.B. auch keine Pflichtfelder die rot umrandet sein müssen

    //Falls eine Fehlermeldung mitgeschickt wurde: diese ausgeben
    if (loadedObject.error) {

      //Beschreibung des Fehlers befindet sich in loadedObject.error
      showMessage(loadedObject.error, 'Fehler aufgetreten');
    }
    else {
      if (!isempty(loadedObject.inhalt)) {
        //kein Fehler: Ausgabe des Formulars
        KuferTools.showMessage({ meldung: KuferTools.Inhalt, title: loadedObject.title, buttons: pButtons, width: pWidth });
        // Funktionen, die nach der Ausgabe des Dialogs/Formulars ausgeführt werden sollen; z.B. Validierungsfunktion für die
        // Eingabefelder. Dazu müssen die Felder bereits vorhanden sein und das Formular via jQuery Selektor angesprochen 
        //  werden
        if (typeof (postHookFunction) === 'function') {
          postHookFunction();
        }
      }
      else {
        var meldungsText = 'Die Eingabemaske für den Dialog ist leer. Hier müssen Eingabefelder hinzugefügt werden. Bitte setzen Sie sich mit der ' +
          'Schulungseinrichtung in Verbindung.';


        KuferTools.showMessage({ meldung: meldungsText, title: 'Fehler', buttons: abbruchButton });
      }
    }
  }); //end jQuery.post(pActionLink, function(data) {...

}//end KuferTools.Utils.loadAndDisplayPage4Ajax


/**
 * Lädt eine Page aus den KuferTools nach, bindet dann deren CSS und JS Dateien ein und 
 * zeigt die Page dann als Dialog an. Während des Ladevorgangs wird der übliche  Ladeeffekt angezeigt.
 * Ist z.B. für Ausgaben des Sightbuilders geeignet.
 * Folgende Parameter werden direkt an showMessage weitergeleitet:
 * pButtons, pWidth, pPosition 
 */
KuferTools.Utils.loadAndDisplayPage = function (pLink, pDataString, pButtons, pWidth, pPosition, pDoAfterDisplay) {
  if (typeof (KuferTools.ReloadSchutz) != 'undefined' && typeof (KuferTools.ReloadSchutz.PostID) != 'undefined')
    KuferTools.ReloadSchutz.PostID++;


  KuferTools.Utils.AjaxLadebildschirm.Start();

  //entspricht jQuery.ajax; d.h. die Methode verwendet Ajax Mechanismus
  jQuery.post(pLink, pDataString, function (data) {

    KuferTools.Utils.AjaxLadebildschirm.Stop();

    var loadedPage = KuferTools.Utils.getObjectFromJson(data);

    //CSS Dateien für die gerade geladene Page einbinden
    if (jQuery.isArray(loadedPage.css)) {
      KuferTools.Utils.loadCssFiles(loadedPage.css);
    }
    //JS Dateien für die gerade geladene Page einbinden
    if (jQuery.isArray(loadedPage.js)) {
      //noch nicht geladene JS Dateien einbinden
      KuferTools.Utils.loadJsFiles(loadedPage.js);
    }
    if (jQuery.trim(loadedPage.jsdeclarations) != '') {
      eval(loadedPage.jsdeclarations);
    }
    registerNamespace("KuferTools.Page");

    //Achtung: die Seite loadedPage.page muss vom Sightbuilder erzeugt sein, damit die Validierung korrekt funktioniert ( siehe 
    // Serverseitige Methode  getAjaxObject4Tab() in TabPageBase bzw. Methode getAjaxResponse() (z.B. in tooladressdaten.php)
    KuferTools.Page = $(loadedPage.page);

    var $formObj = KuferTools.Page.find('input, select, textarea');

    //Falls eine Fehlermeldung mitgeschickt wurde: diese ausgeben
    if (loadedPage.error) {
      showMessage(loadedPage.error, 'Fehler aufgetreten'); //Beschreibung des Fehlers befindet sich in loadedPage.error
    }
    else {
      //Ausgabe des Formulars, nur falls es mit passenden Eingabefeldern vom Server geschickt wurde
      if (!isempty($formObj) && $formObj.length !== 0) {
        //validiert das nachgeladene SightBuilder Formular: das muß immer passieren;  dadurch werden die Pflichtfelder mit roter 
        // Umrandung angezeigt; der Rückgabewert wird hier NICHT ausgewertet;
        validateForm($formObj);
        showMessage(KuferTools.Page, loadedPage.header, pButtons, pWidth, pPosition);
        if (typeof (pDoAfterDisplay) != 'undefined') {
          pDoAfterDisplay();
        }
      }
      // Fehlermeldungen müssten eigentlich in der PHP-Datei über page.error übergeben werden.
      // Der folgende Block ist dafür verantwortlich, dass mit loadAndDisplayPage derzeit nur Formulare ausgegeben werden können.
      else if ($formObj.length == 0) {
        showMessage('Die Eingabemaske ist leer, hier müssen Eingabefelder hinzugefügt werden. Bitte setzen Sie sich mit einem Administrator in Verbindung.', 'Fehler aufgetreten');
      }
      else
        showMessage('<div>Fehler: leeres oder ungültiges Formular vom Server empfangen. Bitte prüfen Sie die Sicht zu den ' +
          loadedPage.header + ' in den Zentralen Einstellungen des Tools.</div>', 'Warnung');
    }
  });
}; //end KuferTools.Utils.loadAndDisplayPage

/**
 * Liefert die bereits auf der Seite vorhandenen CSS Files
 * 
 * @returns {Array|KuferTools.Utils.getLoadedCssFiles.result}
 */
KuferTools.Utils.getLoadedCssFiles = function () {
  var result = new Array();
  jQuery('link[rel=stylesheet]').each(function (index, elem) {
    var href = jQuery(elem).prop('href');
    if (href !== undefined)
      result.push(href);
  });
  return result;
} //end KuferTools.Utils.getLoadedCssFiles

/**
 * Fügt die CSS Dateien aus pCssFiles in die Seite ein, die noch nicht da waren.
 * 
 * @param {Array} pCssFiles 
 */
KuferTools.Utils.loadCssFiles = function (pCssFiles) {
  //erstmal die bereits geladenen CSS Dateien raussuchen
  var alreadyLoadedCss = KuferTools.Utils.getLoadedCssFiles();
  var versionStr = 'version=' + KuferTools.Version; // also z.B. version=4.20.17.01


  //noch nicht geladene CSS Dateien einbinden
  jQuery.each(pCssFiles, function (index, elem) {

    //Falls der Skriptname nicht im Array der bereits geladenen CSS Skripte enthalten ist, wird er angehängt
    // statt: if (jQuery.inArray(elem, alreadyLoadedCss) === -1) {
    if (!KuferTools.Utils.isScriptinArray(elem, alreadyLoadedCss)) {

      //Ein noch nicht vorhandenes CSS File in den Header der Seite einhängen; dazu: letztes Element mit <link href="..." im 
      //Header finden und dort das neugefundene Element als Link anhängen
      jQuery('head').find('link[href]:last').after('<link rel="stylesheet" type="text/css" href="' + elem + '?' + versionStr + '"/>');
      alreadyLoadedCss.push(elem);
    }
  }); // end jQuery.each(..)
} //end KuferTools.Utils.loadCssFiles

/**
 * Liefert die bereits auf der Seite vorhandenen JS Files
 * 
 * @returns {Array|KuferTools.Utils.getLoadedJsFiles.alreadyLoadedScripts}
 */
KuferTools.Utils.getLoadedJsFiles = function () {
  var alreadyLoadedScripts = new Array();
  jQuery('script').each(function (index, elem) {
    var src = jQuery(elem).prop('src');
    if (src !== undefined)
      alreadyLoadedScripts.push(src);
  });
  return alreadyLoadedScripts;
} //end KuferTools.Utils.getLoadedJsFiles


/**
 * Fügt die JS Dateien aus pJsFiles in die Seite ein, die noch nicht da waren.
 * 
 * @param {Array} pJsFiles
 */
KuferTools.Utils.loadJsFiles = function (pJsFiles) {
  var alreadyLoadedScripts = KuferTools.Utils.getLoadedJsFiles();

  var versionStr = 'version=' + KuferTools.Version; // also z.B. version=4.20.17.01

  jQuery.each(pJsFiles, function (index, elem) {
    // zunächst das Script als Stringdarstellung ermitteln
    var addLoadElem = '';
    if (typeof (elem) === 'object') {
      addLoadElem = elem.link;
    }
    else if (typeof (elem) === 'string') {
      addLoadElem = elem;
    }
    //Falls der Skriptname nicht im Array der bereits geladenen Skripte enthalten ist, wird er angehängt
    // statt: if (jQuery.inArray(addLoadElem, alreadyLoadedScripts) === -1) {
    if (!KuferTools.Utils.isScriptinArray(addLoadElem, alreadyLoadedScripts)) {
      // Bisher:
      // jQuery.getScript(elem);
      // Das lädt ein noch nicht vorhandene Javascript File via GET Request vom Server nach; Achtung??: Wurde hier wohl falsch 
      // verwendet!!!. Müsste via getScrip(url) aufgerufen werden

      // Aber viel besser ist folgendes (ohne Nachladen per Ajax)
      //letztes Element mit <script src="..." im Body finden und dort das neugefundene Element als Script anhängen
      jQuery('body').find('script[src]:last').after('<script charset="UTF-8" src="' + addLoadElem + '?' + versionStr + '">');
      alreadyLoadedScripts.push(addLoadElem);
    }
  }); //end jQuery.each(..)
} // end KuferTools.Utils.loadJsFiles


/**
 * Gibt an, ob ein Script String im Array von Script Strings enthalten ist
 * 
 * @param string pSearchScript
 * @param Array(string) pScriptAry
 * @returns boolean true falls der Suchstring im ScriptArray enthalten ist
 */
KuferTools.Utils.isScriptinArray = function (pSearchScript, pScriptAry) {
  var isInside = false;
  // pSearchScript ist relativ zum Pfad (z.B. https://localhost/develop/kufertools ) angegeben: also z.B. 
  // ../jquery/jqueryXXmin.js oder z.B.  settings/sys/layout/js/project.js
  // die Elemente aus dem Script Array haben hingegen folgende absolute Darstellung (mit Pfad und Version):
  // https://localhost/develop/jquery/jqueryXXmin.js?version=4.20.17.01 oder
  // https://localhost/develop/kufertools/settings/sys/layout/js/project.js?version=4.20.17.01

  // statt: if (jQuery.inArray(pSearchScript, pScriptAry) === -1) isInside = false;

  var searchScript = pSearchScript.replace(/^(\.\.\/)+/, "");
  //prüft, ob z.B. jquery/jqueryXXmin.js im ScriptArray enthalten ist, via search von regulären Ausdrücken 
  $.each(pScriptAry, function (index, elem) {
    if (elem.search(searchScript) != -1 && !isInside) {
      isInside = true;
    }
  });
  return isInside;
} //end KuferTools.Utils.isScriptinArray




/**
 * wandelt erstes Zeichen des Strings Großschreibung um
 * Rest beleibt gleich (d.h. klein oder groß)
 * @param string string
 * @returns string
 */
KuferTools.Utils.ucFirst = function (string) {
  return string.substring(0, 1).toUpperCase() + string.substring(1);
}

/**
 * wandelt erstes Zeichen des Strings Großschreibung um
 * Rest wird in Kleinschreibung umgewandelt
 * @param string string
 * @returns string
 */
KuferTools.Utils.ucFirstLc = function (string) {
  return string.substring(0, 1).toUpperCase() + string.substring(1).toLowerCase();
}

/**
 * Wandelt jedes Zeichen in ein HtmlEntitie um.
 * 
 * Vorsicht: einige jQuery-Funktionen encoden automatisch und prüfen nicht auf doppeltes Encoden.
 * Z. B. .val und .attr führen selbst HTML-Encode durch. After und AppendTo nicht, da dort auch HTML-Tags enhalten sein dürfen.
 * 
 * Weitere Tips: .text statt .html verwenden, wenn kein html erwartet wird.
 * 
 * @returns string
 */
KuferTools.Utils.htmlEncode = function (string) {
  if (typeof(string) !== 'undefined') {
    return string.toString().replace(/./g, function (e) { return "&#" + e.charCodeAt(0) + ";"; });
  }
  return '';
};

/**
 * Testet zwei Variablen auf inhaltliche Gleichheit - Arrays und Objekte werden gleich behandelt, indem die einzelnen
 * Member durchgegangen werden.
 * @param mixed a
 * @param mixed b
 * @return bool
 */
function equals(a, b) {
  if (typeof a != typeof b)
    return false;
  if (a == null && b != null)
    return false;
  if (a != null && b == null)
    return false;
  if (typeof a == 'object') {//für Objekte und Arrays
    if (a.length != b.length)
      return false;
    //for (var i = 0 ; i < a.length ; i++) {
    for (var i in a) {
      if (!equals(a[i], b[i]))
        return false;
    }
    return true;
  }

  return a == b;
}

//pads left
String.prototype.lpad = function (padString, length) {
  var str = this;
  while (str.length < length)
    str = padString + str;
  return str;
}

//pads right
String.prototype.rpad = function (padString, length) {
  var str = this;
  while (str.length < length)
    str = str + padString;
  return str;
}

/**
 * Entfernt whitespaces (also Leerzeichen etc)
 *  (entspricht trimstr in PHP )
 * s. auch trim() (project.js)
 */
KuferTools.Utils.trimS = function (str) {
  if (typeof (str) == 'undefined')
    return '';
  str = str.replace(/^\s+/, '');
  for (var i = str.length - 1; i >= 0; i--) {
    if (/\S/.test(str.charAt(i))) {
      str = str.substring(0, i + 1);
      break;
    }
  }
  return str;
}

/**
 *  Entfernen von umgebendem HTML-Gedoehns
 *  entfernt alle HTML Tags ausser die im exceptiontag angegebenen 
 */
KuferTools.Utils.stripHTML = function (str, exceptiontag) {
  if (exceptiontag != undefined)
    //entfernt alle HTML Tags ausser z.B. <img> ..</img> oder <a href..>..</a>(unabhängig von Klein/Großschreibung 
    // wg. i = ignore case)
    regexpression = new RegExp('<[^' + exceptiontag + ']\/?[^' + exceptiontag + '][^>]+(>|$)', "gi");
  else
    regexpression = /<\/?[^>]+(>|$)/g;
  str = str.replace(regexpression, "");
  str = str.replace("&nbsp;", " ");
  return str;
}

/**
 * Liefert einen bestimmten Eintrag aus einem Array, wenn pValueName angegeben ist, 
 * wird die entsprechende Eigenschaft des Objekts im Array verglichen
 * wird das gesuchte Element nicht gefunden, wird null zurückgegeben 
 * Beispiele: var b = new Array({"id":"3"},{"id":"4"});
 * getFromArray(b, 3, "id") => liefert das erste Objekt aus dem Array
 * var a = [1,2,3]; 
 * getFromArray(a, 2) => liefert das zweite Element aus dem Array 
 */
function getFromArray(pArray, pValue, pValueName) {
  if (pArray == null || (typeof pArray.length !== "number"))
    return null;
  if (pValueName != null && typeof (pValueName) === "string")
    for (var x in pArray) {
      if (pArray[x][pValueName] == pValue)
        return pArray[x];
    }
  else {
    for (var x in pArray) {
      if (x == pValue)
        return x;
    }
  }

  return null;
}

/* Array Funktionen */
/**
 * Entfernt das index te Element aus Array array. => man kann hier auch wunderbar splice verwenden
 */
function removeFromArray(array, index) {
  if (typeof (array) == 'object' && (array instanceof Array)) {
    if (index == 0)
      return array.slice(1);
    if (index < array.length) {
      return array.slice(0, index).concat(array.slice(index + 1));
    }
    else {
      return array;
    }
  }
  else
    return null;
}

/**
 * Prüft ob item in arr enthalten ist
 */
function in_array(item, arr) {
  if (arr == null || !(typeof (arr) == 'object' && (arr instanceof Array)))
    return false;
  for (var p = 0; p < arr.length; p++) if (item == arr[p]) return true;
  return false;
}


var _tmplCache = {}
this.parseTemplate = function (str, data) {
  /// <summary>
  /// Client side template parser that uses &lt;#= #&gt; and &lt;# code #&gt; expressions.
  /// and # # code blocks for template expansion.
  /// NOTE: chokes on single quotes in the document in some situations
  ///       use &amp;rsquo; for literals in text and avoid any single quote
  ///       attribute delimiters.
  /// </summary>    
  /// <param name="str" type="string">The text of the template to expand</param>    
  /// <param name="data" type="var">
  /// Any data that is to be merged. Pass an object and
  /// that object's properties are visible as variables.
  /// </param>    
  /// <returns type="string" />  
  var err = "";
  try {
    var func = _tmplCache[str];
    if (!func) {
      var strFunc =
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
        "with(obj){p.push('" +
        //                        str
        //                  .replace(/[\r\t\n]/g, " ")
        //                  .split("<#").join("\t")
        //                  .replace(/((^|#>)[^\t]*)'/g, "$1\r")
        //                  .replace(/\t=(.*?)#>/g, "',$1,'")
        //                  .split("\t").join("');")
        //                  .split("#>").join("p.push('")
        //                  .split("\r").join("\\'") + "');}return p.join('');";

        str.replace(/[\r\t\n]/g, " ")
          .replace(/'(?=[^#]*#>)/g, "\t")
          .split("'").join("\\'")
          .split("\t").join("'")
          .replace(/<#=(.+?)#>/g, "',$1,'")
          .split("<#").join("');")
          .split("#>").join("p.push('")
        + "');}return p.join('');";

      //alert(strFunc);
      func = new Function("obj", strFunc);
      _tmplCache[str] = func;
    }
    return func(data);
  } catch (e) { err = e.message; }
  return "< # ERROR: " + err + " # >";
  //return "< # ERROR: " + err.htmlEncode() + " # >";
}

/**
 * Erweiterte typeof Funktion => damit kann man Arrays von Objekten unterscheiden
 * liefert: 'array', 'null' oder typeof value
 * siehe auch: jQuery.isArray()
 */
KuferTools.Utils.typeOf = function (value) {
  var s = typeof value;
  if (s === 'object') {
    if (value) {
      if (value instanceof Array) {
        s = 'array';
      }
    } else {
      s = 'null';
    }
  }
  return s;
}

/*
 * Schneidet die dargestellten Textinhalte so ab, dass sie in das umgebende Span passen.
**/
KuferTools.Utils.kuerzeInhaltAufZeile = function (klasse) {

  var $spansToHandle = $('span.' + klasse);//.css('display','none');
  if ($spansToHandle.length == 0)
    return;

  $spansToHandle.each(function (index, element) {
    var schrittweite = 3;
    var $element = $(element);
    //alert("Elem: " + $element.html() + ' Anz. HTML Tags: ' + $element.find('*').length);
    //checkt ob innerhalb des Elements noch irgendein HTML Tag gefunden wird; falls ja ist di length > 0
    // und es wird mit dem nächsten Element weitergemacht (return true entspr. continue)
    if ($element.find(':not(br)').length > 0)
      return true;

    // Betrifft z.B. das Feld kurstag_medien
    if ($element.css('overflow-y').toLowerCase() == 'scroll') {
      return true;
    }

    // Hoehe einer Zelle wird nicht benötigt
    var hoeheEinzeilig = $element.css('line-height').match(/[0-9.]+/);
    var vorgegebeneBreite = $element.width();

    // Höhe einer einzelnen Span Zelle: diese kann je nach Einstellung unterschiedlich sein und ist von
    // verschieden zu der Hoehe eines Zeichens (hoeheeinzeilig)
    var vorgegebeneHoehe = Math.max(hoeheEinzeilig, $element.height());

    //Title bei allen Elementen setzen (für den Mouseover Effekt) 
    if ($element.prop("title") == "" || typeof ($element.prop("title")) == 'undefined')
      $element.prop("title", $element.text());

    //++++++++ Schritt 1: Wörter, welche Breiter als die maximale Breite sind werden gekürzt +++++++++++

    var elemHtml = $element.html();
    //Fügt um den Inhalt des Spans noch ein Span ein. Wenn das innere Span nun
    //größer als das äußere ist, dann ist im Span ein Wort länger als das Span.
    //hier genügt es nicht die width auf auto zu setzen, da sonst die Zeilenumbrüche verrutschen können
    $element.html('<span class="widthTest">' + elemHtml + '</span>');
    if (vorgegebeneBreite < $element.find('span.widthTest').width()) {

      //Holt sich alle Wörter des entsprechenden Spans
      var woerter = elemHtml.match(/(\w)+/g);

      //Geht jedes Wort durch, und überprüft, ob es zu lang ist.
      for (var i = 0; i < woerter.length; i++) {
        $element.html('<span class="widthTest">' + woerter[i] + '</span>');
        //Falls das Wort zu lange ist, kürze es, und prüfe die Breite erneut.
        if (vorgegebeneBreite < $element.find('span.widthTest').width()) {
          var originalWort = woerter[i];
          while (vorgegebeneBreite < $element.find('span.widthTest').width()) {
            woerter[i] = woerter[i].substring(0, woerter[i].length - schrittweite);
            $element.html('<span class="widthTest">' + woerter[i] + '</span>');
          }
          //Ersetze das gekürzte Wort in elemHtml, ersetzt dabei noch die letzten drei Zeichen durch ...
          elemHtml = elemHtml.replace(originalWort, woerter[i].substring(0, woerter[i].length - 3) + '...');
        }
      }
    }
    $element.html(elemHtml);

    // ++++++++ Schritt 2: Text so kürzen, sodass die Höhe des Spans nicht überschritten wird.


    //Damit  wird die Höhe des Elements auf die wirkliche Höhe gesetzt Z.B. von 16 auf 32 falls für die Darstellung
    // mehrere Zeilen benötigt werden
    $element.css('height', 'auto');

    //Nur dann abschneiden, wenn es zu viele Zeilen sind (d.h. Höhe einer Zeile < Höhe des Spans)
    if (vorgegebeneHoehe < $element.height()) {
      var origText = $element.html();
      var step = origText.length;
      var aktTextLength = origText.length;

      // BRs müssen gesondert behandelt werden
      if ($element.find('br').length > 0) {

        // einige Nicht-Druckbare Zeichen wie Zeilenumbrüche entfernen
        origText = origText.replace(/[\x00-\x1F]/gi, '');

        // Zunächst BRs vereinheitlichen, dann splitten
        var zeilenAry = origText.replace(/<br[^>]>/gi, '<br>').split('<br>');
        var zeilenAryCount = zeilenAry.length;
        var tmpText = '';
        for (var i = 0; i < zeilenAryCount; i++) {
          var textVorher = tmpText;
          if (i > 0) {
            tmpText += '<br>';
          }
          tmpText += zeilenAry[i];
          $element.html(tmpText);
          if ($element.height() > vorgegebeneHoehe) {
            origText = tmpText;
            aktTextLength = tmpText.length;
            step = aktTextLength - textVorher.length;
            break;
          }
        }
      }
      KuferTools.Utils.KuerzeAufZeileRekursiv(origText, $element, vorgegebeneHoehe, aktTextLength, step);
      // '...' kann under doofen Umständne breiter als die letzten drei abgeschnittenen Zeichen sein, 
      // deshalb wird ein zusätzliches Zeichen entfernt
      $element.html(origText.substring(0, $element.html().replace(/<br>$/, '').length - 4) + '...');
      $element.css('height', vorgegebeneHoehe);
    }
  });  //end each-function

}; // end kuerzeInhaltAufZeile

/**
 * Kürzt den Inhalt einer Tabelle so, dass alle Zeilen einzeilig werden.
 * @param Selecter $pTable Selekter für die Tabelle
 * @param bool pEinzeilig true: Zeilen werden gekürzt, false: Kürzung wird rückgängig gemacht.
 */
KuferTools.Utils.KuerzeTabelleAufZeile = function ($pTable, pEinzeilig) {
  var $tdsInColumnAry = [];
  var $span = $pTable.find('tbody tr td span');
  var hoeheEinzeiligCache = {};
  $span.each(function () {

    // Falls Das Span noch ein A-Tag enthält, muss das gewählt werden, da sonst das schließende </a> 
    // weggekürzt werden kann.
    var $element = $(this);
    var $aTag = $element.find('a');
    if ($aTag.length > 0) {
      $element = $aTag;
    }
    if (pEinzeilig) {

      // Die einzilige Höhe kann im Chrome oder IE leider nicht ohne Test-Item ermittelt werden, da die 'normal' zurückgeben,
      // statt der höhe. Aber ich gehe frecherweise davon aus, dass die Line-Height in einer Spalte immer gleich ist,
      // sodass ich nur einmal pro Spalte das Testitem setze
      // Muss nicht auch bei kuerzeInhaltAufZeile angepasst werden, da geben die Browser nomal die line-height zurück
      var hoeheEinzeilig = $element.css('line-height').match(/[0-9.]+/);
      if (!jQuery.isNumeric(hoeheEinzeilig)) {
        var classes = $element.parents('td').prop('class');
        if (hoeheEinzeiligCache.hasOwnProperty(classes)) {
          hoeheEinzeilig = hoeheEinzeiligCache[classes];
        } else {
          var $testItem = $('<span class="testItem">M</span>');
          $element.append($testItem);
          hoeheEinzeilig = $testItem.height();
          hoeheEinzeiligCache[classes] = hoeheEinzeilig;
          $testItem.remove();
        }
      }

      //Nur dann abschneiden, wenn auch wirklich mehrere Zeilen vorhanden sind (d.h. Höhe einer Zeile < Höhe des Spans)
      if (hoeheEinzeilig < $element.height()) {

        //var anzahlZeichenInDiesemSpan = anzahlZeichen - schrittweite;
        var origText;
        if (!$element.data('origText')) {//sonst wird der originale Text gelöscht, wenn zweimal hintereinander auf "einzeilig" geklickt wird
          origText = $element.text();
          $element.data('origText', origText);
        }
        else
          origText = $element.data('origText');
        KuferTools.Utils.KuerzeAufZeileRekursiv(origText, $element, hoeheEinzeilig, origText.length, origText.length);
      }
    }
    // Handling für mehrzeilig
    else {
      var origText = $element.data('origText');
      if (origText != '')
        $element.text(origText);
    }
  });
};

/**
 * Rekursive Funktion, welche den Text im Target auf die optimale Länge stutzt.
 * Am Ende enthält pTarget den gekürzten Text
 * 
 * @param pText: Originaltext
 * @param $pTarget: Selector auf das aktuelle Zielelement, welcher den Text enthält
 * @param pMaxHeight: Maximale Höhe des Elements
 * @param pAktTextLength: Aktuell untersuchte Textlänge. Variiert bei jedem Durchlauf. Empfohlener Startwert: pText.length
 * @param pStep: Schrittweite, um welche pAktTextLength bei jedem Durchlauf vergrößert oder verkleinert wird.
 *               Wird bei jedem Durchlauf halbiert (ggf. abgerundet). Empfohlener Startwert: pText.length
 */
KuferTools.Utils.KuerzeAufZeileRekursiv = function (pText, $pTarget, pMaxHeight, pAktTextLength, pStep) {
  // Abbruchbedingung: Schrittweite ist bei 0 angelangt
  if (pStep <= 0) {
    // Es kann sein, dass man beim Abbrechen um einen Buchstaben zu weit ist.
    if ($pTarget.height() > pMaxHeight) {
      $pTarget.html(pText.substring(0, pAktTextLength - 1));
    }
    return;
  }
  pStep = Math.floor(pStep / 2);
  $pTarget.html(pText.substring(0, pAktTextLength));
  // Höhe testen. Wenn zu Groß: um Step verkleinern, sonst um Step vergrößern.
  // Es kann an dieser Stelle nicht getestet werden, ob die Optimale länge ereicht wurde,
  // Daher die Zusatzbedingung in der Abbruchbedingung
  if ($pTarget.height() > pMaxHeight) {
    KuferTools.Utils.KuerzeAufZeileRekursiv(pText, $pTarget, pMaxHeight, pAktTextLength - pStep, pStep);
  }
  else if ($pTarget.height() <= pMaxHeight) {
    KuferTools.Utils.KuerzeAufZeileRekursiv(pText, $pTarget, pMaxHeight, pAktTextLength + pStep, pStep);
  }
};

var spanEigentschaftenCache = [];
/*
 * liefert die Breite und Höhe eines Zeichens im Span und die Gesamtbreite des Spams
**/
KuferTools.Utils.getSpanEigenschaften = function ($pElement) {
  //wir brauchen die Breite pro Zeichen => ein M; dazu:  
  //Wegen einiger Firefoxversionen musste die Berechnung umgestellt werden
  //(Probleme beim berechnen der Höhe verschachtelter Spans):
  //Das Span wird geklont, die höhe und breite auf auto gesetzt,
  //und das Testzeichen direkt im Span eingefügt.

  //Cached die Span-Eigenschaften, da diese bei Übersichtsseiten bei allen 
  //Spans mit gleichen Klassen Identisch sind.
  var classes = $pElement.prop('class');
  if (typeof (spanEigentschaftenCache[classes]) != 'undefined')
    return spanEigentschaftenCache[classes];

  var $cloneelem = $pElement.clone(false);
  $cloneelem.html("M");
  $cloneelem.css('visibility', 'hidden').css('height', 'auto').css('width', 'auto');
  //ans Elternelement hängen
  $pElement.parent().append($cloneelem);
  var breiteProZeichen = $cloneelem.width();
  var hoeheEinzeilig = $cloneelem.height();
  $cloneelem.remove();


  //padding-left berücksichtigen, kommt als xpx => px abschneiden
  var breite = $pElement.width(); //.width() ist bereits ohne Padding!

  var spanProperties = [];
  spanProperties[0] = breite;
  spanProperties[1] = breiteProZeichen;
  spanProperties[2] = hoeheEinzeilig;

  spanEigentschaftenCache[classes] = spanProperties;

  return spanProperties;
} //end getSpanEigenschaften


/**
 * Gibt die Eingabeelemente eines Bereichs zurück.
 * 
 * Berücksichtigt:
 * inputs ohne type= image, submit, reset, button, hidden
 * textarea
 * select
 * 
 * Disabled und versteckte Felder werden ebenfalls nicht zurückgegeben.
 * 
 * @param $pTarget jQery-Selector
 * @return jQery-Selector
 */
KuferTools.Utils.getInputableElements = function ($pTarget) {
  var $elements = $();

  $elements = $elements.add($pTarget.find('input').filter('[type!="image"]').filter('[type!="button"]')
    .filter('[type!="submit"]').filter('[type!="reset"]')
    .filter('[type!="hidden"]'));

  $elements = $elements.add($pTarget.find(':is(textarea, select)'));

  $elements = $elements.add($pTarget.find('.mnlist'));

  $elements = $elements.filter(':not( :disabled)');
  //eine disabled Klasse check_disabled wieder hinzufügen: wird beim Validieren gebraucht um ein 
  //form_error Element zu erzeugen
  $elements = $elements.add($pTarget.find('input.check_disabled:disabled'));


  return $elements;
};


/**
 * Überschreibt die Werte aus obj1 mit denen aus obj2 u. fügt die Werte aus obj2 hinzu (falls sie in obj1 nicht existieren)
 * @param obj1
 * @param obj2
 * @returns obj3 neues object, das auf obj1 und obj2 basiert
 */
KuferTools.Utils.mergeObjects = function (obj1, obj2) {
  var obj3 = {};
  for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
  for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
  return obj3;
}


/** Ab hier meine n-m List Implementierung */
NMList = function () {
  //public variables
  this.data = [];//muss eine Kopie der tatsächlichen Daten sein! //Zeilen!
  this.rowTemplate = '';//template für die einzelnen Zeilen
  this.colGroup = ''; //Colgroup für die Tabelle um Spalten mit unterschiedlichen Breiten ausgeben zu können
  this.rowHeader = '';//Zeile mit der Headerzeile (<tr><th>spalte1</th>...</tr>)
  this.prefix = '';//Präfix für die Tabelle, ... nötig, wenn mehr als eine NMList auf einer Seite sind
  this.uiBezeichnung = '';//Text, der vor einfügen, löschen usw. eingefügt werden soll
  this.$target = null;//div, in das die Tabelle und die Buttons eingehängt werden sollen
  this.readOnly = false;//wenn true, werden keine Buttons angezeigt
  // wenn true, wird der Bearbeiten Button nicht angezeigt und ein Double-Click Event auf die Elemente aus der Liste 
  // ist auch nicht möglich (da sie nicht bearbeitbar sein sollen)
  this.noUpdate = false;
  //wenn true, wird der Details Subscreen aus einer Hinterlegung befüllt; dann ist auch kein Bearbeiten mgl.
  // d.h. this.noUpdate ist auch true
  this.subscreenfromHL = false;
  //ist true, wenn die Tabellen sich in der Breite flexibel an das Elementfeld anpassen soll, wenn das Element z.B.
  //in den Zentr. Einstellungen (z.B. Kennzeichen Element) festgelegt wird; Tabelle muss dann in ein übergeordnetes
  //div eingebettet werden
  this.flexibleWidth = false;
  // Daten aus der Hinterlegung für den Details Subscreen (wird z.B. für die Kennzeichenliste im Tool Teilnehmerinfo  oder 
  // die Anwesenheitsliste im Dialog zum  Terminkalender im Dozententool verwendet)
  this.hldata = [];
  // Daten die bereits aus der Hinterlegung im Details Subscreen selektiert wurden; mit Hilfe dieses Arrays wird ein mehrmaliges 
  // Auswählen desselben Eintrags in die Haupttabelle verhindert (wird bisher nur für Anwesenheiten im Dialog zum  Terminkalender verwendet
  this.selectedhldata = [];
  // wenn true, wird zusätzlich ein Button ausgegeben, um alle Einträge auf einmal aus der NMList zu löschen, und die Funktionalität hierfür 
  // bereitgestellt
  this.deleteAll = false;
  //Image zum Button zum Löschen aller Einträge aus der NMList; kann von PHP aus besetzt werden
  this.deleteAllImg = '';

  //öffentliche/privilegierte Methoden, die überschrieben werden müssen
  this.getNewObject = function () { return null; } //Funktion, die ein Objekt liefert, das neu eingefügt wird
  this.showSubscreen = function (pData, pNewItem) { }; //Detailbildschirm für Bearbeiten/Hinzufügen eines Datensatzes: 
  //pNewItem: boolean ob Element neu angelegt wurde oder bearbeiten
  this.restoreSubscreenData = function (pData, $dialog) { }; //Daten aus dem Detailbildschirm für einen Datensatz zurückschreiben
  this.canDelete = function (pData) { return true; };//Funktion ob ein Löschen für diesen Datensatz möglich ist (manchmal sollen nur neue Elemente geändert werden können.
  this.cannotDelete = function () { };
  this.beforeDelete = function (pData) { };//wird vor dem Löschen aufgerufen
  this.postDelete = function () { };//wird nach dem Löschen und vor dem darauffolgenden Rendern aufgerufen 

  //private variables
  var selectedIndex = null;
  var _$targetTbody = null;
  var _initialized = false;
  var _tableId = 'nmList';
  var _tableRowClass = 'nmRow';

  //privileged methods - can access the private variables and methods, and is itself accessible to the public methods and the outside
  this._renderData = function () {
    //if (!_initialized) {
    _initialize(this);
    //}
    _$targetTbody.remove('tr');
    if (this.data && this.data.length > 0) {
      var tabellenInhalt = '';
      for (var i = 0; i < this.data.length; i++) {

        var aktData = this.data[i];
        var htmlEncodedData = Object.keys(aktData).reduce(function (previous, current) {
          previous[current] = (typeof (aktData[current]) == 'string') ? KuferTools.Utils.htmlEncode(aktData[current]) : aktData[current];
          return previous;
        }, {});

        var row = parseTemplate(this.rowTemplate, htmlEncodedData);
        tabellenInhalt += row;
      }
      _$targetTbody.append(tabellenInhalt);
      //den Rows noch die Klasse verpassen
      _$targetTbody.find('tr').addClass(_tableRowClass);
      if (selectedIndex >= this.data.length)
        selectedIndex = null;
      this.highlight();
    }
  };

  this.getCurrentData = function () {
    if (this.data && this.data.length > selectedIndex) {
      return this.data[selectedIndex];
    }
  }

  this._activateButtons = function () {
  };
  this.highlight = function () { //durchaus dazu gedacht, von außen aufgerufen zu werden
    var $trs = _$targetTbody.find('tr.' + _tableRowClass);
    $trs.filter(':odd').removeClass('even active').addClass('odd');
    $trs.filter(':even').removeClass('odd active').addClass('even');
    if (!isNaN(parseFloat(selectedIndex)) && isFinite(selectedIndex))
      $trs.eq(selectedIndex).addClass('active');
  };
  this.showDetailDialog = function (extraAnw) {
  };

  //private methods => hier gibts bei Vererbung keinen Zugriff auf this mehr, deshalb als Parameter param übergeben
  var _initialize = function (param) {
    if (param.$target) {
      //Buttons bauen, falls nicht readOnly

      //zunächst leeres Div
      var $buttons = $('<div id="buttonleiste_vert"></div>');
      if (!param.readOnly) {
        var divString = '<div style="margin-left: 5px; margin-top: 20px; height: 35px; resize: none;"><img src="layout/button_hinterlegung.gif" id="button_insert" alt="' + param.uiBezeichnung + ' einf&uuml;gen" title="' + param.uiBezeichnung + ' einf&uuml;gen"></div>';

        //Bearbeiten Button nur hinzufügen, falls das Update erlaubt ist, ansonsten die Höhe der Buttons vermindern
        if (param.noUpdate)
          $buttons.css('height', '-=35');
        else {
          divString += '<div style="margin-left: 5px; margin-top: 5px; height: 35px; resize: none;"><img src="images/edit.gif" id="button_update" alt="' + param.uiBezeichnung + ' bearbeiten" title="' + param.uiBezeichnung + ' bearbeiten"></div>';
        }
        divString += '<div style="margin-left: 5px; margin-top: 5px; height: 35px; resize: none;"><img src="images/loeschen.gif" id="button_delete" alt="' + param.uiBezeichnung + ' l&ouml;schen" title="' + param.uiBezeichnung + ' l&ouml;schen"></div>';
        //var $buttons = $('<div id="buttonleiste_vert">' + divString +  '</div>' +   '</div>');

        if (param.deleteAll) {
          $buttons.css('height', '+=35');
          divString += '<div style="margin-left: 5px; margin-top: 5px; height: 35px; resize: none;">' + param.deleteAllImg + '</div>';
          //divString +=  '<div style="margin-left: 5px; margin-top: 5px; height: 35px; resize: none;"><img src="images/loeschen.gif" id="button_deleteAll" alt="Alle '+param.uiBezeichnung+' l&ouml;schen" title="Alle '+param.uiBezeichnung+' l&ouml;schen"></div>';
        }
        $buttons.append(divString);
      }

      var tablename = param.prefix + _tableId;
      //Tabelle bauen
      var $table = $('<table id="' + tablename + '"></table>');
      if (param.colGroup) {
        $table.append('<colgroup>' + param.colGroup + '</colgroup>');
      }
      if (param.rowHeader) {
        $table.append('<thead>' + param.rowHeader + '</thead>');
      }
      _$targetTbody = jQuery('<tbody></tbody>');
      $table.append(_$targetTbody);
      param.$target.empty();//leeren, da die initialize Methode ja nicht nur einmal aufgerufen wird
      param.$target.append($buttons);

      // Tabelle ggf. in ein div packen (um sie flexblel in der Breite vergrössern zu können (falls
      //das Feld in den Zentr. Einst. erweitert wird)
      var $appendObject = $table;
      if (param.flexibleWidth) {
        //Id ist dann z.B. kennzListDiv
        $appendObject = $('<div id="' + param.prefix + 'ListDiv"></div>');
        $appendObject.append($table);
      }
      param.$target.append($appendObject);

      //selectedIndex = 0;//ersten Datensatz automatisch auswählen?

      if (!param.readOnly) { //bei readonly gibts keine Buttons!
        //hier noch die onclick Handler für die Buttons
        //-löschen
        $buttons.find('#button_delete').on('click', function () {
          if (selectedIndex != null) {
            if (param.canDelete(param.data[selectedIndex])) {
              param.beforeDelete(param.data[selectedIndex]);
              param.data.splice(selectedIndex, 1);//=> 1 Element an Index selectedIndex aus dem Array param.data entfernen

              //den gelöschten Wert ggf. auch aus dem Array der bereits selektierten Werte (aus der Hinterlegung) entfernen
              // wird nur in Klassen verwendet, die sich die selektierten werte im  Array merken (z.B. in AnwesenheitList) 
              if (param.selectedhldata.length > 0)
                param.selectedhldata.splice(selectedIndex, 1);

              param.selectedIndex = null;
              //Hook methode zum Nachbearbeiten der Dateninhalte in  den abgeleiteten Klassen (ist z.B. in Dokumentelist wg. der
              // Reihenfolge notwendig)
              param.postDelete();
              param.renderData();
            }
            else
              param.cannotDelete();
          }
        });

        $buttons.find('#button_update').on('click', function () {
          if (selectedIndex != null && !isNaN(selectedIndex) && selectedIndex >= 0 && selectedIndex < param.data.length) {
            param.showSubscreen(param.data[selectedIndex], false);
          }
          else
            showMessage('Sie müssen erst durch Klicken eine Zeile auswählen.', 'Hinweis');
        });
        $buttons.find('#button_insert').on('click', function () {
          //Par. param ist hier das aktuelle Objekt this (also die NMList) 
          param.$target.find('#' + tablename + ' tbody');
          param.data.push(param.getNewObject());
          selectedIndex = param.data.length - 1;
          param.renderData();

          //Standard: Subscreen (Details) zum aktuell gewählten Eintrag
          var subscreendata = param.data[selectedIndex];
          // Subscreen wird fest aus  einer Hinterlegung gefüllt
          if (param.subscreenfromHL)
            subscreendata = param.hldata;

          param.showSubscreen(subscreendata, true);
        });

        //Nur, falls deleteAll auch erlaubt ist den Klickhandler hierfür hinzufügen
        if (param.deleteAll) {
          //Klickhandler für deleteall
          $buttons.find('#button_deleteAll').on('click', function () {
            //hier ist param.canDelete für alle Eelmente true, sonst macht deleteAll ja keinen Sinn
            param.data.splice(0); // => ab Index 0 alle Elemente aus dem Array param.data entfernen
            // ggf. auch das Array der bereits selektierten Werte (aus der Hinterlegung) löschen
            // wird nur in Klassen verwendet, die sich die selektierten Werte im  Array merken (z.B. in AnwesenheitList) 
            if (param.selectedhldata.length > 0)
              param.selectedhldata.splice(0);
            param.renderData();
          }); //end Klickhandler für deleteall   
        }// end if (param.deleteAll)   
      } //end if if (!param.readOnly) {

      //Für die Tabelle
      /** Handling der Klick-Events: Bei einfachem Klick nur die gewählte Tabellenzelle highlighten (markieren)
      /** bei Doppelklick: Zelle markieren und Dialog aufrufen 
      /** hierbei: eventhandler an tbody knüpfen; dann muss NICHT fuer jedes neu erzeugte tr Element
       ein Eventhandler angehängt werden;
       Achtung:      
       Auch bereits gespeicherte Werte werden bearbeitet
      */
      _$targetTbody.on('click', function (event) {
        var $eventTarget = $(event.target);
        //selectedIndex muss vor param.highlight ausgeführt werden.
        selectedIndex = $eventTarget.parents('tr.nmRow').index();
        param.highlight();
      });

      _$targetTbody.on('dblclick', function (event) {
        var $eventTarget = $(event.target);
        //Wird durch das Clickevent bereits abgedeckt
        //selectedIndex = $eventTarget.parents('tr.nmRow').index();
        //param.highlight();
        //Doppelklick ist nur im Bearbeiten Modus möglich d.h. wenn auch der Bearbeiten Button angezeigt wird
        if (!param.readOnly && !param.noUpdate)
          param.showSubscreen(param.data[selectedIndex]);
      });

      _initialized = true;
    }
    else {
      throw 'InvalidState: $target ist nicht angegeben';
    }
  } //end _initialize = function(param)...
} //end NMList


/** public methods for NMList */
/** Lädt die Tabelle neu aus Data */
NMList.prototype.renderData = function () {
  this._renderData();
}
NMList.prototype.removeLast = function () {
  this.data.splice(this.data.length - 1, 1);
}

NMList.prototype.removeAll = function () {
  this.data.splice(0, this.data.length);
}

/**
 * Wandelt JSON-Strings, welche mit JsonOperations erzeugt wurden, in Objekte um.
 * Gibt bei ungültigem JSON den String direkt zurück. Kann dazu verwendet werden,
 * um Fehlermeldungen von seitens PHP auszugeben.
 * 
 * Verwendet die potentiell unsichere Methode eval, dafür muss json2.js nicht eingebunden werden.
 */
KuferTools.Utils.getObjectFromJson = function (pJson) {
  if (typeof pJson === 'object') {
    return pJson; // Ist bereits ein Objekt
  }

  pJson = KuferTools.Utils.trimS(pJson);
  var toDecode = pJson;
  var result = { 'page': '' };
  // Beim Abfangen von Exceptions wird die entspr. Exception Meldung (immer ein String) ausgegben
  if (!isempty(toDecode)) {
    try {
      result = JSON.parse(toDecode);
    }
    catch (e) {
      result = toDecode;
    }
  }
  return result;
};

/**
 * Erwartet einen mit JsonOperations erzeugen JSON-String, welcher anschließend base64 encoded wurde
 * 
 * das base64-encoding erleichtert die Handhabung mit Sonder- und Steuerzeichen
 * base64.js muss eingebunden sein.
 */
KuferTools.Utils.getObjectFromJsonBase64 = function (pBase64Json) {
  return KuferTools.Utils.getObjectFromJson(base64.decode(pBase64Json));
};
/**
 * Wandelt ein Opjekt zu seiner JSON-String-Repräsentation um.
 * 
 * Falls es als Value in einem Hidden-Input verwendet werden soll, sollte lieber KuferTools.Utils.getJsonFromObjectBase64
 * verwendet werden, da JSON sonst Probleme mit Steuer- und Sonderzeichen macht.
 * json2.js muss eingebunden sein
 */
KuferTools.Utils.getJsonFromObject = function (pObject) {
  return JSON.stringify(pObject);
};

/**
 * Wandelt ein Opjekt zu seiner JSON-String-Repräsentation um, und codiert es anschließend auf base64
 * 
 * Ideal um JSON-Strings in Values von z. B. Hidden-Inputs zu stecken, da die Sonder- und Steuerzeichen
 * von JSON Probleme machen.
 */
KuferTools.Utils.getJsonFromObjectBase64 = function (pObject) {
  return base64.encode(KuferTools.Utils.getJsonFromObject(pObject));
};

registerNamespace('KuferTools.Utils.Tabs');
/**
 * Gibt die ID des aktiven Tabs zurück. Wenn kein aktiver Tab gefunden wird, wird ein Leerstring zurückgegeben
 * 
 * Die ID kann als Anker für Links verwendet werden, dass der entsprechende Tab beim Laden geöffnet wird.
 * 
 * @returns String
 */
KuferTools.Utils.Tabs.GetIdFromActiveTab = function () {
  var $activeTab = KuferTools.Utils.Tabs.GetActiveTab();
  if ($activeTab.length > 0) {
    return $activeTab.prop('id');
  }
  return '';
};

/**
 * Gibt den aktiven Tab zurück.
 * 
 * @returns String
 */
KuferTools.Utils.Tabs.GetActiveTab = function () {
  return $('div[id^="tabs-"]:visible');
};

/**
 * Liefert false, wenn pString nicht in ein Datum umformatiert werden kann, 
 * oder ein ungültiges Datum darstellt,
 * String im Format TT.MM.JJJJ sonst.
 */
registerNamespace('KuferTools.Utils.Date');
KuferTools.Utils.Date.formatDate = function (pString) {
  if (pString == null) return false;

  //Damit auch Fr. 21.03.13 gefunden wird.
  var tmpDate = pString.match(/[1-9][0-9.]*/);
  if (tmpDate) {
    pString = tmpDate[0];
  } else {
    return false;
  }
  var date = pString.split('.');
  if (date.length < 2 || date.length > 3) {
    return false;
  }
  var day = getIntFromDatePart(date[0]);
  var month = getIntFromDatePart(date[1]);
  var year = getIntFromDatePart(date[2]);
  if (year.toString().length != 4) {
    if (year.toString().length > 4)
      return false;
    year = '2' + year.toString().lpad('0', 3);
    year = getIntFromDatePart(year);
  }
  var dateString = date[0].toString().lpad('0', 2) + "." + date[1].toString().lpad('0', 2) + "." + year.toString();
  if (!(dateString.match(/^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$/) &&
    day >= 1 && day <= 31 &&
    month >= 1 && month <= 12))
    return false;
  //Überprüft das Datum auf Monatslängen und Schaltjahre
  //Fängt beim Monat bei 0 zu zählen an! Konvertiert Daten wie 31.04.2010 zu 01.05.2010!
  var dateObject = new Date(year, month - 1, day);
  if (year != dateObject.getFullYear()
    || month != (dateObject.getMonth() + 1)
    || day != dateObject.getDate())
    return false;
  return dateString;
}

/**
 * Gibt true zurück, falls dateBefore vor dateAfter liegt, sonst false
 * Es erfolgt keine weitere Prüfung auf das korrekte Datumsformat (d.m.Y)
 */
KuferTools.Utils.Date.isDateBefore = function (dateBefore, dateAfter) {
  var dateBeforeAry = dateBefore.split('.');
  var dateAfterAry = dateAfter.split('.');

  //Vergleich nacheinander Jahr, Monat, Tag, und bricht ab, sobald das Ergebnis eindeutig ist.
  for (var i = 2; i >= 0; i--) {
    var beforeTemp = parseInt(dateBeforeAry[i].replace(/^0+/, ''));
    var afterTemp = parseInt(dateAfterAry[i].replace(/^0+/, ''));
    if (beforeTemp > afterTemp)
      return false;
    else if (beforeTemp < afterTemp) {
      return true;
    }
  }
  //Datumswerte sind identisch
  return false;
};

KuferTools.Utils.Date.isDateBeforeOrEqual = function (dateBefore, dateAfter) {
  return dateBefore == dateAfter || KuferTools.Utils.Date.isDateBefore(dateBefore, dateAfter);
};

KuferTools.Utils.isTimeBeforeOrEqual = function (timeBefore, timeAfter) {
  return timeBefore.lpad('0', 5) <= timeAfter.lpad('0', 5);
};

/**
 * Gibt anhand eines Javascript-Date-Objekts die Stringrepräsentation zurück
 * DD.MM.YYYY
 */
KuferTools.Utils.Date.getDateString = function (pDateObj) {
  return KuferTools.Utils.Date.formatDate(
    pDateObj.getDate() + '.' + (pDateObj.getMonth() + 1) + '.' + pDateObj.getFullYear()
  );
}

/**
 * Gibt ein DateObjekt relativ zum übergebenen Datum zurück. Kann auch in die Vergangenheit gehen.
 * daysToAdd 0: heute
 * daysToAdd 1: morgen
 * daysToAdd -1: gestern
 */
KuferTools.Utils.Date.addDays = function (pDateObj, daysToAdd) {
  var date = new Date(pDateObj.getTime()); //Deep copy
  date.setDate(date.getDate() + daysToAdd); // Monats und Jahresumbrüche werden automatisch behandelt.
  return date;
}

/**
 * Gibt ein DateObjekt auf den ersten Wochentag des übergebenen Datums zurück (Montag).
 */
KuferTools.Utils.Date.getFirsDayOfWeek = function (pDateObj) {
  var date = new Date(pDateObj.getTime()); //Deep copy
  //getDay(): Sonntag = 0, Montag = 1 usw.
  //Mathematischer Trick: 0=>6, 1=>0, 2=>1, 3=>2... entspricht die Anzahl der Tage zwischen dem
  //Datum und dem letzten Montag
  var daysFor = (date.getDay() + 6) % 7;
  date.setDate(date.getDate() - (daysFor));
  return date;
}

/**
 * Gibt ein DateObjekt auf den letzten Wochentag des übergebenen Datums zurück (Sonntag)
 */
KuferTools.Utils.Date.getLastDayOfWeek = function (pDateObj) {
  //Benutzt getFirsDayOfWeek auf ein Datum nächster Woche, und geht dann einen Tag zurück.
  var date = new Date(pDateObj.getTime()); //Deep copy
  date.setDate(date.getDate() + 7);
  date = KuferTools.Utils.Date.getFirsDayOfWeek(date);
  date.setDate(date.getDate() - 1);
  return date;
}

/**
 * Gibt ein DateObjekt auf den ersten Tag des übergebenen Monats zurück
 */
KuferTools.Utils.Date.getFirstDayOfMonth = function (pDateObj) {
  var date = new Date(pDateObj.getTime()); //Deep copy
  date.setDate(1);
  return date;
}

/**
 * Gibt ein DateObjekt auf den letzten Tag des übergebenen Monats zurück
 */
KuferTools.Utils.Date.getLastDayOfMonth = function (pDateObj) {
  //Trick: Setzt Monat auf nächsten Monat, den Tag jedoch auf 0 (= letzter Tag des vormonats)
  var date = new Date(pDateObj.getFullYear(), pDateObj.getMonth() + 1, 0);
  return date;
}

KuferTools.Utils.Date.getFirstDayOfYear = function (pDateObj) {
  var date = new Date(pDateObj.getTime()); //Deep copy
  date.setDate(1);
  date.setMonth(0);
  return date;
}

KuferTools.Utils.Date.getLastDayOfYear = function (pDateObj) {
  // Monate beginnen mit 0, Tage mit 1
  var date = new Date(pDateObj.getFullYear(), 11, 31);
  return date;
}

/**
 * Gibt ein JS-Dateobj anhand eines Strings im deutschen Format aus.
 */
KuferTools.Utils.Date.getDateObj = function (pDateString) {
  var parts = pDateString.match(/(\d+)/g);
  // zero-based-Monat, daher -1
  return new Date(parts[2], parts[1] - 1, parts[0]);
}

/**
 * Wandelt einen String rgb( r, g, b ) in #RRGGBB um
 */
KuferTools.Utils.RgbToHex = function (pRgb) {
  var rgb = pRgb.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
  function hex(x) {
    return ("0" + parseInt(x).toString(16)).slice(-2);
  }
  return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
};

KuferTools.Utils.getMinutesProUE = function () {
  var minutesProUe = 45;
  if (KuferTools.Kursplanung && KuferTools.Kursplanung.MinutesProUe) {
    minutesProUe = KuferTools.Kursplanung.MinutesProUe;
  }
  return minutesProUe;
}

registerNamespace("KuferTools.Utils.UiFunctions");
KuferTools.Utils.UiFunctions.setzeCheckboxNachChanged = function (pSelektorChanger, pSelektorCheckBox, pMitHinterlegung) {
  jQuery(pSelektorChanger).on('change', function () {
    jQuery(pSelektorCheckBox).prop('checked', true);
  });
  //man kann nur Custom-Events über jQuery ansteuern => hier den Event fromHLwindow
  //so kann, man z.B. Checkboxen setzen, wenn der Uhrzeit Wert aus der Hinterlegung geändert wurde.
  if (pMitHinterlegung) {
    jQuery(pSelektorChanger).on('fromHLwindow', function () {
      jQuery(pSelektorCheckBox).prop('checked', true);
    });
  }
};
/**
 * Wandelt Minuten in UE um, berücksichtigt hierbei abweichende minProUe des Kurses und 
 * 22/45 = 0,5 UE (KuferSQL/Zentrale Einstellungen/Organisation 22 Minuten von 45 Minuten gelten als 0,5 UE )
 * 
 * Hierzu sollte KuferTools.Kursplanung.MinutesProUe und KuferTools.KurstagPlanung.HALBUE gesetzt sein
 */
KuferTools.Utils.minToUe = function (pMinuten) {
  pMinuten *= 1; //Damit pMinuten nicht als String behandelt wird.
  var minutesProUe = KuferTools.Utils.getMinutesProUE();
  //sicherheitshalber mal 0 UEs zurückgeben bei weniger als 0 Minuten
  if (pMinuten <= 0)
    return 0;

  var halbue = false;
  if (KuferTools.KurstagPlanung.HALBUE == "W")
    halbue = true;
  var result;
  if (halbue && minutesProUe == 45) {
    var min45stel = Math.floor(pMinuten) % 45;
    if (min45stel == 11)
      result = (pMinuten + 0.25) / minutesProUe;
    else if (min45stel == 12)
      result = (pMinuten - 0.75) / minutesProUe;
    else if (min45stel == 22)
      result = (pMinuten + 0.5) / minutesProUe;
    else if (min45stel == 23)
      result = (pMinuten - 0.5) / minutesProUe;
    else if (min45stel == 33)
      result = (pMinuten + 0.75) / minutesProUe;
    else if (min45stel == 34)
      result = (pMinuten - 0.25) / minutesProUe;
    else
      result = pMinuten / minutesProUe;
  }
  else
    result = pMinuten / minutesProUe;
  return Math.round(result * 100) / 100;
};

/**
 * Wandelt UE in Minuten um, berücksichtigt hierbei abweichende minProUe des Kurses und
 * 22/45 = 0,5 UE (KuferSQL/Zentrale Einstellungen/Organisation 22 Minuten von 45 Minuten gelten als 0,5 UE )
 * 
 * Hierzu sollte KuferTools.Kursplanung.MinutesProUe und KuferTools.KurstagPlanung.HALBUE gesetzt sein
 */
KuferTools.Utils.UeToMin = function (pUe) {
  var minutesProUe = KuferTools.Utils.getMinutesProUE();

  var halbue = false;
  if (KuferTools.KurstagPlanung.HALBUE == "W")
    halbue = true;
  var result;
  if (halbue && minutesProUe == 45) {
    var ganzeUe = Math.floor(pUe);
    var teilUe = Math.round((pUe - ganzeUe) * 100) / 100;
    if (teilUe == 0.25)
      result = ganzeUe * minutesProUe + 11;
    else if (teilUe == 0.5)
      result = ganzeUe * minutesProUe + 22;
    else if (teilUe == 0.75)
      result = ganzeUe * minutesProUe + 33;
    else
      result = pUe * minutesProUe;
  }
  else
    result = pUe * minutesProUe;

  return KuferTools.Utils.Math.kuferSQLRound(result);
};

/**
 * Lasst Das Script ms Millisekunden warten
 */
KuferTools.Utils.Pause = function (ms) {
  ms += new Date().getTime();
  while (new Date() < ms) { }
}

registerNamespace('KuferTools.Utils.Math');
/**
 * Rundet zunächst auf 2 stellen hinter dem Komma genau, wenn genau 
 * ",50" steht, wird auf die nächste GERADE Zahl gerunden, sonst kaufmännisch.
 * z.B. 6,50 -> 6; 7,50 -> 8
 * 
 * Entsprechende PHP-Funktion: MathOperations::kuferSQLRound
 */
KuferTools.Utils.Math.kuferSQLRound = function (pDouble) {
  var dtemp = Math.round(pDouble * 100);
  if (dtemp % 100 == 50) {
    dtemp = Math.floor(dtemp / 100);
    if (dtemp % 2 != 0) {
      dtemp++;
    }
    return dtemp;
  }
  return Math.round(pDouble);
};

/**
 * Prüft ob ein Wert eine Nummer ist
 */
KuferTools.Utils.Math.isNumber = function (pVal) {
  return typeof pVal === 'number' && isFinite(pVal);
};

KuferTools.Utils.Math.floatToString = function (pFloat) {
  pFloat = Math.round(pFloat * 100);
  pFloat = String(pFloat);
  while (pFloat.length < 3) { pFloat = '0' + pFloat; }
  return pFloat.substring(0, pFloat.length - 2) + ',' + pFloat.substring(pFloat.length - 2);
};

KuferTools.Utils.Math.stringToFloat = function (pString) {
  return parseFloat(pString.replace(/,/, '.'));
}

registerNamespace('KuferTools.Utils.String');
/**
 * Wandelt Sonderzeichen in die HTML-Repräsentation um.
 * 
 * Achtung: 1. Nicht vollständig, kann aber jederzeit um die benötigten Zeichen erweitert werden.
 *          2. Falls zukünftig Anführungszeichen konvertiert werden, sollte in einem Parameter abgefragt werden,
 *             ob diese mitkonvertiert werden sollen oder nicht.
 */
KuferTools.Utils.String.HTMLEncode = function (pString) {
  var result = pString;
  result = result.replace(/ä/g, '&auml;');
  result = result.replace(/ü/g, '&uuml;');
  result = result.replace(/ö/g, '&ouml;');
  result = result.replace(/Ä/g, '&Auml;');
  result = result.replace(/Ü/g, '&Uuml;');
  result = result.replace(/Ö/g, '&Ouml;');
  result = result.replace(/ß/g, '&szlig;');
  return result;
}

// Gibt true zurück, wenn der String mit dem angegebenen Suffix endet; gibt für ein leeres Suffix
// false zurück (d.h. ein String endet nicht mit Leerstring; notwendig für Vergleich beim check auf erlaubte dateiendungen
// beim Dokumentenupload
String.prototype.endsWith = function (suffix) {
  if (suffix === '')
    return false;
  return this.indexOf(suffix, this.length - suffix.length) !== -1;
};

/**
 * Funktionen für das Setzen und erhalten von Cursorpositionen
 */
registerNamespace('KuferTools.Utils.Focus');

/**
 * Gibt die letzte Start-Position des Cursors von $pTarget zurück.
 * 
 * Nur wenn ein Bereich markiert ist, unterscheiden sich Start- und Endposition
 * 
 * ACHTUNG: Derzeit nicht für IE
 */
KuferTools.Utils.Focus.getCursorStartPosition = function ($pTarget) {
  /* IE */
  if (typeof (document.selection) != 'undefined') {
    var selection = getSelection($pTarget[0]);
    return selection.start;
  }
  /* FireFox, Chrome, Safari, Opera  */
  else if (typeof ($pTarget[0].selectionStart) != 'undefined') {
    return $pTarget[0].selectionStart;
  }
};

/**
 * Gibt die letzte End-Position des Cursors von $pTarget zurück.
 * 
 * Nur wenn ein Bereich markiert ist, unterscheiden sich Start- und Endposition
 */
KuferTools.Utils.Focus.getCursorEndPosition = function ($pTarget) {

  /* IE */
  if (typeof (document.selection) != 'undefined') {
    var selection = getSelection($pTarget[0]);
    return selection.end;
  }
  /* FireFox, Chrome, Safari, Opera */
  else if (typeof ($pTarget[0].selectionEnd) != 'undefined') {
    return $pTarget[0].selectionEnd;
  }
};

/**
 * Von http://stackoverflow.com/questions/3622818/ies-document-selection-createrange-doesnt-include-leading-or-trailing-blank-li/3648244#3648244
 * 
 * Vorsicht bei Textareas: Verschiedene Browser interpretieren den Zeilenumbruch als ein oder zwei Zeichen.
 * Code Modifiziert, damit beim IE Zeilenumbrüche nur als ein Zeichen zählen (wie beim maxlength-Attribut bei IE).
 * Das übergebene Element muss selectiert sein. Es darf kein jQuery-Selector sein.
 */
function getSelection(el) {
  var start = 0, end = 0, normalizedValue, range,
    textInputRange, len, endRange;

  if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
    start = el.selectionStart;
    end = el.selectionEnd;
  } else {
    range = document.selection.createRange();

    if (range && range.parentElement() == el) {

      normalizedValue = el.value.replace(/\r\n/g, "\n");
      // Änderung: die Länge wird nun anhand des "normalizedValue" (ein Zeichen für ln) berechnet
      len = normalizedValue.length;

      // Create a working TextRange that lives only in the input
      textInputRange = el.createTextRange();
      textInputRange.moveToBookmark(range.getBookmark());

      // Check if the start and end of the selection are at the very end
      // of the input, since moveStart/moveEnd doesn't return what we want
      // in those cases
      endRange = el.createTextRange();
      endRange.collapse(false);

      if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
        start = end = len;
      } else {
        start = -textInputRange.moveStart("character", -len);
        // Änderung: Die bisher nur einmal gezählten ln werden hier nicht ein 2. mal dazugezählt.
        //start += normalizedValue.slice(0, start).split("\n").length - 1;

        if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
          end = len;
        } else {
          end = -textInputRange.moveEnd("character", -len);
          // Änderung: Die bisher nur einmal gezählten ln werden hier nicht ein 2. mal dazugezählt.
          //end += normalizedValue.slice(0, end).split("\n").length - 1;
        }
      }
    }
  }

  return {
    start: start,
    end: end
  };
}

/**
 * Ersetzt den markierten Bereich von $pTarget mit pReplaceText. (Derzeit nur für Input und Textarea vorgesehen)
 * 
 * Wenn kein Bereich markiert ist, dann wird an der Stelle des Cursors pReplaceText einfach
 * nur eingefügt
 * 
 * ACHTUNG: Für den Aufruf der Funktion mousedown statt click verwenden! IE zeigt ein SEHR seltsames Verhalten bei click:
 * Abhängig davon, wie lange das Listenelement gedrückt wird, wird der Text wo anders eingefügt.
 * 
 * Tipp: Wenn mousedown verwendet wird
 * $(document).on( "mouseup", function(){$lastTarget.trigger('focus');$(document).unbind("mouseup"); } );
 * innerhalb von mousedown{} einfügen. Damit wird das Textfeld anschließend focusiert.
 */
KuferTools.Utils.Focus.replaceSelected = function ($pTarget, pReplaceText) {

  /* IE: Sonderlösung, da getCursorStartPosition usw. nicht für IE unterstützt werden. */
  if (typeof (document.selection) != 'undefined') {
    $pTarget.trigger('focus');
    var range = document.selection.createRange();

    //Wegen Bugs wird zuvor geprüft, ob die Range innerhalb eines Inputs oder Textareas ist.
    var parentElement = range.parentElement();
    if (parentElement.tagName == 'TEXTAREA' || parentElement.tagName == 'INPUT')
      range.text = pReplaceText;

  }
  else {
    var start = KuferTools.Utils.Focus.getCursorStartPosition($pTarget);
    var ende = KuferTools.Utils.Focus.getCursorEndPosition($pTarget);

    //Nicht $pTarget.val(), da in Opera Zeilenumbrüche bei getCursorStartPosition
    //zwei zeichen sind, bei $pTarget.val() wird aus \n\r jedoch \n.
    var valuePart1 = $pTarget[0].value.substring(0, start);
    var valuePart2 = $pTarget[0].value.substr(ende);

    $pTarget.val(valuePart1 + pReplaceText + valuePart2);
    KuferTools.Utils.Focus.setCursorPosition($pTarget, valuePart1.length + pReplaceText.length);
  }
};

/**
 * Setzt den Cursor an eine bestimmte Position innerhalb von $pTarget, und gibt
 * $pTarget den Focus
 * 
 * ACHTUNG: Derzeit nicht für IE
 */
KuferTools.Utils.Focus.setCursorPosition = function ($pTarget, pPos) {

  /* IE */
  if (typeof (document.selection) != 'undefined') {
    //TODO
  }
  /* FireFox, Chrome, Safari, Opera  */
  else if (typeof ($pTarget[0].setSelectionRange) != 'undefined') {
    $pTarget[0].trigger('focus');
    $pTarget[0].setSelectionRange(pPos, pPos);
  }
};

/**
 * Erlaubt bei einem Input nur bestimmte Eingaben. Vorgabe mit regulären Ausdruck. Die im Pattern gefundenen Zeichen werden entfernt.
 * Beachte: Der reguläre Ausdruck wird derzeit nicht automatisch mit /g (ersetze alle) erweitert.
 * 
 * Noch nicht mit Textareas und Zeilenumbrüchen getestet. Müsste wendern in verschiedenen Browsern getestet werden.
 */
KuferTools.Utils.Focus.InputForbidPattern = function ($pTarget, $pPatternZuErsetzen) {

  var start = KuferTools.Utils.Focus.getCursorStartPosition($pTarget);

  var val = $pTarget.val();
  var lengthBefore = val.length;
  val = val.replace($pPatternZuErsetzen, '', val);
  var lengthAfter = val.length;

  $pTarget.val(val);
  KuferTools.Utils.Focus.setCursorPosition($pTarget, start - (lengthBefore - lengthAfter));
};

/******************************************
 * HIER FUNKTIONEN FÜR ALLGEMEINE DIALOGE *
 *****************************************/

registerNamespace("KuferTools.Utils.Dialoge.Uebersicht.PersonMarkierDialog.ButtonHandling");

/*** UEBERSICHTEN PERSONEN MARKIERUNG ***/
/**
 * Macht aus dem im DOM vorhandenen Dialog #dialog_markierenneu einen Dialog
 * und hängt die entsprechenden Eventhandler an die darin enthaltenen Elemente
 * ".button_span".
 */
KuferTools.Utils.Dialoge.Uebersicht.PersonMarkierDialog.Prepare = function (pTitle) {
  var $dialogmarkieren = $('#dialog_markierenneu');

  // Dialog mit der Abfrage nach markierten Dozenten; wird statt showMessage(..) verwendet, da hier das  '#dialogmarkieren' Element
  // nicht aus dem DOM entfernt werden darf (via remove)
  $dialogmarkieren.dialog({
    autoOpen: false,
    title: pTitle + ' markieren:',
    width: 350,
    modal: true,
    buttons: {
      "Abbrechen": function () { $(this).dialog("close"); }
    }
  });

  $dialogmarkieren.find('.button_span')
    .on('click', KuferTools.Utils.Dialoge.Uebersicht.PersonMarkierDialog.ButtonHandling)
    // Farbwechsel beim focus auf die Buttons
    .on('hover', function () {
      var $this = $(this);
      $this.css('color', 'red');
    }, function () {
      var $this = $(this);
      $this.css('color', 'black');
    });

  /* Nur Kommentar; waere bei erfolgreicher Verwendung von showMessage() relevant; das ist aber nicht der Fall 
   //Aus dem DOM Baum aushängen sonst hängt es nach Klick auf den "alleMarkieren" Button  2mal drin
    if ($dialogmarkieren.html() !== '')
      $dialogmarkieren.detach();
  */
};

/**
 * Vorher muss KuferTools.Utils.Dialoge.Uebersicht.PersonMarkierDialog.Prepare aufgerufen werden!
 */
KuferTools.Utils.Dialoge.Uebersicht.PersonMarkierDialog.ButtonHandling = function (event) {
  var $this = $(this); //aktuell geklickter Button
  var idAttr = $this.prop('id');
  var $dialogmarkieren = $('#dialog_markierenneu');
  if (idAttr === 'markall' || idAttr === 'unmarkall') {

    //falls &markerAction=... (aus vorheriger Markierungs Aktion) schon im Link vorhanden ist, wird es entfernt
    if (KuferTools.SelfLink.indexOf('&markerAction') !== -1) {
      var selfLinkParts = KuferTools.SelfLink.split('&markerAction');
      KuferTools.SelfLink = selfLinkParts[0];
    }

    $dialogmarkieren.dialog('close');
    if (idAttr === 'markall')
      window.location.href = KuferTools.SelfLink + '&markerAction=markiereAlle';
    else  //#unmarkall''
      window.location.href = KuferTools.SelfLink + '&markerAction=loescheMarkierungen';
  }
  else { // 'markside' oder 'unmarkside''
    if (idAttr === 'markside')
      $('input.markable').prop('checked', true);
    else
      $('input.markable').prop('checked', false);
    //Dialog hier nach dem markieren schließen 
    $dialogmarkieren.dialog('close');
    // $this.parent().dialog('close');   
  } //end else
};

/**
 * Funktionen, welche an jQuery.fn.dataTableExt.oSort übergeben werden können, um das Sortierverhalten
 * von bestimmten Datatables-Spalten festzulegen.
 */
registerNamespace("KuferTools.Utils.DataTables");
registerNamespace("KuferTools.Utils.DataTables.oSort");
/**
 * Gibt True zurück, falls a vor b. Funktioniert auch mit z. B. "Fr., 23.12.12 14:13 Uhr"
 */
KuferTools.Utils.DataTables.oSort.CompareDate = function (a, b) {
  a = KuferTools.Utils.stripHTML(a);
  b = KuferTools.Utils.stripHTML(b);
  if (a == '' || a == '&nbsp;')
    a = '30.12.1899';
  if (b == '' || b == '&nbsp;')
    b = '30.12.1899';
  var dateA = KuferTools.Utils.Date.formatDate(a);
  var dateB = KuferTools.Utils.Date.formatDate(b);

  if (dateA && dateB) {
    var aVorB = KuferTools.Utils.Date.isDateBefore(dateA, dateB);
    var bVorA = KuferTools.Utils.Date.isDateBefore(dateB, dateA);
    // Falls das Datum gleich ist, wird auf Time verglichen, insofern vorhanden.
    // Zeit wird am : erkannt.
    if (!aVorB && !bVorA) {
      var timeA = a.match(/[0-9]*:[0-9]*:?[0-9]*?/);
      var timeB = b.match(/[0-9]*:[0-9]*:?[0-9]*?/);
      if (timeA && timeB) {
        var timeAryA = timeA[0].split(':');
        var timeAryB = timeB[0].split(':');
        for (var i = 0; i < 3; i++) {
          if (typeof (timeAryA[i]) != 'undefined' && typeof (timeAryB[i]) != 'undefined') {
            if (parseInt(timeAryA[i], 10) < parseInt(timeAryB[i], 10)) {
              return true;
            } else if (parseInt(timeAryA[i], 10) > parseInt(timeAryB[i], 10)) {
              return false;
            }
          }
        }
      }
    }
    return aVorB;
  }
};
KuferTools.Utils.DataTables.oSort.DateAsc = function (a, b) {
  var aVorB = KuferTools.Utils.DataTables.oSort.CompareDate(a, b);
  return (aVorB) ? -1 : 1;
};
KuferTools.Utils.DataTables.oSort.DateDesc = function (a, b) {
  var aVorB = KuferTools.Utils.DataTables.oSort.CompareDate(a, b);
  return (aVorB) ? 1 : -1;
};

/**
 * Erzeugt mit Hilfe von showMessage einen Dialog, und initialisiert anschließend ein dataTable.
 * 
 * Ist darauf Spezialisiert, dass das DataTable per Ajax geladen wird.
 * Mit Blättern und Filter kann umgegangen werden.
 * 
 * Suchfelder müssen als Namen den per Ajax übergebenen Parameter haben, und als Klasse dataTableSearch.
 * Buttons zum Vorwärtsblättern müssen die Klasse dataTableNext besitzen
 * Buttons zum Rückwärtsblättern müssen die Klasse dataTablePrevious besitzen
 * Wenn vorwärts/rückwärts-blättern möglich ist, bekommt der entsprechende Button die Klasse active.
 * Falls die inaktiven Buttons ausgegraut werden sollen, kann statt img spans mit der entsprechenden Höhe/Breite und Hintergrundbild gearbeitet werden.
 * 
 * @param pMessage showMessage: Inhalt des Dialogs
 * @param pTitle showMessage: Titel des Dialogs
 * @param pButtons showMessage: Button-Objekt für den Dialog
 * @param pWidth showMessage: Breite des Dialogs
 * @param pPosition showMessage: Position des Dialogs als Array mit zwei werten.
 * @param pDataTable jQuery-Selector-String (ohne $(STRING) ), mit welchen das Datatable in der Message angesprochen wird. KEIN SELEKTOR möglich, da das datatable zum Zeitpunkt der Übergabe noch nicht existiert.
 * @param pDataPerPage Anzahl der Datensätze pro Seite. Wird für Blätterfunktionen benötigt.
 * @param pScrollY Höhe der Datatable. Ist diese höher, wird ein Scrollbalken angeboten.
 * @param pUrl4Ajax URL, mit der Per Ajax die Datatable gefüllt wird.
 */
KuferTools.Utils.DataTables.showMessageWithDataTable4Ajax = function (pMessage, pTitle, pButtons, pWidth, pPosition, pDataTable, pDataPerPage, pScrollY, pUrl4Ajax) {
  showMessage(pMessage, pTitle, pButtons, pWidth, pPosition);
  var $dataTable = $(pDataTable, pMessage);
  //Falls die Datatable bereits initialisiert wurde, liegt die Tabelle innerhalb der Klasse dataTables_scrollBody
  if ($('.dataTables_scrollBody .dataTable_tnUebersicht', pMessage).length === 0) {
    $dataTable.dataTable({
      "oLanguage": { "sUrl": "../jquery/plugins/jquery.dataTables.de_DE.txt" },
      "sScrollY": "300px", //Scrollbalken nach unten, angezeigt: 300px (IE)
      "bFilter": false,
      "bInfo": false, //z.B. showing 1 to 10 from 57 entries
      "bScrollCollapse": true,
      "bPaginate": false,
      "bAutoWidth": false,
      "bSort": false, // Serverseitige Sortierung ist nicht für alle Spalten möglich
      "bServerSide": true,
      "iDisplayLength": pDataPerPage,
      "iDisplayStart": 0,
      "fnServerParams": function (aoData) {

        // Searchfield soll ein Obj sein: name: callback oder wert.
        $('.dataTableSearch', pMessage).each(function () {
          aoData.push({ "name": $(this).prop('name'), "value": $(this).val() });
        });
      },
      "fnDrawCallback": function (oSettings) {

        KuferTools.Utils.AjaxLadebildschirm.Stop();

        if (oSettings._iDisplayStart <= 0) { // TODO anders verallgemeinern! sonst müsste man fast die img ebenfalls setzen. obwohl das per obj gut möglich wäre
          //$( '.dataTablePrevious', pMessage ).prop('src', 'layout/button_rueckwaertsg_small.gif');
          $('.dataTablePrevious', pMessage).removeClass('active');
          $('i.fa-arrow-left', pMessage).css("color", "#333");
        } else {
          //$( '.dataTablePrevious', pMessage ).prop('src', 'layout/button_rueckwaerts_small.gif');
          $('.dataTablePrevious', pMessage).addClass('active');
          $('i.fa-arrow-left', pMessage).css("color", "#5cb85c");
        }

        if (oSettings._iDisplayLength + oSettings._iDisplayStart >= oSettings._iRecordsDisplay) {
          //$( '.dataTableNext', pMessage ).prop('src', 'layout/button_vorwaertsg_small.gif');
          $('.dataTableNext', pMessage).removeClass('active');
          $('i.fa-arrow-right', pMessage).css("color", "#333");
        } else {
          //$( '.dataTableNext', pMessage ).prop('src', 'layout/button_vorwaerts_small.gif');
          $('.dataTableNext', pMessage).addClass('active');
          $('i.fa-arrow-right', pMessage).css("color", "#5cb85c");
        }

        // Hier ggf. die Blätterbuttons ausblenden. Später nur ausgrauen
      },
      "sAjaxSource": pUrl4Ajax,
      "sServerMethod": "POST",
      "aaSorting": [] // Anfangs wird nicht sortiert, damit die Sortierung aus den Einstellungen erhalten bleibt.
    });
  }

  //var searchCounter = 0;
  /**
   * Felder im Suchformular. Werden 1 Sekunde nach der Eingabe des letzten Zeichens abgesendet.
   */
  /*
   * Wird nicht mehr verwendet, da es viel mehr Probleme gemacht hat, als geholfen.
   * 
   * $('.dataTableSearch', pMessage).on('keyup', function() {
    // Erst wenn eine Sekunde lang keine Eingaben kommen, wird die Suchanfrage abgesetzt.
    searchCounter++;
    window.setTimeout( function() {
      searchCounter--;
      if(searchCounter == 0) {
        $dataTable.fnPageChange('first');
      }
    }, 1000);
  });*/

  $('.dataTableSearch', pMessage).on('keypress', function (e) {
    if (e.which == 13) {
      $('.dataTableSearchButton', pMessage).trigger('click');
    }
  });

  $('.dataTableSearchButton', pMessage).on('click', function () {

    KuferTools.Utils.AjaxLadebildschirm.Start();

    $dataTable.fnPageChange('first');
  });

  /**
   * Buttons fürs Vorwärtsblättern
   */
  $('.dataTableNext', pMessage).on('click', function () {
    if ($(this).hasClass('active')) {

      KuferTools.Utils.AjaxLadebildschirm.Start();

      $dataTable.fnPageChange('next');
    }
  });
  /**
   * Buttons fürs Zurückblättern
   */
  $('.dataTablePrevious', pMessage).on('click', function () {
    if ($(this).hasClass('active')) {

      KuferTools.Utils.AjaxLadebildschirm.Start();

      $dataTable.fnPageChange('previous');
    }
  });

};


/*******************************************************************************************************************
 * jQuery.fn.extend wird hier verwendet, um eine Sammlung von eigen definierten also  proprietäten jQuery Funktionen
 * anzulegen; sie MÜSSEN hierbei als Attribute eines Objektes $.fn.extend abgelegt werden;  Innerhalb jQuery wird hier 
 * nicht zwischen Funktionen und Methoden, wie an manchen Stellen in den jQuery Foren unterschieden; dort wird folgende
 * Unterscheidung getroffen: Im Unterschied zu Funktionen wird auf Methoden direkt vom DOM Baum aus zugegriffen; d.h. 
 * Funktionen werden immer zusammen mit jQuery Elementen verwendet also z.B.: $('div').meineFunktion(); 
 * jQuery Methoden werden wie folgt verwendet: $.meineMethode().
 * Diese Unterscheidung macht für unsere Zwecke für proprietäre Methoden keinen Sinn; anstatt eine Methode, die beliebig 
 * im DOM Baum aufgerufen wird  und nicht an ein JQuery Element gebunden ist, in jQuery zu definieren, wird sie  gleich 
 * als globale Javascript Funktion unter KuferTools.Utils definiert; also wird verwendet:
 *  
 *                    KuferTools.Utils.meineMethode1  (anstatt $.meineMethode1)
 * 
 ****************************************************************************************************************
 * statt der Sammlung in einem Objekt wäre es auch möglich, die Funktionen bzw. Methoden einzeln zu definieren
 * also z.B
   jQuery.fn.hasId = function(id) {   
   return this.prop('id') == id;
   };
 * dies ist gleichbedeutend mit der folgenden Definition der Funktion unter $.fn.extend
 ********************************************************************************************************
 
 ****************************************************************************************************************
 *  Enthält eine Sammlung von proprietären jQuery Funktionen; diese werden als Attribute eines einzelnen Objekts 
 *  dargestellt
 * Achtung: falls $.extend mehrere Objekte enthält, werden diese zusammen gemerged (siehe auch Anmerkung zu 
 * function showMessage (in function.js)
 ************************************************************************************/
jQuery.fn.extend({
  //liefert true oder false
  hasId: function (id) {
    return this.prop('id') == id;
  },
  // Hängt zusätzlich die Tabid (d.h. z.B &fromTab = tabs-2) des aktuellen Tabs an die Url; damit zeigt der Backlink gleich auf den
  //  richtigen Tabreiter zurück (z.B. tabs-2 für belegte Kurse)         
  addTab2Url: function () {
    var fromTabId = this.parents('.content.tab').prop('id');
    //Anhängen des Tabs an die URL ist nur dann nötig, wenn die aktuelle Tab NICHT tabs-1 ist 
    if (fromTabId !== 'tabs-1') {
      // this ist ein jQuery Element z.B. $('a')
      var link = this.prop('href');
      // Aufsplitten nötig, falls der Link ein tab-Tag enthält z.B. z.B. &toolId=8&..&nr=K1502#tabs-4
      var tabTag = '';
      if (link.indexOf('#') !== -1) {
        var linkParts = link.split('#');
        link = linkParts[0];
        tabTag = '#' + linkParts[1];
      }
      link += '&fromTab=' + fromTabId;
      //ein bereits vorhandenes tab-Tag (z.B. #tabs-4) wird wieder hinten an die URL angehängt
      if (tabTag !== '')
        link += tabTag;
      this.prop('href', link);
    } //end if
  } //end function addTab2Url(..)

  //TODO: weitere Funktionen definieren nach dem Schema
  // meineFunktion1: function(a,b) {...},
  // meineFunktion2: function(x,y,z) {...}, ...   
}); //end jQuery.fn.extend

registerNamespace("KuferTools.Utils.Filter");

KuferTools.Utils.Filter.IsFilled = function (pValue) {
  return KuferTools.Utils.trimS(pValue) !== '';
};

KuferTools.Utils.Filter.IsFilledButNotZero = function (pValue) {
  return KuferTools.Utils.trimS(pValue) !== '' && KuferTools.Utils.trimS(pValue) !== '0';
};

KuferTools.Utils.Filter.IsFilledEmptyStringOk = function (pValue) {
  return KuferTools.Utils.trimS(pValue) !== '-1';
}

KuferTools.Utils.Filter.IsInt = function (pValue) {
  return pValue == parseInt(pValue);
};

KuferTools.Utils.Filter.IsFloatPunkt = function (pValue) {
  return !isNaN(pValue);
};

KuferTools.Utils.Filter.IsFloatKomma = function (pValue) {
  if (pValue.search(/\./) == -1) {
    return KuferTools.Utils.Filter.IsFloatPunkt(pValue.replace(/,/, '.'));
  } else {
    return false;
  }
};

KuferTools.Utils.Filter.IsDate = function (pValue) {
  var dateAry = pValue.split('.');

  // Erwartet Tag und Monat zweistellig
  if (!pValue.match(/^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$/)) {
    return false;
  }

  var day = parseInt(dateAry[0], 10);
  var month = parseInt(dateAry[1], 10);
  var year = parseInt(dateAry[2], 10);

  if (year < 1900) {
    return false;
  }

  // Überprüfung auf Über- und Unterläufe: 31.04. wird im Dateobj zu 01.05.
  var dateObject = new Date(year, month - 1, day);
  if (year != dateObject.getFullYear()
    || month != (dateObject.getMonth() + 1)
    || day != dateObject.getDate()) {
    return false;
  }

  return true;
};

KuferTools.Utils.Filter.IsDateJahrKurz = function (pValue) {

  if (!pValue.match(/^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$/)) {
    return false;
  }
  pValue = pValue.substr(0, 6) + '20' + pValue.substr(6, 2);

  return KuferTools.Utils.Filter.IsDate(pValue);
};

KuferTools.Utils.Filter.IsTime = function (pValue) {
  if (!pValue.match(/^[0-9]{1,2}\:[0-9]{2}$/)) {
    return false;
  }
  var timeAry = pValue.split(':');
  var hour = timeAry[0];
  var min = timeAry[1];

  return hour >= 0 && hour <= 23 && min >= 0 && min <= 59;
};

KuferTools.Utils.Filter.IsOneOrNull = function (pValue) {
  return (pValue == "1" || pValue == "0");
};

KuferTools.Utils.Filter.IsJOrN = function (pValue) {
  return (pValue == "j" || pValue == "J" || pValue == "n" || pValue == "N");
};

/**
 * Für E-Mail-Adressen gibt es sehr viel Spielraum. Es ist nahezu alles Korrekt, sobald es genau ein @ hat.
 * Laut Wikipedia erlaubte Zeichen im Lokalteil: A-Za-z0-9.!#$%&'*+-/=?^_`{|}~
 * Gibt auch ohne Top-Lvl-Domain true zurück.
 * Gibt bei Angabe von Kommentaren, oder den Absendernamen und ähnliche Sonderspässe false zurück.
 */
KuferTools.Utils.Filter.IsEmail = function (pValue) {
  return pValue.search(/^[A-Za-z0-9.!#$%&'*+-\/=?^_`{|}~]+@[A-Za-z0-9.!#$%&'*+-\/=?^_`{|}~]+$/) != -1;
};

/**
 * Bei Teilnehmernamen ist "TN N", "TN M", "TN W" nicht erlaubt
 */
KuferTools.Utils.Filter.IsNotNeutralerTN = function (pValue) {
  return pValue.search(/^TN N|TN M|TN W$/) == -1;
};

KuferTools.Utils.Filter.IsImageJPEG = function (pValue) {
  var von = pValue.lastIndexOf('.');
  var bis = pValue.length;
  var endung = pValue.slice(von, bis).toLowerCase();
  return (endung === '.jpg');
};

KuferTools.Utils.Filter.IsDateTime = function (pValue) {
  var dateTimeAry = pValue.split(' ');
  var result = KuferTools.Utils.Filter.IsDate(dateTimeAry[0]);
  if (result) {
    result = typeof dateTimeAry[1] === 'undefined' || KuferTools.Utils.Filter.IsTime(dateTimeAry[1]);
  }

  return result;
};

KuferTools.Utils.Filter.Mandatory = 1;
KuferTools.Utils.Filter.Integer = 2;
KuferTools.Utils.Filter.Float_Punktsepariert = 4;
KuferTools.Utils.Filter.Float_Kommasepariert = 8;
KuferTools.Utils.Filter.Date = 16;
KuferTools.Utils.Filter.Date_Jahrkurz = 32;
KuferTools.Utils.Filter.Time = 64;
KuferTools.Utils.Filter.EinsNull = 128;
KuferTools.Utils.Filter.JN = 256;
KuferTools.Utils.Filter.Email = 512;
KuferTools.Utils.Filter.MandatoryNoZero = 1024;
KuferTools.Utils.Filter.ImageJPEG = 2048;
KuferTools.Utils.Filter.NeutralerTN = 4096;
KuferTools.Utils.Filter.DateTime = 8192;
KuferTools.Utils.Filter.MandatoryEmptyStringOk = 16384;

/**
 * Funktion um mehrere Filterkriterien auf einmal zu prüfen.
 * 
 * Gibt true zurück, falls Leer, und kein Pflichtfeld
 * 
 * @param pValue zu prüfender Wert
 * @param pModus Nach was gefiltert wird, bitweise codiert. Hierzu können die Konstanten Mandatory, Integer...
 *               verwendet werden. Bei mehreren Kriteren müssen diese per Bitweises ORing (x | y) verknüpft werden
 */
KuferTools.Utils.Filter.FilterValue = function (pValue, pModus) {

  var isOk = true;
  // Per bitweises ANDing ( x & y ) wird festgestellt, welche der Filterkriterien in Modus angegeben wurden.
  // Hinweis: != bindet stärker als &, deshalb ist eine zusützliche Klammerung nötig
  if (isOk && ((pModus & KuferTools.Utils.Filter.Mandatory) != 0)) {
    isOk = isOk && KuferTools.Utils.Filter.IsFilled(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.MandatoryNoZero) != 0)) {
    isOk = KuferTools.Utils.Filter.IsFilledButNotZero(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.MandatoryEmptyStringOk) != 0)) {
    isOk = KuferTools.Utils.Filter.IsFilledEmptyStringOk(pValue);
  }

  // Gibt true zurück, wenn Value leer ist, und kein Mandatoryfeld.
  if (isOk && pValue == "") {
    return true;
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.Integer) != 0)) {
    isOk = KuferTools.Utils.Filter.IsInt(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.Float_Punktsepariert) != 0)) {
    isOk = KuferTools.Utils.Filter.IsFloatPunkt(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.Float_Kommasepariert) != 0)) {
    isOk = KuferTools.Utils.Filter.IsFloatKomma(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.Date) != 0)) {
    isOk = KuferTools.Utils.Filter.IsDate(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.Date_Jahrkurz) != 0)) {
    isOk = KuferTools.Utils.Filter.IsDateJahrKurz(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.Time) != 0)) {
    isOk = KuferTools.Utils.Filter.IsTime(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.EinsNull) != 0)) {
    isOk = KuferTools.Utils.Filter.IsOneOrNull(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.JN) != 0)) {
    isOk = KuferTools.Utils.Filter.IsJOrN(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.Email) != 0)) {
    isOk = KuferTools.Utils.Filter.IsEmail(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.ImageJPEG) != 0)) {
    isOk = KuferTools.Utils.Filter.IsImageJPEG(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.NeutralerTN) != 0)) {
    isOk = KuferTools.Utils.Filter.IsNotNeutralerTN(pValue);
  }

  if (isOk && ((pModus & KuferTools.Utils.Filter.DateTime) != 0)) {
    isOk = KuferTools.Utils.Filter.IsDateTime(pValue);
  }

  return isOk;

};

KuferTools.Utils.Filter.IsDateEarlierOrEqual = function (dateLater) {

  return function (dateEarlier) {
    if (dateLater.length > 0 && KuferTools.Utils.Filter.IsDate(dateLater) && dateEarlier.length > 0 && KuferTools.Utils.Filter.IsDate(dateEarlier)) {
      return KuferTools.Utils.Date.isDateBeforeOrEqual(dateEarlier, dateLater);
    }
    return true;
  };
};

KuferTools.Utils.Filter.IsDateLaterOrEqual = function (dateEarlier) {

  return function (dateLater) {
    if (dateEarlier.length > 0 && KuferTools.Utils.Filter.IsDate(dateEarlier) && dateLater.length > 0 && KuferTools.Utils.Filter.IsDate(dateLater)) {
      return KuferTools.Utils.Date.isDateBeforeOrEqual(dateEarlier, dateLater);
    }
    return true;
  };
};

KuferTools.Utils.Filter.IsTimeEarlierOrEqual = function (timeLater) {
  return function (timeEarlier) {
    if (timeLater.length > 3 && KuferTools.Utils.Filter.IsTime(timeLater) && timeEarlier.length > 3 && KuferTools.Utils.Filter.IsTime(timeEarlier)) {
      return KuferTools.Utils.isTimeBeforeOrEqual(timeEarlier, timeLater);
    }
    return true;
  };
};

KuferTools.Utils.Filter.IsTimeLaterOrEqual = function (timeEarlier) {
  return function (timeLater) {
    if (timeEarlier.length > 3 && KuferTools.Utils.Filter.IsTime(timeEarlier) && timeLater.length > 3 && KuferTools.Utils.Filter.IsTime(timeLater)) {
      return KuferTools.Utils.isTimeBeforeOrEqual(timeEarlier, timeLater);
    }
    return true;
  };
};

KuferTools.Utils.Filter.FilterByArry = function (value, filterArray) {
  var filterLength = filterArray.length;
  for (var i = 0; i < filterLength; i++) {
    if (!filterArray[i](value)) {
      return false;
    }
  }
  return true;
};

/**
 * Gibt ein Objekt zurück, mit dessen Hilfe mehrere Felder per jQuerySelector und Modus auf einmal geprüft werden können.
 * Die Selektoren mit fehlerhaften Eingaben werden zurückgegeben.
 * 
 * z. B.
 * var filterObj = KuferTools.Utils.Filter.FilterSelector();
 * filterObj.addSelector( $('#test1'), KuferTools.Utils.Filter.Mandatory ) .
 *           addSelector( $('#test2'), KuferTools.Utils.Filter.Mandatory | KuferTools.Utils.Filter.Integer ) ;
 * var wrongFilledSelectors = filterObj.filter();
 */
KuferTools.Utils.Filter.FilterSelector = function () {
  /**
   * Array mit den zu prüfenden Selektoren, und den jeweiligen Modus.
   * 
   * { selector: , modus: }
   */
  var filterSettings = [];
  var result = {};
  /**
   * Fügt ein weiteres zu prüfendes Element hinzu
   * 
   * @param $pSelector des zu prüfenden Elements.
   * @param pModus Nach was gefiltert wird, bitweise codiert. Hierzu können die Konstanten Mandatory, Integer...
   *               verwendet werden. Bei mehreren Kriteren müssen diese per Bitweises ORing (x | y) verknüpft werden
   */
  result.addSelector = function ($pSelector, pModus) {
    filterSettings[filterSettings.length] = { selector: $pSelector, modus: pModus };
    return this;
  };

  /**
   * Gibt ein Array mit den fehlerhaften Selektoren zurück.
   */
  result.filter = function () {
    var result = [];
    var filterLength = filterSettings.length;
    for (var i = 0; i < filterLength; i++) {
      filterSettings[i];
      if (!KuferTools.Utils.Filter.FilterValue(filterSettings[i].selector.val(), filterSettings[i].modus)) {
        result[result.length] = filterSettings[i].selector;
      }
    }
    return result;
  };

  return result;
};

registerNamespace("KuferTools.Utils.AjaxLadebildschirm");

KuferTools.Utils.AjaxLadebildschirm.Start = function () {
  if ($('.loader_overlay').length === 0) {
    $('body').append('<div class="loader_overlay"><div class="loader_spinner"></div></div>');
  }
};

/**
 * Ajaxloader nur für einen Teilausschnitt der Seite.
 * 
 * Der Selector sollte Position absolute oder relative haben.
 */
KuferTools.Utils.AjaxLadebildschirm.Partial = function ($selector) {
  $selector.append('<div class="loader_overlay partial"><div class="loader_spinner"></div></div>');
};

KuferTools.Utils.AjaxLadebildschirm.Stop = function () {
  $('.loader_overlay').remove();
};

KuferTools.Utils.RemoveInvalidCharsForBase64Encoding = function (pText) {
  return pText.replace(/[^\x00-\xFF]/g, '');
};
