Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fix for jQuery splitter in IE9

Update:

This issue is with regards to multiple pane splitter which was working fine in all browsers except IE9. The widths for the right pane children do not resize accordingly upon window resize.

(In the context of my application, I also had to put the right pane split method in a different javascript thread using setTimeout, 0 delay, otherwise the widths/top positions go wild in IE9.)

like image 886
bcm Avatar asked Mar 16 '11 05:03

bcm


2 Answers

I had trouble getting the plug-in working until I combined garlon4's solution with another fix required for jQuery 1.6+. Here is what I did:

I added the function:

function browser_resize_auto_fired() {
  // Returns true when the browser natively fires the resize 
  // event attached to the panes elements
  return ($.browser.msie && (parseInt($.browser.version) < 9));
}

Then replaced instances of

!$browser.msie

with

!browser_resize_auto_fired()

Finally, to get the plug-in working under jquery 1.6+, I changed this code:

$(window).bind("resize", function (e) {
  var top = splitter.offset().top;
  var wh = $(window).height();
  splitter.css("height", Math.max(wh - top - splitter._hadjust, splitter._hmin) + "px");
  if (!browser_resize_auto_fired()) splitter.trigger("resize");
}).trigger("resize");

to this:

$(window).bind("resize", function (e) {
  if (e.target == window) {
    var top = splitter.offset().top;
    var wh = $(window).height();
    splitter.css("height", Math.max(wh - top - splitter._hadjust, splitter._hmin) + "px");
    if (!browser_resize_auto_fired()) splitter.trigger("resize");
  }
}).trigger("resize");

Here is the full code for the plug-in, containing the fixes:

/*
 * jQuery.splitter.js - two-pane splitter window plugin
 *
 * version 1.51 (2009/01/09)
 * version 1.52 (2012/05/08) -- Fixes by Roger Martin
 *  * Added check in window resize event handler to run only when the target is the window. This fixes a breaking
 *    change introduced in jQuery 1.6.
 *  * Added support for IE 9+
 *
 * 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 ([email protected])
*/
 ; (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);
     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) {
     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
     bar.css(opts.origin, newPos).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)
            .css(opts.split, splitter._DA - bar._DA - newPos).css(opts.fixed, splitter._DF);
     // IE fires resize for us; all others pay cash
     if (!browser_resize_auto_fired())
       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;
   }
   function browser_resize_auto_fired() {
     // Returns true when the browser natively fires the resize event attached to the panes elements
     return ($.browser.msie && (parseInt($.browser.version) < 9));
   }

   // 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: "e-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: "n-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"
        })
        .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 (e) {
       if (e.target == window) {
         var top = splitter.offset().top;
         var wh = $(window).height();
         splitter.css("height", Math.max(wh - top - splitter._hadjust, splitter._hmin) + "px");
         if (!browser_resize_auto_fired()) splitter.trigger("resize");
       }
     }).trigger("resize");
   }
   else if (opts.resizeToWidth && !browser_resize_auto_fired())
     $(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 get into a 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);
like image 110
Roger Avatar answered Sep 29 '22 02:09

Roger


I also had this problem. I replaced all of the

!$browser.msie

in conditionals to

browser_resize()

and then defined a new function just after the header comments:

function browser_resize()
{
   if (!$.browser.msie)
      return true;
   if (parseInt($.browser.version)<9)
      return false;
   return true;
}
like image 43
garlon4 Avatar answered Sep 29 '22 03:09

garlon4