splitter.js 9.94 KB
Newer Older
frank.xa.zhang's avatar
frank.xa.zhang committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
/*
* jQuery.splitter.js - two-pane splitter window plugin
*
* version 1.51 (2009/01/09)
*
* Dual licensed under the MIT and GPL licenses:
*   http://www.opensource.org/licenses/mit-license.php
*   http://www.gnu.org/licenses/gpl.html
*/

/**
* The splitter() plugin implements a two-pane resizable splitter window.
* The selected elements in the jQuery object are converted to a splitter;
* each selected element should have two child elements, used for the panes
* of the splitter. The plugin adds a third child element for the splitbar.
*
* For more details see: http://methvin.com/splitter/
*
*
* @example $('#MySplitter').splitter();
* @desc Create a vertical splitter with default settings
*
* @example $('#MySplitter').splitter({type: 'h', accessKey: 'M'});
* @desc Create a horizontal splitter resizable via Alt+Shift+M
*
* @name splitter
* @type jQuery
* @param Object options Options for the splitter (not required)
* @cat Plugins/Splitter
* @return jQuery
* @author Dave Methvin (dave.methvin@gmail.com)
*/
;(function($){

$.fn.splitter = function(args){
   args = args || {};
   return this.each(function() {
      var zombie;      // left-behind splitbar for outline resizes
      function startSplitMouse(evt) {
         if ( opts.outline )
            zombie = zombie || bar.clone(false).insertAfter(A);
         panes.css("-webkit-user-select", "none");   // Safari selects A/B text on a move
         bar.addClass(opts.activeClass);
         $('<div class="splitterMask"></div>').insertAfter(bar);
         A._posSplit = A[0][opts.pxSplit] - evt[opts.eventPos];
         $(document)
            .bind("mousemove", doSplitMouse)
            .bind("mouseup", endSplitMouse);
      }
      function doSplitMouse(evt) {
         var newPos = A._posSplit+evt[opts.eventPos];
         if ( opts.outline ) {
            newPos = Math.max(0, Math.min(newPos, splitter._DA - bar._DA));
            bar.css(opts.origin, newPos);
         } else
            resplit(newPos);
      }
      function endSplitMouse(evt) {
         $('div.splitterMask').remove();
         bar.removeClass(opts.activeClass);
         var newPos = A._posSplit+evt[opts.eventPos];
         if ( opts.outline ) {
            zombie.remove(); zombie = null;
            resplit(newPos);
         }
         panes.css("-webkit-user-select", "text");   // let Safari select text again
         $(document)
            .unbind("mousemove", doSplitMouse)
            .unbind("mouseup", endSplitMouse);
      }
      function resplit(newPos) {
         // Constrain new splitbar position to fit pane size limits
         newPos = Math.max(A._min, splitter._DA - B._max,
               Math.min(newPos, A._max, splitter._DA - bar._DA - B._min));
         // Resize/position the two panes
         bar._DA = bar[0][opts.pxSplit];      // bar size may change during dock

         var posOffset = bar.is(':visible') ? bar._DA - 1 : 0;

         bar.css(opts.origin, newPos - posOffset).css(opts.fixed, splitter._DF);
         A.css(opts.origin, 0).css(opts.split, newPos).css(opts.fixed,  splitter._DF);
         B.css(opts.origin, newPos + bar._DA - posOffset)
            .css(opts.split, splitter._DA-bar._DA-newPos).css(opts.fixed,  splitter._DF);
         // IE fires resize for us; all others pay cash
         if ( !IE_10_AND_BELOW )
            panes.trigger("resize");
      }
      function dimSum(jq, dims) {
         // Opera returns -1 for missing min/max width, turn into 0
         var sum = 0;
         for ( var i=1; i < arguments.length; i++ )
            sum += Math.max(parseInt(jq.css(arguments[i])) || 0, 0);
         return sum;
      }

      // Determine settings based on incoming opts, element classes, and defaults
      var vh = (args.splitHorizontal? 'h' : args.splitVertical? 'v' : args.type) || 'v';
      var opts = $.extend({
         activeClass: 'active',   // class name for active splitter
         pxPerKey: 8,         // splitter px moved per keypress
         tabIndex: 0,         // tab order indicator
         accessKey: ''         // accessKey for splitbar
      },{
         v: {               // Vertical splitters:
            keyLeft: 39, keyRight: 37, cursor: "col-resize",
            splitbarClass: "vsplitbar", outlineClass: "voutline",
            type: 'v', eventPos: "pageX", origin: "left",
            split: "width",  pxSplit: "offsetWidth",  side1: "Left", side2: "Right",
            fixed: "height", pxFixed: "offsetHeight", side3: "Top",  side4: "Bottom"
         },
         h: {               // Horizontal splitters:
            keyTop: 40, keyBottom: 38,  cursor: "row-resize",
            splitbarClass: "hsplitbar", outlineClass: "houtline",
            type: 'h', eventPos: "pageY", origin: "top",
            split: "height", pxSplit: "offsetHeight", side1: "Top",  side2: "Bottom",
            fixed: "width",  pxFixed: "offsetWidth",  side3: "Left", side4: "Right"
         }
      }[vh], args);

      // Create jQuery object closures for splitter and both panes
      var splitter = $(this).css({position: "relative"});
      var panes = $(">*", splitter[0]).css({
         position: "absolute",          // positioned inside splitter container
         "z-index": "1",               // splitbar is positioned above
         "-moz-outline-style": "none"   // don't show dotted outline
      });
      var A = $(panes[0]);      // left  or top
      var B = $(panes[1]);      // right or bottom

      // Focuser element, provides keyboard support; title is shown by Opera accessKeys
      var focuser = $('<a href="javascript:void(0)"></a>')
         .attr({accessKey: opts.accessKey, tabIndex: opts.tabIndex, title: opts.splitbarClass})
         .bind($.browser.opera?"click":"focus", function(){ this.focus(); bar.addClass(opts.activeClass) })
         .bind("keydown", function(e){
            var key = e.which || e.keyCode;
            var dir = key==opts["key"+opts.side1]? 1 : key==opts["key"+opts.side2]? -1 : 0;
            if ( dir )
               resplit(A[0][opts.pxSplit]+dir*opts.pxPerKey, false);
         })
         .bind("blur", function(){ bar.removeClass(opts.activeClass) });

      // Splitbar element, can be already in the doc or we create one
      var bar = $(panes[2] || '<div></div>')
         .insertAfter(A).css("z-index", "100").append(focuser)
         .attr({"class": opts.splitbarClass, unselectable: "on"})
         .css({position: "absolute",   "user-select": "none", "-webkit-user-select": "none",
            "-khtml-user-select": "none", "-moz-user-select": "none", "top": "0px"})
         .bind("mousedown", startSplitMouse);
      // Use our cursor unless the style specifies a non-default cursor
      if ( /^(auto|default|)$/.test(bar.css("cursor")) )
         bar.css("cursor", opts.cursor);

      // Cache several dimensions for speed, rather than re-querying constantly
      bar._DA = bar[0][opts.pxSplit];
      splitter._PBF = $.boxModel? dimSum(splitter, "border"+opts.side3+"Width", "border"+opts.side4+"Width") : 0;
      splitter._PBA = $.boxModel? dimSum(splitter, "border"+opts.side1+"Width", "border"+opts.side2+"Width") : 0;
      A._pane = opts.side1;
      B._pane = opts.side2;
      $.each([A,B], function(){
         this._min = opts["min"+this._pane] || dimSum(this, "min-"+opts.split);
         this._max = opts["max"+this._pane] || dimSum(this, "max-"+opts.split) || 9999;
         this._init = opts["size"+this._pane]===true ?
            parseInt($.curCSS(this[0],opts.split)) : opts["size"+this._pane];
      });

      // Determine initial position, get from cookie if specified
      var initPos = A._init;
      if ( !isNaN(B._init) )   // recalc initial B size as an offset from the top or left side
         initPos = splitter[0][opts.pxSplit] - splitter._PBA - B._init - bar._DA;
      if ( opts.cookie ) {
         if ( !$.cookie )
            alert('jQuery.splitter(): jQuery cookie plugin required');
         var ckpos = parseInt($.cookie(opts.cookie));
         if ( !isNaN(ckpos) )
            initPos = ckpos;
         $(window).bind("unload", function(){
            var state = String(bar.css(opts.origin));   // current location of splitbar
            $.cookie(opts.cookie, state, {expires: opts.cookieExpires || 365,
               path: opts.cookiePath || document.location.pathname});
         });
      }
      if ( isNaN(initPos) )   // King Solomon's algorithm
         initPos = Math.round((splitter[0][opts.pxSplit] - splitter._PBA - bar._DA)/2);

      // Resize event propagation and splitter sizing
      if ( opts.anchorToWindow ) {
         // Account for margin or border on the splitter container and enforce min height
         splitter._hadjust = dimSum(splitter, "borderTopWidth", "borderBottomWidth", "marginBottom");
         splitter._hmin = Math.max(dimSum(splitter, "minHeight"), 20);
         $(window).bind("resize", function(){
            var top = splitter.offset().top;
            var wh = $(window).height();
            splitter.css("height", Math.max(wh-top-splitter._hadjust, splitter._hmin)+"px");
            if ( !IE_10_AND_BELOW ) splitter.trigger("resize");
         }).trigger("resize");
      }
      else if ( opts.resizeToWidth && !IE_10_AND_BELOW )
         $(window).bind("resize", function(){
            splitter.trigger("resize");
         });

      // Resize event handler; triggered immediately to set initial position
      splitter.bind("resize", function(e, size){
         // Custom events bubble in jQuery 1.3; don't Yo Dawg
         if ( e.target != this ) return;
         // Determine new width/height of splitter container
         splitter._DF = splitter[0][opts.pxFixed] - splitter._PBF;
         splitter._DA = splitter[0][opts.pxSplit] - splitter._PBA;
         // Bail if splitter isn't visible or content isn't there yet
         if ( splitter._DF <= 0 || splitter._DA <= 0 ) return;
         // Re-divvy the adjustable dimension; maintain size of the preferred pane
         resplit(!isNaN(size)? size : (!(opts.sizeRight||opts.sizeBottom)? A[0][opts.pxSplit] :
            splitter._DA-B[0][opts.pxSplit]-bar._DA));
      }).trigger("resize" , [initPos]);
   });
};

})(jQuery);