Saturday, January 14, 2012

ZK Custom Component - Window dragging every where

... drag an embedded window the dragging window image jumps so that it is positioned where the mouse position is at top left. ... Can I override this default drag shape behaviour?

Yes we can, we can override it by override the ghost function and getDrop function in Class zk.DnD, described as follows:

zk.afterLoad("zk", function () {
    // store the old ghost and getDrop function
    var oldGhost = zk.DnD.ghost;
    var oldGetDrop = zk.DnD.getDrop;
    zk.DnD.ghost = function (drag, ofs, msg) {
        if (jq(drag.node).hasClass('z-window-embedded')) {
            // the dgelm is the drag node follows the mouse cursor,
            var dgelm = document.createElement("div");
            dgelm.id = "zk_ddghost";
    
            zk.copy(dgelm.style, {
                position: "absolute", left: ofs[0] + "px",
                top: ofs[1] + "px"
            });
            
            dgelm.appendChild(getVisualGhost (drag, ofs));
            jq(dgelm).addClass("z-drag-ghost");
            document.body.appendChild(dgelm);
            return dgelm;
        } else // call old ghost if not embedded window
            return oldGhost.apply(this, arguments);
    };
    // called while end drag
    zk.DnD.getDrop = function (drag, pt, evt) {
        if (jq(drag.node).hasClass('z-window-embedded')) {
            restoreWindow(drag, pt);
        } else // call old getDrop if not embedded window
            return oldGetDrop.apply(this, arguments);
    };
});

The fragment above denotes 'do the special for embedded window, do original otherwise'.

The ghost function is called when drag start, it will return a HTML Dom node and make it follow the mouse cursor, our goal is create a dom node, let the cloned window follow that node with calculated offset and hide the original window.

The getDrop function is called when drag finish (release the mouse button), it get the target that the mouse cursor on it, our goal is display the original window with new position.


// get the visual ghost node
function getVisualGhost (drag, ofs) {
    // span is a child with position=relative in drag node,
    // node is a clone of dragged widget,
    // with position=absolute in span
    var span = document.createElement("span"),
        wgt = drag.node,
        node = jq(wgt).clone()[0],
        nstyle = node.style;

    // need a relative positioned span
    // to absolute position the cloned window
    span.style.position = 'relative';
    nstyle.position = 'absolute';
    nstyle.left = wgt.offsetLeft - ofs[0] + 'px';
    nstyle.top = wgt.offsetTop - ofs[1] + 'px';

    // save the diffX and diffY for end drag
    // for update the position in restoreWindow
    wgt.diffX = wgt.offsetLeft - ofs[0]+7;
    wgt.diffY = wgt.offsetTop - ofs[1]+5;

    // hide the original window
    wgt.style.display = 'none';
    span.appendChild(node);

    return span;
}

This fragment clone the window and make it follow the drag node, the span with relative position is required for absolute position the cloned window, then we calculate the left and top of the cloned window, and store it with a little bias '+7, +5' because the position of drag node will be changed later.


// update the position of original window,
// and show it
function restoreWindow(drag, pt) {
    var wgt = drag.control,
        n = wgt.$n(),
        wstyle = n.style,
        diffX = n.diffX,
        diffY = n.diffY;
    if ((typeof diffX) == 'number' && (typeof diffY) == 'number') {
        wstyle.left = pt[0] + diffX + 'px';
        wstyle.top = pt[1] + diffY + 'px';
        wstyle.display = 'block';
        n.diffX = n.diffY = null;
        drag.control._fireOnMove();
    }
}


This fragment display the original window with new position, and fire the move event to notice the server position is changed.


The full zul file is as below:

