首页  ·  知识 ·  前端
JavaScript创建模块化的交互用户界面
Greg Travis  本站原创  综合  编辑:dezai  图片来源:网络
可切换界面 可切换系统很容易使用。先由 Web 页面设计人员将某些部分标志为可切换的。然后就可以在任何一个可切换元素上单击并将该元素拖放到另一个

可切换界面

可切换系统很容易使用。先由 Web 页面设计人员将某些部分标志为可切换的。然后就可以在任何一个可切换元素上单击并将该元素拖放到另一个可切换元素。放开鼠标按钮后,这两个元素就完成了交换。

为了能清楚展示所发生的事情,可以使用一些标准的 GUI 操作。

突出显示被拖动的元素

当第一次单击可切换元素时,在光标下面会出现一个透明的矩形。这个矩形由 coveringDiv() 函数创建,它刚好能覆盖这个可切换元素。实际上是将这个矩形拖放到另一个元素。当拖放时,只有这个透明的矩形会移动 — 初始的元素保持不动直到鼠标按钮被松开为止。

突出显示拖动到的目标

另一个重要的操作是清晰标识出要拖动到的目标元素。当拖动透明的矩形四处移动时,光标可以经过多个可切换元素。当光标悬浮于某个可切换元素之上时,该元素就会通过另一个透明矩形突出显示。这种突出显示就能清楚地标示出此元素就是拖放到的目标。当松开鼠标按钮时,被拖动的元素和拖放到的目标元素就会互换位置,而且所有透明矩形也会消失,直到下一次切换。

激活系统

正如先前提到的,必须要使代码对已有系统影响最小。这就意味着页面设计人员 —工作于 HTML 或 XML— 无需涉及可切换系统。这不是他们的工作。

此页面只需具有如下三项内容:

javascript 标记
标记内的 onload 方法
标记为 swappable 的可切换区域
javascript 标记

必须将以下标记置于页面文件的顶部:


 


此标记在加载过程的早期加载,但它在 body 内的 onload 函数调用之后才会执行。

body 标记内的 Onload 方法

该方法在整个页面加载时调用这个可切换系统。这一点很重要,因为此代码的第一项功能就是在整个页面内搜索可切换的元素。因而,需要确保这些元素已加载。body 内的 onload 方法应该如清单 1 所示。


清单 1. body 内的 onload 处理程序
    

    ... rest of page
 
 


已标记为 swappable 的可切换区域

必须通过 class 参数这样标记每个想要切换的区域。这是页面作者和设计人员需要多加考虑的事情,因为他们需要将此参数添加给每个部分。参见清单 2。


清单 2. 用可切换类注释 div
    


    lorem ipsum lorem ipsum
 

 


寻找可切换的部分

代码所需做的首要事情是寻找页面将被激活的部分。正如之前提到的,这只要求包围这个部分的标记具有 class 参数。要寻找这些部分,需要找到所有具有可切换 class 的标记。此函数不是标准 DOM 库的一部分,但它很容易实现。清单 3 展示了一个示例实现。


清单 3. getElementsByClass() 的实现
    
// By Dustin Diaz
function getElementsByClass(searchClass,node,tag) {
        var classElements = new Array();
        if ( node == null )
                node = document;
        if ( tag == null )
                tag = '*';
        var els = node.getElementsByTagName(tag);
        var elsLen = els.length;
        var pattern = new RegExp("(^|\\\\s)"+searchClass+"(\\\\s|$)");
        for (i = 0, j = 0; i < elsLen; i++) {
                if ( pattern.test(els[i].className) ) {
                        classElements[j] = els[i];
                        j++;
                }
        }
        return classElements;
}
 


交互性的元素

程序一般是通过将各功能块结合在一起而构建起来的。不同的程序员会有不同的实现方式,但作为一种规律,最好是采用多个小的功能块而不是少数几个大的功能块。每个小功能块应该实现一种功能并具有清楚的语义。

不过,在进行 GUI 编程时,这样的构建不太容易。好的 GUI 必须调整很多界面元素并将它们的行为结合起来形成一个能直观工作的整体行为。基于事件的系统通常都是由复杂的交换行为联合起来的回调集合。模块化的交互元素很难创建。

模块化的交互元素

可切换代码就使用了模块化的交互元素。前面,我提到过在可切换系统内有两种主要的交互元素:拖动元素的突出显示和拖动到的目标的突出显示。在代码中,这两个元素的实现是分开的。

本例很好地展示了模块化处理交互性的技巧。正如可切换界面的描述中所提到的,这两个交互性元素常常缠结在一起。突出显示和突出显示的消失都是在一个鼠标操作中发生的,而且它们的发生都对应鼠标输入的不同方面。如果这两个元素是在一个代码片段中实现的,那么代码可能不太容易读懂,因为同时发生的事情很多。

拖动处理程序

