PageSourceSearch

https://www.stadtwerk-am-see.de/de/static/js/HRSysLib/Hilfsfunktionen/CSS.js?nc=1790923960777

js stadtwerk-am-see.de collected 2026-10-04 00:25:32 UTC 37,045 bytes, 1,197 lines download raw bytes

1 
2// 
3// Gibt die CSS-Eigenschaften des übergebenen Elements zurück
4// 
5function getStyleValue (element, cssProperty) 
6{
7   var value = "";
8   if (window.getComputedStyle) 
9   {
10      var styles = window.getComputedStyle(element, null);
11      value = window.getComputedStyle(element, null)[cssProperty];
12   } 
13   else if (element.currentStyle) 
14   {
15      value = element.currentStyle[cssProperty];
16   }
17   return value;
18}
19
20function getElementHeightValue(element)
21{
22   if (!element)
23      return 0;
24      
25   // Der einfache Weg:
26   if(element.getBoundingClientRect)
27   {
28      return element.getBoundingClientRect().bottom - element.getBoundingClientRect().top;
29   }
30   else
31   {
32      return getStyleMeasureValue(getStyleValue(element, "height"));
33   }
34}
35
36function getElementWidthValue(element)
37{
38   if (!element)
39      return 0;
40      
41   // Der einfache Weg:
42   if(element.getBoundingClientRect)
43   {
44      return element.getBoundingClientRect().right - element.getBoundingClientRect().left;
45   }
46   else
47   {
48      return getStyleMeasureValue(getStyleValue(element, "width"));
49   }
50}
51
52function getElementLeftValue(element)
53{
54   if (!element)
55      return 0;
56   
57   // Der einfache Weg:
58   if(element.getBoundingClientRect)
59   {
60      // Die Methode orientiert sich an der Position im Browser, nicht im
61      // Dokument.
62      // Darum den Scrollwert dazurechnen:
63      return element.getBoundingClientRect().left+getScrollPosition().x;
64   }
65   else
66   {
67      return getStyleMeasureValue(getStyleValue(element, "left"));
68   }
69}
70
71function getElementTopValue(element)
72{
73   if (!element)
74      return 0;
75   
76   // Der einfache Weg:
77   if(element.getBoundingClientRect)
78   {
79      // Die Methode orientiert sich an der Position im Browser, nicht im
80      // Dokument.
81      // Darum den Scrollwert dazurechnen:
82      return element.getBoundingClientRect().top+getScrollPosition().y;
83   }
84   else
85   {
86      return getStyleMeasureValue(getStyleValue(element, "top"));
87   }
88}
89
90function getElementRightValue(element)
91{
92   if (!element)
93      return 0;
94   
95   // Der einfache Weg:
96   if(element.getBoundingClientRect)
97   {
98      // Die Methode orientiert sich an der Position im Browser, nicht im
99      // Dokument.
100      // Darum den Scrollwert dazurechnen:
101      return element.getBoundingClientRect().right + getScrollPosition().x;
102   }
103   else
104   {
105      return getStyleMeasureValue(getStyleValue(element, "right"));
106   }
107}
108
109function getElementBottomValue(element)
110{
111   if (!element)
112      return 0;
113   
114   // Der einfache Weg:
115   if(element.getBoundingClientRect)
116   {
117      // Die Methode orientiert sich an der Position im Browser, nicht im
118      // Dokument.
119      // Darum den Scrollwert dazurechnen:
120      return element.getBoundingClientRect().bottom + getScrollPosition().y;
121   }
122   else
123   {
124      return getStyleMeasureValue(getStyleValue(element, "bottom"));
125   }
126}
127
128function getStyleMeasureValue(measureValue)
129{
130   var ergebnis = 0;
131   
132   if (measureValue == null || measureValue == "")
133      return 0;
134   
135   if (isNaN(measureValue))
136   {
137      var numberEnd = false;
138      var numberString = "";
139      for (var i = 0; i < measureValue.length && !numberEnd; i++)
140      {
141         if (!isNaN(measureValue[i]) || measureValue[i] == '.' || measureValue[i] == '-' || measureValue[i] == '+')
142            numberString += measureValue[i];
143         else
144            numberEnd = true;
145      }
146      ergebnis = Number(numberString);
147   }
148   else
149      ergebnis = measureValue;
150   
151   return ergebnis;
152}
153
154/**
155 * Funktion ermittelt die Einheit einer CSS-Größenangabe.
156 * Aus "100px" wird beispielsweise "px". Bei Spezialangaben, wie
157 * auto, wird ein Leerstring zurückgegeben, da es sich nicht um eine Einheit handelt.
158 * 
159 * @param {Object} measureValue Größenangabe als Zeichenkette.
160 */
161function getStyleMeasureUnit(measureValue)
162{
163   var ergebnis = "";
164   
165   if (isNaN(measureValue))
166   {
167      var unitEnd = false;
168      for (var i = measureValue.length - 1; i >=0 && !unitEnd; i--)
169      {
170         if (!isNaN(measureValue[i]) || measureValue[i] == '.' || measureValue[i] == '-' || measureValue[i] == '+')
171            unitEnd = true;
172         else
173            ergebnis = measureValue[i] + ergebnis;
174      }
175      if (!unitEnd)         // Wenn wir bei der Suche nie auf eine Zahl gestoßen sind, bedeutet das,
176         ergebnis = "";      // dass wir z.B. eine Angabe wie auto haben, dann gibts auch keine Einheit.
177   }
178   
179   return ergebnis;
180}
181
182/**
183 * Gibt die Breite des gesamten Dokuments zurück. 
184 * Wenn alle Standardfunktionen auf dem documentElement
185 * nicht greifen, wird die Breite des Body-Elements zurückgegeben.
186 */
187var HRGetDocumentWidth = function()
188{
189   var widthValue = -1;
190   if (document.documentElement.scrollWidth) 
191   {
192      widthValue = document.documentElement.scrollWidth;
193   }
194   if (document.documentElement.offsetWidth)
195   {
196      widthValue = Math.max(widthValue, document.documentElement.offsetWidth);
197   }
198   if (document.documentElement.clientWidth)
199   {
200      widthValue = Math.max(widthValue, document.documentElement.clientWidth);
201   }
202   if (widthValue < 0)
203   {
204      widthValue = HRGetBodyWidth();
205   }
206   return Math.ceil(widthValue);
207}
208
209
210/**
211 * Gibt die Höhe des gesamten Dokuments zurück. 
212 * Wenn alle Standardfunktionen auf dem documentElement
213 * nicht greifen, wird die Höhe des Body-Elements zurückgegeben.
214 * Liefert ggfs. für iFrame-Resizer zu große Werte, da die Höhe
215 * auch durch die Größe des iFrames beeinflusst wird.
216 */
217var HRGetDocumentHeight = function()
218{
219   var heightValue = -1;
220   if (document.documentElement.scrollHeight) 
221   {
222      heightValue = document.documentElement.scrollHeight;
223   }
224   if (document.documentElement.offsetHeight)
225   {
226      heightValue = Math.max(heightValue, document.documentElement.offsetHeight);
227   }
228   if (document.documentElement.clientHeight)
229   {
230      heightValue = Math.max(heightValue, document.documentElement.clientHeight);
231   }
232   if (heightValue < 0)
233   {
234      heightValue = HRGetBodyHeight();
235   }
236   else
237   {
238      /*
239       * Wenn die Funktion genutzt wird, um eine automatische Höhenanpassung bei IFrames
240       * zu realisieren, ist der ermittelter Wert immer um 1 zu hoch. Sollte das
241       * zu negativen Effekten in anderen Einsatzszenarien führen, muss diese Lösung
242       * noch einmal überdacht werden.
243       * 21.03.2016 mhu
244       */
245      heightValue = heightValue - 1;
246   }
247   
248   return Math.ceil(heightValue);
249}
250
251/**
252 * Methode ermittelt die Breite, die das Element braucht (also inklusive Margins).
253 * Wird null als Element übergeben, wird die benötigte Breite für das ganze Dokument
254 * zurückgegeben. Anders als bei HRGetDocumentWidth() wird der Wert nicht von
255 * der Größe des Brwoserfensters oder eines umgebenden iFrames beeinflusst.
256 * 
257 * @param element, dessen benötigte Breite ermittelt werden soll oder null,
258 * wenn die Breite für das ganze Dokument ermittelt werden soll.
259 */
260var HRGetUsedWidth = function(element)
261{
262   var widthValue = -1;
263   if (element == null)
264   {
265      //
266      // Ermittlung für das gesamte Document.
267      // Wir ermitteln dazu den größten Right + margin-right-Wert
268      //
269      try
270      {
271         var maxRight = 0;
272         var alleElemente = document.querySelectorAll('body *');
273         for (var i = 0; i < alleElemente.length; i++)
274         {
275            maxRight = Math.max(maxRight, alleElemente[i].getBoundingClientRect().right + getScrollPosition().x + getStyleMeasureValue(getStyleValue(alleElemente[i], "margin-right")));
276         }
277         widthValue = maxRight;
278      }
279      catch (except)
280      {
281      }
282      //
283      // Bei Fehlern wird die gut abgehangene HRGetDocumentWidth() als Fallback verwendet.
284      // 
285      try
286      {
287         if (widthValue < 0)
288         {
289            widthValue = HRGetDocumentWidth();
290         }
291      }
292      catch (except)
293      {
294      }
295   }
296   else
297   {
298      widthValue = getElementWidthValue(element) + getStyleMeasureValue(getStyleValue(element, "margin-left")) + getStyleMeasureValue(getStyleValue(element, "margin-right"));
299   }
300   return widthValue;
301}
302
303/**
304 * Methode ermittelt die Höhe, die das Element braucht (also inklusive Margins).
305 * Wird null als Element übergeben, wird die benötigte Höhe für das ganze Dokument
306 * zurückgegeben. Anders als bei HRGetDocumentHeight() wird der Wert nicht von
307 * der Größe des Brwoserfensters oder eines umgebenden iFrames beeinflusst.
308 * 
309 * @param element, dessen benötigte Höhe ermittelt werden soll oder null,
310 * wenn die Höhe für das ganze Dokument ermittelt werden soll.
311 */
312var HRGetUsedHeight = function(element)
313{
314   var heightValue = -1;
315   if (element == null)
316   {
317      //
318      // Ermittlung für das gesamte Document.
319      // Wir ermitteln dazu den größten Bottom + margin-bottom-Wert
320      //
321      try
322      {
323         var maxBottom = 0;
324         var alleElemente = document.querySelectorAll('body *');
325         for (var i = 0; i < alleElemente.length; i++)
326         {
327            if (alleElemente[i] != null && alleElemente[i].nodeName && alleElemente[i].nodeName.toLowerCase() != 'option')
328            {
329               maxBottom = Math.max(maxBottom, alleElemente[i].getBoundingClientRect().bottom + getScrollPosition().y + getStyleMeasureValue(getStyleValue(alleElemente[i], "margin-bottom")));
330            }
331         }
332         var theBody = document.getElementsByTagName("BODY")[0];
333         maxBottom = Math.max(maxBottom, theBody.getBoundingClientRect().bottom + getScrollPosition().y + getStyleMeasureValue(getStyleValue(theBody, "margin-bottom")));
334         heightValue = maxBottom;
335      }
336      catch (except)
337      {
338      }
339      //
340      // Bei Fehlern wird die gut abgehangene HRGetDocumentHeight() als Fallback verwendet.
341      //
342      try
343      {
344         if (heightValue < 0)
345         {
346            heightValue = HRGetDocumentHeight();
347         }
348      }
349      catch (except)
350      {
351      }
352   }
353   else
354   {
355      heightValue = getElementHeightValue(element) + getStyleMeasureValue(getStyleValue(element, "margin-top")) + getStyleMeasureValue(getStyleValue(element, "margin-bottom"));
356   }
357   return heightValue;
358}
359
360/**
361 * Liefert die Breite des Body-Tags.
362 * 
363 * @returns Die Breite des Body-Tags in Pixeln.
364 */
365function HRGetBodyWidth()
366{
367   var theBody = document.getElementsByTagName("BODY")[0];
368   var widthValue = getElementWidthValue(theBody);
369   return Math.ceil(widthValue);
370}
371
372/**
373 * Liefert die Höhe des Body-Tags.
374 * 
375 * @returns Die Höhe des Body-Tags in Pixeln.
376 */
377function HRGetBodyHeight()
378{
379   var theBody = document.getElementsByTagName("BODY")[0];
380   var heightValue = getElementHeightValue(theBody);
381   return Math.ceil(heightValue);
382}
383
384/**
385 * Ermittelt die Breite des ViewPorts.
386 * 
387 * @see {https://stackoverflow.com/questions/1248081/get-the-browser-viewport-dimensions-with-javascript}
388 * Stand: 12.12.2019
389 * 
390 * @returns Die Breite des View-Ports.
391 */
392function HRGetViewPortWidth()
393{
394   return Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
395}
396
397/**
398 * Ermittelt die Höhe des ViewPorts.
399 * 
400 * @see {https://stackoverflow.com/questions/1248081/get-the-browser-viewport-dimensions-with-javascript}
401 * Stand: 12.12.2019
402 * 
403 *  @returns Die Höhe des View-Ports.
404 */
405function HRGetViewPortHeight()
406{
407   return Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
408}
409
410/**
411 * Ergänzt die übergebenen CSS-Klasse am Element.
412 * 
413 * @param element Das Element, welches die Klasse erhalten soll.
414 * @param newCSSClass Die neue CSS-Klasse.
415 */
416function addCSSClass(element, newCSSClass)
417{
418   try
419   {
420      if (element && newCSSClass)
421      {
422         if (typeof element == "string")
423            element = document.getElementById(element);
424         
425         if (element.className && element.className.length > 0)
426         {
427            var indexOfClass = element.className.indexOf(newCSSClass);
428            if (   indexOfClass < 0
429                  || (indexOfClass + newCSSClass.length < element.className.length && element.className.charAt(indexOfClass + newCSSClass.length) != ' ')
430                  || (indexOfClass > 0 && element.className.charAt(indexOfClass - 1) != ' '))
431            {
432               element.className += ' ' + newCSSClass;
433            }
434         }
435         else
436            element.className = newCSSClass;
437      }
438   }
439   catch (e)
440   {}
441}
442
443/**
444 * Ergänzt die übergebenen CSS-Klasse an den HTML-Body.
445 * 
446 * @param newCSSClass Die neue CSS-Klasse.
447 */
448function HRAddBodyCSSClass(newCSSClass)
449{
450   addCSSClass(document.getElementsByTagName('body')[0], newCSSClass);
451}
452
453/**
454 * @see HRCSS.removeCSSClass(element, cssClassToRemove)
455 * 
456 * @deprecaded.
457 */
458function removeCSSClass(element, cssClassToRemove)
459{
460   HRCSS.removeCSSClass(element, cssClassToRemove);
461}
462
463/**
464 * @see HRCSS.containsCSSClass(element, cssClass)
465 * 
466 * @deprecaded.
467 */
468function HRContainsCSSClass(element, cssClass)
469{
470   return HRCSS.containsCSSClass(element, cssClass);
471}
472
473/**
474 * Entfernt das erste Auftreten der übergebenen CSS-Klasse des HTML-Body.
475 * 
476 * @param cssClassToRemove Die zu entfernende Klasse.
477 */
478function HRRemoveBodyCSSClass(cssClassToRemove)
479{
480   removeCSSClass(document.getElementsByTagName('body')[0], cssClassToRemove);
481}
482
483function replaceCSSClass(element, oldCSSClass, newCSSClass)
484{
485    try
486    {
487        if (element && oldCSSClass && newCSSClass)
488        {
489            if (typeof element == "string")
490                element = document.getElementById(element);
491            
492            if (element.className && element.className.length > 0)
493            {
494                var newClass = "";
495                var containsElementOldClass = false;
496                var splitedClassNames = element.className.split(" ");
497                for (var index = 0; index < splitedClassNames.length; index++)
498                {
499                    if (splitedClassNames[index] == oldCSSClass)
500                    {
501                        newClass = newClass + newCSSClass +" ";
502                        containsElementOldClass = true;
503                    }
504                    else
505                    {
506                        newClass = newClass + splitedClassNames[index] +" ";
507                    }
508                }
509                
510                if(!containsElementOldClass)
511                {
512                   newClass = newClass + newCSSClass;
513                }
514                element.className = newClass;
515                
516            }
517            else
518            {
519               element.className = newCSSClass;
520            }
521            
522        }
523    }
524    catch (e)
525    {}
526}
527
528/**
529 * Funktion ergänzt die übergebene CSS-Klasse bei dem Element, wenn sie noch nicht vorhanden ist,
530 * andernfalls wird sie entfernt.
531 * 
532 * @param element das Element, bei dem die Klasse gesetzt/gelöscht werden soll. Bei Angabe einer Zeichenkette wird eine ID erwartet, ansonsten das Node-Objekt.
533 * @param cssClassToToggle Die CSS-Klasse die ergänzt oder entfernt werden soll.
534 */
535function toggleCSSClass(element, cssClassToToggle)
536{
537   try
538   {
539      if (element && cssClassToToggle && cssClassToToggle.length > 0)
540      {
541         if (typeof element == "string")
542            element = document.getElementById(element);
543         
544         if (element.className && element.className.length > 0 && element.className.indexOf(cssClassToToggle) >= 0)
545            removeCSSClass(element, cssClassToToggle);
546         else
547            addCSSClass(element, cssClassToToggle);
548      }
549   }
550   catch (e)
551   {}
552}
553
554/**
555 * Bringt ein Element zur Anzeige, in dem die CSS-Eigenschaft display auf 
556 * "block" bzw. "inline" (abhängig davon, ob es sich um ein Block-Element handelt)
557 * gesetzt wird. 
558 * @param element Das Element, das dargestellt werden soll. Es kann die Id des Elements
559 * oder direkt das Element als Objekt übergeben werden.
560 */
561function HRShow(element, displayType)
562{
563   // TODO durch HRElem ersetzen.
564   try
565   {
566      if (element)
567      {
568         if (typeof element == "string")
569            element = document.getElementById(element);
570         
571         if (element)
572         {
573            if (displayType != null)
574            {
575               element.style.display = displayType;
576            }
577            else
578            {
579               element.style.display = '';
580            }
581         }
582      }
583   }
584   catch (e)
585   {}
586}
587
588/**
589 * Blendet ein Element aus, in dem die CSS-Eigenschaft display auf "none" gesetzt wird.
590 * 
591 * @param element Das Element, das ausgeblendet werden soll. Es kann die Id des Elements
592 * oder direkt das Element als Objekt übergeben werden.
593 */
594function HRHide(element)
595{
596   // TODO durch HRElem ersetzen.
597   if (element)
598   {
599      try
600      {
601         if (typeof element == "string")
602            element = document.getElementById(element);
603         
604         if (element)
605         {
606            element.style.display = 'none';
607         }
608      }
609      catch (e)
610      {
611         if (typeof(HRLogError) === 'function')
612            HRLogError(e);
613      }
614   }
615}
616
617/**
618 * Getter für den (ggf. rekursiv bestimmten) ersten Parent-Knoten, der die gesuchte Style-Eigenschaft besitzt.
619 * 
620 * @param element Das Ausgangselement.
621 * @param property Die gesuchte CSS-Eigenschaft.
622 * @param value Der gesuchte Wert zur CSS-Eigenschaft.
623 * @param recursive true, falls wir uns rekursiv den DOM-Baum hochhangeln sollen.
624 * @returns der Parent-Knoten, der das Attribut hat oder null, falls kein solcher Knoten gefunden wurde.
625 */
626function getParentByStyleValue(element, property, value, recursive)
627{
628   var result = null;
629   
630   try
631   {
632      if (element && element.tagName !== 'HTML' && element.parentNode)
633      {
634         if (getStyleValue(element.parentNode, property) === value)
635            result = element.parentNode;
636         else if (recursive)
637            return getParentByStyleValue(element.parentNode, property, value, recursive);
638      }
639   }
640   catch (e)
641   {
642      if (typeof(HRLogError) === 'function')
643         HRLogError(e);
644   }
645   
646   return result;
647}
648
649/**
650 * Bringt die Elemente im übergebenen Array auf die selbe Höhe (diese entspricht der größten Höhe aus allen Elementen).
651 * 
652 * @param array Das Array mit allen Elementen, die auf die selbe Höhe gebracht werden sollen.
653 */
654function equalizeHeight(array)
655{
656   try
657   {
658      if (array)
659      {
660         array = Array.from(array);
661         
662         // Ermittelt die Maximalhöhe aller im Array befindlicher Elemente.
663         var maxHeight = 0;
664         Array.from(array).forEach(el => {
665            maxHeight = Math.max(getElementHeightValue(el), maxHeight);
666         });
667         
668         // Alle Elemente auf die Maximalhöhe bringen.
669         Array.from(array).forEach(el => {
670            el.style.height = maxHeight + "px";
671         });
672      }
673   }
674   catch (e)
675   {
676      if (typeof(HRLogError) === 'function')
677         HRLogError(e);
678   }
679}
680
681/**
682 * Führt eine Zählanimation (von z.B. 0 bis 100 zählen) im Viewport aus. 
683 * 
684 * @param span Ist das Element, welches animiert wird.
685 * @param start Ist die erste Zahl, der Animation.
686 * @param end Letzte Zahl. Danach bricht die Funktion ab. 
687 * @param durationSec Die Dauer der Animation in Sekunden. 
688 */
689function doCountAnimation(el, start, end, durationSec)
690{
691   try
692   {
693      // Animation darf nur einmal ausgeführt werden.
694      let already = false;
695      let duration = 10000;
696      if(durationSec){
697         duration = durationSec * 1000;
698      }
699      if (isInViewport(el))
700      {
701         calcCountAnimation(el, start, end, duration);
702      }
703      else 
704      {
705         // Erst durch das Scrollen, wird der Zähler sichtbar. 
706         window.addEventListener('scroll', function () {
707            if (isInViewport(el))
708            {
709               if (!already)
710               {
711                  calcCountAnimation(el, start, end, duration);
712                  already = true;
713               }
714            }
715         });
716      }
717   }
718   catch (e)
719   {
720      if (typeof(HRLogError) === 'function')
721         HRLogError(e);
722   }
723}
724
725/**
726 * Führt die Animation (von z.B. 0 bis 100 zählen) aus. 
727 * 
728 * @param el Ist das Element, welches animiert wird.
729 * @param start Ist die erste Zahl, der Animation.
730 * @param end Letzte Zahl. Danach bricht die Funktion ab. 
731 * @param onStep Callback-Funktion, die in jedem Schritt der Animation ausgeführt wird.
732 */
733function calcCountAnimation(el, start, end, duration, onStep) 
734{
735   try
736   {
737      let startTimestamp = null; 
738      const step = (timestamp) => {
739         
740         if (!startTimestamp) 
741         { 
742            startTimestamp = timestamp; 
743         }
744         // Misst die vergangene Zeit.
745         const progress = Math.min((timestamp - startTimestamp) / duration, 1);
746         
747         // Span erhält neuen Wert.
748         el.innerHTML = Math.floor(progress * (end - start) + start).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
749         
750         if (onStep && typeof(onStep) === 'function')
751            onStep(Math.floor(progress * (end - start) + start));
752         
753         // Schritte werden wieder wiederholt.
754         if (progress < 1) {
755            window.requestAnimationFrame(step);
756         }
757      }; 
758      window.requestAnimationFrame(step);
759   }
760   catch (e)
761   {
762      if (typeof(HRLogError) === 'function')
763         HRLogError(e);
764   }
765}
766
767/**
768 * @deprecated: Ersetzt durch statischen Aufruf HRCSS.isInViewport.
769 */
770function isInViewport(el)
771{
772   return HRCSS.isInViewport(el);
773}
774
775"use strict";
776
777class HRCSS
778{
779   /**
780    * Stack für die geöffneten Overlays.
781    */
782   static openOverlays = [];
783   
784   /**
785    * Entfernt das erste Auftreten der übergebenen CSS-Klasse des Elementes.
786    * 
787    * @param element Das Element, dessen Klasse entfernt werden soll, bzw. 
788    *                eine Zeichenkette mit dessen Id.
789    * @param cssClassToRemove Die zu entfernende Klasse.
790    */
791   static removeCSSClass(element, cssClassToRemove)
792   {
793      try
794      {
795         if (!element || !cssClassToRemove)
796            return;
797         
798         if (typeof element === 'string')
799            element = document.getElementById(element);
800         
801         if (element)
802         {
803            if (element.classList.contains(cssClassToRemove))
804               element.classList.remove(cssClassToRemove);
805            
806            // Wenn keine Klassen mehr übrig sind, das class-Attribut entfernen
807            if (element.classList.length === 0)
808               element.removeAttribute('class');
809         }
810      }
811      catch (e)
812      {
813         // darf ruhig bleiben...
814      }
815   }
816   
817   /**
818    * Prüft, ob das angegebene Element die CSS-Klasse trägt.
819    * 
820    * @param element Das Element, das geprüft werden soll.
821    * @param cssClass die gesuchte CSS-Klasse.
822    */
823   static containsCSSClass(element, cssClass)
824   {
825      try
826      {
827         if (element && cssClass && cssClass.length > 0)
828         {
829            if (typeof element == "string")
830               element = document.getElementById(element);
831            
832            if (element)
833               return element.classList.contains(cssClass);
834         }
835      }
836      catch (e)
837      {
838      }
839      
840      return false;
841   }
842   
843   /**
844    * Getter für die CSS-Klassen eines DOM-Elements.
845    * 
846    * @param node Das DOM-Element, dessen Klassen ermittelt werden sollen.
847    * @return die CSS-Klassen als Array.
848    */
849   static getClasses(node)
850   {
851      if (node && node.className)
852         return node.className.trim().split(' ').filter(className => !className.trim().isEmpty()).map(classname => classname.trim());
853      
854      return [];
855   }
856   
857   /**
858    * Prüft, ob das Element vollständig im Viewport ist. Für den Fall, dass man prüfen will,
859    * ob das Element nur teilweise im Viewport liegt, @see CSS.intersectViewport.
860    *
861    * @param el Element, welches überprüft werden soll.
862    * @return Gibt true zurück, wenn das Element vollständig im Viewport liegt. 
863    */
864   static isInViewport(el)
865   {
866      const rect = el.getBoundingClientRect();
867      return (rect.top >= 0 
868         &&  rect.left >= 0 
869         && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) 
870         && rect.right <= (window.innerWidth || document.documentElement.clientWidth));
871   }
872   
873   /**
874    * Prüft, ob ein Teil des übergebenen Elementes im Viewport liegt. Für den Fall, dass man prüfen will,
875    * ob das Element vollständig im Viewport liegt, @see CSS.isInViewport.
876    *
877    * @param el Element, welches überprüft werden soll.
878    * @return Gibt true zurück, wenn das Element teilweise den Viewport schneidet. Nur, wenn das Element vollständig 
879    * außerhalb des Vieworts liegt, wird false ausgegeben.
880    */
881   static intersectsViewport(el)
882   {
883      const rect = el.getBoundingClientRect();
884      return (rect.top >= -el.offsetHeight 
885         && rect.left >= -el.offsetWidth
886         && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + el.offsetHeight
887         && rect.right <= (window.innerWidth || document.documentElement.clientWidth) + el.offsetWidth);
888   }
889   
890   /**
891    * Prüft, ob ein gegebenes DOM-Element sichtbar ist (display.none oder hidden). Neben dem Element selbst
892    * werden auch Elternelemente geprüft.
893    *
894    * @param el Das zu prüfende Element.
895    * @return true, falls das DOM-Element nicht sichtbar ist.
896    */
897   static isHidden(el)
898   {
899      try
900      {
901         // Durchläuft die gesamte Elternelement-Kette bis zur Wurzel.
902         for (let current = el; current; current = current.parentElement)
903         {
904            const style = window.getComputedStyle(current);
905            
906            // Prüfen, ob das Element (oder eines seiner Elternelemente) ausgeblendet ist.
907            if (style.display === 'none' || style.visibility === 'hidden')
908               return true;
909         }
910         return false; 
911      }
912      catch (e)
913      {
914         if (typeof(HRLogError) === 'function')
915            HRLogError(e);
916      }
917      
918      return true;
919   }
920   
921   /**
922    * Ermittelt die Einheit von CSS-Ausdrücken wie 100px, 5rem, 10% etc.
923    * 
924    * @param expression Der CSS-Ausdruck.
925    * @return Die Einheit des Ausdrucks oder null, falls keine Einheit gefunden werden konnte.
926    */
927   static getUnit(expression)
928   {
929      try
930      {
931         const unitMatch = expression.match(/[a-zA-Z%]+/);
932         return unitMatch ? unitMatch[0].trim() : null;
933      }
934      catch (e)
935      {
936         if (typeof(HRLogError) === 'function')
937            HRLogError(e);
938      }
939   }
940   
941   /**
942    * Toggelt die Sichtbarkeit eines übergebenen Elements. Es wird mit der 'hide'-Klasse gearbeitet, 
943    * die in unserem Framework immer als Klasse vorhanden sein sollte.
944    * 
945    * @param element Das Element, dessen Sichtbarkeit getoggelt werden soll.
946    * @param isVisible true, falls das Element sichtbar sein soll und false, wenn es versteckt werden soll.
947    */
948   static toggleVisibility(element, isVisible)
949   {
950      if (element)
951          element.classList.toggle('hide', !isVisible);
952   }
953   
954   /**
955    * Steuert den Fokus für ein Overlay mit Toggle-Button.
956    * 
957    * @param toggleButton Der Button, der das Overlay öffnen und schließen soll.
958    * @param overlayElement Das Overlay, was getoggelt wird.
959    * @param optionalCloseButton: Ein optionaler Schließen-Button. Ist dieser gesetzt, wird der Toggle für's Schließen nicht mehr fokussiert.
960    * @param optionalDisableBodyScroll: Sorgt dafür, dass der Body die Klasse "no-scroll" toggelt.
961    * @param allowMultipleOverlays: Sorgt dafür, dass auch mehrere Overlays geöffnet werden können.
962    * @param allowFocusEscape: Erlaubt den Fokus außerhalb des Overlays.
963    */
964   static toggleOverlay(toggleButton, overlayElement, optionalCloseButton, optionalDisableBodyScroll, allowMultipleOverlays, allowFocusEscape)
965   {
966      /**
967       * Liefert alle fokussierbaren Elemente innerhalb eines Elements.
968       * 
969       * @param element Das Wurzelelement für die Suche.
970       * @returns Array von fokussierbaren Elementen.
971       */
972      const getFocusableElements = (element = document) =>
973         [...element.querySelectorAll(
974            'a[href], button:not([disabled]), input:not([type="hidden"]):not([disabled]), textarea, select, details, [tabindex]:not
974([tabindex="-1"])'
975         )].filter(el => 
976            !el.hasAttribute('aria-hidden') && !HRCSS.isHidden(el)
977         );
978      
979      /**
980       * Fokus-Falle für Toggle-Button (und optional Schließen-Button): Verhindert, dass mit der Tabulatortaste der Fokus außerhalb des
981       * Overlays wandert, wenn der Button selbst fokussiert ist. Stattdessen wird direkt zum ersten oder letzten
982       * fokussierbaren Element im Overlay gesprungen.
983       * 
984       * @param e Das übergebene Tastatur-Event (keydown).
985       */
986      const handleButtonFocusTrap = function(e) {
987         const focusableElements = getFocusableElements(overlayElement);
988         const first = focusableElements[0];
989         const last = focusableElements[focusableElements.length - 1];
990         
991         if (e.key === 'Tab')
992         {
993            e.preventDefault();
994            e.shiftKey ? last.focus() : first.focus();
995         }
996      };
997      
998      /**
999       * Fokus-Falle für Elemente innerhalb des Overlays: Verhindert, dass der Fokus beim Erreichen des ersten oder letzten Elements
1000       * mit Tab bzw. Shift+Tab nach draußen wandert. Stattdessen wird der Fokus
1001       * zum definierten Startpunkt zurückgesetzt.
1002       *
1003       * @param e Das KeyboardEvent bei Tastendruck.
1004       */
1005      const handleFocusTrap = function(e) {
1006         if (e.key !== 'Tab')
1007            return;
1008         
1009         const focusableElements = getFocusableElements(overlayElement);
1010         const first = focusableElements[0];
1011         const last = focusableElements[focusableElements.length - 1];
1012         const focusTarget = optionalCloseButton || toggleButton;
1013         
1014         if ((e.shiftKey && document.activeElement === first) || (!e.shiftKey && document.activeElement === last))
1015         {
1016            if (focusTarget === document.activeElement)
1017            {
1018               if (!allowFocusEscape)
1019               {
1020                  e.preventDefault();
1021                  e.shiftKey ? last.focus(): first.focus();
1022               }
1023               else if (HRCSS.openOverlays.length > 0)
1024                  closeOverlay(HRCSS.openOverlays.at(-1).overlayElement);
1025            }
1026            else if (!allowFocusEscape)
1027            {
1028               if (!HRCSS.isHidden(focusTarget))
1029               {
1030                  e.preventDefault();
1031                  focusTarget.focus();
1032               }
1033               // Sonderfall: Der optionale Schließen-Button ist zurzeit nicht verfügbar. Wechsel auf den Toggle-Button.
1034               else if (!HRCSS.isHidden(toggleButton))
1035               {
1036                  e.preventDefault();
1037                  toggleButton.focus();
1038               }
1039            }
1040         }
1041      };
1042      
1043      /**
1044       * Öffnet oder schließt das Overlay.
1045       * 
1046       * Beim Öffnen:
1047       * - Fügt nötige Klassen/Attribute hinzu
1048       * - Setzt Fokus ggf. auf Schließen-Button
1049       * - Aktiviert Fokus-Fallen (Focus Trap)
1050       * - Optional: Scrollverhalten deaktivieren (no-scroll)
1051       * - Speichert Overlay-Zustand in Stack (openOverlays)
1052       * 
1053       * Beim Schließen:
1054       * - Entfernt alle Listener & Klassen
1055       * - Reaktiviert Scrollverhalten
1056       * - Entfernt Overlay aus Stack
1057       */
1058      const toggle = () => {
1059         
1060         const isOpen = overlayElement.classList.toggle('overlay-open');
1061         
1062         if (isOpen) // Öffnet Overlay:
1063         {
1064            if (!allowMultipleOverlays)
1065            {
1066               // Alle offenen Overlays schließen.
1067               while (HRCSS.openOverlays.length > 0)
1068               {
1069                  const lastOverlay = HRCSS.openOverlays.at(-1);
1070                  if (lastOverlay?.toggleButton && lastOverlay.overlayElement && lastOverlay.overlayElement.classList.contains('overlay-open'))
1071                     lastOverlay.toggleButton.click();
1072                  else
1073                  {
1074                     if (lastOverlay?.handlers)
1075                        closeOverlay(lastOverlay.overlayElement);
1076                     else
1077                        HRCSS.openOverlays.pop();
1078                  }
1079                  lastOverlay?.toggleButton.blur();
1080               }
1081            }
1082            
1083            if (optionalCloseButton)
1084               setTimeout(() => optionalCloseButton.focus(), 200);
1085            
1086            setTimeout(() => {
1087               const focusableElements = getFocusableElements(overlayElement);
1088               focusableElements.forEach(el => {
1089                  el.addEventListener('keydown', handleFocusTrap);
1090               });
1091            }, 200);
1092            
1093            toggleButton.addEventListener('keydown', handleButtonFocusTrap);
1094            
1095            if (optionalCloseButton)
1096            {
1097               optionalCloseButton.addEventListener('keydown', handleFocusTrap);
1098               optionalCloseButton.addEventListener('click', () => {
1099                  closeOverlay(overlayElement);
1100                });
1101            }
1102            
1103            if (!optionalDisableBodyScroll)
1104               document.body.classList.add('no-scroll');
1105            
1106            toggleButton.classList.add('open');
1107            toggleButton.setAttribute('aria-expanded', 'true');
1108            addCSSClass(overlayElement, 'overlay-open')
1109            
1110            HRCSS.openOverlays.push({
1111               overlayElement,
1112               toggleButton,
1113               optionalCloseButton,
1114               optionalDisableBodyScroll,
1115               allowFocusEscape,
1116               handlers: {
1117                  focusTrap: handleFocusTrap,
1118                  buttonTrap: handleButtonFocusTrap
1119               }
1120            });
1121         }
1122         else // Schließt Overlay:
1123         {
1124            closeOverlay(overlayElement);
1125         }
1126      };
1127      
1128      /**
1129       * Das Overlay wird wieder geschlossen.
1130       * 
1131       * @param overlayElement Overlay, welches geschlossen wird.
1132       */
1133      const closeOverlay = (overlayElement) => {
1134         const overlay = HRCSS.openOverlays.find(o => o.overlayElement === overlayElement);
1135         if (!overlay)
1136            return;
1137         
1138         const { toggleButton, optionalCloseButton, optionalDisableBodyScroll, handlers } = overlay;
1139         const { focusTrap, buttonTrap } = handlers || {};
1140         // Liefert alle potenziell fokussierbaren Elemente (auch die zurzeit nicht sichtbar sind).
1141         const focusableElements = overlayElement.querySelectorAll(
1142            'a[href], button:not([disabled]), input:not([type="hidden"]):not([disabled]), textarea, select, details, [tabindex]:not([tabindex="-1"])'
1143         );
1144         
1145         if (typeof focusTrap === 'function')
1146            focusableElements.forEach(el => el.removeEventListener('keydown', focusTrap));
1147         
1148         if (typeof buttonTrap === 'function')
1149            toggleButton.removeEventListener('keydown', buttonTrap);
1150         
1151         if (optionalCloseButton)
1152            optionalCloseButton.removeEventListener('keydown', focusTrap);
1153         
1154         overlayElement.classList.remove('overlay-open');
1155         toggleButton.classList.remove('open');
1156         toggleButton.setAttribute('aria-expanded', 'false');
1157         toggleButton.focus();
1158         
1159         if (!optionalDisableBodyScroll)
1160            document.body.classList.remove('no-scroll');
1161         
1162         HRCSS.openOverlays = HRCSS.openOverlays.filter(o => o.overlayElement !== overlayElement);
1163      };
1164      
1165      toggleButton?.addEventListener('click', toggle);
1166      
1167      // Code für das Schließen des zuletzt geöffneten Menü auf Escape.
1168      if (!HRCSS._escapeHandlerRegistered) {
1169         window.addEventListener('keydown', (e) => {
1170            if (e.key === 'Escape' && HRCSS.openOverlays.length > 0)
1171            {
1172               let lastOverlay = HRCSS.openOverlays[HRCSS.openOverlays.length - 1];
1173               // Für den Fall, dass aus dem Focus ausgebrochen werden kann, wird der gesamte Teil des Overlays geschlossen.
1174               if (lastOverlay?.allowFocusEscape)
1175               {
1176                  while (lastOverlay?.allowFocusEscape)
1177                  {
1178                     if (lastOverlay.optionalCloseButton)
1179                        lastOverlay.optionalCloseButton.click();
1180                     else if (lastOverlay.toggleButton)
1181                        lastOverlay.toggleButton.click();
1182                     lastOverlay = HRCSS.openOverlays[HRCSS.openOverlays.length - 1];
1183                  }
1184               }
1185               else // Hier wird das letzte Overlay geschlossen.
1186               {
1187                  if (lastOverlay?.optionalCloseButton)
1188                     lastOverlay.optionalCloseButton.click();
1189                  else if (lastOverlay?.toggleButton)
1190                     lastOverlay.toggleButton.click();
1191               }
1192            }
1193         });
1194         HRCSS._escapeHandlerRegistered = true;
1195      }
1196   }
1197}

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.