Splitter.js won't work with new versions of jQ

2019-02-04 18:34发布

问题:

I'm using Splitter.js in a project.

The code is from http://methvin.com/splitter/ The specific JS is at http://methvin.com/splitter/splitter.js

When using jQuery v1.5.2, the code works correctly.

When I move to jQuery v1.7.2, the code fails, and gives a "Too Much recursion" error. This also appears to happen when I use jQuery 1.6.2

Does anyone have a workaround for this?

I did find an (updated?) version of splitter.js at https://bungeni-exist.googlecode.com/svn-history/r188/xq-framework/trunk/db/framework/assets/bungeni/scripts/splitter.js but this doesn't appear to solve the problem.

Any advice would be appreciated.

回答1:

I was experiencing this same issue. After looking around in the splitter.js file for a while, I came across this section of code:

// 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;

        ......

    }).trigger("resize" , [initPos]);

The "yo dawg" reference was the dead giveaway :)

Sure enough, after debugging it in Chrome, there is excessive recursion in this particular event handler function. The developer who wrote it attempted to resolve the issue, but for some reason the newer version of the JQuery library does not work as expected, and the escape condition is never met. From what I can tell, this particular piece of code is only used during page load to set the initial position of the splitter. I found that the splitter was still usable aside from the stack overflow, and the only reason I noticed the problem was because my javascript code after initializing the splitter wasn't running. If you have time, see if you can find out why this part of the code isn't working and post a fix. If you're in a hurry and don't mind duct-tape, put a try catch around the line of code where you call the .splitter() function. It seems to work fine in both Chrome 19 and IE 9.



回答2:

There's an updated fork of jQuery.splitter that works with jQuery 1.8 (also 1.9 if you restore jQuery.browser) at https://github.com/e1ven/jQuery-Splitter.



回答3:

UI-Layout stays up-to-date and does "splitting" and alot more, and is fairly easy to use.

Extremely Minimalist Example

$('body').layout({ applyDemoStyles: true });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script type="text/javascript" src="http://layout.jquery-dev.net/lib/js/jquery.layout-latest.js"></script>
<div class="ui-layout-center">Center
	<p><a href="http://layout.jquery-dev.com/demos.html">Go to the Demos page</a></p>
	<p>* Pane-resizing is disabled because ui.draggable.js is not linked</p>
	<p>* Pane-animation is disabled because ui.effects.js is not linked</p>
</div>
<div class="ui-layout-north">North</div>
<div class="ui-layout-south">South</div>
<div class="ui-layout-east">East</div>
<div class="ui-layout-west">West</div>

Complex Demo

