Valentin Valchev | 9b5df5f | 2010-03-22 12:45:31 +0000 | [diff] [blame] | 1 | /* |
| 2 | Copyright (c) 2009, http://seyfertdesign.com/jquery/ui-tabs-paging.html |
| 3 | |
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy |
| 5 | of this software and associated documentation files (the "Software"), to deal |
| 6 | in the Software without restriction, including without limitation the rights |
| 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 8 | copies of the Software, and to permit persons to whom the Software is |
| 9 | furnished to do so, subject to the following conditions: |
| 10 | |
| 11 | The above copyright notice and this permission notice shall be included in |
| 12 | all copies or substantial portions of the Software. |
| 13 | |
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 20 | THE SOFTWARE. |
| 21 | */ |
| 22 | |
| 23 | $.extend($.ui.tabs.prototype, { |
| 24 | paging: function(options) { |
| 25 | var opts = { |
| 26 | tabsPerPage: 0, |
| 27 | nextButton: '»', |
| 28 | prevButton: '«', |
| 29 | follow: false, |
| 30 | cycle: false, |
| 31 | selectOnAdd: false, |
| 32 | followOnSelect: false |
| 33 | }; |
| 34 | |
| 35 | opts = $.extend(opts, options); |
| 36 | |
| 37 | var self = this, initialized = false, currentPage, |
| 38 | buttonWidth, containerWidth, allTabsWidth, tabWidths, |
| 39 | maxPageWidth, pages, resizeTimer = null, |
| 40 | windowHeight = $(window).height(), windowWidth = $(window).width(); |
| 41 | |
| 42 | function init() { |
| 43 | destroy(); |
| 44 | |
| 45 | allTabsWidth = 0, currentPage = 0, maxPageWidth = 0, buttonWidth = 0, |
| 46 | pages = new Array(), tabWidths = new Array(), selectedTabWidths = new Array(); |
| 47 | |
| 48 | containerWidth = self.element.width(); |
| 49 | |
| 50 | // loops through LIs, get width of each tab when selected and unselected. |
| 51 | var maxDiff = 0; // the max difference between a selected and unselected tab |
| 52 | self.lis.each(function(i) { |
| 53 | if (i == self.options.selected) { |
| 54 | selectedTabWidths[i] = $(this).outerWidth({ margin: true }); |
| 55 | tabWidths[i] = self.lis.eq(i).removeClass('ui-tabs-selected').outerWidth({ margin: true }); |
| 56 | self.lis.eq(i).addClass('ui-tabs-selected'); |
| 57 | maxDiff = Math.min(maxDiff, Math.abs(selectedTabWidths[i] - tabWidths[i])); |
| 58 | allTabsWidth += tabWidths[i]; |
| 59 | } else { |
| 60 | tabWidths[i] = $(this).outerWidth({ margin: true }); |
| 61 | selectedTabWidths[i] = self.lis.eq(i).addClass('ui-tabs-selected').outerWidth({ margin: true }); |
| 62 | self.lis.eq(i).removeClass('ui-tabs-selected'); |
| 63 | maxDiff = Math.max(maxDiff, Math.abs(selectedTabWidths[i] - tabWidths[i])); |
| 64 | allTabsWidth += tabWidths[i]; |
| 65 | } |
| 66 | }); |
| 67 | // fix padding issues with buttons |
| 68 | // TODO determine a better way to handle this |
| 69 | allTabsWidth += maxDiff + ($.browser.msie?4:0) + 9; |
| 70 | |
| 71 | // if the width of all tables is greater than the container's width, calculate the pages |
| 72 | if (allTabsWidth > containerWidth) { |
| 73 | // create next button |
| 74 | li = $('<li></li>') |
| 75 | .addClass('ui-state-default ui-tabs-paging-next') |
| 76 | .append($('<a href="#"></a>') |
| 77 | .click(function() { page('next'); return false; }) |
| 78 | .html(opts.nextButton)); |
| 79 | |
| 80 | self.lis.eq(self.length()-1).after(li); |
| 81 | buttonWidth = li.outerWidth({ margin: true }); |
| 82 | |
| 83 | // create prev button |
| 84 | li = $('<li></li>') |
| 85 | .addClass('ui-state-default ui-tabs-paging-prev') |
| 86 | .append($('<a href="#"></a>') |
| 87 | .click(function() { page('prev'); return false; }) |
| 88 | .html(opts.prevButton)); |
| 89 | self.lis.eq(0).before(li); |
| 90 | buttonWidth += li.outerWidth({ margin: true }); |
| 91 | |
| 92 | // TODO determine fix for padding issues to next button |
| 93 | buttonWidth += 19; |
| 94 | |
| 95 | var pageIndex = 0, pageWidth = 0, maxTabPadding = 0; |
| 96 | |
| 97 | // start calculating pageWidths |
| 98 | for (var i = 0; i < tabWidths.length; i++) { |
| 99 | // if first tab of page or selected tab's padding larger than the current max, set the maxTabPadding |
| 100 | if (pageWidth == 0 || selectedTabWidths[i] - tabWidths[i] > maxTabPadding) |
| 101 | maxTabPadding = (selectedTabWidths[i] - tabWidths[i]); |
| 102 | |
| 103 | // if first tab of page, initialize pages variable for page |
| 104 | if (pages[pageIndex] == null) { |
| 105 | pages[pageIndex] = { start: i }; |
| 106 | |
| 107 | } else if ((i > 0 && (i % opts.tabsPerPage) == 0) || (tabWidths[i] + pageWidth + buttonWidth + 12) > containerWidth) { |
| 108 | if ((pageWidth + maxTabPadding) > maxPageWidth) |
| 109 | maxPageWidth = (pageWidth + maxTabPadding); |
| 110 | pageIndex++; |
| 111 | pages[pageIndex] = { start: i }; |
| 112 | pageWidth = 0; |
| 113 | } |
| 114 | pages[pageIndex].end = i+1; |
| 115 | pageWidth += tabWidths[i]; |
| 116 | if (i == self.options.selected) currentPage = pageIndex; |
| 117 | } |
| 118 | if ((pageWidth + maxTabPadding) > maxPageWidth) |
| 119 | maxPageWidth = (pageWidth + maxTabPadding); |
| 120 | |
| 121 | // hide all tabs then show tabs for current page |
| 122 | self.lis.hide().slice(pages[currentPage].start, pages[currentPage].end).show(); |
| 123 | if (currentPage == (pages.length - 1) && !opts.cycle) |
| 124 | disableButton('next'); |
| 125 | if (currentPage == 0 && !opts.cycle) |
| 126 | disableButton('prev'); |
| 127 | |
| 128 | // calculate the right padding for the next button |
| 129 | buttonPadding = containerWidth - maxPageWidth - buttonWidth; |
| 130 | if (buttonPadding > 0) |
| 131 | $('.ui-tabs-paging-next', self.element).css({ paddingRight: buttonPadding + 'px' }); |
| 132 | |
| 133 | initialized = true; |
| 134 | } else { |
| 135 | destroy(); |
| 136 | } |
| 137 | |
| 138 | $(window).bind('resize', handleResize); |
| 139 | } |
| 140 | |
| 141 | function page(direction) { |
| 142 | currentPage = currentPage + (direction == 'prev'?-1:1); |
| 143 | |
| 144 | if ((direction == 'prev' && currentPage < 0 && opts.cycle) || |
| 145 | (direction == 'next' && currentPage >= pages.length && !opts.cycle)) |
| 146 | currentPage = pages.length - 1; |
| 147 | else if ((direction == 'prev' && currentPage < 0) || |
| 148 | (direction == 'next' && currentPage >= pages.length && opts.cycle)) |
| 149 | currentPage = 0; |
| 150 | |
| 151 | var start = pages[currentPage].start; |
| 152 | var end = pages[currentPage].end; |
| 153 | self.lis.hide().slice(start, end).show(); |
| 154 | |
| 155 | if (direction == 'prev') { |
| 156 | enableButton('next'); |
| 157 | if (opts.follow && (self.options.selected < start || self.options.selected > (end-1))) self.select(end-1); |
| 158 | if (!opts.cycle && start <= 0) disableButton('prev'); |
| 159 | } else { |
| 160 | enableButton('prev'); |
| 161 | if (opts.follow && (self.options.selected < start || self.options.selected > (end-1))) self.select(start); |
| 162 | if (!opts.cycle && end >= self.length()) disableButton('next'); |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | function disableButton(direction) { |
| 167 | $('.ui-tabs-paging-'+direction, self.element).addClass('ui-tabs-paging-disabled'); |
| 168 | } |
| 169 | |
| 170 | function enableButton(direction) { |
| 171 | $('.ui-tabs-paging-'+direction, self.element).removeClass('ui-tabs-paging-disabled'); |
| 172 | } |
| 173 | |
| 174 | // special function defined to handle IE6 and IE7 resize issues |
| 175 | function handleResize() { |
| 176 | if (resizeTimer) clearTimeout(resizeTimer); |
| 177 | |
| 178 | if (windowHeight != $(window).height() || windowWidth != $(window).width()) |
| 179 | resizeTimer = setTimeout(reinit, 100); |
| 180 | } |
| 181 | |
| 182 | function reinit() { |
| 183 | windowHeight = $(window).height(); |
| 184 | windowWidth = $(window).width(); |
| 185 | init(); |
| 186 | } |
| 187 | |
| 188 | function destroy() { |
| 189 | // remove buttons |
| 190 | $('.ui-tabs-paging-next', self.element).remove(); |
| 191 | $('.ui-tabs-paging-prev', self.element).remove(); |
| 192 | |
| 193 | // show all tabs |
| 194 | self.lis.show(); |
| 195 | |
| 196 | initialized = false; |
| 197 | |
| 198 | $(window).unbind('resize', handleResize); |
| 199 | } |
| 200 | |
| 201 | // reconfigure "ui.tabs" add/remove events to reinit paging |
| 202 | var tabsAdd = self.add; |
| 203 | self.add = function(url, label, index) { |
| 204 | // remove paging buttons before adding a tab |
| 205 | if (initialized) |
| 206 | destroy(); |
| 207 | |
| 208 | tabsAdd.apply(this, [url, label, index]); |
| 209 | |
| 210 | if (opts.selectOnAdd) { |
| 211 | if (index == undefined) index = this.lis.length-1; |
| 212 | this.select(index); |
| 213 | } |
| 214 | // re-initialize paging buttons |
| 215 | init(); |
| 216 | }; |
| 217 | var tabsRemove = self.remove; |
| 218 | self.remove = function(index) { |
| 219 | // remove paging buttons before removing a tab |
| 220 | if (initialized) |
| 221 | destroy(); |
| 222 | |
| 223 | tabsRemove.apply(this, [index]); |
| 224 | |
| 225 | // re-initialize paging buttons |
| 226 | init(); |
| 227 | }; |
| 228 | // reconfigure "ui.tabs" select event to change pages if new tab is selected |
| 229 | var tabsSelect = self.select; |
| 230 | self.select = function(index) { |
| 231 | tabsSelect.apply(this, [index]); |
| 232 | |
| 233 | // if paging is not initialized or it is not configured to |
| 234 | // change pages when a new tab is selected, then do nothing |
| 235 | if (!initialized || !opts.followOnSelect) |
| 236 | return; |
| 237 | |
| 238 | // find the new page based on index of the tab selected |
| 239 | for (i in pages) { |
| 240 | var start = pages[i].start; |
| 241 | var end = pages[i].end; |
| 242 | if (index >= start && index < end) { |
| 243 | // if the the tab selected is not within the currentPage of tabs, then change pages |
| 244 | if (i != currentPage) { |
| 245 | self.lis.hide().slice(start, end).show(); |
| 246 | |
| 247 | currentPage = parseInt(i); |
| 248 | if (currentPage == 0) { |
| 249 | enableButton('next'); |
| 250 | if (!opts.cycle && start <= 0) disableButton('prev'); |
| 251 | } else { |
| 252 | enableButton('prev'); |
| 253 | if (!opts.cycle && end >= self.length()) disableButton('next'); |
| 254 | } |
| 255 | } |
| 256 | break; |
| 257 | } |
| 258 | } |
| 259 | }; |
| 260 | |
| 261 | // add, remove, and destroy functions specific for paging |
| 262 | $.extend($.ui.tabs.prototype, { |
| 263 | pagingDestroy: function() { |
| 264 | destroy(); |
| 265 | } |
| 266 | }); |
| 267 | |
| 268 | init(); |
| 269 | } |
| 270 | }); |