首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

14.5  利用拖曳功能对列表重新排序

一般从功能性的角度来讲,从一系列的元素中选择不止一个元素,应该比只从中选择一个元素更具挑战性。大家可以很容易地想到一个方案,那就是在列表中的每个项旁边放置一个箭头,用户如果想要移动那些列表项就需要重复点击旁边的箭头。不过这种方法用起来不是很方便。利用JavaScript的拖曳能力则可以实现一种比这种方法简单得多的方法,通过这种方法,用户就可以对列表项进行操作,并且能实时看到它们的变化。

 方 法

前一个方法为普通的对象拖曳创建了一些代码,这一节将要介绍的可排序列表就使用了很多前面创建的代码。不过与前面介绍的那种方案比较起来却有很大的区别,当四处拖动选中的元素时,列表中的每个对象必须进行重新排序,以对拖曳行为做出合理的响应,另外当放下了被拖动的对象时,如果排序已经结束,还可以有很多种方式重新记录该列表的结构。

要进行重排序的列表的HTML如下所示:

File: list_order_drag_n_drop.html (excerpt)

<ol id="footballLadder">

    <li>

        Liverpool

    </li>

    <li>

        Manchester United

    </li>

    <li>

        Arsenal

    </li>

    <li>

        Chelsea

    </li>

    <li>

        West Ham

    </li>

    <li>

        Fulham

    </li>

</ol>

用于为每个列表项定位的CSS相当简单,而且很灵活,代码如下:

File: list_order_drag_n_drop.css (excerpt)

ol

{

    list-style: none;

}

li

{

   width: 195px;

   height: 30px;

   margin-bottom: 5px;

   background-color: #666666;

   color: #FFFFFF;

   line-height: 30px;

  

}

初始化函数和mousedown监听者的实现与在前一节中创建的脚本有很大的不同:

File: list_order_drag_n_drop.js (excerpt)

addLoadListener(initSortableList);

function initSortableList()

{

    if (identifyBrowser().indexOf("ie") != −1 &&

         identifyOS() == "mac")

    {

       return false;

    }

    var LIs = document.getElementById("footballLadder"). getElementsByTagName("li");

    for (var i = 0; i < LIs.length; i++)

    {

        attachEventListener(LIs[i], "mousedown",

             mousedownSortableList, false);

        LIs[i].style.cursor = "move";

    }

}

function mousedownSortableList(event)

{

    if (typeof event == "undefined")

    {

        event = window.event;

    }

    if (typeof event.pageY == "undefined")

    {

        event.pageY = event.clientY + getScrollingPosition()[1];

    }

    var target = getEventTarget(event);

    while (target.nodeName.toLowerCase() != "li")

    {

       target = target.parentNode;

    }

    document.currentTarget = target;

    target.clickOriginY = event.pageY;

    attachEventListener(document, "mousemove",

          mousemoveCheckThreshold, false);

    attachEventListener(document, "mouseup", mouseupCancelThreshold,

          false);

    return true;

}

因为列表中的元素都是朝着一个方向移动的,所以不需要考虑水平坐标的处理,不过如果需要,也可以利用上一节中所介绍的方法来实现。

如果鼠标按键在某个可拖曳对象上已经按下了,那么一旦开始拖动,mousemoveCheckThreshold和mouseupCencelThreshold就会监视鼠标的动作,这些函数需要检测鼠标移动的距离是否是合理的,检测完之后才会实现真正的拖曳功能:

File: list_order_drag_n_drop.js (excerpt)

function mousemoveCheckThreshold(event)

{

    if (typeof event == "undefined")

    {

        event = window.event;

    }

    if (typeof event.pageY == "undefined")

    {

        event.pageY = event.clientY + getScrollingPosition()[1];

    }

    var target = document.currentTarget;

    if (Math.abs(target.clickOriginY − event.pageY) > 3)

    {

        if (typeof document.selection != "undefined")

        {

            var textRange = document.selection.createRange();

            textRange.collapse();

            textRange.select();

        }

        detachEventListener(document, "mousemove", mousemoveCheckThreshold, false);

        detachEventListener(document, "mouseup", mouseupCancelThreshold, false);

        attachEventListener(document, "mousemove", mousemoveSortableList, false);

        attachEventListener(document, "mouseup", mouseupSortableList, false);

        var cloneItem = target.cloneNode(true);

        cloneItem.setAttribute("class", "clone");

        cloneItem.style.position = "absolute";

        cloneItem.style.top = getPosition(target)[1] + "px";

        cloneItem.differenceY = parseInt(cloneItem.style.top) − event.pageY;

        cloneItem = target.parentNode.appendChild(cloneItem);

        target.clone = cloneItem;

        target.style.visibility = "hidden";

    }

    stopDefaultAction(event);

    return true;

}

function mouseupCancelThreshold()

{

    detachEventListener(document, "mousemove", mousemoveCheckThreshold, false);

    detachEventListener(document, "mouseup", mouseupCancelThreshold, false);

    return true;

}

如果mousemoveCheckThreshold检测到符合条件的动作(幅度大于3 pixels),那么将会把中间事件监听者去除,然后安装真正的拖曳监听者。在这之前,有一个条件语句,该条件语句是对selection进行处理的。这是为了弥补Windows下低版本的IE浏览器中的bug而设计的,因为在这些版本的浏览器中,即使在拖曳对象的时候也不会取消文本的选择。这种做法只是简单地取消了所有的文本selection,让它们不再起作用。

