|
| 1 | +// maximize-select2-height v1.0.2 |
| 2 | +// (c) Panorama Education 2015 |
| 3 | +// MIT License |
| 4 | + |
| 5 | +// This jQuery/Select2 plugin expands a Select2 dropdown to take up as much |
| 6 | +// height as possible given its position on the page and the current viewport |
| 7 | +// size. The plugin correctly handles: |
| 8 | +// - Dynamic window resizing. |
| 9 | +// - The effects of scroll bars on the viewport. |
| 10 | +// - Select2 rendering dropdowns both upwards and downwards. |
| 11 | + |
| 12 | +// NOTE: The original <select> element that is $().select2()'d *must* have a |
| 13 | +// unique ID for this code to work. (Ex: <select id="my-unique-id"></select>) |
| 14 | + |
| 15 | +(function ($) { |
| 16 | + "use strict"; |
| 17 | + |
| 18 | + // We can find these elements now, since the properties we check on them are |
| 19 | + // all via methods that are recalculated each time. |
| 20 | + var $window = $(window); |
| 21 | + var $document = $(document); |
| 22 | + |
| 23 | + // @param {Object} options The options object passed in when this plugin is |
| 24 | + // initialized |
| 25 | + // @param {Boolean} dropdownDownwards True iff the dropdown is rendered |
| 26 | + // downwards (Select2 sometimes renders the options upwards to better fit on |
| 27 | + // a page) |
| 28 | + // @return {Object} The options passed in, combined with defaults. Keys are: |
| 29 | + // - cushion: The number of pixels between the edge of the dropdown and the |
| 30 | + // edge of the viewable window. [Default: 10, except when a |
| 31 | + // horizontal scroll bar would interfere, in which case it's 30.] |
| 32 | + // NOTE: If a value is passed in, no adjustments for possible |
| 33 | + // scroll bars are made. |
| 34 | + var settings = function (options, dropdownDownwards) { |
| 35 | + return $.extend({ |
| 36 | + cushion: ( |
| 37 | + dropdownDownwards && $document.width() > $window.width() |
| 38 | + ) ? 30 : 10 |
| 39 | + }, options); |
| 40 | + }; |
| 41 | + |
| 42 | + // @param {String} id The DOM element ID for the original <select> node |
| 43 | + // @param {jQuery object} $select2Results The DOM element with class |
| 44 | + // "select2-results" |
| 45 | + // @param {jQuery object} $grandparent The grandparent object of the |
| 46 | + // $select2Results object |
| 47 | + // @param {Object} options The options object passed in when this plugin is |
| 48 | + // initialized |
| 49 | + // @param {Boolean} dropdownDownwards True iff the dropdown is rendered |
| 50 | + // downwards (Select2 sometimes renders the options upwards to better fit on |
| 51 | + // a page) |
| 52 | + // @return {Number} the maximum height of the Select2 results box to display |
| 53 | + var computeMaxHeight = function ( |
| 54 | + id, $select2Results, $grandparent, options, dropdownDownwards |
| 55 | + ) { |
| 56 | + var height; |
| 57 | + var resultsBoxMiscellaniaHeight; |
| 58 | + var widgetBoxOffset; |
| 59 | + |
| 60 | + if (dropdownDownwards) { |
| 61 | + // When the dropdown appears downwards, the formula is: |
| 62 | + // visible window size |
| 63 | + // + out-of-window pixels we've scrolled past |
| 64 | + // - size of content (including offscreen content) above results box |
| 65 | + // ------------------------------------------ |
| 66 | + // total height available to us |
| 67 | + |
| 68 | + // innerHeight is more accurate across browsers than $(window).height(). |
| 69 | + height = window.innerHeight + |
| 70 | + $window.scrollTop() - |
| 71 | + $select2Results.offset().top; |
| 72 | + } else { |
| 73 | + // When the dropdown appears upwards, the formula is: |
| 74 | + // vertical position of the widget (clickable) dropdown box |
| 75 | + // - out-of-window pixels we've scrolled past |
| 76 | + // - height of the search box and other content above the actual results |
| 77 | + // but in the results box |
| 78 | + // ------------------------------------------ |
| 79 | + // total height available to us |
| 80 | + |
| 81 | + // Compute the global vertical offset of the widget box (the one with the |
| 82 | + // downward arrow that the user clicks on to expose options). |
| 83 | + widgetBoxOffset = $("#select2-" + id + "-container"). |
| 84 | + parent().parent().parent().offset().top; |
| 85 | + |
| 86 | + // Compute the height, if any, of search box and other content in the |
| 87 | + // results box but not part of the results. |
| 88 | + resultsBoxMiscellaniaHeight = $grandparent.height() - |
| 89 | + $select2Results.height(); |
| 90 | + height = widgetBoxOffset - |
| 91 | + $window.scrollTop() - |
| 92 | + resultsBoxMiscellaniaHeight; |
| 93 | + } |
| 94 | + |
| 95 | + // Leave a little cushion to prevent the dropdown from |
| 96 | + // rendering off the edge of the viewport. |
| 97 | + return height - settings(options, dropdownDownwards).cushion; |
| 98 | + }; |
| 99 | + |
| 100 | + // Call on a jQuery Select2 element to maximize the height of the dropdown |
| 101 | + // every time it is opened. |
| 102 | + // @param {Object} options The options object passed in when this plugin is |
| 103 | + // initialized |
| 104 | + $.fn.maximizeSelect2Height = function (options) { |
| 105 | + return this.each(function (_, el) { |
| 106 | + // Each time the Select2 is opened, resize it to take up as much vertical |
| 107 | + // space as possible given its position and the current viewport size. |
| 108 | + $(el).on("select2:open", function () { |
| 109 | + // We have to put this code block inside a timeout because we determine |
| 110 | + // whether the dropdown is rendered upwards or downwards via a hack that |
| 111 | + // looks at the CSS classes, and these aren't set until Select2 has a |
| 112 | + // chance to render the box, which occurs after this event fires. |
| 113 | + |
| 114 | + // The alternative solution that avoids using a timeout would be to |
| 115 | + // directly modify the document's stylesheets (instead of the styles for |
| 116 | + // individual elements), but that is both ugly/dangerous and actually |
| 117 | + // impossible for us because we need to modify the styles of a parent |
| 118 | + // node of a given DOM node when the parent has no unique ID, which CSS |
| 119 | + // doesn't have the ability to do. |
| 120 | + setTimeout(function () { |
| 121 | + var $select2Results = $("#select2-" + el.id + "-results"); |
| 122 | + var $parent = $select2Results.parent(); |
| 123 | + var $grandparent = $parent.parent(); |
| 124 | + var dropdownDownwards = $grandparent |
| 125 | + .hasClass("select2-dropdown--below"); |
| 126 | + |
| 127 | + var maxHeight = computeMaxHeight( |
| 128 | + el.id, |
| 129 | + $select2Results, |
| 130 | + $grandparent, |
| 131 | + options, |
| 132 | + dropdownDownwards |
| 133 | + ); |
| 134 | + |
| 135 | + // Set the max height of the relevant DOM elements. We use max-height |
| 136 | + // instead of height directly to correctly handle cases in which there |
| 137 | + // are only a few elements (we don't want a giant empty dropdown box). |
| 138 | + $parent.css("max-height", maxHeight); |
| 139 | + $select2Results.css("max-height", maxHeight); |
| 140 | + |
| 141 | + // Select2 corrects the positioning of the results box on scroll, so |
| 142 | + // we trigger that event here to let it auto-correct. This is done for |
| 143 | + // the case where the dropdown appears upwards; we adjust its max |
| 144 | + // height but we also want to move it up further, lest it cover up the |
| 145 | + // initial dropdown box. |
| 146 | + $(document).trigger("scroll"); |
| 147 | + }); |
| 148 | + }); |
| 149 | + }); |
| 150 | + }; |
| 151 | +})(jQuery); |
0 commit comments