Javascript 拆分器-调整特定节点的大小

Javascript 拆分器-调整特定节点的大小,javascript,xul,Javascript,Xul,拖动拆分器时,如何在xul窗口中调整特定节点的大小? 由于xul窗口的复杂性,无法使用resizebefore/resizeafter属性 我试过在splitter上使用ondrag事件,但它根本不启动ondragstart事件可以正常触发,我可以使用event.offsetY来捕获拆分器移动了多少像素。 使用该值,我可以将其添加到height of need元素中,该元素工作正常,但不幸的是,该事件在每个拖动会话中仅触发一次 有什么想法吗 多谢各位 一个例子来测试它。由于原始xul的复杂性,我

拖动拆分器时,如何在xul窗口中调整特定节点的大小? 由于xul窗口的复杂性,无法使用resizebefore/resizeafter属性

我试过在splitter上使用
ondrag
事件,但它根本不启动
ondragstart
事件可以正常触发,我可以使用
event.offsetY
来捕获拆分器移动了多少像素。 使用该值,我可以将其添加到height of need元素中,该元素工作正常,但不幸的是,该事件在每个拖动会话中仅触发一次

有什么想法吗

多谢各位

一个例子来测试它。由于原始xul的复杂性,我无法更改xul结构(用户可以隐藏和更改行的顺序),因此可能只有javascript解决方案是可行的:


没有指定要调整大小的特定节点的常用方法

与XUL中的所有调整大小一样,其目的是您应该能够对XUL进行编码,这样您就可以让UI使用
元素自动调整布局或布局内部的大小,而无需让JavaScript监听事件并执行调整大小。但是,您当然可以让JavaScript执行大小调整。通常,当您在做一些复杂的事情时,您会遇到
实现中的一个bug,您会发现使用股票功能比微调XUL更容易,或者如果您只是想要编写自己的代码所提供的完整控制。关键是
和底层系统应该为您执行整个调整大小过程

然而,
元素确实有很大的局限性和一些bug,这可能导致您需要编写自己的调整大小的代码。这些限制包括:

  • flex
    属性重载。它用于控制对象的初始放置方式、调整窗口大小时对象的大小以及所有对象的大小调整方式。很可能你希望在每种情况下发生不同的事情
  • 股票中有缺陷
    代码。我观察到至少有两种不同的情况,包括一些显式声明为不灵活的元素仍在调整大小。IIRC,这些似乎主要是在试图使用容器内的
    来更改超出容器大小的对象的大小时出现的
  • 无法明确指定(例如,通过ID)要调整
    大小的元素
[我在考虑更多的限制,但我现在不记得了。]

如果您打算使用JavaScript进行自己的处理,那么您似乎需要完全通过监听来实现该功能。
的移动似乎不会触发。我假设这是因为移动
不被视为“拖放”操作的一部分(即,您实际上不会将其拾取并放到拖放目标上)。虽然我希望能够听到拖拽事件,但很明显他们没有开火

对我来说,
中缺少的最重要的功能是无法通过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>