PageSourceSearch

https://katalogoa.siis.net/themes/bootstrap5/js/truncate.js?_=1779961969

js siis.net collected 2026-10-02 10:21:16 UTC 9,093 bytes, 235 lines download raw bytes

1/* global VuFind, getFocusableNodes */
2
3VuFind.register('truncate', function Truncate() {
4  /**
5   * Initialize the truncation functionality for containers.
6   * @param {string|jQuery|HTMLElement} _container - The container element to apply truncation to
7   * @param {string}                    [_element] - Optional selector for specific elements within the container to truncate
8   * @param {Function}                  [_fill]    - Optional function to modify button markup before insertion
9   */
10  function initTruncate(_container, _element, _fill) {
11    const defaultSettings = {
12      'btn-class': '',
13      'in-place-toggle': false,
14      'label': null,
15      'less-icon': 'truncate-less',
16      'less-label': VuFind.translate('less_ellipsis'),
17      'more-icon': 'truncate-more',
18      'more-label': VuFind.translate('more_ellipsis'),
19      'rows': 3,
20      'top-toggle': Infinity,
21      'wrapper-class': '', // '' will glean from element, false or null will exclude a class
22      'wrapper-tagname': null, // falsey values will glean from element
23      'label-icons': 'before' // 'after' = icon after label, 'before' = icon before label, false = no icons
24    };
25
26    var zeroHeightContainers = [];
27
28    $(_container).not('.truncate-done').each(function truncate() {
29      var container = $(this);
30      var settings = Object.assign({}, defaultSettings, container.data('truncate'));
31
32      var element = typeof _element !== 'undefined'
33        ? container.find(_element)
34        : (typeof settings.element !== 'undefined')
35          ? container.find(settings.element)
36          : false;
37      var fill = typeof _fill === 'undefined' ? function fill(m) { return m; } : _fill;
38      var maxRows = parseFloat(settings.rows);
39      var moreLabel, lessLabel;
40      moreLabel = lessLabel = settings.label;
41      if (moreLabel === null) {
42        moreLabel = settings['more-label'];
43        lessLabel = settings['less-label'];
44      }
45      var btnClass = settings['btn-class'] ? ' ' + settings['btn-class'] : '';
46      var topToggle = settings['top-toggle'];
47      var inPlaceToggle = (element && settings['in-place-toggle'])
48        ? settings['in-place-toggle']
49        : false;
50
51      var parent, numRows, shouldTruncate, truncatedHeight;
52      var wrapperClass = settings['wrapper-class'];
53      var wrapperTagName = settings['wrapper-tagname'];
54      var toggleElements = [];
55
56      if (element) {
57        // Element-based truncation
58        parent = element.parent();
59        numRows = container.find(element).length || 0;
60        shouldTruncate = numRows > maxRows;
61
62        if (wrapperClass === '') {
63          wrapperClass = element.length ? element.prop('class') : '';
64        }
65        if (!wrapperTagName) {
66          wrapperTagName = element.length && element.prop('tagName').toLowerCase();
67        }
68
69        if (shouldTruncate) {
70          element.each(function hideRows(i) {
71            if (i === maxRows) {
72              $(this).addClass('truncate-start');
73            }
74            if (i >= maxRows) {
75              $(this).hide();
76              toggleElements.push(this);
77            }
78          });
79        }
80      } else {
81        // Height-based truncation
82        parent = container;
83        var rowHeight;
84        if (container.children().length > 0) {
85          // Use first child as the height element if available
86          var heightElem = container.children().first();
87          var display = heightElem.css('display');
88          if (display === 'block' || display === 'inline-block') {
89            rowHeight = parseFloat(heightElem.outerHeight());
90          } else {
91            rowHeight = parseFloat(heightElem.css('line-height').replace('px', ''));
92          }
93        } else {
94          rowHeight = parseFloat(container.css('line-height').replace('px', ''));
95        }
96        numRows = container.height() / rowHeight;
97        // Truncate only if it saves at least 1.5 rows. This accounts for the room
98        // the more button takes as well as any fractional imprecision.
99        shouldTruncate = maxRows === 0 || maxRows !== 0 && numRows > maxRows + 1.5;
100
101        if (shouldTruncate) {
102          truncatedHeight = maxRows * rowHeight;
103          container.css('height', truncatedHeight + 'px');
104        }
105      }
106
107      if (shouldTruncate) {
108        var btnMore = '<button type="button" class="btn more-btn' + btnClass + '">'
109          + (settings['label-icons'] === 'before' ? VuFind.icon(settings['more-icon']) + ' ' : '')
110          + '<span>' + moreLabel + '</span>'
111          + (settings['label-icons'] === 'after' ? ' ' + VuFind.icon(settings['more-icon']) : '')
112          + '</button>';
113        var btnLess = '<button type="button" class="btn less-btn' + btnClass + '">'
114          + (settings['label-icons'] === 'before' ? VuFind.icon(settings['less-icon']) + ' ' : '')
115          + '<span>' + lessLabel + '</span>'
116          + (settings['label-icons'] === 'after' ? ' ' + VuFind.icon(settings['less-icon']) : '')
117          + '</button>';
118
119        wrapperClass = wrapperClass ? ' ' + wrapperClass : '';
120        wrapperTagName = wrapperTagName || 'div';
121        var btnWrapper = $('<' + wrapperTagName + ' class="more-less-btn-wrapper' + wrapperClass + '"></' + wrapperTagName + '>');
122        var btnWrapperBtm = btnWrapper.clone().append(fill(btnMore + btnLess));
123        var btnWrapperTop = (numRows > topToggle) ? btnWrapper.clone().append(fill(btnLess)) : false;
124
125        // Attach show/hide buttons to the top and bottom or display in place
126        if (btnWrapperTop) {
127          if (element) {
128            btnWrapperTop.prependTo(parent);
129          } else {
130            btnWrapperTop.insertBefore(parent);
131          }
132        }
133        if (inPlaceToggle) {
134          btnWrapperBtm.insertBefore(parent.find('.truncate-start'));
135        } else if (element) {
136          btnWrapperBtm.appendTo(parent);
137        } else {
138          btnWrapperBtm.insertAfter(parent);
139        }
140
141        btnWrapperBtm.find('.less-btn').hide();
142        if (btnWrapperTop) {
143          btnWrapperTop.hide();
144        }
145
146        var onClickLessBtnHandler = function onClickLessBtn(/*event*/) {
147          btnWrapperBtm.find('.less-btn').hide();
148          if (btnWrapperTop) {
149            btnWrapperTop.hide();
150          }
151          btnWrapperBtm.find('.more-btn').show();
152          if (element) {
153            toggleElements.forEach(function hideToggles(toggleElement) {
154              $(toggleElement).toggle();
155            });
156          } else if (truncatedHeight === 0) {
157            container.hide();
158          } else {
159            container.css('height', truncatedHeight + 'px');
160          }
161          btnWrapperBtm.find('.more-btn').focus();
162        };
163        btnWrapperBtm.find('.less-btn').click(onClickLessBtnHandler);
164        if (btnWrapperTop) {
165          btnWrapperTop.find('.less-btn').click(onClickLessBtnHandler);
166        }
167
168        btnWrapperBtm.find('.more-btn').click(function onClickMoreBtn(/*event*/) {
169          $(this).hide();
170          btnWrapperBtm.find('.less-btn').show();
171          if (btnWrapperTop) {
172            btnWrapperTop.show();
173            btnWrapperTop.find('.less-btn').focus();
174          } else {
175            btnWrapperBtm.find('.less-btn').focus();
176          }
177          if (element) {
178            toggleElements.forEach(function showToggles(toggleElement) {
179              $(toggleElement).toggle();
180            });
181          } else if (truncatedHeight === 0) {
182            container.show();
183          } else {
184            container.css('height', 'auto');
185          }
186        });
187      }
188
189      container.addClass('truncate-done');
190
191      // Make hidden elements unfocusable
192      // - Create IntersectionObserver
193      const root = container.get(0);
194      const observer = new IntersectionObserver(
195        (entries) => {
196          entries.forEach((entry) => {
197            if (entry.intersectionRatio > 0) {
198              entry.target.removeAttribute("tabindex");
199              // restore previous tabindex
200              if (entry.target.dataset && entry.target.dataset.tabindex) {
201                entry.target.setAttribute("tabindex", entry.target.dataset.tabindex);
202                delete entry.target.dataset.tabindex;
203              }
204            } else {
205              // save previous tabindex
206              if (entry.target.getAttribute("tabindex")) {
207                entry.target.dataset.tabindex = entry.target.getAttribute("tabindex");
208              }
209
210              entry.target.setAttribute("tabindex", -1);
211            }
212          });
213        },
214        { root }
215      );
216      // - add all focusable elements of facets to observer
217      getFocusableNodes(root).forEach((el) => observer.observe(el));
218
219      if (truncatedHeight === 0) {
220        zeroHeightContainers.push(container);
221      }
222    });
223
224    // Hide zero-height containers. They are not hidden immediately to allow for
225    // height calculation of nested containers.
226    zeroHeightContainers.forEach(function hideContainer(container) {
227      container.hide();
228      container.css('height', 'auto');
229    });
230  }
231
232  return {
233    initTruncate: initTruncate
234  };
235});

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.