Splitter - resize node

How to resize a specific node in xul window while dragging a splitter? It is not possible to use the resizebefore / resizeafter attributes due to the complexity of the xul window.

I tried using the ondrag event on the splitter, but it doesn't fire at all. The ondragstart event ondragstart terminated, and I can use event.offsetY to fix how many pixels the separator is shifting. Using this value, I could add it to the height element, which works fine, but, unfortunately, this event fires only once per drag and drop session.

Any ideas?

Thanks.

An example for testing. Due to the complexity of my original xul, I cannot change the structure of xul (the user can hide and reorder the lines), so probably only a javascript solution is viable):

 <?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> <window id="testWindow" title="testing resizing element by splitter" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="color: white;" > <vbox id="resizeme" flex="1" style="background-color: yellow; color: black;"> <hbox flex="1"> <label value="#1"/> <hbox flex="1" align="center" pack="center"> <label value="Resizable by top and bottom splitter"/> </hbox> </hbox> </vbox> <splitter tooltiptext="Top splitter"/> <grid flex="1"> <columns> <column/> <column flex="1"/> </columns> <rows> <row style="background-color: black;"> <label value="#2"/> <vbox flex="1" pack="center" align="center"> <label value="Must stay constant size at all times"/> </vbox> </row> <row flex="1" style="background-color: blue;"> <label value="#3"/> <vbox flex="1" pack="center" align="center"> <label value="Resizable by top splitter only"/> </vbox> </row> <row style="background-color: black;"> <label value="#4"/> <hbox flex="1" pack="center" align="center"> <label value="Must stay constant size at all times, content must fit"/> <button label="blah"/> </hbox> </row> <splitter tooltiptext="Bottom splitter"/> <row flex="1" style="background-color: green;"> <label value="#5"/> <vbox flex="1" pack="center" align="center"> <label value="Resizable by bottom splitter only"/> </vbox> </row> <row style="background-color: black;"> <label value="#6"/> <vbox flex="1" pack="center" align="center"> <label value="Must stay constant size at all times"/> </vbox> </row> </rows> </grid> </window> 
+5
source share
1 answer

There is no way to stock specify a specific node for <splitter> to resize.

As with all resizing in XUL, you should be able to encode your XUL so that the user interface resizes your layout or its inside using the <splitter> elements, automatically, without any need for your JavaScript to listen events and performs resizing. However, you can certainly customize your JavaScript to resize the <splitter> . Usually you do this when you do something complicated, you encounter one of the errors in the implementation of <splitter> , it’s just easier for you to fine-tune your XUL to use the stock functionality, or if you just need the full control that provides your own code. The fact is that the <splitter> and the base system must do all the resizing for you.

However, <splitter> elements have significant limitations and a few errors, which may lead to the need to write custom resizing code. These restrictions include:

  • The flex property is overloaded. It is used to control how objects are initially placed, how they change when the window is resized, and how they are resized by all <splitters> . It is possible that you want different things to happen in each case.
  • There are errors in the <splitter> directory. I have observed at least a few different ones, including some where elements explicitly declared as not flexible are still changing. IIRC, they most often occur when you try to use the <splitter> , which is inside the container, to resize an object that exceeds this container.
  • There is no way to explicitly specify (for example, by identifier) ​​elements that should be resized <splitter> .

[I was thinking about additional restrictions, but I don’t remember them at the moment.]

If you intend to use JavaScript to do your own processing, you will need to fully implement the functionality by listening to mouse events . The <splitter> movement does not seem to trigger drag events . I suppose this is because moving a <splitter> not considered part of the “drag and drop” action (i.e. you don’t actually pick it up and throw it at the target point). Although I expected that I could listen to drag events, it is clear that they do not shoot.

For me, the most significant functionality that <splitters> lacks is the <splitters> to specify two elements by identifier that must be changed. Obviously, from the title of your question, it is clear that this is what you will also find that it is significantly lacking.

Adding an identifier to <splitter> :

The following code implements and provides an example of the use of <splitter> elements, which define the ID of the elements to be changed in the resizebefore and resizeafter in XUL.

To use it for a specific <splitter> , you will need to call one of the public functions to register <splitter> , using either the identifier <splitter> or the <splitter> element. For example, the two <spliter> elements in the XUL example (slightly modified from your question code) are registered with:

 splitterById.registerSplitterById("firstSplitter"); splitterById.registerSplitterById("secondSplitter"); 