<zk>
    <script type="text/javascript"><![CDATA[
        zk.afterLoad("zk", function () {
            // store the old ghost and getDrop function
            var oldGhost = zk.DnD.ghost;
            var oldGetDrop = zk.DnD.getDrop;
            zk.DnD.ghost = function (drag, ofs, msg) {
                if (jq(drag.node).hasClass('z-window-embedded')) {
                    // the dgelm is the drag node follows the mouse cursor,
                    var dgelm = document.createElement("div");
                    dgelm.id = "zk_ddghost";
            
                    zk.copy(dgelm.style, {
                        position: "absolute", left: ofs[0] + "px",
                        top: ofs[1] + "px"
                    });
                    
                    dgelm.appendChild(getVisualGhost (drag, ofs));
                    jq(dgelm).addClass("z-drag-ghost");
                    document.body.appendChild(dgelm);
                    return dgelm;
                } else // call old ghost if not embedded window
                    return oldGhost.apply(this, arguments);
            };
            // called while end drag
            zk.DnD.getDrop = function (drag, pt, evt) {
                if (jq(drag.node).hasClass('z-window-embedded')) {
                    restoreWindow(drag, pt);
                }
                // call old getDrop if not embedded window
                return oldGetDrop.apply(this, arguments);
            };
        });
        // get the visual ghost node
        function getVisualGhost (drag, ofs) {
            // span is a child with position=relative in drag node,
            // node is a clone of dragged widget,
            // with position=absolute in span
            var span = document.createElement("span"),
                wgt = drag.node,
                node = jq(wgt).clone()[0],
                nstyle = node.style;

            // need a relative positioned span
            // to absolute position the cloned window
            span.style.position = 'relative';
            nstyle.position = 'absolute';
            nstyle.left = wgt.offsetLeft - ofs[0] + 'px';
            nstyle.top = wgt.offsetTop - ofs[1] + 'px';

            // save the diffX and diffY for end drag
            // for update the position in restoreWindow
            wgt.diffX = wgt.offsetLeft - ofs[0]+7;
            wgt.diffY = wgt.offsetTop - ofs[1]+5;

            // hide the original window
            wgt.style.display = 'none';
            span.appendChild(node);

            return span;
        }
        // update the position of original window,
        // and show it
        function restoreWindow(drag, pt) {
            var wgt = drag.control,
                n = wgt.$n(),
                wstyle = n.style,
                diffX = n.diffX,
                diffY = n.diffY;
            if ((typeof diffX) == 'number' && (typeof diffY) == 'number') {
                wstyle.left = pt[0] + diffX + 'px';
                wstyle.top = pt[1] + diffY + 'px';
                wstyle.display = 'block';
                n.diffX = n.diffY = null;
                drag.control._fireOnMove();
            }
        }
    ]]></script>
    <vbox>
        <hbox>
            <div height="350px" width="350px" style="background-color: red;">
                <button label="test" draggable="true" ></button>
            </div>
            <div height="350px" width="350px" style="background-color: green;">
                <label id="posX" value="position left: " style="font-size: 20px;" />
                <div></div>
                <label id="posY" value="position top: " style="font-size: 20px;" />
            </div>
        </hbox>
        <hbox>
            <div height="350px" width="350px" style="background-color: brown;"></div>
            <div height="350px" width="350px" style="background-color: blue;"></div>
        </hbox>
    </vbox>
    <window width="300px" height="300px" border="normal"
        title="test" draggable="true"
        style="position: absolute; left: 100px; top: 100px;">
        <attribute name="onMove">
            posX.setValue("position left: " + event.getLeft());
            posY.setValue("position top: " + event.getTop());
        </attribute>
    </window>
</zk>

Download:
drag_embedded_window_everywhere.zul
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Components_Practice/WebContent

before_customized__not_desirable_dragging_positioning.swf
after_customized__drag_everywhere.swf
https://github.com/benbai123/ZK_Practice/tree/master/Components/demos/drag_embedded_window_everywhere

2 comments:

  1. Hi ben How can we know which css used by AbsoluteChildren ? As i saw http://books.zkoss.org/wiki/ZK_Style_Guide i didnot get anything about AbsoluteChildren . I want to override .z-absolutechildren-focus( i am not sure if it is present ) style class. Can it be possible?

    ReplyDelete
    Replies
    1. I think absolutechildren does not handle focus, you need to make it focusable manually, see

      http://ben-bai.blogspot.tw/2012/04/zk-make-unfocusable-component-focusable.html

      Delete