新的事件监听者添加上去之后,需要创建目标对象的一个完全克隆,并将其放在目标对象原来的位置上。在排序过程的实现中,不是将实际的目标对象到处移动,而是创建目标对象绝对定位的完全克隆,并将真正的目标对象隐藏起来。这样做是因为需要保留列表中的空隙(被拖动的元素留下的空隙),而且还要提供元素在光标周围移动时的视觉显示。通过对目标对象进行克隆,并将其放置在列表的末尾,就可以方便地对两者进行操作了。在列表的末尾添加上目标对象的克隆,保证了该克隆可以继承和列表元素相关联的任何样式,所以无需为了使其和原来列表保持一致再手动对其进行样式化。另外将目标列表项的visibility设置为“hidden”,即使目标项含有某些链接,也无需去移除,因为隐藏元素不能接收事件。

克隆的克隆

将“克隆”这个类加到某对象的克隆之上,就可以另外添加一些CSS效果。例如透明度,它可以使该克隆看起来就像原始对象的影子。不过要注意下面这段代码并不是在所有的浏览器中都能正常工作:

.clone

{

  opacity: 0.5;

}

一旦创建了下面这种架构,克隆对象的位置以及周围元素的位置就就可以被mousemoveSortableList监听者修改了:

File: list_order_drag_n_drop.js (excerpt)

function mousemoveSortableList(event)

{

    if (typeof event == "undefined")

    {

        event = window.event;

    }

    if (typeof event.pageY == "undefined")

    {

        event.pageY = event.clientY + getScrollingPosition()[1];

    }

    var target = document.currentTarget;

    var clone = target.clone;

    var plannedCloneTop = event.pageY + clone.differenceY;

    var listItems = clone.parentNode.getElementsByTagName("li");

    var firstItemPosition = getPosition(listItems[0]);

    var lastItemPosition = getPosition(listItems[listItems.length − 2]);

    if (plannedCloneTop < firstItemPosition[1])

    {

        plannedCloneTop = firstItemPosition[1];

    }

    else if (plannedCloneTop > lastItemPosition[1])

    {

        plannedCloneTop = lastItemPosition[1];

    }

    clone.style.top = plannedCloneTop + "px";

    var LIs = target.parentNode.getElementsByTagName("li");

    var currentItemHigher = true;

    for (var i = 0; i < LIs.length; i++)

    {

        if (LIs[i] != target && LIs[i] != target.clone)

        {

            if (event.pageY < getPosition(LIs[i])[1] +

                 LIs[i].offsetHeight && currentItemHigher)

            {

                target.parentNode.insertBefore(target, LIs[i]);

                break;

            }

            else if (event.pageY > getPosition(LIs[i])[1] && !currentItemHigher)

            {

                target.parentNode.insertBefore(LIs[i], target);

            }

        }

        else

        {

            currentItemHigher = false;

        }

    }

    stopDefaultAction(event);

    return true;

}

拖动元素的位置计算是用“拖曳行为的实现”这一节中介绍的技术来处理的,不过相对于那一节来说,可排序列表还有其他一些特殊的方面需要考虑。

首先,由于列表的第一个项和最后一项的限制,目标对象克隆的位置就只能处在顶部以及底端之间这个范围中了。如果用户想要将对象的克隆拖动到这个范围之外,那么结果肯定是失败的。从技术上来说,对象的克隆本身就是列表中的最后一项,所以使用倒数第二个元素来标识底端的界限。

当对象的克隆位置确定下来后,还需要进行检查,看这时列表的其他元素应该如何在该克隆的新位置周围进行排序。对除了目标对象和对象的克隆之外的每个列表项,都应该加以检测,看它们的底部边缘是否高于当前光标的位置,而且还要看它们当前是否在目标对象之上。如果列表项满足这些要求,那么该列表项就应该移动到目标元素的下方,为了完成这个任务,可以使用DOM函数insertBefore来对列表进行重新排序。如果列表不满足这些要求,那么就要检查一下,看当前列表元素的顶端边缘是否比当前光标位置低,以及当前元素的位置是否在目标列表项的下方。如果满足了这些要求,就需要将列表重新进行调整,以便使当前列表项处在目标列表项之上。对除此之外的其他情况,只需保持原有的顺序即可。

这个脚本的作用是,当用户到处移动目标对象的克隆时,会造成目标列表项中各项的位置发生自动的改变。用户可以看到列表中的各项实时的顺序变化。

当用户放开鼠标时,脚本立刻删除对象的克隆,并且重新显现目标列表项,这时就又可以看到列表原来的样子了:

File: list_order_drag_n_drop.js (excerpt)

function mouseupSortableList()

{

   var target = document.currentTarget;

   var clone = target.clone;

   clone.parentNode.removeChild(clone);

   target.style.visibility = "visible";

   detachEventListener(document, "mousemove",

      mousemoveSortableList, false);

   detachEventListener(document, "mouseup", mouseupSortableList, false);

   return true;

}

可以看出,drag-and-drop事件监听者最后会被删除,这时列表就恢复原状了。

最终的脚本产生的效果如图14.4所示。

图14.4  使用drag-sortable列表将Manchester United放到正确的位置

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论