/* * jquery.splitter.js - two-pane splitter window plugin * * version 1.01 (01/05/2007) * * 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 element should have two child elements which are used for the panes * of the splitter. The plugin adds a third child element for the splitbar. * * For more details see: http://methvin.com/jquery/splitter/ * * * @example $('#MySplitter').splitter(); * @desc Create a vertical splitter with default settings * * @example $('#MySplitter').splitter({direction: 'h', accessKey: 'M'}); * @desc Create a horizontal splitter resizable via Alt+Shift+M * * @name splitter * @type jQuery * @param String options Options for the splitter * @cat Plugins/Splitter * @return jQuery * @author Dave Methvin (dave.methvin@gmail.com) */ jQuery.fn.splitter = function(opts){ opts = jQuery.extend({ type: 'v', // v=vertical, h=horizontal split activeClass: 'active', // class name for active splitter pxPerKey: 5, // splitter px moved per keypress tabIndex: 0, // tab order indicator accessKey: '' // accelerator key for splitter // initA initB // initial A/B size (pick ONE) // minA maxA minB maxB // min/max pane sizes },{ v: { // Vertical splitters: keyGrowA: 39, // left arrow key keyShrinkA: 37, // right arrow key cursor: "e-resize", // double-arrow horizontal splitbarClass: "vsplitbar", eventPos: "pageX", set: "left", adjust: "width", offsetAdjust: "offsetWidth", adjSide1: "Left", adjSide2: "Right", fixed: "height", offsetFixed: "offsetHeight", fixSide1: "Top", fixSide2: "Bottom" }, h: { // Horizontal splitters: keyGrowA: 40, // down arrow key keyShrinkA: 38, // up arrow key cursor: "n-resize", // double-arrow vertical splitbarClass: "hsplitbar", eventPos: "pageY", set: "top", adjust: "height", offsetAdjust: "offsetHeight", adjSide1: "Top", adjSide2: "Bottom", fixed: "width", offsetFixed: "offsetWidth", fixSide1: "Left", fixSide2: "Right" } }[((opts||{}).type||'v').charAt(0).toLowerCase()], opts||{}); return this.each(function() { function startSplit(e) { splitbar.addClass(opts.activeClass); if ( e.type == "mousedown" ) { paneA._posAdjust = paneA[0][opts.offsetAdjust] - e[opts.eventPos]; jQuery(document) .bind("mousemove", doSplitMouse) .bind("mouseup", endSplit); } return true; // required??? } function doSplitKey(e) { var key = e.which || e.keyCode; var dir = key==opts.keyGrowA? 1 : key==opts.keyShrinkA? -1 : 0; if ( dir ) moveSplitter(paneA[0][opts.offsetAdjust]+dir*opts.pxPerKey); return true; // required??? } function doSplitMouse(e) { moveSplitter(paneA._posAdjust+e[opts.eventPos]); } function endSplit(e) { splitbar.removeClass(opts.activeClass); jQuery(document) .unbind("mousemove", doSplitMouse) .unbind("mouseup", endSplit); } function moveSplitter(np) { // Constrain new position to fit pane size limits; 16=scrollbar fudge factor // TODO: enforce group width in IE6 since it lacks min/max css properties? np = Math.max(paneA._min+paneA._padAdjust, group._adjust - (paneB._max||9999), 16, Math.min(np, paneA._max||9999, group._adjust - splitbar._adjust - Math.max(paneB._min+paneB._padAdjust, 16))); // Resize/position the two panes and splitbar splitbar.css(opts.set, np+"px"); paneA.css(opts.adjust, np-paneA._padAdjust+"px"); paneB.css(opts.set, np+splitbar._adjust+"px") .css(opts.adjust, group._adjust-splitbar._adjust-paneB._padAdjust-np+"px"); // IE fires resize for us; all others pay cash if ( !jQuery.browser.msie ) { paneA.trigger("resize"); paneB.trigger("resize"); } } function cssCache(jq, n, pf, m1, m2) { // IE backCompat mode thinks width/height includes border and padding jq[n] = jQuery.boxModel? (parseInt(jq.css(pf+m1))||0) + (parseInt(jq.css(pf+m2))||0) : 0; } function optCache(jq, pane) { // Opera returns -1px for min/max dimensions when they're not there! jq._min = Math.max(0, opts["min"+pane] || parseInt(jq.css("min-"+opts.adjust)) || 0); jq._max = Math.max(0, opts["max"+pane] || parseInt(jq.css("max-"+opts.adjust)) || 0); } // Create jQuery object closures for splitter group and both panes var group = jQuery(this).css({position: "relative"}); var divs = jQuery(">div", group).css({ position: "absolute", // positioned inside splitter container margin: "0", // remove any stylesheet margin or ... border: "0", // ... border added for non-script situations "-moz-user-focus": "ignore" // disable focusability in Firefox }); var paneA = jQuery(divs[0]); // left or top var paneB = jQuery(divs[1]); // right or bottom // Focuser element, provides keyboard support var focuser = jQuery('') .bind("focus", startSplit).bind("keydown", doSplitKey).bind("blur", endSplit) .attr({accessKey: opts.accessKey, tabIndex: opts.tabIndex}); // Splitbar element, displays actual splitter bar // The select-related properties prevent unintended text highlighting var splitbar = jQuery('
') .insertAfter(paneA).append(focuser) .attr({"class": opts.splitbarClass, unselectable: "on"}) .css({position: "absolute", "-khtml-user-select": "none", "-moz-user-select": "none", "user-select": "none"}) .bind("mousedown", startSplit); if ( /^(auto|default)$/.test(splitbar.css("cursor") || "auto") ) splitbar.css("cursor", opts.cursor); // Cache several dimensions for speed--assume these don't change splitbar._adjust = splitbar[0][opts.offsetAdjust]; cssCache(group, "_borderAdjust", "border", opts.adjSide1+"Width", opts.adjSide2+"Width"); cssCache(group, "_borderFixed", "border", opts.fixSide1+"Width", opts.fixSide2+"Width"); cssCache(paneA, "_padAdjust", "padding", opts.adjSide1, opts.adjSide2); cssCache(paneA, "_padFixed", "padding", opts.fixSide1, opts.fixSide2); cssCache(paneB, "_padAdjust", "padding", opts.adjSide1, opts.adjSide2); cssCache(paneB, "_padFixed", "padding", opts.fixSide1, opts.fixSide2); optCache(paneA, 'A'); optCache(paneB, 'B'); // Initial splitbar position as measured from left edge of splitter paneA._init = (opts.initA==true? parseInt(jQuery.curCSS(paneA[0],opts.adjust)) : opts.initA) || 0; paneB._init = (opts.initB==true? parseInt(jQuery.curCSS(paneB[0],opts.adjust)) : opts.initB) || 0; if ( paneB._init ) paneB._init = group[0][opts.offsetAdjust] - group._borderAdjust - paneB._init - splitbar._adjust; // Set up resize event handler and trigger immediately to set initial position group.bind("resize", function(e,size){ // Determine new width/height of splitter container group._fixed = group[0][opts.offsetFixed] - group._borderFixed; group._adjust = group[0][opts.offsetAdjust] - group._borderAdjust; // Bail if splitter isn't visible or content isn't there yet if ( group._fixed <= 0 || group._adjust <= 0 ) return; // Set the fixed dimension (e.g., height on a vertical splitter) paneA.css(opts.fixed, group._fixed-paneA._padFixed+"px"); paneB.css(opts.fixed, group._fixed-paneB._padFixed+"px"); splitbar.css(opts.fixed, group._fixed+"px"); // Re-divvy the adjustable dimension; maintain size of the preferred pane moveSplitter(size || (!opts.initB? paneA[0][opts.offsetAdjust] : group._adjust-paneB[0][opts.offsetAdjust]-splitbar._adjust)); }).trigger("resize" , [paneA._init || paneB._init || Math.round((group[0][opts.offsetAdjust] - group._borderAdjust - splitbar._adjust)/2)]); }); };