var layoutSettings_Outer = {
    name: "outerLayout",
    defaults: {
        size: "auto",
        minSize: 50,
        paneClass: "pane",
        resizerClass: "resizer",
        togglerClass: "toggler",
        buttonClass: "button",
        contentSelector: ".content",
        contentIgnoreSelector: "span",
        togglerLength_open: 35,
        togglerLength_closed: 35,
        hideTogglerOnSlide: true,
        togglerTip_open: "Close This Pane",
        togglerTip_closed: "Open This Pane",
        resizerTip: "Resize This Pane",
        fxName: "slide",
        fxSpeed_open: 750,
        fxSpeed_close: 1500,
        fxSettings_open: { easing: "easeInQuint" },
        fxSettings_close: { easing: "easeOutQuint" }
    },
    north: {
        spacing_open: 1,
        togglerLength_open: 0,
        togglerLength_closed: -1,
        resizable: false,
        slidable: false,
        fxName: "none"
    },
    south: {
        maxSize: 200,
        spacing_closed: 0,
        slidable: false,
        initClosed: true,
        onhide_start: function() { return confirm("START South pane hide \n\n onhide_start callback \n\n Allow pane to hide?"); },
        onhide_end: function() { alert("END South pane hide \n\n onhide_end callback"); },
        onshow_start: function() { return confirm("START South pane show \n\n onshow_start callback \n\n Allow pane to show?"); },
        onshow_end: function() { alert("END South pane show \n\n onshow_end callback"); },
        onopen_start: function() { return confirm("START South pane open \n\n onopen_start callback \n\n Allow pane to open?"); },
        onopen_end: function() { alert("END South pane open \n\n onopen_end callback"); },
        onclose_start: function() { return confirm("START South pane close \n\n onclose_start callback \n\n Allow pane to close?"); },
        onclose_end: function() { alert("END South pane close \n\n onclose_end callback"); },
        onresize_end: function() { alert("END South pane resize \n\n onresize_end callback \n\n NOTE: onresize_start event was skipped."); }
    },
    west: {
        size: 250,
        spacing_closed: 21,
        togglerLength_closed: 21,
        togglerAlign_closed: "top",
        togglerLength_open: 0,
        togglerTip_open: "Close West Pane",
        togglerTip_closed: "Open West Pane",
        resizerTip_open: "Resize West Pane",
        slideTrigger_open: "click",
        initClosed: true,
        fxSettings_open: { easing: "easeOutBounce" }
    },
    east: {
        size: 250,
        spacing_closed: 21,
        togglerLength_closed: 21,
        togglerAlign_closed: "top",
        togglerLength_open: 0,
        togglerTip_open: "Close East Pane",
        togglerTip_closed: "Open East Pane",
        resizerTip_open: "Resize East Pane",
        slideTrigger_open: "mouseover",
        initClosed: true,
        fxName: "drop",
        fxSpeed: "normal",
        fxSettings: { easing: "" }
    },
    center: {
        paneSelector: "#mainContent",
        minWidth: 200,
        minHeight: 200
    }
};
$(function() {
	var outerLayout, innerLayout;
	outerLayout = $("body").layout(layoutSettings_Outer);
	outerLayout.addToggleBtn("#tbarToggleNorth", "north");
	outerLayout.addOpenBtn("#tbarOpenSouth", "south");
	outerLayout.addCloseBtn("#tbarCloseSouth", "south");
	outerLayout.addPinBtn("#tbarPinWest", "west");
	outerLayout.addPinBtn("#tbarPinEast", "east");
	
	var westSelector = "body > .ui-layout-west",
		eastSelector = "body > .ui-layout-east";
	$("<span></span>").addClass("pin-button").prependTo(westSelector);
	$("<span></span>").addClass("pin-button").prependTo(eastSelector);
	outerLayout.addPinBtn(westSelector + " .pin-button", "west");
	outerLayout.addPinBtn(eastSelector + " .pin-button", "east");
	$("<span></span>").attr("id", "west-closer").prependTo(westSelector);
	$("<span></span>").attr("id", "east-closer").prependTo(eastSelector);
	
	outerLayout.addCloseBtn("#west-closer", "west");
	outerLayout.addCloseBtn("#east-closer", "east");
	
	$("a").each(function() {
	var path = document.location.href;
		if (path.substr(path.length - 1) == "#") path = path.substr(0, path.length - 1);
		if (this.href.substr(this.href.length - 1) == "#") this.href = path + "#";
	});
});
body { font-size: 85%; }
<link href="http://layout.jquery-dev.net/demos/css/complex.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script type="text/javascript" src="http://layout.jquery-dev.net/lib/js/jquery.layout-latest.js"></script>
<script type="text/javascript" src="http://layout.jquery-dev.net/demos/js/complex.js"></script>
<script type="text/javascript" src="http://layout.jquery-dev.net/lib/js/debug.js"></script>