为了使 GUI 的实现模块化,我使用了拖动处理程序。这类似于内置在 GUI 系统的事件处理程序。虽然事件处理程序只处理某种单一事件,拖动处理程序却可以处理整个拖放过程。一个拖动处理程序可处理一系列事件而不只一个单一事件。下面是拖动处理程序的示例骨架,如清单 4 所示。


清单 4. 拖动处理程序的骨架
    
{
    start:
      function( x, y ) {
        // ...
      },

    move:
      function( x, y ) {
        // ...
      },

    done:
      function() {
        // ...
      },
  }
 


这个拖动处理程序是一个对象,具有三个方法:start、move 和 done。当初始化一个拖放动作时,调用 start 方法并传递给这次单击的对应坐标。当四处移动光标时,会反复调用 move 方法,然后同样被传递给光标当前对应的坐标。最后,当鼠标按钮释放后,就会调用 done 方法。

可切换系统同时使用了两个不同的拖动处理程序,这也让您能够干净地处理交互的两个不同方面,即便这两个方面具有复杂的关系。让其中的一个交互成为另一个交互的一部分并不合适。相反,应该能同时无缝地使用这两个交互。

rectangle_drag_handler

这两个拖放处理程序的其中是 rectangle_drag_handler。此处理程序负责移动代表被拖动元素的透明矩形。清单 5 给出了这个 start 方法。


清单 5. rectangle_drag_handler 处理程序
    
function rectangle_drag_handler( target )
  {
    this.start = function( x, y ) {
      this.cover = coveringDiv( target );
      make_translucent( this.cover, .6 );
      this.cover.style.backgroundColor = "#777";
      dea( this.cover );

      this.dragger = new dragger( this.cover, x, y );
    };
    // ...
  }
 


start 方法创建这个透明矩形并将其传递给另一个称为 dragger 的对象。一个 dragger 就是一个对象,它能对应移动的光标移动 DOM 元素。可以将当前的光标的坐标传递给这个 dragger,它会更新所拖动的对象使其跟随光标的移动。

move 方法更新这个 dragger,如清单 6 所示。


清单 6. 更新 dragger
    
this.move = function( x, y ) {
    this.dragger.update( x, y );
  };
 


最后,done 方法(参见清单 7)删除这个透明矩形,因为拖放过程现在已经结束。


清单 7. rectangle_drag_handler 的 done 方法
    
this.move = function( x, y ) {
    this.done = function() {
      this.cover.parentNode.removeChild( this.cover );
    };
}
 


组合拖动处理程序

现在必须找到一种方法来同时使用这两个拖动处理程序。这可以通过 compose_drag_handlers() 函数轻松实现,该函数接受这两个拖动处理程序并将其结合成一个综合的拖动处理程序。这个综合拖动处理程序的使用与一般的拖动处理程序一样。这样,这两个原始的拖动处理程序的行为就实现了无缝结合。

compose_drag_handlers() 函数很容易编写。它看上去很像是一个拖动处理程序,但每个方法都会调用这两个原始拖动处理程序中相应的方法。这个函数如清单 8 所示。


清单 8. compose_drag_handlers()
    
function compose_drag_handlers( a, b )
  {
    return {
    start:
      function( x, y ) {
        a.start( x, y );
        b.start( x, y );
      },

    move:
      function( x, y ) {
        a.move( x, y );
        b.move( x, y );
      },

    done:
      function() {
        a.done();
        b.done();
      },
    }
  }
 


正如您所见,拖动处理程序 a 和 b 被组合到一个综合的拖动处理程序内。如果要调用这个综合处理程序的 start() 方法,实际上就是先后调用 a.start() 和 b.start()。

您需要在名为 prepare_swappable() 的设置函数内调用 compose_drag_handlers,如清单 9 所示。


清单 9. prepare_swappable() 函数
    
function prepare_swappable( o )
  {
    swappables.push( o );
    var sdp = new rectangle_drag_handler( o );
    var hdp = new highlighting_drag_handler( o );
    var both = compose_drag_handlers( sdp, hdp );
    install_drag_handler( o, both );
  }
 


除了其他功能之外,此函数最主要的功能是为可切换元素创建 rectangle_drag_handler 和 highlighting_drag_handler,然后再将它们组合成一个综合的拖动处理程序。最后,这个综合拖动处理程序再通过调用 install_drag_handler() 来激活,这将在接下来的两个小节中详细介绍。

安全安装鼠标处理程序

与常规的事件处理程序不同,一个拖动处理程序可以处理多个事件。尽管如此,它还是需要附加到对象,这与常规的事件处理程序相同。

安装任何一种事件处理程序都是需要技巧的,因为正在修改的元素很可能已经在其内安装了事件处理程序。如果要替换这些事件处理程序,就需要更改页面的行为方式。

为了避免这一问题,可以使用一个名为 install_mouse_handlers() 的实用函数,如清单 10 所示。


清单 10. install_mouse_handlers() 函数
    