splitterById.js:

  /****************************************************************************** * splitterById * * * * XUL <splitter> elements which are registered will resize only the two * * specific elements for which the ID is contained in the <splitter> * * resizebefore and resizeafter attributes. The orient attribute is used to * * specify if the <splitter> is resizing in the "vertical" or "horizontal" * * orientation. "vertical" is the default. * * * * For a particular <splitter> this is an all or nothing choice. In other * * words, you _must_ specify both a before and after element (eg You can not * * mix using an ID on the resizebefore and not on resizeafter with the * * expectation that the after will be resized with the normal <splitter> * * functionality. * * * * On both elements, the attributes minheight, maxheight, minwidth, and * * maxwidth will be obeyed. It may be necessary to explicitly set these * * attributes in order to prevent one or the other element from growing or * * shrinking when the other element is prevented from changing size by other * * XUL UI constraints. For example, an element can not be reduced in size * * beyond the minimum needed to display it. This code does not check for these * * other constraints. Thus, setting these attributes, at least the ones * * specifying the minimum height or minimum width will almost always be * * desirable. * * * * Public methods: * * registerSplitterById(id) : registers the <splitter> with that ID * * registerSplitterByElement(element) : registers the <splitter> element * * unregisterSplitterById(id) : unregisters the <splitter> with that ID * * unregisterSplitterByElement(element) : unregisters the <splitter> element * * * ******************************************************************************/ var splitterById = (function(){ let beforeER = {}; let afterER = {}; let splitIsVertical = true; let origClientY = -1; let origClientX = -1; function ElementRec(_el) { this.element = _el; this.origHeight = getElementHeight(_el); this.origWidth = getElementWidth(_el); //The .minHeight and .maxHeight attributes/properties // do not appear to be valid when first starting, so don't // get them here. //this.minHeight = getMinHeightAsValue(_el); //this.maxHeight = getMaxHeightAsValue(_el); } function getElementHeight(el) { //.height can be invalid and does not indicate the actual // height displayed, only the desired height. let boundingRec = el.getBoundingClientRect(); return boundingRec.bottom - boundingRec.top; } function getElementWidth(el) { //.width can be invalid and does not indicate the actual // width displayed, only the desired width. let boundingRec = el.getBoundingClientRect(); return boundingRec.right - boundingRec.left; } function getMaxHeightAsValue(el) { return asValueWithDefault(el.maxHeight,99999999); } function getMinHeightAsValue(el) { return asValueWithDefault(el.minHeight,0); } function getMaxWidthAsValue(el) { return asValueWithDefault(el.maxHeight,99999999); } function getMinWidthAsValue(el) { return asValueWithDefault(el.minHeight,0); } function asValueWithDefault(value,myDefault) { if(value === null || value === "" || value === undefined) { value = myDefault; } //What is returned by the various attributes/properties is // usually text, but not always. value++; value--; return value; } function storeSplitterStartingValues(el) { //Remember if the splitter is vertical or horizontal, // references to the elements being resized and their initial sizes. splitIsVertical = true; if(el.getAttribute("orient") === "horizontal") { splitIsVertical = false; } beforeER=new ElementRec(document.getElementById(el.getAttribute("resizebefore"))); afterER=new ElementRec(document.getElementById(el.getAttribute("resizeafter"))); if(beforeER.element === undefined || afterER.element === undefined) { //Did not find one or the other element. We must have both. return false; } return true; } function mousedownOnSplitter(event) { if(event.button != 0) { //Only drag with the left button. return; } //Remember the mouse position at the start of the resize. origClientY = event.clientY; origClientX = event.clientX; //Remember what we are acting upon if(storeSplitterStartingValues(event.target)) { //Start listening to mousemove and mouse up events on the whole document. document.addEventListener("mousemove",resizeSplitter,true); document.addEventListener("mouseup",endResizeSplitter,true); } } function endResizeSplitter(event) { if(event.button != 0) { //Only drag with the left button. return; } removeResizeListeners(); } function removeResizeListeners() { //Don't listen to document mousemove, mouseup events when not // actively resizing. document.removeEventListener("mousemove",resizeSplitter,true); document.removeEventListener("mouseup",endResizeSplitter,true); } function resizeSplitter(event) { //Prevent the splitter from acting normally: event.preventDefault(); event.stopPropagation(); //Get the new size for the before and after elements based on the // mouse position relative to where it was when the mousedown event fired. let newBeforeSize = -1; let newAfterSize = -1; if(splitIsVertical) { newBeforeSize = beforeER.origHeight + (event.clientY - origClientY); newAfterSize = afterER.origHeight - (event.clientY - origClientY); } else { newBeforeSize = beforeER.origWidth + (event.clientX - origClientX); newAfterSize = afterER.origWidth - (event.clientX - origClientX); } //Get any maximum and minimum sizes defined for the elements we are changing. //Get these here because they may not have been populated/valid // when the drag was first initiated (ie we should have been able // to do this only once when the mousedown event fired, but testing showed // the values are not necessarily valid at that time. let beforeMinSize; let beforeMaxSize; let afterMinSize; let afterMaxSize; if(splitIsVertical) { beforeMinSize = getMinHeightAsValue(beforeER.element); beforeMaxSize = getMaxHeightAsValue(beforeER.element); afterMinSize = getMinHeightAsValue(afterER.element); afterMaxSize = getMaxHeightAsValue(afterER.element); } else { beforeMinSize = getMinWidthAsValue(beforeER.element); beforeMaxSize = getMaxWidthAsValue(beforeER.element); afterMinSize = getMinWidthAsValue(afterER.element); afterMaxSize = getMaxWidthAsValue(afterER.element); } //Apply the limits to sizes we want to change to. //These do appear to work better sequentially rather than optimized. if(newBeforeSize < beforeMinSize) { //Set to beforeMinSize limit if have passed. let diff = beforeMinSize - newBeforeSize; newBeforeSize += diff; newAfterSize -= diff; } if(newBeforeSize > beforeMaxSize) { //Set to beforeMaxSize limit if have passed. let diff = beforeMaxSize - newBeforeSize; newBeforeSize += diff; newAfterSize -= diff; } if(newAfterSize < afterMinSize) { //Set to afterMinSize limit if have passed. let diff = afterMinSize - newAfterSize; newAfterSize += diff; newBeforeSize -= diff; } if(newAfterSize > afterMaxSize) { //Set to afterMaxSize limit if have passed. let diff = afterMaxSize - newAfterSize; newAfterSize += diff; newBeforeSize -= diff; } //Don't make any changes if we are still violating the limits. //There are some pathological cases where we could still be violating // a limit (where limits are set such that it is not possible to have // a valid height). if(newBeforeSize < beforeMinSize || newBeforeSize > beforeMaxSize || newAfterSize < afterMinSize || newAfterSize > afterMaxSize) { return; } //Make the size changes if(splitIsVertical) { beforeER.element.height = newBeforeSize; afterER.element.height = newAfterSize; } else { beforeER.element.width = newBeforeSize; afterER.element.width = newAfterSize; } } function _registerSplitterById(id) { _registerSplitterByElement(document.getElementById(id)); } function _registerSplitterByElement(el) { el.addEventListener("mousedown",mousedownOnSplitter,false); } function _unregisterSplitterById(id) { _unregisterSplitterByElement(document.getElementById(id)); } function _unregisterSplitterByElement(el) { el.removeEventListener("mousedown",mousedownOnSplitter,false); removeResizeListeners(); } return { registerSplitterById : function(id) { _registerSplitterById(id); }, registerSplitterByElement : function(el) { _registerSplitterByElement(el); }, unregisterSplitterById : function(id) { _unregisterSplitterById(id); }, unregisterSplitterByElement : function(el) { _unregisterSplitterByElement(el); } }; })(); 

XUL example (modified from the question):

 <?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> <window id="testWindow" title="testing resizing element by splitter" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="color: white;" > <vbox id="resizeme" height="120" minheight="30" maxheight="250" style="background-color: yellow; color: black;"> <hbox flex="1"> <label value="#1"/> <hbox flex="1" align="center" pack="center"> <label id="yellowLabel" value="Resizable by top and bottom splitter"/> </hbox> </hbox> </vbox> <splitter id="firstSplitter" tooltiptext="Top splitter" orient="vertical" resizebefore="resizeme" resizeafter="blueVbox"/> <grid> <columns> <column/> <column flex="1"/> </columns> <rows> <row style="background-color: black;"> <label value="#2"/> <vbox pack="center" align="center"> <label value="Must stay constant size at all times"/> </vbox> </row> <row id="blueRow" style="background-color: blue;"> <label value="#3"/> <vbox id="blueVbox" height="120" minheight="30" pack="center" align="center"> <label id="blueLabel" value="Resizable by top splitter only"/> </vbox> </row> <row style="background-color: black;"> <label value="#4"/> <hbox pack="center" align="center"> <label value="Must stay constant size at all times, content must fit"/> <button label="blah"/> </hbox> </row> <splitter id="secondSplitter" tooltiptext="Bottom splitter" orient="vertical" resizebefore="resizeme" resizeafter="greenVbox"/> <row id="greenRow" style="background-color: green;"> <label value="#5"/> <vbox id="greenVbox" height="120" minheight="30" pack="center" align="center"> <label id="greenLabel" value="Resizable by bottom splitter only"/> </vbox> </row> <row style="background-color: black;"> <label value="#6"/> <vbox pack="center" align="center"> <label value="Must stay constant size at all times"/> </vbox> </row> </rows> </grid> <script type="application/x-javascript" src="file://b:/SplitterById.js"/> <script type="application/javascript"> <![CDATA[ splitterById.registerSplitterById("firstSplitter"); splitterById.registerSplitterById("secondSplitter"); ]]> </script> </window> 

This example looks like this: Resize with splitterById

[Note. Although the code is written to work with both vertical and horizontal <splitters> , I tested it only with vertical <splitters> in the above example.]

Using <splitter> usually (without listening to events):
The example you originally had in your question was significantly less complex than the example you have now. It was entirely possible to encode it using strictly XUL so that the <splitter> function would function as you requested.

There are several ways (many of which interact in different combinations) that can be used to control which object or objects are modified using the <splitter> element, or the overall size of the overall layout. Among other things, they include the use of the resizebefore and resizeafter the <splitter> attribute in combination with the corresponding values ​​for the flex attribute on the elements of your XUL and potentially including those elements in box , hbox or vbox that are used only for the distribution of "flex". In addition, it may be desirable to specify a lot of restrictions for each element in an area that changes using the various attributes available for the XUL element (additional MDN documents: 1 , 2 , 3 ).

One of the things you missed is that the flex attribute may be a different value than just 1 or 0 . A numeric value is used to proportionally determine the size of the resizing that is performed for a specific element relative to other elements affected by the resizing (this is resizing due to <splitter> or resizing of a container element (e.g., <window> , <box> , <vbox> , <hbox> , etc.), which includes the item you are interested in).

Trial and error:
To get exactly the functionality that you want in a particular layout, you probably need to run a trial version and an error. You can find the XUL XUL Explorer prototype tool to make this useful, depending on what you do. For example, if your code dynamically creates your XUL, then XUL Explorer does not help much. However, even when building my XUL layout dynamically, I used the XUL Explorer to quickly see how the changes in the XUL that I build will look / behave.

Your (original) concrete example:
[Note. Below is the first example that was included in the question. This example was significantly less complex than the one currently in question. In particular, it did not have a <splitter> inside the container ( <grid> in the new example), which was desirable for resizing elements outside of this container.]

In your specific example, the described behavior can be achieved by setting the flex value on the green <vbox> to a large value relative to other elements.

As with many user interface issues, it's hard to put into words everything you want. For example, in this case you did not specify the initial sizes for other <vbox> elements. To show more of what is happening with <splitter> and using a different value for flex on the green <vbox> , I included the initial / default height for the other <vbox> elements. This will cause those elements starting at that height, and then only decrease to the minimum height, as soon as the green <vbox> decreases to the minimum height.

Note. You use the style attribute, and part of it is min-height: 30px; . If you don't put this in a CSS class, then it might be better / easier to use the XUL minheight attribute. This will facilitate the change programmatically if you want to do it. Given that this is sample code, you can simply paste this to not include the CSS file as well.

Code:

 <?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> <window id="testWindow" title="testing resizing element by splitter" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" > <hbox flex="1"> <vbox flex="1"> <vbox flex="1" height="80" pack="center" align="center" style="background-color: blue; min-height: 30px; color: white;"> <label value="this should stay constant size until green element reached its minimum size"/> </vbox> <vbox id="resizeme" flex="10000" height="80" pack="center" align="center" style="background-color: green; min-height: 30px; color: white;"> <label value="only this should be resized until it reached minimum size of 30px"/> </vbox> <vbox flex="1" height="80" pack="center" align="center" style="background-color: red; min-height: 30px; color: white;"> <label value="this should stay constant size until green element reached its minimum size"/> </vbox> </vbox> </hbox> <splitter/> <vbox flex="1"/> </window> 

How it looks when using <splitter> to resize:
Resize <vbox> elements with flex

+1
source

All Articles