Javascript 拆分器-调整特定节点的大小
拖动拆分器时,如何在xul窗口中调整特定节点的大小? 由于xul窗口的复杂性,无法使用resizebefore/resizeafter属性 我试过在splitter上使用Javascript 拆分器-调整特定节点的大小,javascript,xul,Javascript,Xul,拖动拆分器时,如何在xul窗口中调整特定节点的大小? 由于xul窗口的复杂性,无法使用resizebefore/resizeafter属性 我试过在splitter上使用ondrag事件,但它根本不启动ondragstart事件可以正常触发,我可以使用event.offsetY来捕获拆分器移动了多少像素。 使用该值,我可以将其添加到height of need元素中,该元素工作正常,但不幸的是,该事件在每个拖动会话中仅触发一次 有什么想法吗 多谢各位 一个例子来测试它。由于原始xul的复杂性,我
ondrag
事件,但它根本不启动ondragstart
事件可以正常触发,我可以使用event.offsetY
来捕获拆分器移动了多少像素。
使用该值,我可以将其添加到height of need元素中,该元素工作正常,但不幸的是,该事件在每个拖动会话中仅触发一次
有什么想法吗
多谢各位
一个例子来测试它。由于原始xul的复杂性,我无法更改xul结构(用户可以隐藏和更改行的顺序),因此可能只有javascript解决方案是可行的:
没有指定要调整大小的特定节点的常用方法 与XUL中的所有调整大小一样,其目的是您应该能够对XUL进行编码,这样您就可以让UI使用
元素自动调整布局或布局内部的大小,而无需让JavaScript监听事件并执行调整大小。但是,您当然可以让JavaScript执行大小调整。通常,当您在做一些复杂的事情时,您会遇到
实现中的一个bug,您会发现使用股票功能比微调XUL更容易,或者如果您只是想要编写自己的代码所提供的完整控制。关键是
和底层系统应该为您执行整个调整大小过程
然而,
元素确实有很大的局限性和一些bug,这可能导致您需要编写自己的调整大小的代码。这些限制包括:
属性重载。它用于控制对象的初始放置方式、调整窗口大小时对象的大小以及所有对象的大小调整方式。很可能你希望在每种情况下发生不同的事情flex
- 股票中有缺陷
代码。我观察到至少有两种不同的情况,包括一些显式声明为不灵活的元素仍在调整大小。IIRC,这些似乎主要是在试图使用容器内的
来更改超出容器大小的对象的大小时出现的 - 无法明确指定(例如,通过ID)要调整
大小的元素
的移动似乎不会触发。我假设这是因为移动
不被视为“拖放”操作的一部分(即,您实际上不会将其拾取并放到拖放目标上)。虽然我希望能够听到拖拽事件,但很明显他们没有开火
对我来说,
中缺少的最重要的功能是无法通过ID指定要调整大小的两个元素。显然,从你问题的标题来看,很明显,这也是你发现的明显不足之处
添加指定ID到
:
下面的代码实现并提供了使用元素的示例,这些元素在XUL中的resizebefore
和resizeafter
属性中指定要调整大小的元素的ID
为了在特定的
上使用它,您需要调用一个公共函数,使用
的ID或
元素注册
。例如,示例XUL中的两个
元素(从您问题中的代码进行了一些修改)注册到:
splitterById.registerSplitterById("firstSplitter");
splitterById.registerSplitterById("secondSplitter");
splitterById.js:
使用
调整大小时的外观:好的,添加了示例。我的错误,混淆了两个示例…现在修复。两个拆分器都应该调整黄色框的大小,但是顶部拆分器应该只影响蓝色行,底部拆分器应该只影响绿色行。请注意,如果在打开xul文件并向上移动底部拆分器后,它跳下,意外地调整黑色#4行的大小,则会发生一种奇怪的行为。感谢您提供如此详细的解释和工作示例!不幸的是,经过一些额外的测试,我的示例没有完成。。。我没想到是纯xul解决方案,所以这个例子被证明太简单了。我已经用更完整的例子更新了我原来的问题,恐怕不能用xul来解决。@vanowm:我已经显著地更新了我的答案。现在,中包含了与问题中当前示例一起使用的JavaScript代码。事实上,代码实现了一种通用方法,即在
的XUL属性中使用ID来显式指定
将调整大小的两个元素。非常感谢!这太完美了!
splitterById.registerSplitterById("firstSplitter");
splitterById.registerSplitterById("secondSplitter");
/******************************************************************************
* splitterById *
* *
* XUL <splitter> elements which are registered will resize only the two *
* specific elements for which the ID is contained in the <splitter>'s *
* 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 (e.g. 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 (i.e. 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);
}
};
})();
<?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>
<?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>