jquery.listnav-2.1.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /*
  2. *
  3. * jQuery listnav plugin
  4. * Copyright (c) 2009 iHwy, Inc.
  5. * Author: Jack Killpatrick
  6. *
  7. * Version 2.1 (08/09/2009)
  8. * Requires jQuery 1.3.2, jquery 1.2.6 or jquery 1.2.x plus the jquery dimensions plugin
  9. *
  10. * Visit http://www.ihwy.com/labs/jquery-listnav-plugin.aspx for more information.
  11. *
  12. * Dual licensed under the MIT and GPL licenses:
  13. * http://www.opensource.org/licenses/mit-license.php
  14. * http://www.gnu.org/licenses/gpl.html
  15. *
  16. */
  17. (function($) {
  18. $.fn.listnav = function(options) {
  19. var opts = $.extend({}, $.fn.listnav.defaults, options);
  20. var letters = ['_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '-'];
  21. var firstClick = false;
  22. opts.prefixes = $.map(opts.prefixes, function(n) { return n.toLowerCase(); });
  23. return this.each(function() {
  24. var $wrapper, list, $list, $letters, $letterCount, id;
  25. id = this.id;
  26. //$wrapper = $('#' + id + '-nav'); // user must abide by the convention: <ul id="myList"> for list and <div id="myList-nav"> for nav wrapper
  27. $wrapper = $('<div/>');
  28. $list = $(this);
  29. $list.before($wrapper);
  30. var counts = {}, allCount = 0, isAll = true, numCount = 0, prevLetter = '';
  31. function init() {
  32. $wrapper.append(createLettersHtml());
  33. $letters = $('.ln-letters', $wrapper).slice(0, 1); // will always be a single item
  34. if (opts.showCounts) $letterCount = $('.ln-letter-count', $wrapper).slice(0, 1); // will always be a single item
  35. addClasses();
  36. addNoMatchLI();
  37. if (opts.flagDisabled) addDisabledClass();
  38. bindHandlers();
  39. if (!opts.includeAll) $list.show(); // show the list in case the recommendation for includeAll=false was taken
  40. // remove nav items we don't need
  41. //
  42. if (!opts.includeAll) $('.all', $letters).remove();
  43. if (!opts.includeNums) $('._', $letters).remove();
  44. if (!opts.includeOther) $('.-', $letters).remove();
  45. $(':last', $letters).addClass('ln-last'); // allows for styling a case where last item needs right border set (because items before that only have top, left and bottom so that border between items isn't doubled)
  46. if ($.cookie && (opts.cookieName != null)) {
  47. var cookieLetter = $.cookie(opts.cookieName);
  48. if (cookieLetter != null) opts.initLetter = cookieLetter;
  49. }
  50. // decide what to show first
  51. //
  52. if (opts.initLetter != '') {
  53. firstClick = true;
  54. $('.' + opts.initLetter.toLowerCase(), $letters).slice(0, 1).click(); // click the initLetter if there was one
  55. }
  56. else {
  57. if (opts.includeAll) $('.all', $letters).addClass('ln-selected'); // showing all: we don't need to click this: the whole list is already loaded
  58. else { // ALL link is hidden, click the first letter that will display LI's
  59. for (var i = ((opts.includeNums) ? 0 : 1); i < letters.length; i++) {
  60. if (counts[letters[i]] > 0) {
  61. firstClick = true;
  62. $('.' + letters[i], $letters).slice(0, 1).click();
  63. break;
  64. }
  65. }
  66. }
  67. }
  68. }
  69. // positions the letter count div above the letter links (so we only have to do it once: after this we just change it's left position via mouseover)
  70. //
  71. function setLetterCountTop() {
  72. $letterCount.css({ top: $('.a', $letters).slice(0, 1).offset({ margin: false, border: true }).top - $letterCount.outerHeight({ margin: true }) }); // note: don't set top based on '.all': it might not be visible
  73. }
  74. // adds a class to each LI that has text content inside of it (ie, inside an <a>, a <div>, nested DOM nodes, etc)
  75. //
  76. function addClasses() {
  77. var str, firstChar, firstWord, spl, $this, hasPrefixes = (opts.prefixes.length > 0);
  78. $($list).children().each(function() {
  79. $this = $(this), firstChar = '';
  80. if(opts.attribute){
  81. str = $.trim($this.attr(opts.attribute)).toLowerCase();
  82. }
  83. if(!opts.attribute || str == ''){
  84. str = $.trim($this.text()).toLowerCase();
  85. }
  86. if (str != '') {
  87. if (hasPrefixes) {
  88. spl = str.split(' ');
  89. if ((spl.length > 1) && ($.inArray(spl[0], opts.prefixes) > -1)) {
  90. firstChar = spl[1].charAt(0);
  91. addLetterClass(firstChar, $this, true);
  92. }
  93. }
  94. firstChar = str.charAt(0);
  95. addLetterClass(firstChar, $this);
  96. }
  97. });
  98. }
  99. function addLetterClass(firstChar, $el, isPrefix) {
  100. if (/\W/.test(firstChar)) firstChar = '-'; // not A-Z, a-z or 0-9, so considered "other"
  101. if (!isNaN(firstChar)) firstChar = '_'; // use '_' if the first char is a number
  102. $el.addClass('ln-' + firstChar);
  103. if (counts[firstChar] == undefined) counts[firstChar] = 0;
  104. counts[firstChar]++;
  105. if (!isPrefix) allCount++;
  106. }
  107. function addDisabledClass() {
  108. for (var i = 0; i < letters.length; i++) {
  109. if (counts[letters[i]] == undefined) $('.' + letters[i], $letters).addClass('ln-disabled');
  110. }
  111. }
  112. function addNoMatchLI() {
  113. $list.append('<li class="ln-no-match" style="display:none">' + opts.noMatchText + '</li>');
  114. }
  115. function getLetterCount(el) {
  116. if ($(el).hasClass('all')) return allCount;
  117. else {
  118. var count = counts[$(el).attr('class').split(' ')[0]];
  119. return (count != undefined) ? count : 0; // some letters may not have a count in the hash
  120. }
  121. }
  122. function bindHandlers() {
  123. // sets the top position of the count div in case something above it on the page has resized
  124. //
  125. if (opts.showCounts) {
  126. $wrapper.mouseover(function() {
  127. setLetterCountTop();
  128. });
  129. }
  130. // mouseover for each letter: shows the count above the letter
  131. //
  132. if (opts.showCounts) {
  133. $('a', $letters).mouseover(function() {
  134. var left = $(this).position().left;
  135. var width = ($(this).outerWidth({ margin: true }) - 1) + 'px'; // the -1 is to tweak the width a bit due to a seeming inaccuracy in jquery ui/dimensions outerWidth (same result in FF2 and IE6/7)
  136. var count = getLetterCount(this);
  137. $letterCount.css({ left: left, width: width }).text(count).show(); // set left position and width of letter count, set count text and show it
  138. });
  139. // mouseout for each letter: hide the count
  140. //
  141. $('a', $letters).mouseout(function() {
  142. $letterCount.hide();
  143. });
  144. }
  145. // click handler for letters: shows/hides relevant LI's
  146. //
  147. $('a', $letters).click(function() {
  148. $('a.ln-selected', $letters).removeClass('ln-selected');
  149. var letter = $(this).attr('class').split(' ')[0];
  150. if (letter == 'all') {
  151. $list.children().show();
  152. $list.children('.ln-no-match').hide();
  153. isAll = true;
  154. } else {
  155. if (isAll) {
  156. $list.children().hide();
  157. isAll = false;
  158. } else if (prevLetter != '') $list.children('.ln-' + prevLetter).hide();
  159. var count = getLetterCount(this);
  160. if (count > 0) {
  161. $list.children('.ln-no-match').hide(); // in case it's showing
  162. $list.children('.ln-' + letter).show();
  163. }
  164. else $list.children('.ln-no-match').show();
  165. prevLetter = letter;
  166. }
  167. if ($.cookie && (opts.cookieName != null)) $.cookie(opts.cookieName, letter);
  168. $(this).addClass('ln-selected');
  169. $(this).blur();
  170. if (!firstClick && (opts.onClick != null)) opts.onClick(letter);
  171. else firstClick = false;
  172. return false;
  173. });
  174. }
  175. // creates the HTML for the letter links
  176. //
  177. function createLettersHtml() {
  178. var html = [];
  179. for (var i = 1; i < letters.length; i++) {
  180. if (html.length == 0) html.push('<a class="all" href="#">ALL</a><a class="_" href="#">0-9</a>');
  181. html.push('<a class="' + letters[i] + '" href="#">' + ((letters[i] == '-') ? '...' : letters[i].toUpperCase()) + '</a>');
  182. }
  183. return '<div class="ln-letters">' + html.join('') + '</div>' + ((opts.showCounts) ? '<div class="ln-letter-count" style="display:none; position:absolute; top:0; left:0; width:20px;">0</div>' : ''); // the styling for ln-letter-count is to give us a starting point for the element, which will be repositioned when made visible (ie, should not need to be styled by the user)
  184. }
  185. init();
  186. });
  187. };
  188. $.fn.listnav.defaults = {
  189. initLetter: '',
  190. includeAll: true,
  191. includeOther: true,
  192. includeNums: true,
  193. flagDisabled: true,
  194. noMatchText: 'No matching entries',
  195. showCounts: false,
  196. cookieName: null,
  197. onClick: null,
  198. prefixes: [],
  199. attribute: false,
  200. };
  201. })(jQuery);