function install_mouse_handlers( target, onmouseup, onmousedown, onmousemove )
  {
    var original_handlers = {
      onmouseup: target.onmouseup,
      onmousedown: target.onmousedown,
      onmousemove: target.onmousemove
    };

    target.onmouseup = onmouseup;
    target.onmousedown = onmousedown;
    target.onmousemove = onmousemove;

    return {
      restore: function() {
        target.onmouseup = original_handlers.onmouseup;
        target.onmousedown = original_handlers.onmousedown;
        target.onmousemove = original_handlers.onmousemove;
       }
    };
  }
 


install_mouse_handlers() 函数负责向特定的对象添加特定的鼠标处理程序。它返回的是一个对象,可使用该对象恢复原始的处理程序。这样一来,当拖放过程结束后,就可以调用 restore() 函数,恢复到拖放过程开始之前的状态。

激活拖动处理程序

针对拖放操作的拖动处理程序使用了这三个鼠标处理程序:onmousedown、onmouseup 和 onmousemove。不过,开始时,只需安装 mousedown 处理程序,因为此时您尚在等待激发初始化拖放过程的单击。

当单击发生时,就需要安装 mousemove 和 mouseup 处理程序。而且,在此时,不再需要 mousedown 处理程序,因为已经进行单击。该处理程序将被删除,在拖放过程完成后再恢复它。最初的 mousedown 处理程序如清单 11 所示。


清单 11. 最初的 mousedown 处理程序
    
var onmousedown = function( e ) {
    var x = e.clientX;
    var y = e.clientY;

    p.start( x, y );

    var target_handler_restorer = null;
    var document_handler_restorer = null;

    var onmousemove = function( e ) {
      var x = e.clientX;
      var y = e.clientY;

      p.move( x, y );
    };

    var onmouseup = function( e ) {
      p.done();
      target_handler_restorer.restore();
      document_handler_restorer.restore();
    };

    target_handler_restorer =
      install_mouse_handlers( target, onmouseup, null, onmousemove );
    document_handler_restorer =
      install_mouse_handlers( document, onmouseup, null, onmousemove );

    e.stopPropagation();

    return false;
  };
 


在初始化拖放序列并调用此处理程序时,它会创建 onmousemove 和 onmouseup 处理程序并能在目标元素内安装它们。当然,它还会使用一个 install_mouse_handlers(),以便以后的卸载。

还有一点需要注意:是在 document 对象内安装这些处理程序的。这一点十分关键,因为在拖放过程中用户可能会将光标拖过整个页面。如果鼠标超出可切换元素的范围 — 您很可能还想收到这些事件。同样地,可以使用 install_mouse_handlers() 以便以后恢复它们。

 

 


 回页首
 

 

将它们组合起来

至此,我已经介绍了很多不同的类和函数。其中的每一个类或函数本身都十分简单,因此更重要的是要了解它们是如何协同工作的。

下面是对整个拖放过程的一个总结:

单击一个可切换元素。
此元素的 onmousedown 处理程序将被调用,它安装 onmousemove 和 onmouseup 处理程序。
移动鼠标,这些处理程序将被调用。
这些处理程序反过来调用前面安装的拖动处理程序。
这个拖动处理程序实际上是一个复合拖动处理程序,综合了两个不同的拖动处理程序的效果。
其中的一个拖动处理程序 rectangle_drag_handler 负责向光标附加一个代表被拖动元素的透明矩形。
另一个拖动处理程序 highlighting_drag_handler 负责突出显示鼠标移过的那些可切换元素,以显示可以进行元素拖动的地方。
当在目标元素之上释放鼠标按钮时,highlighting_drag_handler 的 done() 方法就会切换这两个元素。这个拖动处理程序将被卸载,只留下最初的 onmousedown 处理程序,准备好开始下一轮的拖放过程。


 


 回页首
 

 

结束语

拖放操作相对简单,但它涉及了几个交互过程,用来跟踪整个过程的用户输入和提供即时反馈。本文展示如何将模块化的交互元素组合成统一整体来构建完整的 GUI。

这种做法有很多好处。由于代码是模块化的,因此更容易编写和维护。所需的函数和类的代码没有一个是超过 40 行的,并且它们通常更短。

每一个交互元素都会实现一个典型的 GUI 过程或效果,所以可在其他上下文中重用它们。可以开发这些交互元素的丰富的库,从而通过组合各个部分构建更复杂的 UI。


 

本文作者:Greg Travis 来源:本站原创
CIO之家 www.ciozj.com 微信公众号:imciow
   
免责声明:本站转载此文章旨在分享信息,不代表对其内容的完全认同。文章来源已尽可能注明,若涉及版权问题,请及时与我们联系,我们将积极配合处理。同时,我们无法对文章内容的真实性、准确性及完整性进行完全保证,对于因文章内容而产生的任何后果,本账号不承担法律责任。转载仅出于传播目的,读者应自行对内容进行核实与判断。请谨慎参考文章信息,一切责任由读者自行承担。
延伸阅读