splitter.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /*
  2. * jquery.splitter.js - two-pane splitter window plugin
  3. *
  4. * version 1.01 (01/05/2007)
  5. *
  6. * Dual licensed under the MIT and GPL licenses:
  7. * http://www.opensource.org/licenses/mit-license.php
  8. * http://www.gnu.org/licenses/gpl.html
  9. */
  10. /**
  11. * The splitter() plugin implements a two-pane resizable splitter window.
  12. * The selected elements in the jQuery object are converted to a splitter;
  13. * each element should have two child elements which are used for the panes
  14. * of the splitter. The plugin adds a third child element for the splitbar.
  15. *
  16. * For more details see: http://methvin.com/jquery/splitter/
  17. *
  18. *
  19. * @example $('#MySplitter').splitter();
  20. * @desc Create a vertical splitter with default settings
  21. *
  22. * @example $('#MySplitter').splitter({direction: 'h', accessKey: 'M'});
  23. * @desc Create a horizontal splitter resizable via Alt+Shift+M
  24. *
  25. * @name splitter
  26. * @type jQuery
  27. * @param String options Options for the splitter
  28. * @cat Plugins/Splitter
  29. * @return jQuery
  30. * @author Dave Methvin (dave.methvin@gmail.com)
  31. */
  32. jQuery.fn.splitter = function(opts){
  33. opts = jQuery.extend({
  34. type: 'v', // v=vertical, h=horizontal split
  35. activeClass: 'active', // class name for active splitter
  36. pxPerKey: 5, // splitter px moved per keypress
  37. tabIndex: 0, // tab order indicator
  38. accessKey: '' // accelerator key for splitter
  39. // initA initB // initial A/B size (pick ONE)
  40. // minA maxA minB maxB // min/max pane sizes
  41. },{
  42. v: { // Vertical splitters:
  43. keyGrowA: 39, // left arrow key
  44. keyShrinkA: 37, // right arrow key
  45. cursor: "e-resize", // double-arrow horizontal
  46. splitbarClass: "vsplitbar",
  47. eventPos: "pageX", set: "left",
  48. adjust: "width", offsetAdjust: "offsetWidth", adjSide1: "Left", adjSide2: "Right",
  49. fixed: "height", offsetFixed: "offsetHeight", fixSide1: "Top", fixSide2: "Bottom"
  50. },
  51. h: { // Horizontal splitters:
  52. keyGrowA: 40, // down arrow key
  53. keyShrinkA: 38, // up arrow key
  54. cursor: "n-resize", // double-arrow vertical
  55. splitbarClass: "hsplitbar",
  56. eventPos: "pageY", set: "top",
  57. adjust: "height", offsetAdjust: "offsetHeight", adjSide1: "Top", adjSide2: "Bottom",
  58. fixed: "width", offsetFixed: "offsetWidth", fixSide1: "Left", fixSide2: "Right"
  59. }
  60. }[((opts||{}).type||'v').charAt(0).toLowerCase()], opts||{});
  61. return this.each(function() {
  62. function startSplit(e) {
  63. splitbar.addClass(opts.activeClass);
  64. if ( e.type == "mousedown" ) {
  65. paneA._posAdjust = paneA[0][opts.offsetAdjust] - e[opts.eventPos];
  66. jQuery(document)
  67. .bind("mousemove", doSplitMouse)
  68. .bind("mouseup", endSplit);
  69. }
  70. return true; // required???
  71. }
  72. function doSplitKey(e) {
  73. var key = e.which || e.keyCode;
  74. var dir = key==opts.keyGrowA? 1 : key==opts.keyShrinkA? -1 : 0;
  75. if ( dir )
  76. moveSplitter(paneA[0][opts.offsetAdjust]+dir*opts.pxPerKey);
  77. return true; // required???
  78. }
  79. function doSplitMouse(e) {
  80. moveSplitter(paneA._posAdjust+e[opts.eventPos]);
  81. }
  82. function endSplit(e) {
  83. splitbar.removeClass(opts.activeClass);
  84. jQuery(document)
  85. .unbind("mousemove", doSplitMouse)
  86. .unbind("mouseup", endSplit);
  87. }
  88. function moveSplitter(np) {
  89. // Constrain new position to fit pane size limits; 16=scrollbar fudge factor
  90. // TODO: enforce group width in IE6 since it lacks min/max css properties?
  91. np = Math.max(paneA._min+paneA._padAdjust, group._adjust - (paneB._max||9999), 16,
  92. Math.min(np, paneA._max||9999, group._adjust - splitbar._adjust -
  93. Math.max(paneB._min+paneB._padAdjust, 16)));
  94. // Resize/position the two panes and splitbar
  95. splitbar.css(opts.set, np+"px");
  96. paneA.css(opts.adjust, np-paneA._padAdjust+"px");
  97. paneB.css(opts.set, np+splitbar._adjust+"px")
  98. .css(opts.adjust, group._adjust-splitbar._adjust-paneB._padAdjust-np+"px");
  99. // IE fires resize for us; all others pay cash
  100. if ( !jQuery.browser.msie ) {
  101. paneA.trigger("resize");
  102. paneB.trigger("resize");
  103. }
  104. }
  105. function cssCache(jq, n, pf, m1, m2) {
  106. // IE backCompat mode thinks width/height includes border and padding
  107. jq[n] = jQuery.boxModel? (parseInt(jq.css(pf+m1))||0) + (parseInt(jq.css(pf+m2))||0) : 0;
  108. }
  109. function optCache(jq, pane) {
  110. // Opera returns -1px for min/max dimensions when they're not there!
  111. jq._min = Math.max(0, opts["min"+pane] || parseInt(jq.css("min-"+opts.adjust)) || 0);
  112. jq._max = Math.max(0, opts["max"+pane] || parseInt(jq.css("max-"+opts.adjust)) || 0);
  113. }
  114. // Create jQuery object closures for splitter group and both panes
  115. var group = jQuery(this).css({position: "relative"});
  116. var divs = jQuery(">div", group).css({
  117. position: "absolute", // positioned inside splitter container
  118. margin: "0", // remove any stylesheet margin or ...
  119. border: "0", // ... border added for non-script situations
  120. "-moz-user-focus": "ignore" // disable focusability in Firefox
  121. });
  122. var paneA = jQuery(divs[0]); // left or top
  123. var paneB = jQuery(divs[1]); // right or bottom
  124. // Focuser element, provides keyboard support
  125. var focuser = jQuery('<a href="javascript:void(0)"></a>')
  126. .bind("focus", startSplit).bind("keydown", doSplitKey).bind("blur", endSplit)
  127. .attr({accessKey: opts.accessKey, tabIndex: opts.tabIndex});
  128. // Splitbar element, displays actual splitter bar
  129. // The select-related properties prevent unintended text highlighting
  130. var splitbar = jQuery('<div></div>')
  131. .insertAfter(paneA).append(focuser)
  132. .attr({"class": opts.splitbarClass, unselectable: "on"})
  133. .css({position: "absolute", "-khtml-user-select": "none",
  134. "-moz-user-select": "none", "user-select": "none"})
  135. .bind("mousedown", startSplit);
  136. if ( /^(auto|default)$/.test(splitbar.css("cursor") || "auto") )
  137. splitbar.css("cursor", opts.cursor);
  138. // Cache several dimensions for speed--assume these don't change
  139. splitbar._adjust = splitbar[0][opts.offsetAdjust];
  140. cssCache(group, "_borderAdjust", "border", opts.adjSide1+"Width", opts.adjSide2+"Width");
  141. cssCache(group, "_borderFixed", "border", opts.fixSide1+"Width", opts.fixSide2+"Width");
  142. cssCache(paneA, "_padAdjust", "padding", opts.adjSide1, opts.adjSide2);
  143. cssCache(paneA, "_padFixed", "padding", opts.fixSide1, opts.fixSide2);
  144. cssCache(paneB, "_padAdjust", "padding", opts.adjSide1, opts.adjSide2);
  145. cssCache(paneB, "_padFixed", "padding", opts.fixSide1, opts.fixSide2);
  146. optCache(paneA, 'A');
  147. optCache(paneB, 'B');
  148. // Initial splitbar position as measured from left edge of splitter
  149. paneA._init = (opts.initA==true? parseInt(jQuery.curCSS(paneA[0],opts.adjust)) : opts.initA) || 0;
  150. paneB._init = (opts.initB==true? parseInt(jQuery.curCSS(paneB[0],opts.adjust)) : opts.initB) || 0;
  151. if ( paneB._init )
  152. paneB._init = group[0][opts.offsetAdjust] - group._borderAdjust - paneB._init - splitbar._adjust;
  153. // Set up resize event handler and trigger immediately to set initial position
  154. group.bind("resize", function(e,size){
  155. // Determine new width/height of splitter container
  156. group._fixed = group[0][opts.offsetFixed] - group._borderFixed;
  157. group._adjust = group[0][opts.offsetAdjust] - group._borderAdjust;
  158. // Bail if splitter isn't visible or content isn't there yet
  159. if ( group._fixed <= 0 || group._adjust <= 0 ) return;
  160. // Set the fixed dimension (e.g., height on a vertical splitter)
  161. paneA.css(opts.fixed, group._fixed-paneA._padFixed+"px");
  162. paneB.css(opts.fixed, group._fixed-paneB._padFixed+"px");
  163. splitbar.css(opts.fixed, group._fixed+"px");
  164. // Re-divvy the adjustable dimension; maintain size of the preferred pane
  165. moveSplitter(size || (!opts.initB? paneA[0][opts.offsetAdjust] :
  166. group._adjust-paneB[0][opts.offsetAdjust]-splitbar._adjust));
  167. }).trigger("resize" , [paneA._init || paneB._init ||
  168. Math.round((group[0][opts.offsetAdjust] - group._borderAdjust - splitbar._adjust)/2)]);
  169. });
  170. };