zoukankan      html  css  js  c++  java
  • ZLComboBox自定义控件开发详解

    【引言】距离上一回写博客已经有一些时日了,之前的爱莲iLinkIT系列主要是讲解了如何用NodeJS来实现一个简单的“文件传送”软件,属于JavaScript中在服务器端的应用。

    今天,我们就回归到JavaScript的主战场 ---- 前端设计,一起来聊聊如何开发一个“自定义”的Web控件(基于jQuery),属于基础级别,高手请轻拍,还望不吝赐教,先谢过。

    我们先假设这样一个场景,BOSS下达了命令,要求开发一款“幼儿早教”的应用,方便小朋友进行选择操作,而你则负责设计一个“下拉框”的Web控件,方便团队中其他弟兄使用,设计师的MM已经把效果图设计好了,如下:

    总的来说,这是一个类似“下拉框”的Web控件:

    a. 用户单击控件之后,显示下拉列表。

    b. 选中下拉列表中的某项(例如:桃子),会将选中项的图片显示出来。

    接下来,就让我们一步一步来实现这样的功能,我们先对整个讲解过程做一个整体的规划,以便我们心中有谱:

    1. 实现控件的外观部分:包括HTML代码的组织,CSS样式设计。

    2. 实现控件的基本框架:用JavaScript实现控件的交互设计。

    3. 优化控件的可扩展性:让控件可以接受JavaScript回调函数。

    4. 优化控件的可用性:可以动态传入选项的数据,达到自定义选项列表的目的。

    最后,我们来做一个简单的回顾和总结。单击此处下载演示包,解压缩后打开index.html页面体验。

    实现控件的外观部分

    我们先来分析一下整个控件的几个组成部分,然后组织我们的HTML代码,为了后面设计CSS样式表的方便,在组织HTML代码时,就应该遵照相关规范(例如:公司的样式表命名规范),完成class的名称设计。

    控件的组成

    控件的最外层是一个大的容器,包含控件的各个组成部分,其中的组件包括:
    1. 结果显示框

    可以将选中的选项显示在结果显示框中。

    2. 下拉框按钮

    控制下拉框的显示与隐藏,单击按钮,显示下拉列表,再次单击时,隐藏下拉框,不同的状态下,箭头的方向不一样。

    3. 下拉框

    下拉框中包含多个可供用户选择的“项”。

    4. 下拉框中的项

    选项包含图片和文字两个部分,当前被选中的选项,应该呈现不同的外观。 

    HTML代码

    根据前面的组成分析,我们可以实现如下的HTML代码(index.html):

     1 <div class="zl_combobox">
     2 <div class="cb_input"><img src="images/f_apple.png" /><span>苹果</span></div>
     3 <span class="cb_btn"></span>
     4         <ul class="item_list">
     5                 <li><img src="images/f_apple.png" /><span>苹果</span></li>
     6                 <li><img src="images/f_lemon.png" /><span>柠檬</span></li>
     7                 <li><img src="images/f_peach.png" /><span>桃子</span></li>
     8                 <li><img src="images/f_watermelon.png" /><span>西瓜</span></li>
     9         </ul>
    10 </div>

    其中:

    • zl_combobox:代表整个Web控件。
    • cb_input:结果显示框。
    • cb_btn: 下拉框按钮。
    • item_list:下拉框。下拉框中的“项”用<li>元素来表示。

    CSS样式表设计

    接下来我们要根据控件最终的效果图和前面设计的HTML代码,来完成样式表的设计。在设计样式表时,一般分为两个部分:

    • 基础外观:包括各个子部件的部件、关联关系、呈现外观等。
    • 状态样式:要考虑到控件在交互过程中的状态变化,通过‘开关’来实现不同状态下的外观,方便后续的JavaScript的控制。

    基础外观的相关CSS代码(zlbox.css)如下:

     1 .zl_combobox{
     2     position:relative;
     3     width:180px;
     4     font-size:1.5em;
     5     margin-left:5px;
     6     color:#323232;
     7 }
     8 .zl_combobox .cb_input{
     9     width:170px;
    10     height:60px;
    11     padding:0px 5px;
    12     line-height:60px;
    13     border:1px #A9A9A9 solid;
    14     background-color:#F2F2F2;
    15     cursor:pointer;
    16 }
    17 .zl_combobox .cb_input:after,
    18 .zl_combobox .item_list li:after{
    19     content:'';
    20     clear:both;
    21     display:table;
    22 }
    23 .zl_combobox .cb_input span,
    24 .zl_combobox .item_list li span{
    25     display:block;
    26     padding-left:10px;
    27     height:60px;
    28     line-height:60px;
    29 }
    30 .zl_combobox .cb_input img,
    31 .zl_combobox .item_list li img{
    32     display:block;
    33     width:50px;
    34     height:50px;
    35 }
    36 .zl_combobox .cb_input>img,
    37 .zl_combobox .cb_input>span,
    38 .zl_combobox .item_list li>img,
    39 .zl_combobox .item_list li>span
    40 {
    41     float:left;
    42 }
    43 .zl_combobox .cb_btn{
    44     position:absolute;
    45     right:0px;
    46     top:1px;
    47     width:60px;
    48     height:60px;
    49     background:url('img/cb_btn_down.png') no-repeat center center;
    50     cursor:pointer;
    51 }
    52 .zl_combobox .item_list{
    53     display:none;
    54     position:absolute;
    55     right:0px;
    56     top:62px;
    57     width:180px;
    58     line-height:60px;
    59     background-color:#FEFEFE;
    60     z-index:999;
    61 }
    62 .zl_combobox .item_list li{
    63     padding-left:10px;
    64     border-bottom:2px #CCCCCC dotted;
    65     cursor:pointer;
    66 }

    因为本文重点在于介绍Web控件的设计过程,关于CSS设计的细节,就不展开细讲,如有疑问,咱们在评论中交流讨论。

    那么,整个控件在交互过程中,会有哪些状态需要考虑呢?

    1. 默认的状态:不显示下拉框,仅仅显示“结果显示框”和“下拉框按钮”。

    2. 下拉框展开状态:显示下拉框,并且下拉框按钮的箭头变为向上。

    3. 选项的鼠标悬停状态:当鼠标悬停到选项时,改变不同的样式。

    (当然,如果你确定你的控件只是在手机/Pad上使用,可以不考虑鼠标悬停)。

    4. 被选中的选项的状态:这个可以根据需要设计。

    状态相关的样式的CSS代码(zlbox.css)如下:

    1 .zl_combobox.selected .item_list{
    2     display:block;
    3 }
    4 .zl_combobox.selected .cb_btn{
    5     background:url('img/cb_btn_up.png') no-repeat center center;
    6 }
    7 .zl_combobox .item_list li:hover{
    8     color:#5281F8;
    9 }

    注意到,我们通过判断控件主体.zl_combobox是否有selected的class来控制下拉框的显示状态。

    至此,控件的外观部分设计已经完成,我们可以看到,Web控件的外观设计有以下几个特点:

    a. 布局属性:Web控件可能在各种场合中使用,所以,我们一般不对控件主体的布局属性进行设置(举例中我们仅仅设置了margin-left:5px,约定控件在使用过程中,距离它前面的控件为5px)。

    b. 基础属性:像字体大小、字体颜色这样基础属性,在控件主体中尽量设置一下,确保控件的风格可以得到保证。比如:常规情况下,背景色是白色的,而字体的颜色是黑色,这时候,黑色的字体在白色背景的下拉框中可以显示出来。假设你没有设置控件主体的字体颜色,而在某个使用环境中,背景变成了黑色,字体颜色设置为白色,由于样式具有继承的特性,这时候就会变成在白色下拉框中的字体颜色是白色的。所以,在控件主体中设置基础属性,可以有效避免外部环境的“入侵”,让控件的风格自成一体。

    实现控件的基本框架

    工程文件组织

    尽管前面我们已经讲解了控件的外观设计,也写了相关的HTML代码和CSS代码,但其实有一个本来应该先做的事情没有完成,就是工程文件组织,如下所示:

    控件主文件夹的说明如下:

    • css:保存项目用到的CSS相关的文件。
    • images:保存需要在项目中直接引用的图片资源。
    • js:保存项目用到的JavaScript相关的文件。
    • index.html:HTML主体文件。

    CSS文件夹下的内容:

    • img:保存在css样式表中要引用的背景图片资源。
    • index.css:与工程相关的样式表,比如:页面布局等。
    • reset.css:一个重置样式表,将HTML中元素的标准特性重置。
    • zlbox.css:与控件相关的样式表。

    JS文件夹下的内容:

    • jquery.zlbox.js:与控件相关的JavaScript文件,因为我们用到了jQuery库,所以以jquery为前缀。
    • jquery.1.7.1.js:jQuery库文件。

    本来应该还有一个index.js,用于保存工程相关的js代码,考虑到我们的演示demo的内容比较简单,工程相关的js代码调用就直接放到index.html文件中。

    关于工程文件的组织和命名,各个公司应该有各自的规范标准,实际使用过程中,请遵照公司的规范。本文的论述将按以上的组织来进行。

    JavaScript代码设计

    总算到了我们Web控件设计的核心环节,我们的控件是基于jQuery库来设计的。jQuery插件的设计方法有很多种,我们依据我们的业务特点,选用了下面的基本框架模式,核心代码如下:

     1 $.fn.czl_combobox = function( options )
     2     {
     3        this.each( function()  
     4        {
     5           var instance = $.data( this , 'czl_combobox' );
     6           if( !instance )
     7           {
     8             $.data( this, 'czl_combobox' , new $.ZLComboBox( options , this ) );
     9           }
    10           
    11        });//end of each
    12        
    13        return this;  
    14 };
    15 $.ZLComboBox = function( options , element )
    16 {    
    17     this.$el= $( element ); 
    18     this._init( options );
    19 };
    20 $.ZLComboBox.defaults = {
    21     
    22 };
    23 $.ZLComboBox.prototype = {   
    24 _init : function( options ) {
    25    //初始化函数
    26 },
    27 _loadEvents:function( ){
    28   //控件相关的事件注册
    29 }
    30   };

    我们先来看一下调用这个Web控件的代码,然后我们对照起来分析:

    $('.zl_combobox').czl_combobox( {} );

    让前端的HTML元素和后端的JavaScript代码关联起来,czl_combobox 这个函数是关键,所以,我们就从$.fn.czl_combobox这个函数入手,来看看它到底是如何关联的,先看代码:

     1 $.fn.czl_combobox = function( options )
     2  {
     3        this.each( function()  
     4        {
     5           var instance = $.data( this , 'czl_combobox' );
     6           if( !instance )
     7           {
     8             $.data( this, 'czl_combobox' , new $.ZLComboBox( options , this ) );
     9           }
    10           
    11        });//end of each
    12        
    13        return this;  
    14 };

    1. czl_combobox是$.fn的成员,意味着凡是jQuery对象都可以调用它。

    2. 通过$.data()获取jQuery对象(对应一个HTML元素)的名称为czl_combobox的数据对象。

       如果不存在,那么,就通过$.data()新建一个ZLComboBox 的对象。

       如果已存在,那么,就不重复创建对象。

    3. 创建对象时,把当前jQuery对象和参数options传入。

    经过分析,我们发现$.fn.czl_combobox也仅仅是桥梁,还不是控件的核心,真正的核心是ZLComboBox对象,这个对象可以通过对应的HTML元素访问到。

    现在,我们依据控件的业务特征,先来预估一下ZLComboBox对象能做什么:

    1. 既然将HTML元素对应的DOM对象作为参数传给ZLComboBox对象,那么,ZLComboBox对象就能对相关的HTML元素以及它的子元素进行操作。

    2. 同时将参数options传递给ZLComboBox对象,意味着,可以根据业务需要对ZLComboBox对象进行一些”定制”操作,包括:属性和行为。

    下面我们再来看一下ZLComboBox的核心代码:

    1. 在它的构造函数中,将传入的HTML DOM对象保存到自己的一个属性成员中,并且将参数options传递给自己的一个初始化方法(_init())。

    2. 在它的原型中,定义了_init() 和 _loadEvents()两个方法。

    我们的目的是要让控件能够响应’单击’事件,并且对控件的各个子部件进行状态的改变,所以我们可以这样操作:

    1. 在_init中,通过控件主体的 DOM对象,获得它的各个子部件的jQuery对象。

    2. 在_loadEvents中,对子部件的jQuery对象绑定响应事件。

    3. 增加一个属性selected_index,记录当前选中的项的序号。

    4. 定义一个方法_show_itemlist,用来操作下拉框的显示和隐藏。

    完整的JS代码(zlbox.js)如下,可对照注释进行理解:

     1 (function($){
     2 $.fn.czl_combobox = function( options )
     3     {
     4        this.each( function()  
     5        {
     6           var instance = $.data( this , 'czl_combobox' );
     7           if( !instance )
     8           {
     9             //主体功能通过一个对象实现 
    10             $.data( this, 'czl_combobox' , new $.ZLComboBox( options , this ) );           
    11  }
    12         });//end of each
    13        
    14        return this;  
    15     };
    16     $.ZLComboBox = function( options , element )
    17     {    
    18         this.$el= $( element ); 
    19         this._init( options );
    20     };
    21      
    22     $.ZLComboBox.prototype = {   
    23           _init : function( options )
    24           {
    25             //相关的控件
    26             this.comboBox = this.$el ;
    27             
    28             //相关的HTML控件
    29             this.cb_btn = this.comboBox.children( '.cb_btn' ).eq(0);
    30             this.cb_input = this.comboBox.children( '.cb_input' ).eq(0);
    31             this.cb_item = this.comboBox.find( 'li' );
    32 
    33             //控件的状态,标记是否显示下拉列表
    34             this.cb_showitem_status = false ;     
    35             
    36             //初始化默认的值,默认选中最后一个选项
    37             this.selected_index = this.cb_item.length-1 ;                       
    38             this.cb_input.html( this.cb_item.eq(this.selected_index).html() );  
    39 
    40             //注册响应事件
    41             this._loadEvents();
    42           },
    43          _show_itemlist:function(){
    44                 if( this.cb_showitem_status === false ){
    45                     this.comboBox.addClass( 'selected' );
    46                     this.cb_showitem_status = true;
    47                 }
    48                 else{
    49                     this.comboBox.removeClass( 'selected' );
    50                     this.cb_showitem_status = false;
    51                 }
    52                 return ;
    53          },
    54          _loadEvents:function(){
    55             var _self = this;   
    56             //1_单击下拉箭头
    57             this.cb_btn.on( 'click' , function( event ){ 
    58                 _self._show_itemlist( );
    59                 return ;
    60             });
    61             //2_单击编辑框,也同样进行下拉框的状态切换
    62             this.cb_input.on( 'click' , function( event ){ 
    63                 _self._show_itemlist( );
    64                 return ;
    65             });
    66             //3_单击选项
    67             this.cb_item.on( 'click' , function( event ){ 
    68                 var index = $(this).index();
    69                 if( _self.selected_index !== index ){
    70                     //设置选项的值
    71                     _self.cb_input.html( $(this).html() );
    72                     
    73                     //设置当前选中的项
    74                     _self.selected_index = index ;
    75                     
    76                     //隐藏下拉框
    77                     _self._show_itemlist();
    78                 }
    79                 return ;
    80             });
    81          }
    82 };
    83 }(jQuery));

    插件验证:

    在index.html中,增加对插件调用的代码:

    <script type="text/javascript">
    $('.zl_combobox').czl_combobox( {} );
    </script>

    打开index.html网页,我们发现自定义的控件已经可以响应用户的单击事件,效果如下:

    优化控件的可扩展性

    有了前面的基础,接下来的理解就会比较简单。前面我们在分析控件的调用过程中,有一个参数options传到ZLComboBox对象中,但是,在最后的_init和_loadEvents方法中都没有用到过,那么,这个options的意义在哪里呢?

    我们还是从业务的需求出发,然后再来考虑我们如何实现:

    • 如果将控件用在“幼儿教学”的应用中,用户选中一种水果之后,可以播放与这种水果相关的介绍视频。
    • 如果将控件用在“卖水果”的应用中,用户选中一种水果,可以显示这种水果的价格、产地等信息。

    这就意味着,在不同的应用场景中,用户做出“选择水果”的操作时,触发的后续动作是不一样的,我们的控件应该支持这种操作才对。

    如果在初始化控件的时候,传入一个“回调函数”,当事件触发时,调用一下这个传入的回调函数,那不就达到我们的目的了吗?这样,不同的业务场景,我们只要传入不同的回调函数即可。

    回调函数是JavaScript的最拿手的,现在,我们就来优化我们的控件,让它支持回调函数。

    这里也先假设一个场景:

    当用户选中一个选项之后,我们就将选项中水果的图片显示出来,这时候,调用这个控件的方式变成这样:

    1 $('#fruit_box').czl_combobox( {
    2             selectItemEvent:function( index ){
    3                 var html_content = $( '.item_list li img' ).get( index ).outerHTML;
    4                 $('#result_box').html( html_content );
    5             }
    6 } );

    注意到两点:

    1. #fruit_box 为控件. zl_combobox对应的id,在同一个应用中可能存在多个zl_combobox控件,当要传入回调函数时,一般就用id去引用对应的控件元素,因为相同的控件,在不同的应用场景,行为是不同的,传入的回调函数也应该不同,所以不建议通过$(‘. zl_combobox’)一次性对所有的控件进行初始化。

    2.传入的回调函数的名称是selectItemEvent,带有一个index 的参数。

    现在,我们来优化控件的核心对象ZLComboBox中的内容:

    1. 新增一个options成员,用来保存传入的参数对象:

    _init:function( options ){
         //
         this. options = options ;
    }

    2. 在下拉列表框中的选项单击事件中,增加对回调函数的调用:

           //….
           //隐藏下拉框
        _self._show_itemlist();
        //调用回调函数
        _self.options.selectItemEvent( index );

    然后,我们再来验证一下效果,打开index.html,从下拉框中选中一个选项,效果如下:

    至此,我们最初设定的目的是已经达成了。为了完整性,再补充一下控件“扩展性”的另外一个特征:默认值设定。

    还是以之前的业务场景为例:如果用户没有传入回调函数,我们希望控件就把选中项的序号通过提示框显示出来。如果有传入会调用函数,就调用用户传入的回调函数。也就是说,给控件增加默认的行为,当调用时没有传入指定参数,就调用默认的值

    现在我们就来实现控件的默认值设定:

    1. 给ZLComboBox对象增加defaults成员,代码如下:

    1 //默认值设置
    2     $.ZLComboBox.defaults = {
    3         selectItemEvent:function( index ){
    4             alert( index );
    5             return ;
    6         }
    7     };

    2. 在_init方法中,保存传入的options对象采用如下的方式:

    this.options = $.extend( true , {} , $.ZLComboBox.defaults , options );

    这样,如果options中没有指定相关的成员,就调用defaults中的成员。这是jQuery插件处理传入参数的一种方式,$.extend为jQuery库定义的方法。 

    优化控件的可用性

    从理解语言特性来看,这部分内容与上一部分没有什么差异,我们只是从业务角度来看,对实现方式做一些区分。

    回到我们控件的场景,本文实现的控件我们取名为ZLComboBox,ZL为前缀,显然,它的行为特征与标准的组合框控件是类似的,组合框控件最常见的使用方式就是在表单(Form)中,那么,我们的控件也应该支持在表单(Form)中使用。

    如果ZLComboBox是作为表单的一个组件,那么,就需要在用户提交表单数据时,能够获取到用户到底选了哪个选项。从目前来看,似乎只能判断.cb_input容器中的内容,而这个内容是这个样子:

    <img src="images/f_apple.png" /><span>苹果</span>

    显然,用户处理起来非常不方便。

    也许你已经想到了,可以通过传入回调函数的方式,当用户选中一个选项之后,就将这个序号值(index)更新到某个隐藏的<input>元素中,表单提交数据时,提交这个隐藏的<input>中的值即可,这当然是一种处理方式。

    其实,我们可以给控件的核心对象ZLComboBox增加一个方法getSelectedIndex,用来获取当前用户选中的项的序号,代码如下:

    1 _loadEvents:function(){
    2             //
    3          },
    4          getSelectedIndex:function(){
    5                 return this.selected_index;
    6          }

    这时候,控件的调用方式变成:

    1 $('#fruit_box').czl_combobox( {
    2             selectItemEvent:function( index ){
    3                 var html_content = $( '.item_list li img' ).get( index ).outerHTML;
    4                 $('#result_box').html( html_content );
    5             }
    6 } );
    7 
    8 //在表单提交前的数据处理中,取得用户选中项的序号
    9 var index = $('#fruit_box').data( ‘czl_combobox’ ). getSelectedIndex() ;

    重点在第9行。

    当然,你也许会觉得,这种方式还不如之前动态更新隐藏<input>的方式自然,这里的主要目的是给大家一个特性解释,具体情况当然依据业务需要来确定。

    另外,也许你已经发现了,每次调用我们自定义的控件,在HTML代码中都要写入一大堆内容,能不能在使用时HTML代码中仅仅定义<div class=” zl_combobox”></div>?然后将选项通过参数options传入,在ZLComboBox的_init方法中动态生产子部件呢?答案是:当然可以,就留作练习吧。

    总结

    通过前面的内容介绍,我们大致理解了自定义控件的意义,以及开发一个Web自定义控件的大致过程,希望能给大家带来一些启发。当然,我们的举例,实现方式都仅仅是为了讲解整个过程,在实际的开发过程中,除了业务诉求之外,还需要考虑其他方面的要求,比如性能方面:将整个控件库中CSS样式用到的背景图片优化成"雪碧"图,减少加载时间。或者安全性的优化:将某些插件的框架由'伪类'(new)调整为'闭包'模式,增强控件的安全性。

    完整的示例代码,请单击此处下载。

    感谢诸位捧场^_^~~

  • 相关阅读:
    go语言教程零基础入门到精通
    php探针文件内容
    一篇文章揭穿创业公司的套路
    Google资深工程师深度讲解Go语言面向接口(五)
    完全解析<atlalloc.h>
    巧妙的Section — — 剖析ATL OBJECT_MAP的自动建立
    ATL中的各种CriticalSection
    C++中的INL
    如何剖析一个类
    ATL线程模型解析
  • 原文地址:https://www.cnblogs.com/alai88/p/5281602.html
Copyright © 2011-2022 走看看