<div class="ui-layout-west">
	<div class="header">Outer - West</div>
	<div class="content">
		<h3><b>Outer Layout</b></h3>
		<ul>
			<li><a href="#" onClick="outerLayout.toggle('north')">Toggle North</a></li>
			<li><a href="#" onClick="outerLayout.toggle('south')">Toggle South</a></li>
			<li><a href="#" onClick="outerLayout.toggle('west')"> Toggle West</a></li>
			<li><a href="#" onClick="outerLayout.toggle('east')"> Toggle East</a></li>
			<li><a href="#" onClick="outerLayout.hide('north')">Hide North</a></li>
			<li><a href="#" onClick="outerLayout.hide('south')">Hide South</a></li>
			<li><a href="#" onClick="outerLayout.show('south', false)">Unhide South</a></li>
			<li><a href="#" onClick="outerLayout.hide('east')"> Hide East</a></li>
			<li><a href="#" onClick="outerLayout.show('east', false)">Unhide East</a></li>
			<li><a href="#" onClick="outerLayout.open('east')"> Open East</a></li>
			<li><a href="#" onClick="outerLayout.open('north'); outerLayout.sizePane('north', 'auto')">  Resize North="auto"</a></li>
			<li><a href="#" onClick="outerLayout.sizePane('north', 100); outerLayout.open('north')">  Resize North=100</a></li>
			<li><a href="#" onClick="outerLayout.sizePane('north', 300); outerLayout.open('north')">  Resize North=300</a></li>
			<li><a href="#" onClick="outerLayout.sizePane('north', 10000); outerLayout.open('north')">Resize North=10000</a></li>
			<li><a href="#" onClick="outerLayout.open('south'); outerLayout.sizePane('south', 'auto')">  Resize South="auto"</a></li>
			<li><a href="#" onClick="outerLayout.sizePane('south', 100); outerLayout.open('south')">  Resize South=100</a></li>
			<li><a href="#" onClick="outerLayout.sizePane('south', 300); outerLayout.open('south')">  Resize South=300</a></li>
			<li><a href="#" onClick="outerLayout.sizePane('south', 10000); outerLayout.open('south')">Resize South=10000</a></li>
			<li><a href="#" onClick="outerLayout.panes.north.css('backgroundColor','#FCC')">North Color = Red</a></li>
			<li><a href="#" onClick="outerLayout.panes.north.css('backgroundColor','#CFC')">North Color = Green</a></li>
			<li><a href="#" onClick="outerLayout.panes.north.css('backgroundColor','')">    North Color = Default</a></li>
			<li><a href="#" onClick="alert('outerLayout.name = \''+outerLayout.options.name+'\'')">Show Layout Name</a></li>
			<li><a href="#" onClick="showOptions(outerLayout,'defaults')">Show Options.Defaults</a></li>
			<li><a href="#" onClick="showOptions(outerLayout,'north')">   Show Options.North</a></li>
			<li><a href="#" onClick="showOptions(outerLayout,'south')">   Show Options.South</a></li>
			<li><a href="#" onClick="showOptions(outerLayout,'west')">    Show Options.West</a></li>
			<li><a href="#" onClick="showOptions(outerLayout,'east')">    Show Options.East</a></li>
			<li><a href="#" onClick="showOptions(outerLayout,'center')">  Show Options.Center</a></li>
			<li><a href="#" onClick="showState(outerLayout,'container')"> Show State.Container</a></li>
			<li><a href="#" onClick="showState(outerLayout,'north')">     Show State.North</a></li>
			<li><a href="#" onClick="showState(outerLayout,'south')">     Show State.South</a></li>
			<li><a href="#" onClick="showState(outerLayout,'west')">      Show State.West</a></li>
			<li><a href="#" onClick="showState(outerLayout,'east')">      Show State.East</a></li>
			<li><a href="#" onClick="showState(outerLayout,'center')">    Show State.Center</a></li>
		</ul>
	</div>

	<div class="footer">Automatically positioned footer</div>
</div>
<div class="ui-layout-east">
	<div class="header">Outer - East</div>
	<div class="subhead">I'm a subheader</div>
	<div class="content">
		<h3><b>Inner Layout</b></h3>
		<ul id="createInner">
			<li><a href="#" onClick="createInnerLayout(); return false;">CREATE Inner Layout</a></li>
		</ul>
		<ul id="innerCommands" style="display: none;">
			<li><a href="#" onClick="innerLayout.toggle('north')">Toggle North</a></li>
			<li><a href="#" onClick="innerLayout.toggle('south')">Toggle South</a></li>
			<li><a href="#" onClick="innerLayout.toggle('west')"> Toggle West</a></li>
			<li><a href="#" onClick="innerLayout.toggle('east')"> Toggle East</a></li>
			<li><a href="#" onClick="innerLayout.hide('north')">Hide North</a></li>
			<li><a href="#" onClick="innerLayout.hide('south')">Hide South</a></li>
			<li><a href="#" onClick="innerLayout.hide('west')"> Hide West</a></li>
			<li><a href="#" onClick="innerLayout.hide('east')"> Hide East</a></li>
			<li><a href="#" onClick="innerLayout.show('east')"> Show East</a></li>
			<li><a href="#" onClick="innerLayout.sizePane('north', 50); innerLayout.open('north')">   Resize North=50</a></li>
			<li><a href="#" onClick="innerLayout.sizePane('north', 300); innerLayout.open('north')">  Resize North=300</a></li>
			<li><a href="#" onClick="innerLayout.sizePane('north', 10000); innerLayout.open('north')">Resize North=10000</a></li>
			<li><a href="#" onClick="innerLayout.sizePane('south', 50); innerLayout.open('south')">   Resize South=50</a></li>
			<li><a href="#" onClick="innerLayout.sizePane('south', 300); innerLayout.open('south')">  Resize South=300</a></li>
			<li><a href="#" onClick="innerLayout.sizePane('south', 10000); innerLayout.open('south')">Resize South=10000</a></li>
			<li><a href="#" onClick="innerLayout.panes.north.css('backgroundColor','#FCC')">North Color = Red</a></li>
			<li><a href="#" onClick="innerLayout.panes.north.css('backgroundColor','#CFC')">North Color = Green</a></li>
			<li><a href="#" onClick="innerLayout.panes.north.css('backgroundColor','')">    North Color = Default</a></li>
			<li><a href="#" onClick="alert('innerLayout.name = \''+innerLayout.options.name+'\'')">Show Layout Name</a></li>
			<li><a href="#" onClick="showOptions(innerLayout,'defaults')">Show Options.Defaults</a></li>
			<li><a href="#" onClick="showOptions(innerLayout,'north')">   Show Options.North</a></li>
			<li><a href="#" onClick="showOptions(innerLayout,'south')">   Show Options.South</a></li>
			<li><a href="#" onClick="showOptions(innerLayout,'west')">    Show Options.West</a></li>
			<li><a href="#" onClick="showOptions(innerLayout,'east')">    Show Options.East</a></li>
			<li><a href="#" onClick="showOptions(innerLayout,'center')">  Show Options.Center</a></li>
			<li><a href="#" onClick="showState(innerLayout,'container')"> Show State.Container</a></li>
			<li><a href="#" onClick="showState(innerLayout,'north')">     Show State.North</a></li>
			<li><a href="#" onClick="showState(innerLayout,'south')">     Show State.South</a></li>
			<li><a href="#" onClick="showState(innerLayout,'west')">      Show State.West</a></li>
			<li><a href="#" onClick="showState(innerLayout,'east')">      Show State.East</a></li>
			<li><a href="#" onClick="showState(innerLayout,'center')">    Show State.Center</a></li>
		</ul>
	</div>
	<div class="footer">I'm a footer</div>
	<div class="footer">I'm another footer</div>
	<div class="footer">Unlimited headers &amp; footers</div>
