1/** 2 * Initializes the tab widget 3 * 4 * @param {string} menuEntries 5 * @param {string} contentEntries 6 * @param {object} options 7 * @return {void} 8 */ 9var TabBar = function(menuEntries, contentEntries, options) { 10 /** 11 * Available options 12 * 13 * startIndex: starting tab index (beginning at 0) 14 * enableAutoPlay: usage of an auto play mechanism to switch between tabs 15 * autoPlayInterval: the time gap between the switch between two slides 16 * enableMouseOver: usage of mouse over in addition the normal click event for changing a tab 17 * classPrefix: prefix for all assigned classes 18 * hashName: prefix for the location hash listener 19 * pollingInterval: location hash polling interval 20 * animateCallback: animation callback function 21 * 22 * Events: 23 * onBeforeInitialize 24 * onAfterInitialize 25 * onTabChange 26 * 27 * @cfg {Object} 28 */ 29 this.options = { 30 startIndex: 0, 31 enableAutoPlay: false, 32 autoPlayInterval: 7000, 33 enableMouseOver: false, 34 classPrefix: 'tx-dftabs-', 35 hashName: 'tab', 36 pollingInterval: 1000, 37 animationCallback: null, 38 39 onBeforeInitialize: null, 40 onAfterInitialize: null, 41 onTabChange: null, 42 changeHashOnTabChange: false 43 }; 44 $.extend(this.options, options); 45 46 /** 47 * Tab Entry Array with Fields of Type "Object" 48 * 49 * Structure: 50 * menuItem: related menu element 51 * contentItem: related content element 52 * 53 * @type {Array} 54 */ 55 this.elementMap = []; 56 57 /** 58 * The Active Tab 59 * 60 * @type {int} 61 */ 62 this.previousTab = this.options.startIndex; 63 64 /** 65 * The AutoPlay Instance 66 * 67 * @type {int} 68 */ 69 this.autoPlay = 0; 70 71 /** 72 * Timed Display Method 73 * 74 * @type {int} 75 */ 76 this.timedDisplayFunction = 0; 77 78 for (var i = 0; i < menuEntries.length; ++i) { 79 this.elementMap[i] = {}; 80 this.elementMap[i].menuItem = $(menuEntries[i]); 81 this.elementMap[i].contentItem = $(contentEntries[i]); 82 } 83 84 this.finalizeInitialisation(); 85}; 86 87/** 88 * A tab-bar widget with very basic functionality 89 * 90 * @author Stefan Galinski <[email protected]> 91 */ 92TabBar.prototype = { 93 /** 94 * Finalizes the initialisation 95 * 96 * @return {void} 97 */ 98 finalizeInitialisation: function() { 99 this.trigger('beforeInitialize', this); 100 101 this.previousTab = this.options.startIndex; 102 this.parseContentLinks().addEvents().initAutoPlay().addLocationHashPolling(); 103 104 this.trigger('afterInitialize', this); 105 }, 106 107 /** 108 * Parses all links and adds a smooth scrolling to the tab if the link 109 * references to an internal tab on the very same page 110 * 111 * @return {TabBar} 112 */ 113 parseContentLinks: function() { 114 $.each(this.elementMap, function(index, element) { 115 var links = element.contentItem.find('a'); 116 links.each(function(index, link) { 117 var parts = link.href.split('#'); 118 if (parts[1] && parts[1].substr(this.options.hashName.length) === this.options.hashName 119 && parts[0] === location.href.split('#')[0]) { 120 var hashIndex = parts[1].substr(this.options.hashName.length); 121 $(link).click(this.scrollToTab.bind(this, hashIndex)); 122 } 123 }.bind(this)); 124 }.bind(this)); 125 126 return this; 127 }, 128 129 /** 130 * Scrolls to the menu item of the given tab index 131 * 132 * @param {int} tabIndex 133 * @return {void} 134 */ 135 scrollToTab: function(tabIndex) { 136 $(window).scrollTop(this.elementMap[tabIndex].menuItem.offset().y); 137 }, 138 139 /** 140 * Adds the requested events to the tab menu elements 141 * 142 * @return {TabBar} 143 */ 144 addEvents: function() { 145 $.each(this.elementMap, function(index, element) { 146 if (this.options.enableMouseOver) { 147 element.menuItem.mouseenter(this.timedDisplay.bind(this, index)); 148 element.contentItem.mouseenter(this.clearTimedDisplay.bind(this)); 149 } else { 150 element.menuItem.click(function(event) { 151 event.preventDefault(); 152 this.display.call(this, index) 153 }.bind(this)); 154 } 155 156 if (this.options.enableAutoPlay) { 157 if (this.options.enableMouseOver) { 158 element.menuItem.mouseenter(this.stopAutoPlay.bind(this)); 159 element.menuItem.mouseleave(this.startAutoPlay.bind(this)); 160 element.menuItem.click(this.startAutoPlay.bind(this)); 161 } else { 162 element.menuItem.click(this.stopAutoPlay.bind(this)); 163 } 164 } 165 }.bind(this)); 166 167 return this; 168 }, 169 170 /** 171 * Initializes the autoplay mechanism based on the visibility state 172 * 173 * Note: If the visibility state isn't available, the autoplay functionality is 174 * started directly. 175 * 176 * @return {TabBar} 177 */ 178 initAutoPlay: function() { 179 var hidden = null, 180 visibilityChange = null; 181 if (typeof document.hidden !== 'undefined') { 182 hidden = 'hidden'; 183 visibilityChange = 'visibilitychange'; 184 } else if (typeof document.mozHidden !== 'undefined') { 185 hidden = 'mozHidden'; 186 visibilityChange = 'mozvisibilitychange'; 187 } else if (typeof document.msHidden !== 'undefined') { 188 hidden = 'msHidden'; 189 visibilityChange = 'msvisibilitychange'; 190 } else if (typeof document.webkitHidden !== 'undefined') { 191 hidden = 'webkitHidden'; 192 visibilityChange = 'webkitvisibilitychange'; 193 } 194 195 if (visibilityChange) { 196 document.addEventListener( 197 visibilityChange, this.toggleAutoplayBasedOnVisibility.bind(this, [hidden]), false 198 );
199 this.toggleAutoplayBasedOnVisibility(hidden); 200 } else { 201 this.startAutoPlay(); 202 } 203 204 return this; 205 }, 206 207 /** 208 * Adds an URL hash evaluation with a polling interval of 1000ms to grant the possibility to 209 * manually open content elements by changing the location hash inside the URL (e.g. acc123). 210 * 211 * @return {void} 212 */ 213 addLocationHashPolling: function() { 214 setTimeout(this.hashHandler.bind(this), 1); 215 setInterval(this.hashHandler.bind(this), 100); 216 }, 217 218 /** 219 * Location Hash Handler 220 * 221 * @return {void} 222 */ 223 hashHandler: function() { 224 if (this.locationHash === window.location.hash) { 225 return; 226 } 227 228 var index = -1; 229 this.locationHash = window.location.hash; 230 if (this.locationHash !== '') { 231 var matchExpression = new RegExp(this.options.hashName + '(\\d+)', 'i'); 232 var matchResult = matchExpression.exec(this.locationHash); 233 index = parseInt(matchResult ? matchResult[1] : '', 10); 234 if (index >= 0 && index < this.elementMap.length) { 235 this.display(index, false); 236 } 237 } else { 238 if (this.previousTab !== this.options.startIndex) { 239 this.display(this.options.startIndex, false); 240 } 241 } 242 }, 243 244 /** 245 * Adds a small delay before displaying a tab (useful for mouseover) 246 * 247 * Note: The delay method is saved in the class property "timedDisplayFunction". 248 * 249 * @param {int} nextTabIndex 250 * @return {TabBar} 251 */ 252 timedDisplay: function(nextTabIndex) { 253 this.clearTimedDisplay(); 254 this.timedDisplayFunction = setTimeout(this.display.bind(this, nextTabIndex), 250); 255 256 return this; 257 }, 258 259 /** 260 * Clears the timed display function 261 * 262 * @return {TabBar} 263 */ 264 clearTimedDisplay: function() { 265 clearTimeout(this.timedDisplayFunction); 266 267 return this; 268 }, 269 270 /** 271 * Displays the given tab index 272 * 273 * @param {int} nextTabIndex 274 * @param {Boolean} triggeredByAutoPlay 275 * @return {TabBar} 276 */ 277 display: function(nextTabIndex, triggeredByAutoPlay) { 278 if (triggeredByAutoPlay !== true) { 279 triggeredByAutoPlay = false; 280 } 281 282 nextTabIndex = parseInt(nextTabIndex, 10); 283 var tabIndexInRange = nextTabIndex >= 0 && nextTabIndex < this.elementMap.length; 284 if (isNaN(nextTabIndex) || this.previousTab === nextTabIndex || !tabIndexInRange) { 285 return this; 286 } 287 288 if (this.options.animationCallback) { 289 this.options.animationCallback.call(this, nextTabIndex, triggeredByAutoPlay); 290 } else { 291 this.animate(nextTabIndex); 292 } 293 294 this.trigger('tabChange', this, this.previousTab, nextTabIndex); 295 296 if (this.options.changeHashOnTabChange) { 297 // save current scroll position in order to apply it after hash changed 298 // this prevents the content from jumping around as the tabs change 299 var currentScrollY = window.scrollY; 300 window.location.hash = this.options.hashName + nextTabIndex; 301 window.scrollTo(0, currentScrollY); 302 } 303 304 return this; 305 }, 306 307 /** 308 * Default "animation" of the transition between two tabs. In real it's just 309 * a toggling of the selected classes for the content and menu items. Define 310 * your own animation function to get a nice effect. 311 * 312 * @param {int} nextTabIndex 313 * @return {TabBar} 314 */ 315 animate: function(nextTabIndex) { 316 this.toggleContentItemSelectionClasses(nextTabIndex); 317 this.toggleMenuEntrySelectionClasses(nextTabIndex); 318 this.previousTab = nextTabIndex; 319 320 return this; 321 }, 322 323 /** 324 * Toggles the "tabContentSelected" class on the last and current content elements 325 * 326 * @param {int} nextTabIndex 327 * @return {TabBar} 328 */ 329 toggleContentItemSelectionClasses: function(nextTabIndex) { 330 var selectedClass = this.options.classPrefix + 'tabContentSelected'; 331 this.elementMap[this.previousTab].contentItem.removeClass(selectedClass); 332 this.elementMap[nextTabIndex].contentItem.addClass(selectedClass); 333 334 return this; 335 }, 336 337 /** 338 * Toggles the "tabMenuEntrySelected" class on the last and current menu entries 339 * 340 * @param {int} nextTabIndex 341 * @return {TabBar} 342 */ 343 toggleMenuEntrySelectionClasses: function(nextTabIndex) { 344 var selectedClass = this.options.classPrefix + 'tabMenuEntrySelected'; 345 this.elementMap[nextTabIndex].menuItem.addClass(selectedClass); 346 this.elementMap[this.previousTab].menuItem.removeClass(selectedClass); 347 348 return this; 349 }, 350 351 /** 352 * Toggles the autplay setting based on the visibility state of the page 353 * 354 * @param {string} hidden field with the hidden state in the document object 355 * @return {TabBar} 356 */ 357 toggleAutoplayBasedOnVisibility: function(hidden) { 358 if (!document[hidden]) { 359 this.startAutoPlay(); 360 } else { 361 this.stopAutoPlay(); 362 } 363 364 return this; 365 }, 366 367 /** 368 * Implements the auto-play mechanism 369 * 370 * @return {void} 371 */ 372 autoPlayMechanism: function() { 373 if (this.previousTab < this.elementMap.length - 1) { 374 this.display(this.previousTab + 1, true); 375 } else { 376 this.display(0, true); 377 } 378 }, 379 380 /** 381 * Starts the auto-play mechanism 382 * 383 * @return {TabBar} 384 */
385 startAutoPlay: function() { 386 if (this.options.enableAutoPlay && !this.autoPlay) { 387 this.autoPlay = setInterval(this.autoPlayMechanism.bind(this), this.options.autoPlayInterval); 388 } 389 390 return this; 391 }, 392 393 /** 394 * Stops the auto-play mechanism 395 * 396 * @return {TabBar} 397 */ 398 stopAutoPlay: function() { 399 clearInterval(this.autoPlay); 400 this.autoPlay = 0; 401 402 return this; 403 }, 404 405 /** 406 * Triggers an event 407 * 408 * @return {void} 409 */ 410 trigger: function(event) { 411 var onFunction = 'on' + event.charAt(0).toUpperCase() + event.slice(1); 412 if (typeof this.options[onFunction] === 'function') { 413 var args = Array.prototype.slice.call(arguments); 414 args.shift(); 415 this.options[onFunction].call(this, args[0], args[1], args[2], args[3]); 416 } 417 } 418};
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.