1/** 2 * jQuery Expanding Grid plugin. 3 * 4 * By Dan Boulet - https://danboulet.com 5 */ 6(function ($, window, document) { 7 8// Enable strict mode 9"use strict"; 10 11/** 12 * Return the last element in the current row of a grid layout. 13 */ 14var getLastSiblingInRow = function (element) { 15 var candidate = element, 16 elementTop = element.offsetTop; 17 18 // Loop through the elementâs next siblings and look for the first one which 19 // is positioned further down the page. 20 while (candidate.nextElementSibling !== null) { 21 if (candidate.nextElementSibling.offsetTop > elementTop) { 22 return candidate; 23 } 24 candidate = candidate.nextElementSibling; 25 } 26 return candidate; 27}; 28 29/** 30 * Calculate the distance that we need to scroll the page to bring a 31 * section, defined as the area between the top and bottom, into view. 32 */ 33var calculatePageScrollDistance = function (top, bottom) { 34 var windowScrollDistance = $(window).scrollTop(), 35 windowHeight = $(window).height(), 36 scrollDistanceToTop, 37 scrollDistanceToBottom; 38 39 // Scroll to the top of the section if the we are already scrolled past it. 40 if (windowScrollDistance >= top) { 41 return top - windowScrollDistance; 42 } 43 // Do nothing if there is enough space to show the section without having to scroll. 44 else if ((windowScrollDistance + windowHeight) >= bottom) { 45 return 0; 46 } 47 else { 48 // Find the maximum distance we can scroll without passing the top of the section. 49 scrollDistanceToTop = top - windowScrollDistance; 50 // Find the distance we need to scroll to reveal the entire section. 51 scrollDistanceToBottom = bottom - (windowScrollDistance + windowHeight); 52 53 return Math.min(scrollDistanceToTop, scrollDistanceToBottom); 54 } 55}; 56 57/** 58 * Create the expanding preview grid. 59 */ 60var expandingGrid = function (context, options) { 61 var defaults = { 62 animationDuration: 250, 63 linksSelector: '.links a', 64 expandingAreaSelector: '.expanding-container', 65 closeButtonMarkup: '<a href="#" class="close-button">Close</a>', 66 spacerMarkup: '<span class="spacer" aria-hidden="true"/>', 67 elementActiveClass: 'active', 68 elementExpandedClass: 'expanded', 69 onExpandBefore: false, 70 onExpandAfter: false 71 }; 72 73 var settings = $.extend({}, defaults, options); 74 75 var isExpanded = false; 76 var activeLink = false; 77 var activeExpandedArea = false; 78 var activeExpandedAreaTop = false; 79 var activeExpandedAreaHeight = false; 80 var lastItemInActiveRow = false; 81 var activeRowChanged = false; 82 var checkExpandedAreaResize = false; 83 var $links = $(settings.linksSelector, context); 84 var $expandingAreas = $(settings.expandingAreaSelector, context); 85 var $closeButton = $(settings.closeButtonMarkup); 86 var $spacer = $(settings.spacerMarkup); 87 var $secondarySpacer = $spacer.clone(); 88 89 /** 90 * Scroll a section of the page into view, using animation. 91 */ 92 var scrollSectionIntoView = function (top, bottom, duration, callback) { 93 var animate; 94 var scroll = 0; 95 var distance = calculatePageScrollDistance(top, bottom); 96 var windowScrollDistance = $(window).scrollTop(); 97 var timeLeft; 98 99 // Set default duration. 100 duration = (typeof duration === 'undefined') ? settings.animationDuration : duration; 101 timeLeft = duration; 102 103 var start = new Date().getTime(); 104 var last = start; 105 var tick = function() { 106 timeLeft = Math.max(duration - (new Date() - start), 0); 107 108 var x = (timeLeft === 0 || distance === 0) ? 0 : ((new Date() - last) / timeLeft * distance); 109 var diff = (distance > 0 ? Math.min(x, distance) : Math.max(x, distance)); 110 distance = distance - diff; 111 scroll += diff; 112 window.scrollTo(0, windowScrollDistance + scroll); 113 114 last = new Date().getTime(); 115 116 if (last - start <= duration) { 117 animate = (window.requestAnimationFrame && requestAnimationFrame(tick)) || setTimeout(tick, 16); 118 } 119 else { 120 if (typeof callback === 'function') { 121 callback(); 122 } 123 } 124 }; 125 126 tick(); 127 }; 128 129 // Process the links. 130 $links.each(function () { 131 var $this = $(this); 132 var targetId = $this.attr('href').match(/#([^\?]+)/)[1]; 133 var target = document.getElementById(targetId); 134 135 if (target) { 136 $this.click(function (event) { 137 var clickedLink = this; 138 var scrollTargetOffset; 139 var closeButtonAnimationDelay; 140 141 event.preventDefault(); 142 143 // Is this link already expanded? 144 if (isExpanded && activeLink === clickedLink) { 145 // Close it. 146 $closeButton.click(); 147 } 148 // Otherwise, expand it. 149 else { 150 $links.removeClass(settings.elementActiveClass).filter($this).addClass(settings.elementActiveClass).parent('li').each(function () { 151 var lastSibling = getLastSiblingInRow(this); 152 activeRowChanged = lastSibling !== lastItemInActiveRow; 153 if (activeRowChanged) { 154 lastItemInActiveRow = lastSibling; 155 } 156 // If we are changing rows, replace spacer with secondary spacer. 157 if (isExpanded && activeRowChanged) { 158 $secondarySpacer.height($spacer.height()); 159 $spacer.height(0).replaceWith($secondarySpacer); 160 } 161 $(lastItemInActiveRow).after($spacer); 162 });
163 if (isExpanded && activeRowChanged) { 164 $secondarySpacer.animate({height: 0}, settings.animationDuration, function () { 165 $(this).detach(); 166 }); 167 $closeButton.removeClass(settings.elementActiveClass).hide(); 168 } 169 scrollTargetOffset = ($secondarySpacer.position().top < $spacer.position().top ? $secondarySpacer.height() : 0); 170 activeExpandedAreaTop = ($spacer.position().top - scrollTargetOffset); 171 $expandingAreas.removeClass(settings.elementExpandedClass).hide().filter(target).each(function () { 172 var $this = $(this); 173 var autoHeight = $this.height(); 174 var autoOuterHeight = $this.outerHeight(); 175 var initialHeight = (isExpanded && activeExpandedAreaHeight && (activeRowChanged === false)) ? activeExpandedAreaHeight : 0; 176 177 stopExpandedAreaMonitor(); 178 179 $spacer.animate({height: autoHeight + 'px'}, settings.animationDuration); 180 181 $this.css({ 182 height: initialHeight + 'px', 183 position: 'absolute', 184 left: 0, 185 top: $spacer.position().top + 'px' 186 }).show(0, function () { 187 // Callback. 188 if (typeof settings.onExpandBefore === 'function') { 189 settings.onExpandBefore.call(this); 190 } 191 }).animate({ 192 height: autoHeight + 'px', 193 top: activeExpandedAreaTop + 'px' 194 }, settings.animationDuration, function () { 195 $this.css({height: 'auto'}).addClass(settings.elementExpandedClass); 196 197 // Set a timer to monitor changes to expanded areaâs height. 198 activeExpandedAreaHeight = $this.height(); 199 checkExpandedAreaResize = setInterval(function () { 200 var activeExpandedAreaNewHeight = $this.height(); 201 if (activeExpandedAreaNewHeight !== activeExpandedAreaHeight) { 202 activeExpandedAreaHeight = activeExpandedAreaNewHeight; 203 syncExpandedAreaWithSpacer(); 204 } 205 }, 1000); 206 207 // Callback. 208 if (typeof settings.onExpandAfter === 'function') { 209 settings.onExpandAfter.call(this); 210 } 211 }); 212 213 // Scroll the page to bring the active link and preview into view. 214 var scrollTargetTop = $(clickedLink).offset().top - scrollTargetOffset; 215 var scrollTargetBottom = $this.offset().top + autoOuterHeight + 20 - scrollTargetOffset; 216 scrollSectionIntoView(scrollTargetTop, scrollTargetBottom); 217 }); 218 219 // Activate close button. 220 closeButtonAnimationDelay = (isExpanded && activeRowChanged && ($this.parent().index() > $(activeLink).parent().index())) ? settings.animationDuration : (settings.animationDuration / 4); 221 $closeButton.css({ 222 position: 'absolute', 223 right: 0, 224 top: activeExpandedAreaTop + 'px' 225 }).delay(closeButtonAnimationDelay).fadeIn(settings.animationDuration, function () { 226 $(this).addClass(settings.elementActiveClass); 227 }); 228 229 // Set global variables. 230 activeLink = this; 231 activeExpandedArea = target; 232 isExpanded = true; 233 } 234 }); 235 } 236 }); 237 238 // Process the close button. 239 $closeButton.appendTo(context).hide().click(function (event) { 240 var $activeLink = $(activeLink); 241 var activeLinkTopOffset = $activeLink.offset().top; 242 var activeLinkBottomOffset = activeLinkTopOffset + $activeLink.outerHeight(); 243 244 event.preventDefault(); 245 246 // DOM manipulation and animations. 247 $links.removeClass(settings.elementActiveClass); 248 $expandingAreas.slideUp(settings.animationDuration).removeClass(settings.elementExpandedClass); 249 $closeButton.removeClass('active').hide(); 250 $spacer.animate({height: 0}, settings.animationDuration, function () { 251 $spacer.detach(); 252 }); 253 254 // Scroll the page to bring the active link into view. 255 scrollSectionIntoView(activeLinkTopOffset, activeLinkBottomOffset); 256 257 stopExpandedAreaMonitor(); 258 259 // Reset global variables. 260 isExpanded = false; 261 activeLink = false; 262 activeExpandedArea = false; 263 }); 264 265 /** 266 * Stop monitoring size of expanded area. 267 */ 268 var stopExpandedAreaMonitor = function () { 269 if (checkExpandedAreaResize) { 270 clearInterval(checkExpandedAreaResize); 271 } 272 }; 273 274 /** 275 * Match preview and spacer in height and position. 276 */ 277 var syncExpandedAreaWithSpacer = function () { 278 if (activeExpandedArea && isExpanded) { 279 $spacer.height($(activeExpandedArea).height()); 280 activeExpandedAreaTop = $spacer.position().top; 281 $closeButton.add(activeExpandedArea).css({top: activeExpandedAreaTop + 'px'}); 282 } 283 }; 284 285 /** 286 * Place spacer in proper position within grid. 287 */ 288 var positionSpacer = function () { 289 var lastSibling; 290 if (activeLink && lastItemInActiveRow && isExpanded) { 291 // Remove spacer. 292 $spacer.detach(); 293 lastSibling = getLastSiblingInRow($(activeLink).parent()[0]); 294 // Reposition spacer, if necessary. 295 if (lastItemInActiveRow !== lastSibling) { 296 console.log(lastSibling); 297 lastItemInActiveRow = lastSibling; 298 } 299 // Restore spacer. 300 $(lastItemInActiveRow).after($spacer); 301 } 302 }; 303 304 // React to window resize. 305 $(window).resize(function () { 306 if (isExpanded) { 307 positionSpacer(); 308 syncExpandedAreaWithSpacer(); 309 } 310 }); 311}; 312 313// Create the jQuery plugin. 314$.fn.expandingGrid = function (options) { 315 return this.each(function () { 316 expandingGrid(this, options); 317 }); 318}; 319 320})(jQuery, window, document); 321 322jQuery(document).ready(function ($) { 323 $('.expanding-grid').expandingGrid(); 324});
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.