</div>
<div class="ui-layout-north">
	<div class="header">Outer - North</div>
	<div class="content">
		I only have toggler when 'closed' - I cannot be resized - and I do not 'slide open'
	</div>
	<ul class="toolbar">
		<li id="tbarToggleNorth" class="first"><span></span>Toggle NORTH</li>
		<li id="tbarOpenSouth"><span></span>Open SOUTH</li>
		<li id="tbarCloseSouth"><span></span>Close SOUTH</li>
		<li id="tbarPinWest"><span></span>Pin/Unpin WEST</li>
		<li id="tbarPinEast" class="last"><span></span>Pin/Unpin EAST</li>
	</ul>
</div>
<div class="ui-layout-south">
	<div class="header">Outer - South</div>
	<div class="content">
		<p>I only have a resizer/toggler when 'open'</p>
	</div>
</div>
<div id="mainContent">
	<div class="ui-layout-center">
		<h3 class="header">Inner - Center</h3>
		<div class="ui-layout-content">
			<p id="createInner2" style="font-weight: bold;"><a href="#" onClick="createInnerLayout(); return false;">Click here to CREATE the Inner Layout</a></p>
			<p>See the <a href="#" onclick="outerLayout.open('east'); return false;">Outer-East pane</a> for commands to manipulate the Inner Layout</p>
			<p><a href="../demos.html">Go to the Demos page</a></p>
			<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
			<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
			<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
			<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
		</div>
		<div class="footer">Center panes can have headers &amp; footers too</div>
	</div>
	
	<div class="ui-layout-north"> Inner - North</div>
	<div class="ui-layout-south"> Inner - South</div>
	<div class="ui-layout-west">  Inner - West</div>
	<div class="ui-layout-east">  Inner - East
		<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
		<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
		<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
		<p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p><p>...</p>
	</div>

</div>

Advanced Complex Demo



回答4:

I ran into the same problem in the process of upgrading a site to jQuery 1.8. After a bit of debugging and poking around in the code, it seems to me there were two issues: the resize event was getting triggered unnecessarily in a few places, and it was getting triggered on the splitter before the splitter's resize event handler had been setup. I switched the order of the last two chunks of code to make sure the splitter's resize event handler gets set up first and tidied up a few lines. It now looks like this:

  // Resize event handler
  splitter.bind("resize", function(e, size){
    // 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));
    e.stopPropagation();
  });

  // 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);
    splitter._bottomOffset = opts.bottomOffset ? opts.bottomOffset : 0;
    $(window).bind("resize", function(){
      var top = splitter.offset().top;
      var wh = $(window).height() - splitter._bottomOffset;
      splitter.css("height", Math.max(wh-top-splitter._hadjust, splitter._hmin)+"px");
      splitter.trigger("resize");
    }).trigger("resize");
  }
  else if ( opts.resizeToWidth )
    $(window).bind("resize", function(){
      splitter.trigger("resize");
    });

Also, make sure you remove

if ( !$.browser.msie ) panes.trigger("resize");

from the reSplit function. I've uploaded the whole thing to a GitHub repo if you want to see it all in one place.



回答5:

Per Steven Hunt's answer, I have a workaround by adding try/catch blocks, and removing a recursive call.

Replace the block below (from around line 149ish) with this modified version-

        try
        {

          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");
            }).trigger("resize");

          }
          else if ( opts.resizeToWidth && !$.browser.msie )
            $(window).bind("resize", function(){
                splitter.trigger("resize"); 
            });
          }
        catch(err)
        {
        }