PageSourceSearch

https://www.kb.dk/profiles/contrib/rdl_profile/themes/rdltheme/assets/js/custom-script.js?tlu2e1

js kb.dk collected 2026-09-24 08:46:55 UTC 13,659 bytes, 354 lines download raw bytes

1(function($) {
2// Add table class
3  $(document).ready(function() {
4
5    // Accessibility issue. Check if the aria-required attribute exists
6    // on an HTML element already holding a required attribute.
7    // If such an element exist, make sure that the aria-required attribute is 'true'
8    let oRequired = jQuery('.article').find(`[aria-required='']`);
9    if (oRequired.attr('required') == 'required' || oRequired.attr('required')) {
10      oRequired.attr('aria-required', 'true');
11    }
12
13    $('article table').each(function () {
14      var s = $(this).attr('style');
15      classes = '';
16      if (s !== undefined) {
17        const styles = s.split(";");
18        styles.forEach(function countEntry(entry) {
19          t = entry.split(':');
20          if (t[0] == 'border-style' || t[0] == 'border-bottom-style') {
21            classes = t[1];
22          }
23        }, this);
24      }
25      $(this).addClass('table');
26      $(this).addClass(classes);
27      $(this).addClass($(this).data('classes'));
28      $(this).wrap('<div class="table-responsive "></div>');
29    });
30    $('article ul:not(.breadcrumb, .list-unstyled, .dropdown-menu), article ol, article dl').each(function () {
31      $(this).addClass('rdl-list');
32    });
33
34    $('div[data-drupal-messages-fallback]').detach().prependTo('#mainContent .page-container');
35    function moveMessages() {
36      if ($('#rdl-block-message').length == 0) {
37        $('#rdl-system-message').parent().detach().prependTo('#mainHeader .rdl-system-messages-wrapper');
38      }
39      // Move message to the top of the block
40      if ($('#rdl-block-message').length == 1) {
41        $('#rdl-system-message').parent().removeClass('container-fluid').addClass('container');
42        const errorDiv = document.querySelector('.rdl-system-message-error');
43        if (errorDiv) {
44          errorDiv.setAttribute('tabindex', '-1');
45          errorDiv.focus({ preventScroll: true });
46          errorDiv.scrollIntoView({ behavior: 'smooth', block: 'start' });
47        }
48      }
49    }
50
51    // Because they are not there on load
52    window.setTimeout(moveMessages, 10);
53  });
54})(jQuery);
55
56// TODO: Replaced by working patternlab.
57(function($) {
58  $buttons = $('.rdl-filter-btn-group');
59  $buttons.find('.rdl-show-more').click(function(e) {
60    $buttons = $('.rdl-filter-btn-group');
61    $buttons.toggleClass('show-all-filters');
62  });
63})(jQuery);
64
65(function ($) {
66  $(document).ready(function () {
67    var gateway_nids = '';
68    $('[data-type=download-spot]').each(function(){
69      gateway_nids = gateway_nids + ',' + $(this).data('id');
70    });
71    Cookies.set('gateway_nids', gateway_nids, { path: '/', sameSite: 'lax'});
72  });
73})(jQuery, window.Cookies);
74
75
76/* Functions to be overwritte from the RISE repo concerning the content spot banner.
77Should later be implemented in the RISE repo, since functionality is needed on other sites aswell, imho.
78Functionality makes it possible to have several content spot banners with filters on the same site. */
79
80('use strict');
81
82function filterSelectedDisplay(display, filter) {
83  //get all the content spot button sections
84  let drupalButtonGroup = document.querySelectorAll('.rdl-filter-btn-group');
85  //run through them
86  Array.prototype.slice.call(drupalButtonGroup).forEach(function(selection) {
87    //check if their data-heading matches the display sent from the function
88    if(selection.getAttribute('data-heading') === display) {
89      //get the buttons from that selected content spot banner section
90      let buttons = selection.querySelectorAll('button');
91      //run through the buttons
92      Array.prototype.slice.call(buttons).forEach(function(button) {
93        //check if the button matches the filter sent from the function
94        if(button.innerHTML === filter) {
95          //if it does, set the active class.
96          button.classList.add('active');
97        }
98        else {
99          //if it does not, make sure the class is removed.
100          button.classList.remove('active');
101        }
102      });
103    }
104  });
105  //get all the content spot blurb sections
106  let drupalContentSpotFilterGroup = document.querySelectorAll('.rdl-grid-container-wrapper');
107  //run through them
108  Array.prototype.slice.call(drupalContentSpotFilterGroup).forEac
108h(function(selection) {
109    //check if their data-heading matches the display sent from the function
110    if(selection.getAttribute('data-heading') === display) {
111      //get all the blurbs in the section
112      let blurbs = selection.querySelectorAll('[data-filter]');
113      //run through the blurbs
114      Array.prototype.slice.call(blurbs).forEach(function(blurb) {
115        //get an array of all the data-filters the given blurb
116        let blurbFilters = blurb.getAttribute('data-filter').split(',');
117        //remove the last one, since we know it'll be empty because of the comma.
118        blurbFilters.pop();
119        //declare variable to keep track if we should show the blurb
120        let beShown = false;
121        //run through the filters on the given blurb
122        Array.prototype.slice.call(blurbFilters).forEach(function(filterOption) {
123          //if the filter option matches the filter given to the function, set true
124          if(filterOption === filter) {
125            beShown = true;
126          }
127        });
128        //when the run trough them all is complete, check if the blurb should be shown.
129        if(beShown === true) {
130          //if it should, remove the hide class from the element.
131          blurb.classList.remove('hide');
132        }
133        else {
134          //if it should not, add the hide class to the element.
135          blurb.classList.add('hide');
136        }
137      });
138    }
139  });
140  //finally, update the URL based on the filter and the display.
141  filterURLmanipulation(display, filter);
142}
143
144function filterURLmanipulation(display, filter) {
145  //get the URL and split it at the first questionmark
146  let siteArg = window.location.href.split(/\?(.*)/)[1];
147  if(siteArg != undefined) {
148    siteArg = '/?' + siteArg.split(/\#(.*)/)[0];
149  }
150  else {
151    siteArg = '';
152  }
153  let queryStr = window.location.href.split(/\#(.*)/);
154  //remove that last element, since we get a last entry to the array which is emtpy.
155  queryStr.pop();
156  //if we only have the URL, add the new filter.
157  if(queryStr.length === 0) {
158    //add filter based on the arguments.
159    let hashString = display + '=' + filter;
160    location.href = siteArg + '#' + hashString;
161  }
162  //if the URL already have filters added.
163  else if(queryStr.length > 0) {
164    //create holding variables.
165    let requestObj = {};
166    let level1 = [];
167    //split the rest of the array at the & to get each content spot id and their associated filter option.
168    level1 = queryStr[1].split('&');
169    //run though the filters
170    for (let i = 0; i < level1.length; i++) {
171      //split the filter into id and filter option.
172      let level2 = level1[i].split('=');
173      //store the values in the object.
174      requestObj[level2[0]] = level2[1];
175    }
176    //create holding variables.
177    let paramString = '';
178    let paramSet = false;
179    //run through the object.
180    Object.keys(requestObj).forEach(function(item, index) {
181      //if the display given matches the argument from the fuction, we update the filter option.
182      if(item === display) {
183        requestObj[item] = filter;
184        //and we indicate the filter already exists in the URL.
185        paramSet = true;
186      }
187      //if this is the first loop, add the id and filter to the string without & before.
188      if(index === 0) {
189        paramString += item + '=' + requestObj[item];
190      }
191      //else add it with & before.
192      else {
193        paramString += '&' + item + '=' + requestObj[item];
194      }
195  });
196  //If the argument filter was not in the object of already existing filters, add it afterwards.
197  if(paramSet === false) {
198    paramString = paramString += '&' + display + '=' + filter;
199  }
200  //set the filters in the URL.
201  location.href = siteArg + '#' + paramString;
202  }
203}
204
205function showFiltersFromURL() {
206  //get the URL and split it at the first questionmark
207  let queryStr = window.location.href.split(/\#(.*)/);
208  //remove that last element, since we get a last entry to the array which is emtpy.
209  queryStr.pop();
210  //remove the URL, thus leaving us with the arguments.
211  queryStr.shift();
212  //check if we even have a filter pressent
213  if(queryStr.length >= 1) {
214    //splut the filters if possible.
215    let queryParams = queryStr[0].split('&');
216    //run through the filters.
217    for(let x=0;x<queryParams.length;x++) {
218      //split the selected param into the display and the filter.
219      let selectedParams = queryParams[x].split('=');
220      //run the function to display selected filter on selected display.
221      filterSelectedDisplay(selectedParams[0], decodeURI(selectedParams[1]));
222    }
223  }
224}
225
226(function ($, Drupal, cookies) {
227    $(document).ready(function () {
228      if ($('ul.rdl-secondary-nav > li.dropdown-relative').find('.collapse').length == 1 && (cookie
228s.get('dropdown_showed') == undefined || cookies.get('dropdown_showed') != 1)) {
229        $('ul.rdl-secondary-nav > li.dropdown-relative').find('.collapse').css('display', 'inline-flex');
230        const d = new Date();
231        d.setTime(d.getTime() + (365 * 24 * 60 * 60 * 1000));
232        let expires = "expires=" + d.toUTCString();
233        document.cookie = "dropdown_showed=1;" + expires + ";path=/"
234        setTimeout(() => {
235          $('ul.rdl-secondary-nav > li.dropdown-relative').find('.collapse').removeAttr('style');
236      }, 5000);
237      }
238    });
239})(jQuery, Drupal, window.Cookies);
240
241// Accessibility issue. If a Bootstrap-select is present,
242// append the aria-controls with the id of the nearest select
243(function ($, Drupal) {
244  Drupal.behaviors.customBootstrapSelect = {
245    attach: function (context, settings) {
246      // Initialize the MutationObserver
247      var observer = new MutationObserver(function (mutations) {
248        mutations.forEach(function (mutation) {
249          // Check if the mutation added new nodes
250          if (mutation.addedNodes.length) {
251            $(mutation.addedNodes).find('button[role="combobox"]').each(function () {
252              var selectId = $(this).closest('.bootstrap-select').find('select').attr('id');
253              $(this).attr('aria-controls', selectId);
254            });
255          }
256        });
257      });
258
259      // Observe the document body for changes
260      observer.observe(document.body, {
261        childList: true,
262        subtree: true
263      });
264
265      // Run immediately in case the elements are already present
266      $('button[role="combobox"]').each(function () {
267        var selectId = $(this).closest('.bootstrap-select').find('select').attr('id');
268        $(this).attr('aria-controls', selectId);
269      });
270    }
271  };
272
273  $(document).ready(function() {
274    $('.rdl-overlay-link').on('focus', function () {
275      $(this).parent().find('.rdl-hero-content .row').addClass("hero-focus");
276    });
277    $('.rdl-overlay-link').on('blur', function () {
278      $(this).parent().find('.rdl-hero-content .row').removeClass("hero-focus");
279    });
280    $('.rdl-card-link .rdl-overlay-link').on('mouseenter', function() {
281      $(this).parent().find('.rdl-spot-header a').css("text-decoration", "underline");
282    });
283    $('.rdl-card-link .rdl-overlay-link').on('mouseleave', function() {
284      $(this).parent().find('.rdl-spot-header a').css("text-decoration", "none");
285    });
286  });
287})(jQuery, Drupal);
288
289(function ($, Drupal) {
290  $(window).on('load', function () {
291    function setHeroButtonPlacement() {
292      const $button = $('.rdl-hero-first .rdl-hero-button');
293      const $lead = $('.rdl-hero .rdl-hero-lead');
294
295      if ($button.length && $lead.length) {
296        const top = $lead.outerHeight() + 90;
297        $button.css({
298          top: top + 'px',
299          opacity: 1
300        });
301        // Repaint parent since Chrome can't position absolute items in a clip-path on page-load.
302        $button.parent().style.transform = 'translateZ(0)';
303      }
304    }
305    // Use "double" calls (requestAnimationFrame) so Chrome will render the button correct on page-load.
306    requestAnimationFrame(function () {
307      requestAnimationFrame(setHeroButtonPlacement);
308    });
309    $(window).on('resize', setHeroButtonPlacement);
310
311  });
312})(jQuery, Drupal);
313
314// Check if we have a form error. If so, set focus on first element with invalid data
315(function ($, Drupal) {
316  $(document).ready(function () {
317    $('.webform-submission-form .is-invalid:first').focus();
318  });
319})(jQuery);
320
321// Handle aria-expanded and sr-only-text for fact spots
322(function ($, Drupal) {
323  Drupal.behaviors.rdlToggleSpot = {
324    attach: function (context, settings) {
325      $('.rdl-toggle-spot-button', context).each(function () {
326        let $button = $(this);
327        let openText = $button.attr('data-open-text');
328        let closedText = $button.attr('data-closed-text');
329        let expanded = $button.attr('aria-expanded');
330        let $controls = $('#' + $button.attr('aria-controls'));
331
332        $button.on('click', function() {
333          if (expanded === "false") {
334            $(this).attr('aria-expanded', 'true');
335            $(this).attr('title', closedText);
336            $(this).find('.sr-only').text(closedText);
337            $controls.removeAttr('hidden');
338            $controls.removeAttr('inert');
339          }
340          else {
341            $(this).attr('aria-expanded', 'false');
342            $(this).attr('title', openText);
343            $(this).find('.sr-only').text(openText);
344            $controls.attr('hidden', '');
345            $controls.attr('inert', '');
346          }
347          expanded = $(this).attr('aria-expanded');
348        });
349      });
350    }
351  };
352})(jQuery, Drupal);
353
354

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.