PageSourceSearch

https://www.vhs-tdf-ndk.de/fileadmin/kuferweb/webtools/js/utils.js?version=4.20.92.06

js vhs-tdf-ndk.de collected 2026-10-04 00:30:52 UTC 107,694 bytes, 2,724 lines download raw bytes

1/**
2 * @copyright (c) Kufer Software GmbH, Oderstraße 7, D-84453 Mühldorf
3 */
4
5if (typeof KuferTools == 'undefined') KuferTools = {};
6if (typeof KuferTools.Utils == 'undefined') KuferTools.Utils = {};
7
8/**
9 * Fuegt einem jquery Objekt (ziel) ein select mit den angegebenen Optionen hinzu
10 * BeispielAufruf:
11 *   var options=[{"val": "wert1", "text": "text1","farbe": '#00FFFF'}, {"val": "wert2", "text": "text2", "farbe": "yellow"}];
12 *   KuferTools.Utils.addSelect($('#testTabelle'), 'name', 'selectId', 'selectClass', 'wert2', options);
13 */
14KuferTools.Utils.addSelect = function (ziel, name, id, classname, selectedValue, options, disabled) {
15  //Die neue jQuery-Version (1.9) wirft bei $('#') fehler
16  if (id != '')
17    ziel.find('#' + id).remove();
18  var $select = $('<select name="' + name + '" id="' + id + '" class="' + classname + '"' + (disabled ? ' disabled' : '') + '></select>');
19  ziel.append($select);
20  jQuery.each(options, function (val, item) {
21    //*DS: umgebaut auf den String, weil ansonsten das select mit showMessage nicht(!!!) funktioniert hat
22    var option = '<option value="' + item.val + '"';
23
24    if (item.val == selectedValue) {
25      option += ' selected';
26      if (item.farbe != '')
27        $select.css('background-color', item.farbe);
28    }
29    if (item.farbe != '') {
30      option += ' style="background-color: ' + item.farbe + '"';
31    }
32
33    option += '>' + item.text + '</option>';
34
35    $select.append($(option));
36  });
37}
38
39/**
40 * Liefert false, wenn nicht alle Selects mit der Klasse pKlasse den gleichen Wert
41 * gewählt haben, ansonsten den bei allen gewählten Wert.
42 */
43KuferTools.Utils.getValueAllerSelectsMitKlasse = function (pKlasse) {
44  var selektierteOptionen = jQuery('select.' + pKlasse + ' option:selected');
45
46  // sind alle selektierten Optionen gleich ?
47  for (var i = 1; i < selektierteOptionen.length; ++i) {
48    if ($(selektierteOptionen[i]).val() != $(selektierteOptionen[i - 1]).val()) {
49      return false; //nein => false zurückgeben
50    }
51  }
52
53  return jQuery(selektierteOptionen[0]).val();
54}
55
56/**
57 * Speichert das Formular des Inputbuttons per AJAX Request!
58 * Ruft mit dem Rückgabewert von Ajax die angegebene callBackFunction auf
59 * 
60 * event: submit des Formulars
61 * callBackFunction(data): wird mit der Rückgabe von jQuery.post aufgerufen.
62 */
63KuferTools.Utils.ajaxPost = function (event) {
64  event.preventDefault();
65  var form = $(event.currentTarget);
66  var link = form.prop('action');
67  return KuferTools.Utils.doAjaxPost(form, link);
68}
69
70KuferTools.Utils.doAjaxPost = function (form, link) {
71  const formData = new FormData();
72  formData.set('ajax', 'content');
73
74  // Versteckte Eingabefelder
75  form.find('input[type=hidden]').each(function () {
76    formData.set(jQuery(this).prop('name'), jQuery(this).val());
77  });
78  // Eingabefelder
79  form.find('input:text').each(function () {
80    formData.set(jQuery(this).prop('name'), jQuery(this).val());
81  });
82  // Passwortfelder
83  form.find('input[type=password]').each(function () {
84    formData.set(jQuery(this).prop('name'), jQuery(this).val());
85  });
86  // Absendebutton
87  form.find('input[type=submit]').each(function () {
88    formData.set(jQuery(this).prop('name'), jQuery(this).val());
89  });
90  // Angewählte Checkboxen
91  form.find('input[type=radio]').each(function () {
92    if (this.checked) {
93      formData.set(jQuery(this).prop('name'), jQuery(this).val());
94    }
95  });
96  // Angewählte Radio-Groups
97  form.find('input[type=checkbox]').each(function () {
98    if (this.checked) {
99      formData.set(jQuery(this).prop('name'), jQuery(this).val());
100    }
101  });
102  // Textarea
103  form.find('textarea').each(function () {
104    if (typeof (jQuery(this).prop('name')) != 'undefined' && jQuery(this).prop('name') !== '') {
105      jQuery(this).css('display', 'block');
106      formData.set(jQuery(this).prop('name'), jQuery(this).val());
107    }
108  });
109  // Select
110  form.find('select').each(function () {
111    if (typeof (jQuery(this).prop('name')) != 'undefined' && jQuery(this).prop('name') !== '') {
112      formData.set(jQuery(this).prop('name'), jQuery(this).val());
113    }
114  });
115
116  //Falls der ReloadSchutz aktiv ist, muss die PostID hochgesetzt werden, da ein zweiter Submit
117  //vom Reloadschutz abgefangen wird.
118  if (typeof (KuferTools.ReloadSchutz) != 'undefined' && typeof (KuferTools.ReloadSchutz.PostID) != 'undefined')
119    KuferTools.ReloadSchutz.PostID++;
120
121  KuferTools.Utils.AjaxLadebildschirm.Start();
122
123  return fetch(link, {
124    method: 'POST',
125    body: formData,
126    headers: new Headers({
127      'X-Requested-With': 'XMLHttpRequest'
128    })
129  }).finally(() => {
130    KuferTools.Utils.AjaxLadebildschirm.Stop();
131  });
132}
133
134/**
135 * Schickt ein Post-Request ab. Kein AJAX, es wird nach dem Absenden eine neue Seite angezeigt.
136 * 
137 * @param string pAction Zieladresse
138 * @param object pDataObj Attributnamen werden Key, Werte werden Value für Post
139 *                        Beispiel: {"schlüssel1": 1, "schlüssel2": 2}
140 */
141KuferTools.Utils.Post = function (pAction, pDataObj) {
142  var formStr = '<form method="post" action="' + pAction + '">';
143
144  jQuery.each(pDataObj, function (key, value) {
145    formStr += '<input type="hidden" name="' + key + '" value="">';
146  });
147  formStr += '</form>';
148
149  var $form = jQuery(formStr);
150
151  jQuery('body').append($form);
152
153  //*TH: Hier per jQuery bei den Hidden-Inputs die Werte setzen, anstatt es oben direkt im HTML zusammenzubauen,
154  //damit die Characters escaped werden. Falls wer eine bessere Lösung weiß, gerne umsetzen.
155  jQuery.each(pDataObj, function(key, value) {
156    jQuery('input[name="' + key + '"]').val(value);
157  });
158
159  $form.trigger('submit');
160};
161
162/**
163 * Gibt einen Fehlermeldungsdialog aus, falls vom Server fehlerhafte Daten empfangen wurden
164 * 
165 * @param {Object} pLoadedObj
166 * @param {string} pLink
167 * @param {Object|undefined} [pAbbruchButton=undefined]
168 * @returns Null
169 */
170KuferTools.Utils.showAjaxErrorDialog = function (pLoadedObj, pLink, pAbbruchButton) {
171  // Falls kein oder ein fehlerhaftes Objekt vom Server zurückgegeben wird (z.B. wegen einer 
172  // Exception in PHP), wird eine Fehlermeldung ausgegeben.
173  if (!KuferTools.Utils.isAjaxObjectCorrect(pLoadedObj)) {
174
175    var abbruchButton;
176    if (typeof pAbbruchButton !== 'undefined') {
177      abbruchButton = pAbbruchButton;
178    }
179    else {
180      abbruchButton = {
181        "Abbrechen": function () { $(this).dialog("close"); }
182      };
183    }
184
185    var meldungsText = 'Fehlerhafte Daten vom Server empfangen. ';
186    if (typeof pLoadedObj === 'string' && $(pLoadedObj).hasClass('accessDenied')) {
187      // Meldung: Sie haben keinen Zugriff.
188      meldungsText = $(pLoadedObj).html();
189    }
190    meldungsText += 'Bitte setzen Sie sich mit der Schulungseinrichtung in Verbindung. ';
191    meldungsText += 'Dies kann passieren, wenn Sie sich ausgeloggt haben oder ausgeloggt wurden. ';
192    meldungsText += '';
193    meldungsText += '<p style="color: red">Änderungen die gespeichert werden sollten, konnten nicht übernommen werden.</p>';
194    console.log('Falsche Daten in der Ajax-Response zum Ajax-Request mit dem Link ' + pLink + '. ');
195
196    if (typeof KuferTools.showMessage !== 'undefined') {
197      KuferTools.showMessage({ meldung: meldungsText, title: 'Achtung', buttons: abbruchButton });
198    } else { //in den alten Mitarbeitertools gibt es die Funktion KuferTools.showMessage nicht
199      showMessage(meldungsText, 'Achtung', abbruchButton); //TODO: entfernen, wenn die alten KuferTools gelöscht werden
200    }
201    KuferTools.Utils.AjaxLadebildschirm.Stop();
202    // Achtung: wird ausgewertet, um aus der aufrufenden Funktion auszusteigen;  daher ist hier true 
203    // für die Rückgabe nötig
204    return true;
205  }
206  return;
207}
208
209
210/**
211 * Prüft ob das vom Server empfangene Ajax Response Objekt korrekt ist
212 * 
213 * Falls der Inhalt des Ajax Response Objekts auf Serverseite leer ist, wird am Client ein Objekt der Form
214 * {page: ''} erzeugt, das für eine Fehlerausgabe im Dialog benutzt wird.
215 * 
216 * @param {Object} pLoadedObj
217 * @returns {Boolean} true, falls das Objekt ein korrekte Form hat.
218 */
219KuferTools.Utils.isAjaxObjectCorrect = function (pLoadedObj) {
220  if (typeof pLoadedObj !== 'object' || isempty(pLoadedObj) ||
221    (typeof pLoadedObj.page !== 'undefined' && isempty(pLoadedObj.page)))
222    return false;
223  return true;
224}
225
226/**
227 * Gibt einen Dialog für Zugriff verweigert aus
228 * 
229 * Wird ähnlich zu KuferTools.Utils.showAjaxErrorDialog verwendet; Allerdings wird hier kein
230 * Objekt sondern nur ein String mit dem Sichteninhalt vom Server empfangen und nur der Fall einer
231 * Zugriffsverletzung ausgegeben
232 * 
233 * @param string pData
234 * @returns {undefined|Boolean}
235 */
236KuferTools.Utils.showAccessDeniedDialog = function (pData) {
237  if (typeof pData === 'string' && $(pData).find('.accessDenied').length > 0) {
238    // Meldung: Sie haben keinen Zugriff.
239    var meldungsText = $(pData).html();
240    var abbruchButton = {
241      "Abbrechen": function () { $(this).dialog("close"); }
242    };
243    if (typeof KuferTools.showMessage !== 'undefined') {
244      KuferTools.showMessage({ meldung: meldungsText, title: 'Hinweis', buttons: abbruchButton });
245    } else { //in den alten Mitarbeitertools gibt es die Funktion KuferTools.showMessage nicht
246      showMessage(meldungsText, 'Hinweis', abbruchButton); //TODO: entfernen, wenn die alten KuferTools gelöscht werden
247    }
248    KuferTools.Utils.AjaxLadebildschirm.Stop();
249    // Achtung: wird ausgewertet, um aus der aufrufenden Funktion auszusteigen;  daher ist hier true 
250    // für die Rückgabe nötig
251    return true;
252  }
253  return;
254};
255
256/**
257 * Gibt einen Dialog für nicht eingeloggt aus
258 * 
259 * Wird ähnlich zu KuferTools.Utils.showAjaxErrorDialog verwendet; Allerdings wird hier kein
260 * Objekt sondern nur ein String mit dem Sichteninhalt vom Server empfangen.
261 * 
262 * @param string pData
263 * @returns {undefined|Boolean}
264 */
265KuferTools.Utils.showNotLoggedInDialog = function (pData) {
266  if (typeof pData === 'string' && $(pData).find('.notLoggedIn').length > 0) {
267    var meldungsText = $(pData).html();
268    var abbruchButton = {
269      "Abbrechen": function () { $(this).dialog("close"); }
270    };
271    if (typeof KuferTools.showMessage !== 'undefined') {
272      KuferTools.showMessage({ meldung: meldungsText, title: 'Hinweis', buttons: abbruchButton });
273    } else { //in den alten Mitarbeitertools gibt es die Funktion KuferTools.showMessage nicht
274      showMessage(meldungsText, 'Hinweis', abbruchButton); //TODO: entfernen, wenn die alten KuferTools gelöscht werden
275    }
276    KuferTools.Utils.AjaxLadebildschirm.Stop();
277    // Achtung: wird ausgewertet, um aus der aufrufenden Funktion auszusteigen;  daher ist hier true 
278    // für die Rückgabe nötig
279    return true;
280  }
281  return;
282};
283
284
285/**
286 * Allgemeine Funktionen zum Senden eine Ajax Requests und Handeln der AjaxResponse vom Server (im Responsive Stand)
287 * 
288 * Die Ajax Response wird auf Serverseite von der Funktion getAjaxObject($pView), die im entsprechenden Presenter
289 * (z.B. VerhaltensListeAuswahlPresenter) aufgerufen wird, aufgebaut. Ein solcher spezieller Presenter zum Bearbeiten eines
290 * Ajax Requests gibt die für eine  Ajax Response notwendigen Informationen in einem Std Objekt zurück (also u.a. zusätzlich zur
291 * Sicht noch benötigte .js und .css Files)
292 * 
293 * @param string pActionLink
294 * @param button pButtons
295 * @param string pWidth
296 * @param array(function) callBackFunctionAry  ermöglicht ggf. spezielle Handlings beim Bearbeiten der Ajax response
297 * @param function postHookFunction
298 * Folgende Parameter werden direkt an showMessage weitergeleitet:  pButtons, pWidth
299 */
300KuferTools.Utils.loadAndDisplayPage4Ajax = function (pActionLink, pButtons, pWidth, callBackFunctionAry, postHookFunction) {
301
302  KuferTools.Utils.AjaxLadebildschirm.Start();
303
304
305  //schickt ein Ajax Request an den ActionLink und  bearbeitet die Ajax Response vom Server; empfängt hierbei ein Datenobjekt (data), 
306  //das die HTMML Daten der Seite (bzw. Sicht) und zusätzl.Informationen (wie css u. js Dateien u. Deklarationen)  enthält
307
308  jQuery.post(pActionLink, function (data) {
309    KuferTools.Utils.AjaxLadebildschirm.Stop();
310    //wird ggf. in Fehlerfällen gebraucht
311    var abbruchButton = {
312      "Abbrechen": function () { $(this).dialog("close"); }
313    };
314
315    var loadedObject = KuferTools.Utils.getObjectFromJson(data);
316
317    // Falls kein oder ein fehlerhaftes Objekt zurückgegeben wird, wird eine Fehlermeldung ausgegeben.
318    if (KuferTools.Utils.showAjaxErrorDialog(loadedObject, pActionLink, abbruchButton))
319      return;
320
321    //CSS Dateien für die gerade geladene Page einbinden
322    if (jQuery.isArray(loadedObject.css)) {
323      KuferTools.Utils.loadCssFiles(loadedObject.css);
324    }
325    //JS Dateien für die gerade geladene Page einbinden
326    if (jQuery.isArray(loadedObject.js)) {
327      //noch nicht geladene JS Dateien einbinden
328      KuferTools.Utils.loadJsFiles(loadedObject.js);
329    }
330    if (jQuery.trim(loadedObject.jsdeclarations) !== '') {
331      eval(loadedObject.jsdeclarations);
332    }
333    registerNamespace("KuferTools.Inhalt");
334
335    //Achtung: die Seite loadedObject.inhalt enthält den Seiteninhalt
336    KuferTools.Inhalt = $(loadedObject.inhalt);
337
338    //Callback Funktionen, die vor der Ausgabe ausgeführt werden sollen, z.B. zum verändern von Eingabefeldern
339    if (typeof (callBackFunctionAry) != 'undefined') {
340      var callbackLen = callBackFunctionAry.length;
341      for (var i = 0; i < callbackLen; i++) {
342        var callBackFunction = callBackFunctionAry[i];
343        callBackFunction(); //ist hier z.B. spezielles Handling für Fach- und  Teilnehmeroptionen
344      }
345    }
346
347    // Hier keine Validierung von Formularelementen (Input-, Selectfeldern etc.) einbauen; Formular hat hier nämlich nichts mit dem 
348    // Sightbuilder zu tun; daher gibt es  z.B. auch keine Pflichtfelder die rot umrandet sein müssen
349
350    //Falls eine Fehlermeldung mitgeschickt wurde: diese ausgeben
351    if (loadedObject.error) {
352
353      //Beschreibung des Fehlers befindet sich in loadedObject.error
354      showMessage(loadedObject.error, 'Fehler aufgetreten');
355    }
356    else {
357      if (!isempty(loadedObject.inhalt)) {
358        //kein Fehler: Ausgabe des Formulars
359        KuferTools.showMessage({ meldung: KuferTools.Inhalt, title: loadedObject.title, buttons: pButtons, width: pWidth });
360        // Funktionen, die nach der Ausgabe des Dialogs/Formulars ausgeführt werden sollen; z.B. Validierungsfunktion für die
361        // Eingabefelder. Dazu müssen die Felder bereits vorhanden sein und das Formular via jQuery Selektor angesprochen 
362        //  werden
363        if (typeof (postHookFunction) === 'function') {
364          postHookFunction();
365        }
366      }
367      else {
368        var meldungsText = 'Die Eingabemaske für den Dialog ist leer. Hier müssen Eingabefelder hinzugefügt werden. Bitte setzen Sie sich mit der ' +
369          'Schulungseinrichtung in Verbindung.';
370
371
372        KuferTools.showMessage({ meldung: meldungsText, title: 'Fehler', buttons: abbruchButton });
373      }
374    }
375  }); //end jQuery.post(pActionLink, function(data) {...
376
377}//end KuferTools.Utils.loadAndDisplayPage4Ajax
378
379
380/**
381 * Lädt eine Page aus den KuferTools nach, bindet dann deren CSS und JS Dateien ein und 
382 * zeigt die Page dann als Dialog an. Während des Ladevorgangs wird der übliche  Ladeeffekt angezeigt.
383 * Ist z.B. für Ausgaben des Sightbuilders geeignet.
384 * Folgende Parameter werden direkt an showMessage weitergeleitet:
385 * pButtons, pWidth, pPosition 
386 */
387KuferTools.Utils.loadAndDisplayPage = function (pLink, pDataString, pButtons, pWidth, pPosition, pDoAfterDisplay) {
388  if (typeof (KuferTools.ReloadSchutz) != 'undefined' && typeof (KuferTools.ReloadSchutz.PostID) != 'undefined')
389    KuferTools.ReloadSchutz.PostID++;
390
391
392  KuferTools.Utils.AjaxLadebildschirm.Start();
393
394  //entspricht jQuery.ajax; d.h. die Methode verwendet Ajax Mechanismus
395  jQuery.post(pLink, pDataString, function (data) {
396
397    KuferTools.Utils.AjaxLadebildschirm.Stop();
398
399    var loadedPage = KuferTools.Utils.getObjectFromJson(data);
400
401    //CSS Dateien für die gerade geladene Page einbinden
402    if (jQuery.isArray(loadedPage.css)) {
403      KuferTools.Utils.loadCssFiles(loadedPage.css);
404    }
405    //JS Dateien für die gerade geladene Page einbinden
406    if (jQuery.isArray(loadedPage.js)) {
407      //noch nicht geladene JS Dateien einbinden
408      KuferTools.Utils.loadJsFiles(loadedPage.js);
409    }
410    if (jQuery.trim(loadedPage.jsdeclarations) != '') {
411      eval(loadedPage.jsdeclarations);
412    }
413    registerNamespace("KuferTools.Page");
414
415    //Achtung: die Seite loadedPage.page muss vom Sightbuilder erzeugt sein, damit die Validierung korrekt funktioniert ( siehe 
416    // Serverseitige Methode  getAjaxObject4Tab() in TabPageBase bzw. Methode getAjaxResponse() (z.B. in tooladressdaten.php)
417    KuferTools.Page = $(loadedPage.page);
418
419    var $formObj = KuferTools.Page.find('input, select, textarea');
420
421    //Falls eine Fehlermeldung mitgeschickt wurde: diese ausgeben
422    if (loadedPage.error) {
423      showMessage(loadedPage.error, 'Fehler aufgetreten'); //Beschreibung des Fehlers befindet sich in loadedPage.error
424    }
425    else {
426      //Ausgabe des Formulars, nur falls es mit passenden Eingabefeldern vom Server geschickt wurde
427      if (!isempty($formObj) && $formObj.length !== 0) {
428        //validiert das nachgeladene SightBuilder Formular: das muß immer passieren;  dadurch werden die Pflichtfelder mit roter 
429        // Umrandung angezeigt; der Rückgabewert wird hier NICHT ausgewertet;
430        validateForm($formObj);
431        showMessage(KuferTools.Page, loadedPage.header, pButtons, pWidth, pPosition);
432        if (typeof (pDoAfterDisplay) != 'undefined') {
433          pDoAfterDisplay();
434        }
435      }
436      // Fehlermeldungen müssten eigentlich in der PHP-Datei über page.error übergeben werden.
437      // Der folgende Block ist dafür verantwortlich, dass mit loadAndDisplayPage derzeit nur Formulare ausgegeben werden können.
438      else if ($formObj.length == 0) {
439        showMessage('Die Eingabemaske ist leer, hier müssen Eingabefelder hinzugefügt werden. Bitte setzen Sie sich mit einem Administrator in Verbindung.', 'Fehler aufgetreten');
440      }
441      else
442        showMessage('<div>Fehler: leeres oder ungültiges Formular vom Server empfangen. Bitte prüfen Sie die Sicht zu den ' +
443          loadedPage.header + ' in den Zentralen Einstellungen des Tools.</div>', 'Warnung');
444    }
445  });
446}; //end KuferTools.Utils.loadAndDisplayPage
447
448/**
449 * Liefert die bereits auf der Seite vorhandenen CSS Files
450 * 
451 * @returns {Array|KuferTools.Utils.getLoadedCssFiles.result}
452 */
453KuferTools.Utils.getLoadedCssFiles = function () {
454  var result = new Array();
455  jQuery('link[rel=stylesheet]').each(function (index, elem) {
456    var href = jQuery(elem).prop('href');
457    if (href !== undefined)
458      result.push(href);
459  });
460  return result;
461} //end KuferTools.Utils.getLoadedCssFiles
462
463/**
464 * Fügt die CSS Dateien aus pCssFiles in die Seite ein, die noch nicht da w
464aren.
465 * 
466 * @param {Array} pCssFiles 
467 */
468KuferTools.Utils.loadCssFiles = function (pCssFiles) {
469  //erstmal die bereits geladenen CSS Dateien raussuchen
470  var alreadyLoadedCss = KuferTools.Utils.getLoadedCssFiles();
471  var versionStr = 'version=' + KuferTools.Version; // also z.B. version=4.20.17.01
472
473
474  //noch nicht geladene CSS Dateien einbinden
475  jQuery.each(pCssFiles, function (index, elem) {
476
477    //Falls der Skriptname nicht im Array der bereits geladenen CSS Skripte enthalten ist, wird er angehängt
478    // statt: if (jQuery.inArray(elem, alreadyLoadedCss) === -1) {
479    if (!KuferTools.Utils.isScriptinArray(elem, alreadyLoadedCss)) {
480
481      //Ein noch nicht vorhandenes CSS File in den Header der Seite einhängen; dazu: letztes Element mit <link href="..." im 
482      //Header finden und dort das neugefundene Element als Link anhängen
483      jQuery('head').find('link[href]:last').after('<link rel="stylesheet" type="text/css" href="' + elem + '?' + versionStr + '"/>');
484      alreadyLoadedCss.push(elem);
485    }
486  }); // end jQuery.each(..)
487} //end KuferTools.Utils.loadCssFiles
488
489/**
490 * Liefert die bereits auf der Seite vorhandenen JS Files
491 * 
492 * @returns {Array|KuferTools.Utils.getLoadedJsFiles.alreadyLoadedScripts}
493 */
494KuferTools.Utils.getLoadedJsFiles = function () {
495  var alreadyLoadedScripts = new Array();
496  jQuery('script').each(function (index, elem) {
497    var src = jQuery(elem).prop('src');
498    if (src !== undefined)
499      alreadyLoadedScripts.push(src);
500  });
501  return alreadyLoadedScripts;
502} //end KuferTools.Utils.getLoadedJsFiles
503
504
505/**
506 * Fügt die JS Dateien aus pJsFiles in die Seite ein, die noch nicht da waren.
507 * 
508 * @param {Array} pJsFiles
509 */
510KuferTools.Utils.loadJsFiles = function (pJsFiles) {
511  var alreadyLoadedScripts = KuferTools.Utils.getLoadedJsFiles();
512
513  var versionStr = 'version=' + KuferTools.Version; // also z.B. version=4.20.17.01
514
515  jQuery.each(pJsFiles, function (index, elem) {
516    // zunächst das Script als Stringdarstellung ermitteln
517    var addLoadElem = '';
518    if (typeof (elem) === 'object') {
519      addLoadElem = elem.link;
520    }
521    else if (typeof (elem) === 'string') {
522      addLoadElem = elem;
523    }
524    //Falls der Skriptname nicht im Array der bereits geladenen Skripte enthalten ist, wird er angehängt
525    // statt: if (jQuery.inArray(addLoadElem, alreadyLoadedScripts) === -1) {
526    if (!KuferTools.Utils.isScriptinArray(addLoadElem, alreadyLoadedScripts)) {
527      // Bisher:
528      // jQuery.getScript(elem);
529      // Das lädt ein noch nicht vorhandene Javascript File via GET Request vom Server nach; Achtung??: Wurde hier wohl falsch 
530      // verwendet!!!. Müsste via getScrip(url) aufgerufen werden
531
532      // Aber viel besser ist folgendes (ohne Nachladen per Ajax)
533      //letztes Element mit <script src="..." im Body finden und dort das neugefundene Element als Script anhängen
534      jQuery('body').find('script[src]:last').after('<script charset="UTF-8" src="' + addLoadElem + '?' + versionStr + '">');
535      alreadyLoadedScripts.push(addLoadElem);
536    }
537  }); //end jQuery.each(..)
538} // end KuferTools.Utils.loadJsFiles
539
540
541/**
542 * Gibt an, ob ein Script String im Array von Script Strings enthalten ist
543 * 
544 * @param string pSearchScript
545 * @param Array(string) pScriptAry
546 * @returns boolean true falls der Suchstring im ScriptArray enthalten ist
547 */
548KuferTools.Utils.isScriptinArray = function (pSearchScript, pScriptAry) {
549  var isInside = false;
550  // pSearchScript ist relativ zum Pfad (z.B. https://localhost/develop/kufertools ) angegeben: also z.B. 
551  // ../jquery/jqueryXXmin.js oder z.B.  settings/sys/layout/js/project.js
552  // die Elemente aus dem Script Array haben hingegen folgende absolute Darstellung (mit Pfad und Version):
553  // https://localhost/develop/jquery/jqueryXXmin.js?version=4.20.17.01 oder
554  // https://localhost/develop/kufertools/settings/sys/layout/js/project.js?version=4.20.17.01
555
556  // statt: if (jQuery.inArray(pSearchScript, pScriptAry) === -1) isInside = false;
557
558  var searchScript = pSearchScript.replace(/^(\.\.\/)+/, "");
559  //prüft, ob z.B. jquery/jqueryXXmin.js im ScriptArray enthalten ist, via search von regulären Ausdrücken 
560  $.each(pScriptAry, function (index, elem) {
561    if (elem.search(searchScript) != -1 && !isInside) {
562      isInside = true;
563    }
564  });
565  return isInside;
566} //end KuferTools.Utils.isScriptinArray
567
568
569
570
571/**
572 * wandelt erstes Zeichen des Strings Großschreibung um
573 * Rest beleibt gleich (d.h. klein oder groß)
574 * @param string string
575 * @returns string
576 */
577KuferTools.Utils.ucFirst = function (string) {
578  return string.substring(0, 1).toUpperCase() + string.substring(1);
579}
580
581/**
582 * wandelt erstes Zeichen des Strings Großschreibung um
583 * Rest wird in Kleinschreibung umgewandelt
584 * @param string string
585 * @returns string
586 */
587KuferTools.Utils.ucFirstLc = function (string) {
588  return string.substring(0, 1).toUpperCase() + string.substring(1).toLowerCase();
589}
590
591/**
592 * Wandelt jedes Zeichen in ein HtmlEntitie um.
593 * 
594 * Vorsicht: einige jQuery-Funktionen encoden automatisch und prüfen nicht auf doppeltes Encoden.
595 * Z. B. .val und .attr führen selbst HTML-Encode durch. After und AppendTo nicht, da dort auch HTML-Tags enhalten sein dürfen.
596 * 
597 * Weitere Tips: .text statt .html verwenden, wenn kein html erwartet wird.
598 * 
599 * @returns string
600 */
601KuferTools.Utils.htmlEncode = function (string) {
602  if (typeof(string) !== 'undefined' && string !== null) {
603    return string.toString().replace(/./g, function (e) { return "&#" + e.charCodeAt(0) + ";"; });
604  }
605  return '';
606};
607
608/**
609 * Testet zwei Variablen auf inhaltliche Gleichheit - Arrays und Objekte werden gleich behandelt, indem die einzelnen
610 * Member durchgegangen werden.
611 * @param mixed a
612 * @param mixed b
613 * @return bool
614 */
615function equals(a, b) {
616  if (typeof a != typeof b)
617    return false;
618  if (a == null && b != null)
619    return false;
620  if (a != null && b == null)
621    return false;
622  if (typeof a == 'object') {//für Objekte und Arrays
623    if (a.length != b.length)
624      return false;
625    //for (var i = 0 ; i < a.length ; i++) {
626    for (var i in a) {
627      if (!equals(a[i], b[i]))
628        return false;
629    }
630    return true;
631  }
632
633  return a == b;
634}
635
636//pads left
637String.prototype.lpad = function (padString, length) {
638  var str = this;
639  while (str.length < length)
640    str = padString + str;
641  return str;
642}
643
644//pads right
645String.prototype.rpad = function (padString, length) {
646  var str = this;
647  while (str.length < length)
648    str = str + padString;
649  return str;
650}
651
652/**
653 * Entfernt whitespaces (also Leerzeichen etc)
654 *  (entspricht trimstr in PHP )
655 * s. auch trim() (project.js)
656 */
657KuferTools.Utils.trimS = function (str) {
658  if (typeof (str) == 'undefined')
659    return '';
660  str = str.replace(/^\s+/, '');
661  for (var i = str.length - 1; i >= 0; i--) {
662    if (/\S/.test(str.charAt(i))) {
663      str = str.substring(0, i + 1);
664      break;
665    }
666  }
667  return str;
668}
669
670/**
671 *  Entfernen von umgebendem HTML-Gedoehns
672 *  entfernt alle HTML Tags ausser die im exceptiontag angegebenen 
673 */
674KuferTools.Utils.stripHTML = function (str, exceptiontag) {
675  if (exceptiontag != undefined)
676    //entfernt alle HTML Tags ausser z.B. <img> ..</img> oder <a href..>..</a>(unabhängig von Klein/Großschreibung 
677    // wg. i = ignore case)
678    regexpression = new RegExp('<[^' + exceptiontag + ']\/?[^' + exceptiontag + '][^>]+(>|$)', "gi");
679  else
680    regexpression = /<\/?[^>]+(>|$)/g;
681  str = str.replace(regexpression, "");
682  str = str.replace("&nbsp;", " ");
683  return str;
684}
685
686let renderCounter = 0;
687/**
688 * Fässt mehrere Änderungen an einen Seitenabschnitt in einem Renderschritt zusammen.
689 * Kann stark die Performance verbessern, wenn in einer Auflistung alle Elemente einzeln geändert werden, und jedesmal
690 * ein Seiten-Rendern anstößt
691 *
692 * Vorsicht:
693 * 1. Es kann dazu führen, dass Trigger auf den Elementen nicht angestoßen werden
694 * 2. Die Elemente im Abschnitt können nicht neu per Selector geladen werden, bestehende Selectoren funktionieren weiterhin
695 *
696 * @param $element jQuery-Selector für den Abschnitt mit den Änderungen. Falls der Selector mehrere Elemente findet, wird dies nur für das erste Element durchgeführt.
697 * @param func die Funktion mit den Änderungen ohne Parameter. (Funktionen mit Parametern können in eine anonyme Funktion ohne Parameter geändert werden)
698 */
699KuferTools.Utils.renderInEinenSchritt = function($element, func) {
700  renderCounter++;
701
702  const idName = 'utils-platzhalter-' + renderCounter;
703  const $parent = $element.first().parent();
704  $parent.after('<div id="'+idName+'" style="display: none;"></div>');
705  $parent.detach();
706
707  func();
708
709  $('#' + idName).after($parent);
710  $('#' + idName).remove();
711
712
713}
714
715/**
716 * Liefert einen bestimmten Eintrag aus einem Array, wenn pValueName angegeben ist, 
717 * wird die entsprechende Eigenschaft des Objekts im Array verglichen
718 * wird das gesuchte Element nicht gefunden, wird null zurückgegeben 
719 * Beispiele: var b = new Array({"id":"3"},{"id":"4"});
720 * getFromArray(b, 3, "id") => liefert das erste Objekt aus dem Array
721 * var a = [1,2,3]; 
722 * getFromArray(a, 2) => liefert das zweite Element aus dem Array 
723 */
724function getFromArray(pArray, pValue, pValueName) {
725  if (pArray == null || (typeof pArray.length !== "number"))
726    return null;
727  if (pValueName != null && typeof (pValueName) === "string")
728    for (var x in pArray) {
729      if (pArray[x][pValueName] == pValue)
730        return pArray[x];
731    }
732  else {
733    for (var x in pArray) {
734      if (x == pValue)
735        return x;
736    }
737  }
738
739  return null;
740}
741
742/* Array Funktionen */
743/**
744 * Entfernt das index te Element aus Array array. => man kann hier auch wunderbar splice verwenden
745 */
746function removeFromArray(array, index) {
747  if (typeof (array) == 'object' && (array instanceof Array)) {
748    if (index == 0)
749      return array.slice(1);
750    if (index < array.length) {
751      return array.slice(0, index).concat(array.slice(index + 1));
752    }
753    else {
754      return array;
755    }
756  }
757  else
758    return null;
759}
760
761/**
762 * Prüft ob item in arr enthalten ist
763 */
764function in_array(item, arr) {
765  if (arr == null || !(typeof (arr) == 'object' && (arr instanceof Array)))
766    return false;
767  for (var p = 0; p < arr.length; p++) if (item == arr[p]) return true;
768  return false;
769}
770
771
772var _tmplCache = {}
773parseTemplate = function (str, data) {
774  /// <summary>
775  /// Client side template parser that uses &lt;#= #&gt; and &lt;# code #&gt; expressions.
776  /// and # # code blocks for template expansion.
777  /// NOTE: chokes on single quotes in the document in some situations
778  ///       use &amp;rsquo; for literals in text and avoid any single quote
779  ///       attribute delimiters.
780  /// </summary>    
781  /// <param name="str" type="string">The text of the template to expand</param>    
782  /// <param name="data" type="var">
783  /// Any data that is to be merged. Pass an object and
784  /// that object's properties are visible as variables.
785  /// </param>    
786  /// <returns type="string" />  
787  var err = "";
788  try {
789    var func = _tmplCache[str];
790    if (!func) {
791      var strFunc =
792        "var p=[],print=function(){p.push.apply(p,arguments);};" +
793        "with(obj){p.push('" +
794        //                        str
795        //                  .replace(/[\r\t\n]/g, " ")
796        //                  .split("<#").join("\t")
797        //                  .replace(/((^|#>)[^\t]*)'/g, "$1\r")
798        //                  .replace(/\t=(.*?)#>/g, "',$1,'")
799        //                  .split("\t").join("');")
800        //                  .split("#>").join("p.push('")
801        //                  .split("\r").join("\\'") + "');}return p.join('');";
802
803        str.replace(/[\r\t\n]/g, " ")
804          .replace(/'(?=[^#]*#>)/g, "\t")
805          .split("'").join("\\'")
806          .split("\t").join("'")
807          .replace(/<#=(.+?)#>/g, "',$1,'")
808          .split("<#").join("');")
809          .split("#>").join("p.push('")
810        + "');}return p.join('');";
811
812      //alert(strFunc);
813      func = new Function("obj", strFunc);
814      _tmplCache[str] = func;
815    }
816    return func(data);
817  } catch (e) { err = e.message; }
818  return "< # ERROR: " + err + " # >";
819  //return "< # ERROR: " + err.htmlEncode() + " # >";
820}
821
822/**
823 * Erweiterte typeof Funktion => damit kann man Arrays von Objekten unterscheiden
824 * liefert: 'array', 'null' oder typeof value
825 * siehe auch: jQuery.isArray()
826 */
827KuferTools.Utils.typeOf = function (value) {
828  var s = typeof value;
829  if (s === 'object') {
830    if (value) {
831      if (value instanceof Array) {
832        s = 'array';
833      }
834    } else {
835      s = 'null';
836    }
837  }
838  return s;
839}
840
841/*
842 * Schneidet die dargestellten Textinhalte so ab, dass sie in das umgebende Span passen.
843**/
844KuferTools.Utils.kuerzeInhaltAufZeile = function (klasse) {
845
846  var $spansToHandle = $('span.' + klasse);//.css('display','none');
847  if ($spansToHandle.length == 0)
848    return;
849
850  $spansToHandle.each(function (index, element) {
851    var schrittweite = 3;
852    var $element = $(element);
853    //alert("Elem: " + $element.html() + ' Anz. HTML Tags: ' + $element.find('*').length);
854    //checkt ob innerhalb des Elements noch irgendein HTML Tag gefunden wird; falls ja ist di length > 0
855    // und es wird mit dem nächsten Element weitergemacht (return true entspr. continue)
856    if ($element.find(':not(br)').length > 0)
857      return true;
858
859    // Betrifft z.B. das Feld kurstag_medien
860    if ($element.css('overflow-y').toLowerCase() == 'scroll') {
861      return true;
862    }
863
864    // Hoehe einer Zelle wird nicht benötigt
865    var hoeheEinzeilig = $element.css('line-height').match(/[0-9.]+/);
866    var vorgegebeneBreite = $element.width();
867
868    // Höhe einer einzelnen Span Zelle: diese kann je nach Einstellung unterschiedlich sein und ist von
869    // verschieden zu der Hoehe eines Zeichens (hoeheeinzeilig)
870    var vorgegebeneHoehe = Math.max(hoeheEinzeilig, $element.height());
871
872    //Title bei allen Elementen setzen (für den Mouseover Effekt) 
873    if ($element.prop("title") == "" || typeof ($element.prop("title")) == 'undefined')
874      $element.prop("title", $element.text());
875
876    //++++++++ Schritt 1: Wörter, welche Breiter als die maximale Breite sind werden gekürzt +++++++++++
877
878    var elemHtml = $element.html();
879    //Fügt um den Inhalt des Spans noch ein Span ein. Wenn das innere Span nun
880    //größer als das äußere ist, dann ist im Span ein Wort länger als das Span.
881    //hier genügt es nicht die width auf auto zu setzen, da sonst die Zeilenumbrüche verrutschen können
882    $element.html('<span class="widthTest">' + elemHtml + '</span>');
883    if (vorgegebeneBreite < $element.find('span.widthTest').width()) {
884
885      //Holt sich alle Wörter des entsprechenden Spans
886      var woerter = elemHtml.match(/(\w)+/g);
887
888      //Geht jedes Wort durch, und überprüft, ob es zu lang ist.
889      for (var i = 0; i < woerter.length; i++) {
890        $element.html('<span class="widthTest">' + woerter[i] + '</span>');
891        //Falls das Wort zu lange ist, kürze es, und prüfe die Breite erneut.
892        if (vorgegebeneBreite < $element.find('span.widthTest').width()) {
893          var originalWort = woerter[i];
894          while (vorgegebeneBreite < $element.find('span.widthTest').width()) {
895            woerter[i] = woerter[i].substring(0, woerter[i].length - schrittweite);
896            $element.html('<span class="widthTest">' + woerter[i] + '</span>');
897          }
898          //Ersetze das gekürzte Wort in elemHtml, ersetzt dabei noch die letzten drei Zeichen durch ...
899          elemHtml = elemHtml.replace(originalWort, woerter[i].substring(0, woerter[i].length - 3) + '...');
900        }
901      }
902    }
903    $element.html(elemHtml);
904
905    // ++++++++ Schritt 2: Text so kürzen, sodass die Höhe des Spans nicht überschritten wird.
906
907
908    //Damit  wird die Höhe des Elements auf die wirkliche Höhe gesetzt Z.B. von 16 auf 32 falls für die Darstellung
909    // mehrere Zeilen benötigt werden
910    $element.css('height', 'auto');
911
912    //Nur dann abschneiden, wenn es zu viele Zeilen sind (d.h. Höhe einer Zeile < Höhe des Spans)
913    if (vorgegebeneHoehe < $element.height()) {
914      var origText = $element.html();
915      var step = origText.length;
916      var aktTextLength = origText.length;
917
918      // BRs müssen gesondert behandelt werden
919      if ($element.find('br').length > 0) {
920
921        // einige Nicht-Druckbare Zeichen wie Zeilenumbrüche entfernen
922        origText = origText.replace(/[\x00-\x1F]/gi, '');
923
924        // Zunächst BRs vereinheitlichen, dann splitten
925        var zeilenAry = origText.replace(/<br[^>]>/gi, '<br>').split('<br>');
926        var zeilenAryCount = zeilenAry.length;
927        var tmpText = '';
928        for (var i = 0; i < zeilenAryCount; i++) {
929          var textVorher = tmpText;
930          if (i > 0) {
931            tmpText += '<br>';
932          }
933          tmpText += zeilenAry[i];
934          $element.html(tmpText);
935          if ($element.height() > vorgegebeneHoehe) {
936            origText = tmpText;
937            aktTextLength = tmpText.length;
938            step = aktTextLength - textVorher.length;
939            break;
940          }
941        }
942      }
943      KuferTools.Utils.KuerzeAufZeileRekursiv(origText, $element, vorgegebeneHoehe, aktTextLength, step);
944      // '...' kann under doofen Umständne breiter als die letzten drei abgeschnittenen Zeichen sein, 
945      // deshalb wird ein zusätzliches Zeichen entfernt
946      $element.html(origText.substring(0, $element.html().replace(/<br>$/, '').length - 4) + '...');
947      $element.css('height', vorgegebeneHoehe);
948    }
949  });  //end each-function
950
951}; // end kuerzeInhaltAufZeile
952
953/**
954 * Kürzt den Inhalt einer Tabelle so, dass alle Zeilen einzeilig werden.
955 * @param Selecter $pTable Selekter für die Tabelle
956 * @param bool pEinzeilig true: Zeilen werden gekürzt, false: Kürzung wird rückgängig gemacht.
957 */
958KuferTools.Utils.KuerzeTabelleAufZeile = function ($pTable, pEinzeilig) {
959  var $tdsInColumnAry = [];
960  var $span = $pTable.find('tbody tr td span');
961  var hoeheEinzeiligCache = {};
962  $span.each(function () {
963
964    // Falls Das Span noch ein A-Tag enthält, muss das gewählt werden, da sonst das schließende </a> 
965    // weggekürzt werden kann.
966    var $element = $(this);
967    var $aTag = $element.find('a');
968    if ($aTag.length > 0) {
969      $element = $aTag;
970    }
971    if (pEinzeilig) {
972
973      // Die einzilige Höhe kann im Chrome oder IE leider nicht ohne Test-Item ermittelt werden, da die 'normal' zurückgeben,
974      // statt der höhe. Aber ich gehe frecherweise davon aus, dass die Line-Height in einer Spalte immer gleich ist,
975      // sodass ich nur einmal pro Spalte das Testitem setze
976      // Muss nicht auch bei kuerzeInhaltAufZeile angepasst werden, da geben die Browser nomal die line-height zurück
977      var hoeheEinzeilig = $element.css('line-height').match(/[0-9.]+/);
978      if (!jQuery.isNumeric(hoeheEinzeilig)) {
979        var classes = $element.parents('td').prop('class');
980        if (hoeheEinzeiligCache.hasOwnProperty(classes)) {
981          hoeheEinzeilig = hoeheEinzeiligCache[classes];
982        } else {
983          var $testItem = $('<span class="testItem">M</span>');
984          $element.append($testItem);
985          hoeheEinzeilig = $testItem.height();
986          hoeheEinzeiligCache[classes] = hoeheEinzeilig;
987          $testItem.remove();
988        }
989      }
990
991      //Nur dann abschneiden, wenn auch wirklich mehrere Zeilen vorhanden sind (d.h. Höhe einer Zeile < Höhe des Spans)
992      if (hoeheEinzeilig < $element.height()) {
993
994        //var anzahlZeichenInDiesemSpan = anzahlZeichen - schrittweite;
995        var origText;
996        if (!$element.data('origText')) {//sonst wird der originale Text gelöscht, wenn zweimal hintereinander auf "einzeilig" geklickt wird
997          origText = $element.text();
998          $element.data('origText', origText);
999        }
1000        else
1001          origText = $element.data('origText');
1002        KuferTools.Utils.KuerzeAufZeileRekursiv(origText, $element, hoeheEinzeilig, origText.length, origText.length);
1003      }
1004    }
1005    // Handling für mehrzeilig
1006    else {
1007      var origText = $element.data('origText');
1008      if (origText != '')
1009        $element.text(origText);
1010    }
1011  });
1012};
1013
1014/**
1015 * Rekursive Funktion, welche den Text im Target auf die optimale Länge stutzt.
1016 * Am Ende enthält pTarget den gekürzten Text
1017 * 
1018 * @param pText: Originaltext
1019 * @param $pTarget: Selector auf das aktuelle Zielelement, welcher den Text enthält
1020 * @param pMaxHeight: Maximale Höhe des Elements
1021 * @param pAktTextLength: Aktuell untersuchte Textlänge. Variiert bei jedem Durchlauf. Empfohlener Startwert: pText.length
1022 * @param pStep: Schrittweite, um welche pAktTextLength bei jedem Durchlauf vergrößert oder verkleinert wird.
1023 *               Wird bei jedem Durchlauf halbiert (ggf. abgerundet). Empfohlener Startwert: pText.length
1024 */
1025KuferTools.Utils.KuerzeAufZeileRekursiv = function (pText, $pTarget, pMaxHeight, pAktTextLength, pStep) {
1026  // Abbruchbedingung: Schrittweite ist bei 0 angelangt
1027  if (pStep <= 0) {
1028    // Es kann sein, dass man beim Abbrechen um einen Buchstaben zu weit ist.
1029    if ($pTarget.height() > pMaxHeight) {
1030      $pTarget.html(pText.substring(0, pAktTextLength - 1));
1031    }
1032    return;
1033  }
1034  pStep = Math.floor(pStep / 2);
1035  $pTarget.html(pText.substring(0, pAktTextLength));
1036  // Höhe testen. Wenn zu Groß: um Step verkleinern, sonst um Step vergrößern.
1037  // Es kann an dieser Stelle nicht getestet werden, ob die Optimale länge ereicht wurde,
1038  // Daher die Zusatzbedingung in der Abbruchbedingung
1039  if ($pTarget.height() > pMaxHeight) {
1040    KuferTools.Utils.KuerzeAufZeileRekursiv(pText, $pTarget, pMaxHeight, pAktTextLength - pStep, pStep);
1041  }
1042  else if ($pTarget.height() <= pMaxHeight) {
1043    KuferTools.Utils.KuerzeAufZeileRekursiv(pText, $pTarget, pMaxHeight, pAktTextLength + pStep, pStep);
1044  }
1045};
1046
1047var spanEigentschaftenCache = [];
1048/*
1049 * liefert die Breite und Höhe eines Zeichens im Span und die Gesamtbreite des Spams
1050**/
1051KuferTools.Utils.getSpanEigenschaften = function ($pElement) {
1052  //wir brauchen die Breite pro Zeichen => ein M; dazu:  
1053  //Wegen einiger Firefoxversionen musste die Berechnung umgestellt werden
1054  //(Probleme beim berechnen der Höhe verschachtelter Spans):
1055  //Das Span wird geklont, die höhe und breite auf auto gesetzt,
1056  //und das Testzeichen direkt im Span eingefügt.
1057
1058  //Cached die Span-Eigenschaften, da diese bei Übersichtsseiten bei allen 
1059  //Spans mit gleichen Klassen Identisch sind.
1060  var classes = $pElement.prop('class');
1061  if (typeof (spanEigentschaftenCache[classes]) != 'undefined')
1062    return spanEigentschaftenCache[classes];
1063
1064  var $cloneelem = $pElement.clone(false);
1065  $cloneelem.html("M");
1066  $cloneelem.css('visibility', 'hidden').css('height', 'auto').css('width', 'auto');
1067  //ans Elternelement hängen
1068  $pElement.parent().append($cloneelem);
1069  var breiteProZeichen = $cloneelem.width();
1070  var hoeheEinzeilig = $cloneelem.height();
1071  $cloneelem.remove();
1072
1073
1074  //padding-left berücksichtigen, kommt als xpx => px abschneiden
1075  var breite = $pElement.width(); //.width() ist bereits ohne Padding!
1076
1077  var spanProperties = [];
1078  spanProperties[0] = breite;
1079  spanProperties[1] = breiteProZeichen;
1080  spanProperties[2] = hoeheEinzeilig;
1081
1082  spanEigentschaftenCache[classes] = spanProperties;
1083
1084  return spanProperties;
1085} //end getSpanEigenschaften
1086
1087
1088/**
1089 * Gibt die Eingabeelemente eines Bereichs zurück.
1090 * 
1091 * Berücksichtigt:
1092 * inputs ohne type= image, submit, reset, button, hidden
1093 * textarea
1094 * select
1095 * 
1096 * Disabled und versteckte Felder werden ebenfalls nicht zurückgegeben.
1097 * 
1098 * @param $pTarget jQery-Selector
1099 * @return jQery-Selector
1100 */
1101KuferTools.Utils.getInputableElements = function ($pTarget) {
1102  var $elements = $();
1103
1104  $elements = $elements.add($pTarget.find('input').filter('[type!="image"]').filter('[type!="button"]')
1105    .filter('[type!="submit"]').filter('[type!="reset"]')
1106    .filter('[type!="hidden"]'));
1107
1108  $elements = $elements.add($pTarget.find(':is(textarea, select)'));
1109
1110  $elements = $elements.add($pTarget.find('.mnlist'));
1111
1112  $elements = $elements.filter(':not( :disabled)');
1113  //eine disabled Klasse check_disabled wieder hinzufügen: wird beim Validieren gebraucht um ein 
1114  //form_error Element zu erzeugen
1115  $elements = $elements.add($pTarget.find('input.check_disabled:disabled'));
1116
1117
1118  return $elements;
1119};
1120
1121
1122/**
1123 * Überschreibt die Werte aus obj1 mit denen aus obj2 u. fügt die Werte aus obj2 hinzu (falls sie in obj1 nicht existieren)
1124 * @param obj1
1125 * @param obj2
1126 * @returns obj3 neues object, das auf obj1 und obj2 basiert
1127 */
1128KuferTools.Utils.mergeObjects = function (obj1, obj2) {
1129  var obj3 = {};
1130  for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
1131  for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
1132  return obj3;
1133}
1134
1135
1136/** Ab hier meine n-m List Implementierung */
1137NMList = function () {
1138  //public variables
1139  this.data = [];//muss eine Kopie der tatsächlichen Daten sein! //Zeilen!
1140  this.rowTemplate = '';//template für die einzelnen Zeilen
1141  this.colGroup = ''; //Colgroup für die Tabelle um Spalten mit unterschiedlichen Breiten ausgeben zu können
1142  this.rowHeader = '';//Zeile mit der Headerzeile (<tr><th>spalte1</th>...</tr>)
1143  this.prefix = '';//Präfix für die Tabelle, ... nötig, wenn mehr als eine NMList auf einer Seite sind
1144  this.uiBezeichnung = '';//Text, der vor einfügen, löschen usw. eingefügt werden soll
1145  this.$target = null;//div, in das die Tabelle und die Buttons eingehängt werden sollen
1146  this.readOnly = false;//wenn true, werden keine Buttons angezeigt
1147  // wenn true, wird der Bearbeiten Button nicht angezeigt und ein Double-Click Event auf die Elemente aus der Liste 
1148  // ist auch nicht möglich (da sie nicht bearbeitbar sein sollen)
1149  this.noUpdate = false;
1150  //wenn true, wird der Details Subscreen aus einer Hinterlegung befüllt; dann ist auch kein Bearbeiten mgl.
1151  // d.h. this.noUpdate ist auch true
1152  this.subscreenfromHL = false;
1153  //ist true, wenn die Tabellen sich in der Breite flexibel an das Elementfeld anpassen soll, wenn das Element z.B.
1154  //in den Zentr. Einstellungen (z.B. Kennzeichen Element) festgelegt wird; Tabelle muss dann in ein übergeordnetes
1155  //div eingebettet werden
1156  this.flexibleWidth = false;
1157  // Daten aus der Hinterlegung für den Details Subscreen (wird z.B. für die Kennzeichenliste im Tool Teilnehmerinfo  oder 
1158  // die Anwesenheitsliste im Dialog zum  Terminkalender im Dozententool verwendet)
1159  this.hldata = [];
1160  // Daten die bereits aus der Hinterlegung im Details Subscreen selektiert wurden; mit Hilfe dieses Arrays wird ein mehrmaliges 
1161  // Auswählen desselben Eintrags in die Haupttabelle verhindert (wird bisher nur für Anwesenheiten im Dialog zum  Terminkalender verwendet
1162  this.selectedhldata = [];
1163  // 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 
1164  // bereitgestellt
1165  this.deleteAll = false;
1166  //Image zum Button zum Löschen aller Einträge aus der NMList; kann von PHP aus besetzt werden
1167  this.deleteAllImg = '';
1168
1169  //öffentliche/privilegierte Methoden, die überschrieben werden müssen
1170  this.getNewObject = function () { return null; } //Funktion, die ein Objekt liefert, das neu eingefügt wird
1171  this.showSubscreen = function (pData, pNewItem) { }; //Detailbildschirm für Bearbeiten/Hinzufügen eines Datensatzes: 
1172  //pNewItem: boolean ob Element neu angelegt wurde oder bearbeiten
1173  this.restoreSubscreenData = function (pData, $dialog) { }; //Daten aus dem Detailbildschirm für einen Datensatz zurückschreiben
1174  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.
1175  this.cannotDelete = function () { };
1176  this.beforeDelete = function (pData) { };//wird vor dem Löschen aufgerufen
1177  this.postDelete = function () { };//wird nach dem Löschen und vor dem darauffolgenden Rendern aufgerufen 
1178
1179  //private variables
1180  var selectedIndex = null;
1181  var _$targetTbody = null;
1182  var _initialized = false;
1183  var _tableId = 'nmList';
1184  var _tableRowClass = 'nmRow';
1185
1186  //privileged methods - can access the private variables and methods, and is itself accessible to the public methods and the outside
1187  this._renderData = function () {
1188    //if (!_initialized) {
1189    _initialize(this);
1190    //}
1191    _$targetTbody.remove('tr');
1192    if (this.data && this.data.length > 0) {
1193      var tabellenInhalt = '';
1194      for (var i = 0; i < this.data.length; i++) {
1195
1196        var aktData = this.data[i];
1197        var htmlEncodedData = Object.keys(aktData).reduce(function (previous, current) {
1198          previous[current] = (typeof (aktData[current]) == 'string') ? KuferTools.Utils.htmlEncode(aktData[current]) : aktData[current];
1199          return previous;
1200        }, {});
1201
1202        var row = parseTemplate(this.rowTemplate, htmlEncodedData);
1203        tabellenInhalt += row;
1204      }
1205      _$targetTbody.append(tabellenInhalt);
1206      //den Rows noch die Klasse verpassen
1207      _$targetTbody.find('tr').addClass(_tableRowClass);
1208      if (selectedIndex >= this.data.length)
1209        selectedIndex = null;
1210      this.highlight();
1211    }
1212  };
1213
1214  this.getCurrentData = function () {
1215    if (this.data && this.data.length > selectedIndex) {
1216      return this.data[selectedIndex];
1217    }
1218  }
1219
1220  this._activateButtons = function () {
1221  };
1222  this.highlight = function () { //durchaus dazu gedacht, von außen aufgerufen zu werden
1223    var $trs = _$targetTbody.find('tr.' + _tableRowClass);
1224    $trs.filter(':odd').removeClass('even active').addClass('odd');
1225    $trs.filter(':even').removeClass('odd active').addClass('even');
1226    if (!isNaN(parseFloat(selectedIndex)) && isFinite(selectedIndex))
1227      $trs.eq(selectedIndex).addClass('active');
1228  };
1229  this.showDetailDialog = function (extraAnw) {
1230  };
1231
1232  //private methods => hier gibts bei Vererbung keinen Zugriff auf this mehr, deshalb als Parameter param übergeben
1233  var _initialize = function (param) {
1234    if (param.$target) {
1235      //Buttons bauen, falls nicht readOnly
1236
1237      //zunächst leeres Div
1238      var $buttons = $('<div id="buttonleiste_vert"></div>');
1239      if (!param.readOnly) {
1240        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>';
1241
1242        //Bearbeiten Button nur hinzufügen, falls das Update erlaubt ist, ansonsten die Höhe der Buttons vermindern
1243        if (param.noUpdate)
1244          $buttons.css('height', '-=35');
1245        else {
1246          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>';
1247        }
1248        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>';
1249        //var $buttons = $('<div id="buttonleiste_vert">' + divString +  '</div>' +   '</div>');
1250
1251        if (param.deleteAll) {
1252          $buttons.css('height', '+=35');
1253          divString += '<div style="margin-left: 5px; margin-top: 5px; height: 35px; resize: none;">' + param.deleteAllImg + '</div>';
1254          //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>';
1255        }
1256        $buttons.append(divString);
1257      }
1258
1259      var tablename = param.prefix + _tableId;
1260      //Tabelle bauen
1261      var $table = $('<table id="' + tablename + '"></table>');
1262      if (param.colGroup) {
1263        $table.append('<colgroup>' + param.colGroup + '</colgroup>');
1264      }
1265      if (param.rowHeader) {
1266        $table.append('<thead>' + param.rowHeader + '</thead>');
1267      }
1268      _$targetTbody = jQuery('<tbody></tbody>');
1269      $table.append(_$targetTbody);
1270      param.$target.empty();//leeren, da die initialize Methode ja nicht nur einmal aufgerufen wird
1271      param.$target.append($buttons);
1272
1273      // Tabelle ggf. in ein div packen (um sie flexblel in der Breite vergrössern zu können (falls
1274      //das Feld in den Zentr. Einst. erweitert wird)
1275      var $appendObject = $table;
1276      if (param.flexibleWidth) {
1277        //Id ist dann z.B. kennzListDiv
1278        $appendObject = $('<div id="' + param.prefix + 'ListDiv"></div>');
1279        $appendObject.append($table);
1280      }
1281      param.$target.append($appendObject);
1282
1283      //selectedIndex = 0;//ersten Datensatz automatisch auswählen?
1284
1285      if (!param.readOnly) { //bei readonly gibts keine Buttons!
1286        //hier noch die onclick Handler für die Buttons
1287        //-löschen
1288        $buttons.find('#button_delete').on('click', function () {
1289          if (selectedIndex != null) {
1290            if (param.canDelete(param.data[selectedIndex])) {
1291              param.beforeDelete(param.data[selectedIndex]);
1292              param.data.splice(selectedIndex, 1);//=> 1 Element an Index selectedIndex aus dem Array param.data entfernen
1293
1294              //den gelöschten Wert ggf. auch aus dem Array der bereits selektierten Werte (aus der Hinterlegung) entfernen
1295              // wird nur in Klassen verwendet, die sich die selektierten werte im  Array merken (z.B. in AnwesenheitList) 
1296              if (param.selectedhldata.length > 0)
1297                param.selectedhldata.splice(selectedIndex, 1);
1298
1299              param.selectedIndex = null;
1300              //Hook methode zum Nachbearbeiten der Dateninhalte in  den abgeleiteten Klassen (ist z.B. in Dokumentelist wg. der
1301              // Reihenfolge notwendig)
1302              param.postDelete();
1303              param.renderData();
1304            }
1305            else
1306              param.cannotDelete();
1307          }
1308        });
1309
1310        $buttons.find('#button_update').on('click', function () {
1311          if (selectedIndex != null && !isNaN(selectedIndex) && selectedIndex >= 0 && selectedIndex < param.data.length) {
1312            param.showSubscreen(param.data[selectedIndex], false);
1313          }
1314          else
1315            showMessage('Sie müssen erst durch Klicken eine Zeile auswählen.', 'Hinweis');
1316        });
1317        $buttons.find('#button_insert').on('click', function () {
1318          //Par. param ist hier das aktuelle Objekt this (also die NMList) 
1319          param.$target.find('#' + tablename + ' tbody');
1320          param.data.push(param.getNewObject());
1321          selectedIndex = param.data.length - 1;
1322          param.renderData();
1323
1324          //Standard: Subscreen (Details) zum aktuell gewählten Eintrag
1325          var subscreendata = param.data[selectedIndex];
1326          // Subscreen wird fest aus  einer Hinterlegung gefüllt
1327          if (param.subscreenfromHL)
1328            subscreendata = param.hldata;
1329
1330          param.showSubscreen(subscreendata, true);
1331        });
1332
1333        //Nur, falls deleteAll auch erlaubt ist den Klickhandler hierfür hinzufügen
1334        if (param.deleteAll) {
1335          //Klickhandler für deleteall
1336          $buttons.find('#button_deleteAll').on('click', function () {
1337            //hier ist param.canDelete für alle Eelmente true, sonst macht deleteAll ja keinen Sinn
1338            param.data.splice(0); // => ab Index 0 alle Elemente aus dem Array param.data entfernen
1339            // ggf. auch das Array der bereits selektierten Werte (aus der Hinterlegung) löschen
1340            // wird nur in Klassen verwendet, die sich die selektierten Werte im  Array merken (z.B. in AnwesenheitList) 
1341            if (param.selectedhldata.length > 0)
1342              param.selectedhldata.splice(0);
1343            param.renderData();
1344          }); //end Klickhandler für deleteall   
1345        }// end if (param.deleteAll)   
1346      } //end if if (!param.readOnly) {
1347
1348      //Für die Tabelle
1349      /** Handling der Klick-Events: Bei einfachem Klick nur die gewählte Tabellenzelle highlighten (markieren)
1350      /** bei Doppelklick: Zelle markieren und Dialog aufrufen 
1351      /** hierbei: eventhandler an tbody knüpfen; dann muss NICHT fuer jedes neu erzeugte tr Element
1352       ein Eventhandler angehängt werden;
1353       Achtung:      
1354       Auch bereits gespeicherte Werte werden bearbeitet
1355      */
1356      _$targetTbody.on('click', function (event) {
1357        var $eventTarget = $(event.target);
1358        //selectedIndex muss vor param.highlight ausgeführt werden.
1359        selectedIndex = $eventTarget.parents('tr.nmRow').index();
1360        param.highlight();
1361      });
1362
1363      _$targetTbody.on('dblclick', function (event) {
1364        var $eventTarget = $(event.target);
1365        //Wird durch das Clickevent bereits abgedeckt
1366        //selectedIndex = $eventTarget.parents('tr.nmRow').index();
1367        //param.highlight();
1368        //Doppelklick ist nur im Bearbeiten Modus möglich d.h. wenn auch der Bearbeiten Button angezeigt wird
1369        if (!param.readOnly && !param.noUpdate)
1370          param.showSubscreen(param.data[selectedIndex]);
1371      });
1372
1373      _initialized = true;
1374    }
1375    else {
1376      throw 'InvalidState: $target ist nicht angegeben';
1377    }
1378  } //end _initialize = function(param)...
1379} //end NMList
1380
1381
1382/** public methods for NMList */
1383/** Lädt die Tabelle neu aus Data */
1384NMList.prototype.renderData = function () {
1385  this._renderData();
1386}
1387NMList.prototype.removeLast = function () {
1388  this.data.splice(this.data.length - 1, 1);
1389}
1390
1391NMList.prototype.removeAll = function () {
1392  this.data.splice(0, this.data.length);
1393}
1394
1395/**
1396 * Wandelt JSON-Strings, welche mit JsonOperations erzeugt wurden, in Objekte um.
1397 * Gibt bei ungültigem JSON den String direkt zurück. Kann dazu verwendet werden,
1398 * um Fehlermeldungen von seitens PHP auszugeben.
1399 * 
1400 * Verwendet die potentiell unsichere Methode eval, dafür muss json2.js nicht eingebunden werden.
1401 */
1402KuferTools.Utils.getObjectFromJson = function (pJson) {
1403  if (typeof pJson === 'object') {
1404    return pJson; // Ist bereits ein Objekt
1405  }
1406
1407  pJson = KuferTools.Utils.trimS(pJson);
1408  var toDecode = pJson;
1409  var result = { 'page': '' };
1410  // Beim Abfangen von Exceptions wird die entspr. Exception Meldung (immer ein String) ausgegben
1411  if (!isempty(toDecode)) {
1412    try {
1413      result = JSON.parse(toDecode);
1414    }
1415    catch (e) {
1416      result = toDecode;
1417    }
1418  }
1419  return result;
1420};
1421
1422/**
1423 * Erwartet einen mit JsonOperations erzeugen JSON-String, welcher anschließend base64 encoded wurde
1424 * 
1425 * das base64-encoding erleichtert die Handhabung mit Sonder- und Steuerzeichen
1426 * base64.js muss eingebunden sein.
1427 */
1428KuferTools.Utils.getObjectFromJsonBase64 = function (pBase64Json) {
1429  return KuferTools.Utils.getObjectFromJson(Base64.decode(pBase64Json));
1430};
1431/**
1432 * Wandelt ein Opjekt zu seiner JSON-String-Repräsentation um.
1433 * 
1434 * Falls es als Value in einem Hidden-Input verwendet werden soll, sollte lieber KuferTools.Utils.getJsonFromObjectBase64
1435 * verwendet werden, da JSON sonst Probleme mit Steuer- und Sonderzeichen macht.
1436 * json2.js muss eingebunden sein
1437 */
1438KuferTools.Utils.getJsonFromObject = function (pObject) {
1439  return JSON.stringify(pObject);
1440};
1441
1442/**
1443 * Wandelt ein Opjekt zu seiner JSON-String-Repräsentation um, und codiert es anschließend auf base64
1444 * 
1445 * Ideal um JSON-Strings in Values von z. B. Hidden-Inputs zu stecken, da die Sonder- und Steuerzeichen
1446 * von JSON Probleme machen.
1447 */
1448KuferTools.Utils.getJsonFromObjectBase64 = function (pObject) {
1449  return Base64.encode(KuferTools.Utils.getJsonFromObject(pObject));
1450};
1451
1452registerNamespace('KuferTools.Utils.Tabs');
1453/**
1454 * Gibt die ID des aktiven Tabs zurück. Wenn kein aktiver Tab gefunden wird, wird ein Leerstring zurückgegeben
1455 * 
1456 * Die ID kann als Anker für Links verwendet werden, dass der entsprechende Tab beim Laden geöffnet wird.
1457 * 
1458 * @returns String
1459 */
1460KuferTools.Utils.Tabs.GetIdFromActiveTab = function () {
1461  var $activeTab = KuferTools.Utils.Tabs.GetActiveTab();
1462  if ($activeTab.length > 0) {
1463    return $activeTab.prop('id');
1464  }
1465  return '';
1466};
1467
1468/**
1469 * Gibt den aktiven Tab zurück.
1470 * 
1471 * @returns String
1472 */
1473KuferTools.Utils.Tabs.GetActiveTab = function () {
1474  return $('div[id^="tabs-"]:visible');
1475};
1476
1477/**
1478 * Liefert false, wenn pString nicht in ein Datum umformatiert werden kann, 
1479 * oder ein ungültiges Datum darstellt,
1480 * String im Format TT.MM.JJJJ sonst.
1481 */
1482registerNamespace('KuferTools.Utils.Date');
1483KuferTools.Utils.Date.formatDate = function (pString) {
1484  if (pString == null) return false;
1485
1486  //Damit auch Fr. 21.03.13 gefunden wird.
1487  var tmpDate = pString.match(/[1-9][0-9.]*/);
1488  if (tmpDate) {
1489    pString = tmpDate[0];
1490  } else {
1491    return false;
1492  }
1493  var date = pString.split('.');
1494  if (date.length < 2 || date.length > 3) {
1495    return false;
1496  }
1497  var day = getIntFromDatePart(date[0]);
1498  var month = getIntFromDatePart(date[1]);
1499  var year = getIntFromDatePart(date[2]);
1500  if (year.toString().length != 4) {
1501    if (year.toString().length > 4)
1502      return false;
1503    year = '2' + year.toString().lpad('0', 3);
1504    year = getIntFromDatePart(year);
1505  }
1506  var dateString = date[0].toString().lpad('0', 2) + "." + date[1].toString().lpad('0', 2) + "." + year.toString();
1507  if (!(dateString.match(/^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$/) &&
1508    day >= 1 && day <= 31 &&
1509    month >= 1 && month <= 12))
1510    return false;
1511  //Überprüft das Datum auf Monatslängen und Schaltjahre
1512  //Fängt beim Monat bei 0 zu zählen an! Konvertiert Daten wie 31.04.2010 zu 01.05.2010!
1513  var dateObject = new Date(year, month - 1, day);
1514  if (year != dateObject.getFullYear()
1515    || month != (dateObject.getMonth() + 1)
1516    || day != dateObject.getDate())
1517    return false;
1518  return dateString;
1519}
1520
1521/**
1522 * Gibt true zurück, falls dateBefore vor dateAfter liegt, sonst false
1523 * Es erfolgt keine weitere Prüfung auf das korrekte Datumsformat (d.m.Y)
1524 */
1525KuferTools.Utils.Date.isDateBefore = function (dateBefore, dateAfter) {
1526  var dateBeforeAry = dateBefore.split('.');
1527  var dateAfterAry = dateAfter.split('.');
1528
1529  //Vergleich nacheinander Jahr, Monat, Tag, und bricht ab, sobald das Ergebnis eindeutig ist.
1530  for (var i = 2; i >= 0; i--) {
1531    var beforeTemp = parseInt(dateBeforeAry[i].replace(/^0+/, ''));
1532    var afterTemp = parseInt(dateAfterAry[i].replace(/^0+/, ''));
1533    if (beforeTemp > afterTemp)
1534      return false;
1535    else if (beforeTemp < afterTemp) {
1536      return true;
1537    }
1538  }
1539  //Datumswerte sind identisch
1540  return false;
1541};
1542
1543KuferTools.Utils.Date.isDateBeforeOrEqual = function (dateBefore, dateAfter) {
1544  return dateBefore == dateAfter || KuferTools.Utils.Date.isDateBefore(dateBefore, dateAfter);
1545};
1546
1547KuferTools.Utils.isTimeBeforeOrEqual = function (timeBefore, timeAfter) {
1548  return timeBefore.lpad('0', 5) <= timeAfter.lpad('0', 5);
1549};
1550
1551/**
1552 * Gibt anhand eines Javascript-Date-Objekts die Stringrepräsentation zurück
1553 * DD.MM.YYYY
1554 */
1555KuferTools.Utils.Date.getDateString = function (pDateObj) {
1556  return KuferTools.Utils.Date.formatDate(
1557    pDateObj.getDate() + '.' + (pDateObj.getMonth() + 1) + '.' + pDateObj.getFullYear()
1558  );
1559}
1560
1561/**
1562 * Gibt ein DateObjekt relativ zum übergebenen Datum zurück. Kann auch in die Vergangenheit gehen.
1563 * daysToAdd 0: heute
1564 * daysToAdd 1: morgen
1565 * daysToAdd -1: gestern
1566 */
1567KuferTools.Utils.Date.addDays = function (pDateObj, daysToAdd) {
1568  var date = new Date(pDateObj.getTime()); //Deep copy
1569  date.setDate(date.getDate() + daysToAdd); // Monats und Jahresumbrüche werden automatisch behandelt.
1570  return date;
1571}
1572
1573/**
1574 * Gibt ein DateObjekt auf den ersten Wochentag des übergebenen Datums zurück (Montag).
1575 */
1576KuferTools.Utils.Date.getFirsDayOfWeek = function (pDateObj) {
1577  var date = new Date(pDateObj.getTime()); //Deep copy
1578  //getDay(): Sonntag = 0, Montag = 1 usw.
1579  //Mathematischer Trick: 0=>6, 1=>0, 2=>1, 3=>2... entspricht die Anzahl der Tage zwischen dem
1580  //Datum und dem letzten Montag
1581  var daysFor = (date.getDay() + 6) % 7;
1582  date.setDate(date.getDate() - (daysFor));
1583  return date;
1584}
1585
1586/**
1587 * Gibt ein DateObjekt auf den letzten Wochentag des übergebenen Datums zurück (Sonntag)
1588 */
1589KuferTools.Utils.Date.getLastDayOfWeek = function (pDateObj) {
1590  //Benutzt getFirsDayOfWeek auf ein Datum nächster Woche, und geht dann einen Tag zurück.
1591  var date = new Date(pDateObj.getTime()); //Deep copy
1592  date.setDate(date.getDate() + 7);
1593  date = KuferTools.Utils.Date.getFirsDayOfWeek(date);
1594  date.setDate(date.getDate() - 1);
1595  return date;
1596}
1597
1598/**
1599 * Gibt ein DateObjekt auf den ersten Tag des übergebenen Monats zurück
1600 */
1601KuferTools.Utils.Date.getFirstDayOfMonth = function (pDateObj) {
1602  var date = new Date(pDateObj.getTime()); //Deep copy
1603  date.setDate(1);
1604  return date;
1605}
1606
1607/**
1608 * Gibt ein DateObjekt auf den letzten Tag des übergebenen Monats zurück
1609 */
1610KuferTools.Utils.Date.getLastDayOfMonth = function (pDateObj) {
1611  //Trick: Setzt Monat auf nächsten Monat, den Tag jedoch auf 0 (= letzter Tag des vormonats)
1612  return new Date(pDateObj.getFullYear(), pDateObj.getMonth() + 1, 0);
1613}
1614
1615KuferTools.Utils.Date.NameOfMonth = function(pDateObj) {
1616  let month = pDateObj.getMonth();
1617
1618  // In JS werden Monate ab 0 gezählt
1619  switch(month) {
1620    default:
1621    case 0: return 'Januar';
1622    case 1: return 'Februar';
1623    case 2: return 'März';
1624    case 3: return 'April';
1625    case 4: return 'Mai';
1626    case 5: return 'Juni';
1627    case 6: return 'Juli';
1628    case 7: return 'August';
1629    case 8: return 'September';
1630    case 9: return 'Oktober';
1631    case 10: return 'November';
1632    case 11: return 'Dezember';
1633  }
1634};
1635
1636KuferTools.Utils.Date.getFirstDayOfYear = function (pDateObj) {
1637  var date = new Date(pDateObj.getTime()); //Deep copy
1638  date.setDate(1);
1639  date.setMonth(0);
1640  return date;
1641}
1642
1643KuferTools.Utils.Date.getLastDayOfYear = function (pDateObj) {
1644  // Monate beginnen mit 0, Tage mit 1
1645  return new Date(pDateObj.getFullYear(), 11, 31);
1646}
1647
1648/**
1649 * Gibt ein JS-Dateobj anhand eines Strings im deutschen Format au
1649s.
1650 */
1651KuferTools.Utils.Date.getDateObj = function (pDateString) {
1652  var parts = pDateString.match(/(\d+)/g);
1653  // zero-based-Monat, daher -1
1654  return new Date(parts[2], parts[1] - 1, parts[0]);
1655}
1656
1657/**
1658 * Wandelt einen String rgb( r, g, b ) in #RRGGBB um
1659 */
1660KuferTools.Utils.RgbToHex = function (pRgb) {
1661  var rgb = pRgb.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/);
1662  function hex(x) {
1663    return ("0" + parseInt(x).toString(16)).slice(-2);
1664  }
1665  return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
1666};
1667
1668KuferTools.Utils.getMinutesProUE = function () {
1669  var minutesProUe = 45;
1670  if (KuferTools.Kursplanung && KuferTools.Kursplanung.MinutesProUe) {
1671    minutesProUe = KuferTools.Kursplanung.MinutesProUe;
1672  }
1673  return minutesProUe;
1674}
1675
1676registerNamespace("KuferTools.Utils.UiFunctions");
1677KuferTools.Utils.UiFunctions.setzeCheckboxNachChanged = function (pSelektorChanger, pSelektorCheckBox, pMitHinterlegung) {
1678  jQuery(pSelektorChanger).on('change', function () {
1679    jQuery(pSelektorCheckBox).prop('checked', true);
1680  });
1681  //man kann nur Custom-Events über jQuery ansteuern => hier den Event fromHLwindow
1682  //so kann, man z.B. Checkboxen setzen, wenn der Uhrzeit Wert aus der Hinterlegung geändert wurde.
1683  if (pMitHinterlegung) {
1684    jQuery(pSelektorChanger).on('fromHLwindow', function () {
1685      jQuery(pSelektorCheckBox).prop('checked', true);
1686    });
1687  }
1688};
1689/**
1690 * Wandelt Minuten in UE um, berücksichtigt hierbei abweichende minProUe des Kurses und 
1691 * 22/45 = 0,5 UE (KuferSQL/Zentrale Einstellungen/Organisation 22 Minuten von 45 Minuten gelten als 0,5 UE )
1692 * 
1693 * Hierzu sollte KuferTools.Kursplanung.MinutesProUe und KuferTools.KurstagPlanung.HALBUE gesetzt sein
1694 */
1695KuferTools.Utils.minToUe = function (pMinuten) {
1696  pMinuten *= 1; //Damit pMinuten nicht als String behandelt wird.
1697  var minutesProUe = KuferTools.Utils.getMinutesProUE();
1698  //sicherheitshalber mal 0 UEs zurückgeben bei weniger als 0 Minuten
1699  if (pMinuten <= 0)
1700    return 0;
1701
1702  var halbue = false;
1703  if (KuferTools.KurstagPlanung.HALBUE == "W")
1704    halbue = true;
1705  var result;
1706  if (halbue && minutesProUe == 45) {
1707    var min45stel = Math.floor(pMinuten) % 45;
1708    if (min45stel == 11)
1709      result = (pMinuten + 0.25) / minutesProUe;
1710    else if (min45stel == 12)
1711      result = (pMinuten - 0.75) / minutesProUe;
1712    else if (min45stel == 22)
1713      result = (pMinuten + 0.5) / minutesProUe;
1714    else if (min45stel == 23)
1715      result = (pMinuten - 0.5) / minutesProUe;
1716    else if (min45stel == 33)
1717      result = (pMinuten + 0.75) / minutesProUe;
1718    else if (min45stel == 34)
1719      result = (pMinuten - 0.25) / minutesProUe;
1720    else
1721      result = pMinuten / minutesProUe;
1722  }
1723  else
1724    result = pMinuten / minutesProUe;
1725  return Math.round(result * 100) / 100;
1726};
1727
1728/**
1729 * Wandelt UE in Minuten um, berücksichtigt hierbei abweichende minProUe des Kurses und
1730 * 22/45 = 0,5 UE (KuferSQL/Zentrale Einstellungen/Organisation 22 Minuten von 45 Minuten gelten als 0,5 UE )
1731 * 
1732 * Hierzu sollte KuferTools.Kursplanung.MinutesProUe und KuferTools.KurstagPlanung.HALBUE gesetzt sein
1733 */
1734KuferTools.Utils.UeToMin = function (pUe) {
1735  var minutesProUe = KuferTools.Utils.getMinutesProUE();
1736
1737  var halbue = false;
1738  if (KuferTools.KurstagPlanung.HALBUE == "W")
1739    halbue = true;
1740  var result;
1741  if (halbue && minutesProUe == 45) {
1742    var ganzeUe = Math.floor(pUe);
1743    var teilUe = Math.round((pUe - ganzeUe) * 100) / 100;
1744    if (teilUe == 0.25)
1745      result = ganzeUe * minutesProUe + 11;
1746    else if (teilUe == 0.5)
1747      result = ganzeUe * minutesProUe + 22;
1748    else if (teilUe == 0.75)
1749      result = ganzeUe * minutesProUe + 33;
1750    else
1751      result = pUe * minutesProUe;
1752  }
1753  else
1754    result = pUe * minutesProUe;
1755
1756  return KuferTools.Utils.Math.kuferSQLRound(result);
1757};
1758
1759/**
1760 * Lasst Das Script ms Millisekunden warten
1761 */
1762KuferTools.Utils.Pause = function (ms) {
1763  ms += new Date().getTime();
1764  while (new Date() < ms) { }
1765}
1766
1767registerNamespace('KuferTools.Utils.Math');
1768/**
1769 * Rundet zunächst auf 2 stellen hinter dem Komma genau, wenn genau 
1770 * ",50" steht, wird auf die nächste GERADE Zahl gerunden, sonst kaufmännisch.
1771 * z.B. 6,50 -> 6; 7,50 -> 8
1772 * 
1773 * Entsprechende PHP-Funktion: MathOperations::kuferSQLRound
1774 */
1775KuferTools.Utils.Math.kuferSQLRound = function (pDouble) {
1776  var dtemp = Math.round(pDouble * 100);
1777  if (dtemp % 100 == 50) {
1778    dtemp = Math.floor(dtemp / 100);
1779    if (dtemp % 2 != 0) {
1780      dtemp++;
1781    }
1782    return dtemp;
1783  }
1784  return Math.round(pDouble);
1785};
1786
1787/**
1788 * Prüft ob ein Wert eine Nummer ist
1789 */
1790KuferTools.Utils.Math.isNumber = function (pVal) {
1791  return typeof pVal === 'number' && isFinite(pVal);
1792};
1793
1794KuferTools.Utils.Math.floatToString = function (pFloat, truncateDecimalplacesIfZero = false) {
1795  pFloat = Math.round(pFloat * 100);
1796  pFloat = String(pFloat);
1797  while (pFloat.length < 3) {
1798    pFloat = '0' + pFloat;
1799  }
1800  let decimalPlaces = pFloat.substring(pFloat.length - 2);
1801  if (truncateDecimalplacesIfZero && decimalPlaces === "00") {
1802    return pFloat.substring(0, pFloat.length - 2);
1803  } else {
1804    return pFloat.substring(0, pFloat.length - 2) + ',' + pFloat.substring(pFloat.length - 2);
1805  }
1806};
1807
1808KuferTools.Utils.Math.stringToFloat = function (pString) {
1809  return parseFloat(pString.replace(/,/, '.'));
1810}
1811
1812registerNamespace('KuferTools.Utils.String');
1813/**
1814 * Wandelt Sonderzeichen in die HTML-Repräsentation um.
1815 * 
1816 * Achtung: 1. Nicht vollständig, kann aber jederzeit um die benötigten Zeichen erweitert werden.
1817 *          2. Falls zukünftig Anführungszeichen konvertiert werden, sollte in einem Parameter abgefragt werden,
1818 *             ob diese mitkonvertiert werden sollen oder nicht.
1819 */
1820KuferTools.Utils.String.HTMLEncode = function (pString) {
1821  var result = pString;
1822  result = result.replace(/ä/g, '&auml;');
1823  result = result.replace(/ü/g, '&uuml;');
1824  result = result.replace(/ö/g, '&ouml;');
1825  result = result.replace(/Ä/g, '&Auml;');
1826  result = result.replace(/Ü/g, '&Uuml;');
1827  result = result.replace(/Ö/g, '&Ouml;');
1828  result = result.replace(/ß/g, '&szlig;');
1829  return result;
1830}
1831
1832// Gibt true zurück, wenn der String mit dem angegebenen Suffix endet; gibt für ein leeres Suffix
1833// false zurück (d.h. ein String endet nicht mit Leerstring; notwendig für Vergleich beim check auf erlaubte dateiendungen
1834// beim Dokumentenupload
1835String.prototype.endsWith = function (suffix) {
1836  if (suffix === '')
1837    return false;
1838  return this.indexOf(suffix, this.length - suffix.length) !== -1;
1839};
1840
1841/**
1842 * Funktionen für das Setzen und erhalten von Cursorpositionen
1843 */
1844registerNamespace('KuferTools.Utils.Focus');
1845
1846/**
1847 * Gibt die letzte Start-Position des Cursors von $pTarget zurück.
1848 * 
1849 * Nur wenn ein Bereich markiert ist, unterscheiden sich Start- und Endposition
1850 * 
1851 * ACHTUNG: Derzeit nicht für IE
1852 */
1853KuferTools.Utils.Focus.getCursorStartPosition = function ($pTarget) {
1854  /* IE */
1855  if (typeof (document.selection) != 'undefined') {
1856    var selection = getSelection($pTarget[0]);
1857    return selection.start;
1858  }
1859  /* FireFox, Chrome, Safari, Opera  */
1860  else if (typeof ($pTarget[0].selectionStart) != 'undefined') {
1861    return $pTarget[0].selectionStart;
1862  }
1863};
1864
1865/**
1866 * Gibt die letzte End-Position des Cursors von $pTarget zurück.
1867 * 
1868 * Nur wenn ein Bereich markiert ist, unterscheiden sich Start- und Endposition
1869 */
1870KuferTools.Utils.Focus.getCursorEndPosition = function ($pTarget) {
1871
1872  /* IE */
1873  if (typeof (document.selection) != 'undefined') {
1874    var selection = getSelection($pTarget[0]);
1875    return selection.end;
1876  }
1877  /* FireFox, Chrome, Safari, Opera */
1878  else if (typeof ($pTarget[0].selectionEnd) != 'undefined') {
1879    return $pTarget[0].selectionEnd;
1880  }
1881};
1882
1883/**
1884 * Von http://stackoverflow.com/questions/3622818/ies-document-selection-createrange-doesnt-include-leading-or-trailing-blank-li/3648244#3648244
1885 * 
1886 * Vorsicht bei Textareas: Verschiedene Browser interpretieren den Zeilenumbruch als ein oder zwei Zeichen.
1887 * Code Modifiziert, damit beim IE Zeilenumbrüche nur als ein Zeichen zählen (wie beim maxlength-Attribut bei IE).
1888 * Das übergebene Element muss selectiert sein. Es darf kein jQuery-Selector sein.
1889 */
1890function getSelection(el) {
1891  var start = 0, end = 0, normalizedValue, range,
1892    textInputRange, len, endRange;
1893
1894  if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
1895    start = el.selectionStart;
1896    end = el.selectionEnd;
1897  } else {
1898    range = document.selection.createRange();
1899
1900    if (range && range.parentElement() == el) {
1901
1902      normalizedValue = el.value.replace(/\r\n/g, "\n");
1903      // Änderung: die Länge wird nun anhand des "normalizedValue" (ein Zeichen für ln) berechnet
1904      len = normalizedValue.length;
1905
1906      // Create a working TextRange that lives only in the input
1907      textInputRange = el.createTextRange();
1908      textInputRange.moveToBookmark(range.getBookmark());
1909
1910      // Check if the start and end of the selection are at the very end
1911      // of the input, since moveStart/moveEnd doesn't return what we want
1912      // in those cases
1913      endRange = el.createTextRange();
1914      endRange.collapse(false);
1915
1916      if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
1917        start = end = len;
1918      } else {
1919        start = -textInputRange.moveStart("character", -len);
1920        // Änderung: Die bisher nur einmal gezählten ln werden hier nicht ein 2. mal dazugezählt.
1921        //start += normalizedValue.slice(0, start).split("\n").length - 1;
1922
1923        if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
1924          end = len;
1925        } else {
1926          end = -textInputRange.moveEnd("character", -len);
1927          // Änderung: Die bisher nur einmal gezählten ln werden hier nicht ein 2. mal dazugezählt.
1928          //end += normalizedValue.slice(0, end).split("\n").length - 1;
1929        }
1930      }
1931    }
1932  }
1933
1934  return {
1935    start: start,
1936    end: end
1937  };
1938}
1939
1940/**
1941 * Ersetzt den markierten Bereich von $pTarget mit pReplaceText. (Derzeit nur für Input und Textarea vorgesehen)
1942 * 
1943 * Wenn kein Bereich markiert ist, dann wird an der Stelle des Cursors pReplaceText einfach
1944 * nur eingefügt
1945 * 
1946 * ACHTUNG: Für den Aufruf der Funktion mousedown statt click verwenden! IE zeigt ein SEHR seltsames Verhalten bei click:
1947 * Abhängig davon, wie lange das Listenelement gedrückt wird, wird der Text wo anders eingefügt.
1948 * 
1949 * Tipp: Wenn mousedown verwendet wird
1950 * $(document).on( "mouseup", function(){$lastTarget.trigger('focus');$(document).unbind("mouseup"); } );
1951 * innerhalb von mousedown{} einfügen. Damit wird das Textfeld anschließend focusiert.
1952 */
1953KuferTools.Utils.Focus.replaceSelected = function ($pTarget, pReplaceText) {
1954
1955  /* IE: Sonderlösung, da getCursorStartPosition usw. nicht für IE unterstützt werden. */
1956  if (typeof (document.selection) != 'undefined') {
1957    $pTarget.trigger('focus');
1958    var range = document.selection.createRange();
1959
1960    //Wegen Bugs wird zuvor geprüft, ob die Range innerhalb eines Inputs oder Textareas ist.
1961    var parentElement = range.parentElement();
1962    if (parentElement.tagName == 'TEXTAREA' || parentElement.tagName == 'INPUT')
1963      range.text = pReplaceText;
1964
1965  }
1966  else {
1967    var start = KuferTools.Utils.Focus.getCursorStartPosition($pTarget);
1968    var ende = KuferTools.Utils.Focus.getCursorEndPosition($pTarget);
1969
1970    //Nicht $pTarget.val(), da in Opera Zeilenumbrüche bei getCursorStartPosition
1971    //zwei zeichen sind, bei $pTarget.val() wird aus \n\r jedoch \n.
1972    var valuePart1 = $pTarget[0].value.substring(0, start);
1973    var valuePart2 = $pTarget[0].value.substring(ende);
1974
1975    $pTarget.val(valuePart1 + pReplaceText + valuePart2);
1976    KuferTools.Utils.Focus.setCursorPosition($pTarget, valuePart1.length + pReplaceText.length);
1977  }
1978};
1979
1980/**
1981 * Setzt den Cursor an eine bestimmte Position innerhalb von $pTarget, und gibt
1982 * $pTarget den Focus
1983 * 
1984 * ACHTUNG: Derzeit nicht für IE
1985 */
1986KuferTools.Utils.Focus.setCursorPosition = function ($pTarget, pPos) {
1987
1988  /* IE */
1989  if (typeof (document.selection) != 'undefined') {
1990    //TODO
1991  }
1992  /* FireFox, Chrome, Safari, Opera  */
1993  else if (typeof ($pTarget[0].setSelectionRange) != 'undefined') {
1994    $pTarget[0].trigger('focus');
1995    $pTarget[0].setSelectionRange(pPos, pPos);
1996  }
1997};
1998
1999/**
2000 * Erlaubt bei einem Input nur bestimmte Eingaben. Vorgabe mit regulären Ausdruck. Die im Pattern gefundenen Zeichen werden entfernt.
2001 * Beachte: Der reguläre Ausdruck wird derzeit nicht automatisch mit /g (ersetze alle) erweitert.
2002 * 
2003 * Noch nicht mit Textareas und Zeilenumbrüchen getestet. Müsste wendern in verschiedenen Browsern getestet werden.
2004 */
2005KuferTools.Utils.Focus.InputForbidPattern = function ($pTarget, $pPatternZuErsetzen) {
2006
2007  var start = KuferTools.Utils.Focus.getCursorStartPosition($pTarget);
2008
2009  var val = $pTarget.val();
2010  var lengthBefore = val.length;
2011  val = val.replace($pPatternZuErsetzen, '', val);
2012  var lengthAfter = val.length;
2013
2014  $pTarget.val(val);
2015  KuferTools.Utils.Focus.setCursorPosition($pTarget, start - (lengthBefore - lengthAfter));
2016};
2017
2018/******************************************
2019 * HIER FUNKTIONEN FÜR ALLGEMEINE DIALOGE *
2020 *****************************************/
2021
2022registerNamespace("KuferTools.Utils.Dialoge.Uebersicht.PersonMarkierDialog.ButtonHandling");
2023
2024/*** UEBERSICHTEN PERSONEN MARKIERUNG ***/
2025/**
2026 * Macht aus dem im DOM vorhandenen Dialog #dialog_markierenneu einen Dialog
2027 * und hängt die entsprechenden Eventhandler an die darin enthaltenen Elemente
2028 * ".button_span".
2029 */
2030KuferTools.Utils.Dialoge.Uebersicht.PersonMarkierDialog.Prepare = function (pTitle) {
2031  var $dialogmarkieren = $('#dialog_markierenneu');
2032
2033  // Dialog mit der Abfrage nach markierten Dozenten; wird statt showMessage(..) verwendet, da hier das  '#dialogmarkieren' Element
2034  // nicht aus dem DOM entfernt werden darf (via remove)
2035  $dialogmarkieren.dialog({
2036    autoOpen: false,
2037    title: pTitle + ' markieren:',
2038    width: 350,
2039    modal: true,
2040    buttons: {
2041      "Abbrechen": function () { $(this).dialog("close"); }
2042    }
2043  });
2044
2045  $dialogmarkieren.find('.button_span')
2046    .on('click', KuferTools.Utils.Dialoge.Uebersicht.PersonMarkierDialog.ButtonHandling)
2047    // Farbwechsel beim focus auf die Buttons
2048    .on('hover', function () {
2049      var $this = $(this);
2050      $this.css('color', 'red');
2051    }, function () {
2052      var $this = $(this);
2053      $this.css('color', 'black');
2054    });
2055
2056  /* Nur Kommentar; waere bei erfolgreicher Verwendung von showMessage() relevant; das ist aber nicht der Fall 
2057   //Aus dem DOM Baum aushängen sonst hängt es nach Klick auf den "alleMarkieren" Button  2mal drin
2058    if ($dialogmarkieren.html() !== '')
2059      $dialogmarkieren.detach();
2060  */
2061};
2062
2063/**
2064 * Vorher muss KuferTools.Utils.Dialoge.Uebersicht.PersonMarkierDialog.Prepare aufgerufen werden!
2065 */
2066KuferTools.Utils.Dialoge.Uebersicht.PersonMarkierDialog.ButtonHandling = function (event) {
2067  var $this = $(this); //aktuell geklickter Button
2068  var idAttr = $this.prop('id');
2069  var $dialogmarkieren = $('#dialog_markierenneu');
2070  if (idAttr === 'markall' || idAttr === 'unmarkall') {
2071
2072    //falls &markerAction=... (aus vorheriger Markierungs Aktion) schon im Link vorhanden ist, wird es entfernt
2073    if (KuferTools.SelfLink.indexOf('&markerAction') !== -1) {
2074      var selfLinkParts = KuferTools.SelfLink.split('&markerAction');
2075      KuferTools.SelfLink = selfLinkParts[0];
2076    }
2077
2078    $dialogmarkieren.dialog('close');
2079    if (idAttr === 'markall')
2080      window.location.href = KuferTools.SelfLink + '&markerAction=markiereAlle';
2081    else  //#unmarkall''
2082      window.location.href = KuferTools.SelfLink + '&markerAction=loescheMarkierungen';
2083  }
2084  else { // 'markside' oder 'unmarkside''
2085    if (idAttr === 'markside')
2086      $('input.markable').prop('checked', true);
2087    else
2088      $('input.markable').prop('checked', false);
2089    //Dialog hier nach dem markieren schließen 
2090    $dialogmarkieren.dialog('close');
2091    // $this.parent().dialog('close');   
2092  } //end else
2093};
2094
2095/**
2096 * Funktionen, welche an jQuery.fn.dataTableExt.oSort übergeben werden können, um das Sortierverhalten
2097 * von bestimmten Datatables-Spalten festzulegen.
2098 */
2099registerNamespace("KuferTools.Utils.DataTables");
2100registerNamespace("KuferTools.Utils.DataTables.oSort");
2101/**
2102 * Gibt True zurück, falls a vor b. Funktioniert auch mit z. B. "Fr., 23.12.12 14:13 Uhr"
2103 */
2104KuferTools.Utils.DataTables.oSort.CompareDate = function (a, b) {
2105  a = KuferTools.Utils.stripHTML(a);
2106  b = KuferTools.Utils.stripHTML(b);
2107  if (a == '' || a == '&nbsp;')
2108    a = '30.12.1899';
2109  if (b == '' || b == '&nbsp;')
2110    b = '30.12.1899';
2111  var dateA = KuferTools.Utils.Date.formatDate(a);
2112  var dateB = KuferTools.Utils.Date.formatDate(b);
2113
2114  if (dateA && dateB) {
2115    var aVorB = KuferTools.Utils.Date.isDateBefore(dateA, dateB);
2116    var bVorA = KuferTools.Utils.Date.isDateBefore(dateB, dateA);
2117    // Falls das Datum gleich ist, wird auf Time verglichen, insofern vorhanden.
2118    // Zeit wird am : erkannt.
2119    if (!aVorB && !bVorA) {
2120      var timeA = a.match(/[0-9]*:[0-9]*:?[0-9]*?/);
2121      var timeB = b.match(/[0-9]*:[0-9]*:?[0-9]*?/);
2122      if (timeA && timeB) {
2123        var timeAryA = timeA[0].split(':');
2124        var timeAryB = timeB[0].split(':');
2125        for (var i = 0; i < 3; i++) {
2126          if (typeof (timeAryA[i]) != 'undefined' && typeof (timeAryB[i]) != 'undefined') {
2127            if (parseInt(timeAryA[i], 10) < parseInt(timeAryB[i], 10)) {
2128              return true;
2129            } else if (parseInt(timeAryA[i], 10) > parseInt(timeAryB[i], 10)) {
2130              return false;
2131            }
2132          }
2133        }
2134      }
2135    }
2136    return aVorB;
2137  }
2138};
2139KuferTools.Utils.DataTables.oSort.DateAsc = function (a, b) {
2140  var aVorB = KuferTools.Utils.DataTables.oSort.CompareDate(a, b);
2141  return (aVorB) ? -1 : 1;
2142};
2143KuferTools.Utils.DataTables.oSort.DateDesc = function (a, b) {
2144  var aVorB = KuferTools.Utils.DataTables.oSort.CompareDate(a, b);
2145  return (aVorB) ? 1 : -1;
2146};
2147
2148/**
2149 * Erzeugt mit Hilfe von showMessage einen Dialog, und initialisiert anschließend ein dataTable.
2150 * 
2151 * Ist darauf Spezialisiert, dass das DataTable per Ajax geladen wird.
2152 * Mit Blättern und Filter kann umgegangen werden.
2153 * 
2154 * Suchfelder müssen als Namen den per Ajax übergebenen Parameter haben, und als Klasse dataTableSearch.
2155 * Buttons zum Vorwärtsblättern müssen die Klasse dataTableNext besitzen
2156 * Buttons zum Rückwärtsblättern müssen die Klasse dataTablePrevious besitzen
2157 * Wenn vorwärts/rückwärts-blättern möglich ist, bekommt der entsprechende Button die Klasse active.
2158 * Falls die inaktiven Buttons ausgegraut werden sollen, kann statt img spans mit der entsprechenden Höhe/Breite und Hintergrundbild gearbeitet werden.
2159 * 
2160 * @param {string} pMessage showMessage: Inhalt des Dialogs
2161 * @param {string} pTitle showMessage: Titel des Dialogs
2162 * @param {Record<string,function(): void>} pButtons showMessage: Button-Objekt für den Dialog
2163 * @param {string} pWidth showMessage: Breite des Dialogs
2164 * @param pPosition showMessage: Position des Dialogs als Array mit zwei werten.
2165 * @param {string} 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.
2166 * @param {number} pDataPerPage Anzahl der Datensätze pro Seite. Wird für Blätterfunktionen benötigt.
2167 * @param pScrollY Höhe der Datatable. Ist diese höher, wird ein Scrollbalken angeboten.
2168 * @param {string|URL} pUrl4Ajax URL, mit der Per Ajax die Datatable gefüllt wird.
2169 * @return {Promise<jQuery>}
2170 */
2171KuferTools.Utils.DataTables.showMessageWithDataTable4Ajax = function (pMessage, pTitle, pButtons, pWidth, pPosition, pDataTable, pDataPerPage, pScrollY, pUrl4Ajax, afterDrawTable) {
2172  showMessage(pMessage, pTitle, pButtons, pWidth, pPosition);
2173  var $dataTable = $(pDataTable, pMessage);
2174  //Falls die Datatable bereits initialisiert wurde, liegt die Tabelle innerhalb der Klasse dataTables_scrollBody
2175  let result = undefined;
2176  if ($('.dataTables_scrollBody .dataTable_tnUebersicht', pMessage).length === 0) {
2177    result = new Promise((resolve, reject) => {
2178      try {
2179        $dataTable.dataTable({
2180          "oLanguage": { "sUrl": "../jquery/plugins/jquery.dataTables.de_DE.txt" },
2181          "sScrollY": "300px", //Scrollbalken nach unten, angezeigt: 300px (IE)
2182          "bFilter": false,
2183          "bInfo": false, //z.B. showing 1 to 10 from 57 entries
2184          "bScrollCollapse": true,
2185          "bPaginate": false,
2186          "bAutoWidth": false,
2187          "bSort": false, // Serverseitige Sortierung ist nicht für alle Spalten möglich
2188          "bServerSide": true,
2189          "iDisplayLength": pDataPerPage,
2190          "iDisplayStart": 0,
2191          "fnServerParams": function (aoData) {
2192
2193            // Searchfield soll ein Obj sein: name: callback oder wert.
2194            $('.dataTableSearch', pMessage).each(function () {
2195              aoData.push({ "name": $(this).prop('name'), "value": $(this).val() });
2196            });
2197          },
2198          "fnInitComplete": function (_settings, _json) {
2199            resolve($dataTable);
2200          },
2201          "fnDrawCallback": function (oSettings) {
2202
2203            if(typeof(afterDrawTable) === 'function' ) {
2204              afterDrawTable();
2205            }
2206
2207            KuferTools.Utils.AjaxLadebildschirm.Stop();
2208
2209            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
2210              //$( '.dataTablePrevious', pMessage ).prop('src', 'layout/button_rueckwaertsg_small.gif');
2211              $('.dataTablePrevious', pMessage).removeClass('active');
2212              $('i.fa-arrow-left', pMessage).css("color", "#333");
2213            } else {
2214              //$( '.dataTablePrevious', pMessage ).prop('src', 'layout/button_rueckwaerts_small.gif');
2215              $('.dataTablePrevious', pMessage).addClass('active');
2216              $('i.fa-arrow-left', pMessage).css("color", "#5cb85c");
2217            }
2218
2219            if (oSettings._iDisplayLength + oSettings._iDisplayStart >= oSettings._iRecordsDisplay) {
2220              //$( '.dataTableNext', pMessage ).prop('src', 'layout/button_vorwaertsg_small.gif');
2221              $('.dataTableNext', pMessage).removeClass('active');
2222              $('i.fa-arrow-right', pMessage).css("color", "#333");
2223            } else {
2224              //$( '.dataTableNext', pMessage ).prop('src', 'layout/button_vorwaerts_small.gif');
2225              $('.dataTableNext', pMessage).addClass('active');
2226              $('i.fa-arrow-right', pMessage).css("color", "#5cb85c");
2227            }
2228
2229            // Hier ggf. die Blätterbuttons ausblenden. Später nur ausgrauen
2230          },
2231          "sAjaxSource": pUrl4Ajax,
2232          "sServerMethod": "POST",
2233          "aaSorting": [] // Anfangs wird nicht sortiert, damit die Sortierung aus den Einstellungen erhalten bleibt.
2234        });
2235      } catch (err) {
2236        reject(err);
2237      }
2238    });
2239  } else {
2240    result = new Promise((resolve, _) => {
2241      resolve($dataTable);
2242    });
2243  }
2244
2245  //var searchCounter = 0;
2246  /**
2247   * Felder im Suchformular. Werden 1 Sekunde nach der Eingabe des letzten Zeichens abgesendet.
2248   */
2249  /*
2250   * Wird nicht mehr verwendet, da es viel mehr Probleme gemacht hat, als geholfen.
2251   * 
2252   * $('.dataTableSearch', pMessage).on('keyup', function() {
2253    // Erst wenn eine Sekunde lang keine Eingaben kommen, wird die Suchanfrage abgesetzt.
2254    searchCounter++;
2255    window.setTimeout( function() {
2256      searchCounter--;
2257      if(searchCounter == 0) {
2258        $dataTable.fnPageChange('first');
2259      }
2260    }, 1000);
2261  });*/
2262
2263  $('.dataTableSearch', pMessage).on('keypress', function (e) {
2264    if (e.which == 13) {
2265      $('.dataTableSearchButton', pMessage).trigger('click');
2266    }
2267  });
2268
2269  $('.dataTableSearchButton', pMessage).on('click', function () {
2270
2271    KuferTools.Utils.AjaxLadebildschirm.Start();
2272
2273    $dataTable.fnPageChange('first');
2274  });
2275
2276  /**
2277   * Buttons fürs Vorwärtsblättern
2278   */
2279  $('.dataTableNext', pMessage).on('click', function () {
2280    if ($(this).hasClass('active')) {
2281
2282      KuferTools.Utils.AjaxLadebildschirm.Start();
2283
2284      $dataTable.fnPageChange('next');
2285    }
2286  });
2287  /**
2288   * Buttons fürs Zurückblättern
2289   */
2290  $('.dataTablePrevious', pMessage).on('click', function () {
2291    if ($(this).hasClass('active')) {
2292
2293      KuferTools.Utils.AjaxLadebildschirm.Start();
2294
2295      $dataTable.fnPageChange('previous');
2296    }
2297  });
2298
2299  return result;
2300};
2301
2302
2303/*******************************************************************************************************************
2304 * jQuery.fn.extend wird hier verwendet, um eine Sammlung von eigen definierten also  proprietäten jQuery Funktionen
2305 * anzulegen; sie MÜSSEN hierbei als Attribute eines Objektes $.fn.extend abgelegt werden;  Innerhalb jQuery wird hier 
2306 * nicht zwischen Funktionen und Methoden, wie an manchen Stellen in den jQuery Foren unterschieden; dort wird folgende
2307 * Unterscheidung getroffen: Im Unterschied zu Funktionen wird auf Methoden direkt vom DOM Baum aus zugegriffen; d.h. 
2308 * Funktionen werden immer zusammen mit jQuery Elementen verwendet also z.B.: $('div').meineFunktion(); 
2309 * jQuery Methoden werden wie folgt verwendet: $.meineMethode().
2310 * Diese Unterscheidung macht für unsere Zwecke für proprietäre Methoden keinen Sinn; anstatt eine Methode, die beliebig 
2311 * im DOM Baum aufgerufen wird  und nicht an ein JQuery Element gebunden ist, in jQuery zu definieren, wird sie  gleich 
2312 * als globale Javascript Funktion unter KuferTools.Utils definiert; also wird verwendet:
2313 *  
2314 *                    KuferTools.Utils.meineMethode1  (anstatt $.meineMethode1)
2315 * 
2316 ****************************************************************************************************************
2317 * statt der Sammlung in einem Objekt wäre es auch möglich, die Funktionen bzw. Methoden einzeln zu definieren
2318 * also z.B
2319   jQuery.fn.hasId = function(id) {   
2320   return this.prop('id') == id;
2321   };
2322 * dies ist gleichbedeutend mit der folgenden Definition der Funktion unter $.fn.extend
2323 ********************************************************************************************************
2324 
2325 ****************************************************************************************************************
2326 *  Enthält eine Sammlung von proprietären jQuery Funktionen; diese werden als Attribute eines einzelnen Objekts 
2327 *  dargestellt
2328 * Achtung: falls $.extend mehrere Objekte enthält, werden diese zusammen gemerged (siehe auch Anmerkung zu 
2329 * function showMessage (in function.js)
2330 ************************************************************************************/
2331jQuery.fn.extend({
2332  //liefert true oder false
2333  hasId: function (id) {
2334    return this.prop('id') == id;
2335  },
2336  // 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
2337  //  richtigen Tabreiter zurück (z.B. tabs-2 für belegte Kurse)         
2338  addTab2Url: function () {
2339    var fromTabId = this.parents('.content.tab').prop('id');
2340    //Anhängen des Tabs an die URL ist nur dann nötig, wenn die aktuelle Tab NICHT tabs-1 ist 
2341    if (fromTabId !== 'tabs-1') {
2342      // this ist ein jQuery Element z.B. $('a')
2343      var link = this.prop('href');
2344      // Aufsplitten nötig, falls der Link ein tab-Tag enthält z.B. z.B. &toolId=8&..&nr=K1502#tabs-4
2345      var tabTag = '';
2346      if (link.indexOf('#') !== -1) {
2347        var linkParts = link.split('#');
2348        link = linkParts[0];
2349        tabTag = '#' + linkParts[1];
2350      }
2351      link += '&fromTab=' + fromTabId;
2352      //ein bereits vorhandenes tab-Tag (z.B. #tabs-4) wird wieder hinten an die URL angehängt
2353      if (tabTag !== '')
2354        link += tabTag;
2355      this.prop('href', link);
2356    } //end if
2357  } //end function addTab2Url(..)
2358
2359  //TODO: weitere Funktionen definieren nach dem Schema
2360  // meineFunktion1: function(a,b) {...},
2361  // meineFunktion2: function(x,y,z) {...}, ...   
2362}); //end jQuery.fn.extend
2363
2364registerNamespace("KuferTools.Utils.Filter");
2365
2366KuferTools.Utils.Filter.IsFilled = function (pValue) {
2367  return KuferTools.Utils.trimS(pValue) !== '';
2368};
2369
2370KuferTools.Utils.Filter.IsFilledButNotZero = function (pValue) {
2371  return KuferTools.Utils.trimS(pValue) !== '' && KuferTools.Utils.trimS(pValue) !== '0';
2372};
2373
2374KuferTools.Utils.Filter.IsFilledEmptyStringOk = function (pValue) {
2375  return KuferTools.Utils.trimS(pValue) !== '-1';
2376}
2377
2378KuferTools.Utils.Filter.IsInt = function (pValue) {
2379  return pValue == parseInt(pValue);
2380};
2381
2382KuferTools.Utils.Filter.IsFloatPunkt = function (pValue) {
2383  return !isNaN(pValue);
2384};
2385
2386KuferTools.Utils.Filter.IsFloatKomma = function (pValue) {
2387  if (pValue.search(/\./) == -1) {
2388    return KuferTools.Utils.Filter.IsFloatPunkt(pValue.replace(/,/, '.'));
2389  } else {
2390    return false;
2391  }
2392};
2393
2394KuferTools.Utils.Filter.IsDate = function (pValue) {
2395  var dateAry = pValue.split('.');
2396
2397  // Erwartet Tag und Monat zweistellig
2398  if (!pValue.match(/^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$/)) {
2399    return false;
2400  }
2401
2402  var day = parseInt(dateAry[0], 10);
2403  var month = parseInt(dateAry[1], 10);
2404  var year = parseInt(dateAry[2], 10);
2405
2406  if (year < 1900) {
2407    return false;
2408  }
2409
2410  // Überprüfung auf Über- und Unterläufe: 31.04. wird im Dateobj zu 01.05.
2411  var dateObject = new Date(year, month - 1, day);
2412  if (year != dateObject.getFullYear()
2413    || month != (dateObject.getMonth() + 1)
2414    || day != dateObject.getDate()) {
2415    return false;
2416  }
2417
2418  return true;
2419};
2420
2421KuferTools.Utils.Filter.IsDateJahrKurz = function (pValue) {
2422
2423  if (!pValue.match(/^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$/)) {
2424    return false;
2425  }
2426  pValue = pValue.substring(0, 6) + '20' + pValue.substring(6, 8);
2427
2428  return KuferTools.Utils.Filter.IsDate(pValue);
2429};
2430
2431KuferTools.Utils.Filter.IsTime = function (pValue) {
2432  if (!pValue.match(/^[0-9]{1,2}\:[0-9]{2}$/)) {
2433    return false;
2434  }
2435  var timeAry = pValue.split(':');
2436  var hour = timeAry[0];
2437  var min = timeAry[1];
2438
2439  return hour >= 0 && hour <= 23 && min >= 0 && min <= 59;
2440};
2441
2442KuferTools.Utils.Filter.IsOneOrNull = function (pValue) {
2443  return (pValue == "1" || pValue == "0");
2444};
2445
2446KuferTools.Utils.Filter.IsJOrN = function (pValue) {
2447  return (pValue == "j" || pValue == "J" || pValue == "n" || pValue == "N");
2448};
2449
2450/**
2451 * Für E-Mail-Adressen gibt es sehr viel Spielraum. Es ist nahezu alles Korrekt, sobald es genau ein @ hat.
2452 * Laut Wikipedia erlaubte Zeichen im Lokalteil: A-Za-z0-9.!#$%&'*+-/=?^_`{|}~
2453 * Gibt auch ohne Top-Lvl-Domain true zurück.
2454 * Gibt bei Angabe von Kommentaren, oder den Absendernamen und ähnliche Sonderspässe false zurück.
2455 */
2456KuferTools.Utils.Filter.IsEmail = function (pValue) {
2457  return pValue.search(/^[A-Za-z0-9.!#$%&'*+-\/=?^_`{|}~]+@[A-Za-z0-9.!#$%&'*+-\/=?^_`{|}~]+$/) != -1;
2458};
2459
2460/**
2461 * Bei Teilnehmernamen ist "TN N", "TN M", "TN W" nicht erlaubt
2462 */
2463KuferTools.Utils.Filter.IsNotNeutralerTN = function (pValue) {
2464  return pValue.search(/^TN N|TN M|TN W$/) == -1;
2465};
2466
2467KuferTools.Utils.Filter.IsImageJPEG = function (pValue) {
2468  const von = pValue.lastIndexOf('.');
2469  const bis = pValue.length;
2470  const endung = pValue.slice(von, bis).toLowerCase();
2471  return (endung === '.jpg' || endung === '.jpeg');
2472};
2473
2474KuferTools.Utils.Filter.IsDateTime = function (pValue) {
2475  var dateTimeAry = pValue.split(' ');
2476  var result = KuferTools.Utils.Filter.IsDate(dateTimeAry[0]);
2477  if (result) {
2478    result = typeof dateTimeAry[1] === 'undefined' || KuferTools.Utils.Filter.IsTime(dateTimeAry[1]);
2479  }
2480
2481  return result;
2482};
2483
2484KuferTools.Utils.Filter.Mandatory = 1;
2485KuferTools.Utils.Filter.Integer = 2;
2486KuferTools.Utils.Filter.Float_Punktsepariert = 4;
2487KuferTools.Utils.Filter.Float_Kommasepariert = 8;
2488KuferTools.Utils.Filter.Date = 16;
2489KuferTools.Utils.Filter.Date_Jahrkurz = 32;
2490KuferTools.Utils.Filter.Time = 64;
2491KuferTools.Utils.Filter.EinsNull = 128;
2492KuferTools.Utils.Filter.JN = 256;
2493KuferTools.Utils.Filter.Email = 512;
2494KuferTools.Utils.Filter.MandatoryNoZero = 1024;
2495KuferTools.Utils.Filter.ImageJPEG = 2048;
2496KuferTools.Utils.Filter.NeutralerTN = 4096;
2497KuferTools.Utils.Filter.DateTime = 8192;
2498KuferTools.Utils.Filter.MandatoryEmptyStringOk = 16384;
2499
2500/**
2501 * Funktion um mehrere Filterkriterien auf einmal zu prüfen.
2502 * 
2503 * Gibt true zurück, falls Leer, und kein Pflichtfeld
2504 * 
2505 * @param pValue zu prüfender Wert
2506 * @param pModus Nach was gefiltert wird, bitweise codiert. Hierzu können die Konstanten Mandatory, Integer...
2507 *               verwendet werden. Bei mehreren Kriteren müssen diese per Bitweises ORing (x | y) verknüpft werden
2508 */
2509KuferTools.Utils.Filter.FilterValue = function (pValue, pModus) {
2510
2511  var isOk = true;
2512  // Per bitweises ANDing ( x & y ) wird festgestellt, welche der Filterkriterien in Modus angegeben wurden.
2513  // Hinweis: != bindet stärker als &, deshalb ist eine zusützliche Klammerung nötig
2514  if (isOk && ((pModus & KuferTools.Utils.Filter.Mandatory) != 0)) {
2515    isOk = isOk && KuferTools.Utils.Filter.IsFilled(pValue);
2516  }
2517
2518  if (isOk && ((pModus & KuferTools.Utils.Filter.MandatoryNoZero) != 0)) {
2519    isOk = KuferTools.Utils.Filter.IsFilledButNotZero(pValue);
2520  }
2521
2522  if (isOk && ((pModus & KuferTools.Utils.Filter.MandatoryEmptyStringOk) != 0)) {
2523    isOk = KuferTools.Utils.Filter.IsFilledEmptyStringOk(pValue);
2524  }
2525
2526  // Gibt true zurück, wenn Value leer ist, und kein Mandatoryfeld.
2527  if (isOk && pValue == "") {
2528    return true;
2529  }
2530
2531  if (isOk && ((pModus & KuferTools.Utils.Filter.Integer) != 0)) {
2532    isOk = KuferTools.Utils.Filter.IsInt(pValue);
2533  }
2534
2535  if (isOk && ((pModus & KuferTools.Utils.Filter.Float_Punktsepariert) != 0)) {
2536    isOk = KuferTools.Utils.Filter.IsFloatPunkt(pValue);
2537  }
2538
2539  if (isOk && ((pModus & KuferTools.Utils.Filter.Float_Kommasepariert) != 0)) {
2540    isOk = KuferTools.Utils.Filter.IsFloatKomma(pValue);
2541  }
2542
2543  if (isOk && ((pModus & KuferTools.Utils.Filter.Date) != 0)) {
2544    isOk = KuferTools.Utils.Filter.IsDate(pValue);
2545  }
2546
2547  if (isOk && ((pModus & KuferTools.Utils.Filter.Date_Jahrkurz) != 0)) {
2548    isOk = KuferTools.Utils.Filter.IsDateJahrKurz(pValue);
2549  }
2550
2551  if (isOk && ((pModus & KuferTools.Utils.Filter.Time) != 0)) {
2552    isOk = KuferTools.Utils.Filter.IsTime(pValue);
2553  }
2554
2555  if (isOk && ((pModus & KuferTools.Utils.Filter.EinsNull) != 0)) {
2556    isOk = KuferTools.Utils.Filter.IsOneOrNull(pValue);
2557  }
2558
2559  if (isOk && ((pModus & KuferTools.Utils.Filter.JN) != 0)) {
2560    isOk = KuferTools.Utils.Filter.IsJOrN(pValue);
2561  }
2562
2563  if (isOk && ((pModus & KuferTools.Utils.Filter.Email) != 0)) {
2564    isOk = KuferTools.Utils.Filter.IsEmail(pValue);
2565  }
2566
2567  if (isOk && ((pModus & KuferTools.Utils.Filter.ImageJPEG) != 0)) {
2568    isOk = KuferTools.Utils.Filter.IsImageJPEG(pValue);
2569  }
2570
2571  if (isOk && ((pModus & KuferTools.Utils.Filter.NeutralerTN) != 0)) {
2572    isOk = KuferTools.Utils.Filter.IsNotNeutralerTN(pValue);
2573  }
2574
2575  if (isOk && ((pModus & KuferTools.Utils.Filter.DateTime) != 0)) {
2576    isOk = KuferTools.Utils.Filter.IsDateTime(pValue);
2577  }
2578
2579  return isOk;
2580
2581};
2582
2583KuferTools.Utils.Filter.IsDateEarlierOrEqual = function (dateLater) {
2584
2585  return function (dateEarlier) {
2586    if (dateLater.length > 0 && KuferTools.Utils.Filter.IsDate(dateLater) && dateEarlier.length > 0 && KuferTools.Utils.Filter.IsDate(dateEarlier)) {
2587      return KuferTools.Utils.Date.isDateBeforeOrEqual(dateEarlier, dateLater);
2588    }
2589    return true;
2590  };
2591};
2592
2593KuferTools.Utils.Filter.IsDateLaterOrEqual = function (dateEarlier) {
2594
2595  return function (dateLater) {
2596    if (dateEarlier.length > 0 && KuferTools.Utils.Filter.IsDate(dateEarlier) && dateLater.length > 0 && KuferTools.Utils.Filter.IsDate(dateLater)) {
2597      return KuferTools.Utils.Date.isDateBeforeOrEqual(dateEarlier, dateLater);
2598    }
2599    return true;
2600  };
2601};
2602
2603KuferTools.Utils.Filter.IsTimeEarlierOrEqual = function (timeLater) {
2604  return function (timeEarlier) {
2605    if (timeLater.length > 3 && KuferTools.Utils.Filter.IsTime(timeLater) && timeEarlier.length > 3 && KuferTools.Utils.Filter.IsTime(timeEarlier)) {
2606      return KuferTools.Utils.isTimeBeforeOrEqual(timeEarlier, timeLater);
2607    }
2608    return true;
2609  };
2610};
2611
2612KuferTools.Utils.Filter.IsTimeLaterOrEqual = function (timeEarlier) {
2613  return function (timeLater) {
2614    if (timeEarlier.length > 3 && KuferTools.Utils.Filter.IsTime(timeEarlier) && timeLater.length > 3 && KuferTools.Utils.Filter.IsTime(timeLater)) {
2615      return KuferTools.Utils.isTimeBeforeOrEqual(timeEarlier, timeLater);
2616    }
2617    return true;
2618  };
2619};
2620
2621KuferTools.Utils.Filter.FilterByArry = function (value, filterArray) {
2622  var filterLength = filterArray.length;
2623  for (var i = 0; i < filterLength; i++) {
2624    if (!filterArray[i](value)) {
2625      return false;
2626    }
2627  }
2628  return true;
2629};
2630
2631/**
2632 * Gibt ein Objekt zurück, mit dessen Hilfe mehrere Felder per jQuerySelector und Modus auf einmal geprüft werden können.
2633 * Die Selektoren mit fehlerhaften Eingaben werden zurückgegeben.
2634 * 
2635 * z. B.
2636 * var filterObj = KuferTools.Utils.Filter.FilterSelector();
2637 * filterObj.addSelector( $('#test1'), KuferTools.Utils.Filter.Mandatory ) .
2638 *           addSelector( $('#test2'), KuferTools.Utils.Filter.Mandatory | KuferTools.Utils.Filter.Integer ) ;
2639 * var wrongFilledSelectors = filterObj.filter();
2640 */
2641KuferTools.Utils.Filter.FilterSelector = function () {
2642  /**
2643   * Array mit den zu prüfenden Selektoren, und den jeweiligen Modus.
2644   * 
2645   * { selector: , modus: }
2646   */
2647  var filterSettings = [];
2648  var result = {};
2649  /**
2650   * Fügt ein weiteres zu prüfendes Element hinzu
2651   * 
2652   * @param $pSelector des zu prüfenden Elements.
2653   * @param pModus Nach was gefiltert wird, bitweise codiert. Hierzu können die Konstanten Mandatory, Integer...
2654   *               verwendet werden. Bei mehreren Kriteren müssen diese per Bitweises ORing (x | y) verknüpft werden
2655   */
2656  result.addSelector = function ($pSelector, pModus) {
2657    filterSettings[filterSettings.length] = { selector: $pSelector, modus: pModus };
2658    return this;
2659  };
2660
2661  /**
2662   * Gibt ein Array mit den fehlerhaften Selektoren zurück.
2663   */
2664  result.filter = function () {
2665    var result = [];
2666    var filterLength = filterSettings.length;
2667    for (var i = 0; i < filterLength; i++) {
2668      if (!KuferTools.Utils.Filter.FilterValue(filterSettings[i].selector.val(), filterSettings[i].modus)) {
2669        result[result.length] = filterSettings[i].selector;
2670      }
2671    }
2672    return result;
2673  };
2674
2675  return result;
2676};
2677
2678registerNamespace("KuferTools.Utils.AjaxLadebildschirm");
2679
2680KuferTools.Utils.AjaxLadebildschirm.Start = function () {
2681  if (!document.body.querySelector('.loader_overlay')) {
2682    const element = document.createElement('div');
2683    element.classList.add('loader_overlay');
2684    element.ariaBusy = true;
2685    element.ariaLive = 'assertive';
2686    element.role = 'alert';
2687
2688    const innerElement = document.createElement('div');
2689    innerElement.classList.add('loader_spinner');
2690    innerElement.ariaLabel="Der Inhalt wird geladen.";
2691    element.appendChild(innerElement);
2692
2693    document.body.appendChild(element);
2694  }
2695};
2696
2697/**
2698 * Ajaxloader nur für einen Teilausschnitt der Seite.
2699 * 
2700 * Der Selector sollte Position absolute oder relative haben.
2701 */
2702KuferTools.Utils.AjaxLadebildschirm.Partial = function ($selector) {
2703  $selector.append('<div class="loader_overlay partial" aria-live="assertive" aria-busy="true" role="alert"><div class="loader_spinner" aria-label="Der Inhalt wird geladen."></div></div>');
2704};
2705
2706KuferTools.Utils.AjaxLadebildschirm.Stop = function () {
2707  const loaderOverlays = document.body.querySelectorAll('.loader_overlay');
2708  for (const loaderOverlay of loaderOverlays) {
2709    loaderOverlay.remove();
2710  }
2711};
2712
2713KuferTools.Utils.ClickOrEnter = function($selector, handler)
2714{
2715  $selector.on('click', handler);
2716  $selector.on('keydown', function(event) {
2717
2718    if (event.key !== "Enter") {
2719        return;
2720    }
2721    // Der Timeout verhindert, dass durch das Keydown-Event gleich sich ggf. öffnende Dialogfenster gleich wieder schließen.
2722    setTimeout(() => { handler(event); }, 10);
2723  });
2724};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.