通用Combobox(Select)以至一般控件的设计与实现

 文涉及到combobox设计与实现上的一些方法论,并提供了一个参考实现.

Combobox(以下简称combox),也就是通常的意义上的Select控件,最具代表的是html里的select元素,
它可以在有限的空间内提供多个选项给用户选择,这个与菜单十分相似,并作为表单元素将数据提交到服务器.
种种原因使得有时有必要自定义一个combox控件,来代替自带的select,来分析一下html的select有什么基本功能.

>与form集成,字段可提交

>控件禁用或禁用单个选项

>选择,可以指定选择任意项或不选

>键盘导航
支持键盘上下键导航选项,ENTER,ESC键关闭下拉并响应

>滚动
如果列表项过多而下拉空间有限,就应该出现滚动条,
并且在用户选择选项过程中,选项要相应的滚动到可视范围内

>下拉框下拉时定位到可视范围内

>下拉框上下文切换时自动消失
即点击combox外时下拉框自动消失

如果是自定义的combox控件,可扩展功能有

>可编辑或不可编辑

>控件自定事件

>ajax加载
根据响应动态加载项

>过滤
用户在输入过程中过滤条项并自动定位

>定制下拉框
下拉框弹出的内容是可定制的,根据实现需要定制,如一个列表,一颗树,一个表格等

>在定制下拉框后combox的基本行为保持不应
自定下拉框后不能改变控件应用的基本行为,如选择,键导航等

>多样式支持,如控件的hover,点击,下拉时都有不同外观效果,下拉框具有阴影等

如果自定义combox控件,以上的基本功能是要实现的,不同方式有不同实现,越灵活就越不简单,.
现在考虑一个在封闭式环境下的实现,
这所谓的封闭环境是指为特定的需求应用而实现,代码考虑不重用,在响应过程中不与外界进行通讯,功能与控件完全一体化,具有很强的针对性实现,
这种实现较为简单,根据需求可部分实现select功能,有时为了设计与实现上的便利,用到多种的hack.
例如在HTML模板上的设计,可将下拉框的HTML组织在控制自身里,
而不是将下拉框的加在BODY中,这种设计简单明了,下拉定位方便,但也造成下拉框不够通用,在样式控制方面也要一定的技巧.
这类封闭环境下的产物常见有的一些自动完成控件,拿来就可以用.

下面来考虑一个通用的,可扩的设计方案
这里所谓通用,是指一个集成的环境里面,不同应用是共享的,代码是可重用的,来对上面combox所有功能分析一下,给控件解耦,提取出一些独立的功能,并给出一个与combox无关的控件设计模型.

一个控件模型可由以下一个或多个模型的组合而成
1.事件模型(event model)
具有事件模型的控件有添加事件监听器,发送,和移除事件的能力.
给控件建立事件模型是很有意义的,外部可监听控件一些感兴趣的事件并利用事件进行通讯,如MVC模型的实现多是基于事件(消息)的驱动.
实现了事件模型的控件具有可扩性,因为事件可有多个监听器,或者说通常意义上的回调,这个当然与单个回调效果不一样了.
有必要说说HTML事件模型与自定的不同,HTML受限于HTML结点而不是控件的事件,并且外界并不能向结点发送事件.

2.选择模型(selection model)
具有选择模型的控件具有单选,多选,并给合键盘导航选择等功能.

3.加载模型(loading model)
加载模型使得控件有自动Ajax加载并组装数据的能力

4.装饰模型(decoration model)
装饰模型与前面的几个模型不同,前面的通常是控件自身就支持,或是控件组成的不可或缺部分,而装饰模型不是控件自身就有的,它显得可有可无,只起来装饰效果,
而装饰时充分利用了其它的几个模型,例如在加载的时候给控件装饰一个”加载中”图标,利用事件模型作监听,利用加载模型加载前触发,在加载后移除效果.

此外不同应用还可独立出不同的模型,这里就不多说了.

通用的控件建好模型后,利用这些设计一个通用而功能齐全的combox控件.
combox控件根据视图划分为触发部分和下拉部分,其中下拉部分作为一个外部控件引入.
为了支持下拉的灵活实现,并且要求不同实现要具有一致的行为,下拉部分这控件必须支持上面所说的几个模型.
这样一来,Combobox就具有了大部分功能,并且行为始终保持一致.

实现参考:

http://www.bgscript.com/bgjs/samples/combo.html

讲一下实现参考例子里的内容:
里面的combox的下拉控件都实现了以上几种模型,
下拉框作为selector引入到combox,
selector在里面的分别是组控件,树型控件和小视图分组控件,
它们在combox中都表现出一致的行为,不加修改就可放到combox中.
下拉时出现loading图标和树型控件的loading图标是装饰模型实现后引起的效果,
它监听由加载模型触发的open,final事件并作出相应的处理.

 

代码:

<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 <title>CCombox</title>
  <script type="text/javascript" src="../bglib.js"></script>
  <script type="text/javascript" src="../ui/shadow.js"></script>
  <script type="text/javascript" src="../ui/container.js"></script>
  <script type="text/javascript" src="../ui/loading.js"></script>
  <script type="text/javascript" src="../ui/foldable.js"></script>
  <script type="text/javascript" src="../more/folderview.js"></script>
  <script type="text/javascript" src="../ui/group.js"></script>
  <script type="text/javascript" src="../ui/form.js"></script>
  <script type="text/javascript" src="../ui/combo.js"></script>
  <script type="text/javascript">CTemplate.BLANK_IMG = '../default/s.gif';</script>
  <script type="text/javascript" src="../ui/tree.js"></script>
  <link rel="stylesheet" href="../default/global.css" type="text/css"/>
  <link rel="stylesheet" href="../default/ru_share.css" type="text/css"/>
  <link rel="stylesheet" href="resources/example.css" type="text/css"/>
 
<style>
 .g-tree .g-panel-body, .g-folderview-wrap{border:1px solid #CCC;border-top:0;}
 .g-folderview-ctx dd{margin:5px;}
</style>
</head>
<body lang="zh">
 <p>
 下拉框作为selector引入到combox,<br/>
 selector在里面的分别是组控件,树型控件和小视图分组控件,<br/>
 它们在combox中都表现出一致的行为,不加修改就可放到combox中.<br/>
 下拉时出现loading图标和树型控件的loading图标是装饰模型实现后引起的效果,<br/>
 它监听由加载模型触发的open,final事件并作出相应的处理.<br/>
 在firebug下可察看事件
 </p>
 <div style="margin:20px 30px;position:relative;">
 <h2>带滚动条,默认下拉,可编辑</h2>
 <div id="def_combo"></div>
 
 <h2>随内容伸缩,下拉,可编辑</h2>
 
 <div id="auto_combo"></div>
 
 <h2>禁用</h2>
 <div id="disable_combo"></div>
 <h2>不可编辑</h2>
 <div id="unedit_combo"></div>

 <h2>自定下拉控件和过滤方式</h2>
 <div id="tree_combo"></div>
 
 <h2>自定下拉控件和过滤方式 + 滚动 + Ajax动态加载</h2>
 <div id="tree_ajax_combo"></div>
 
 <h2>只要基于CSelectedContainer容器类的控件都可以</h2>
 <div id="other_combo"></div>
 </div>
</body>
</html>
<script>
  Event.ready(function(){
   //
   // 默认下拉
   //
   var cb = new CCombox({showTo:'def_combo', autoRender:true,width:300,array:[
    {title:'粉绿色',icon:'icoIbx'},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoIbx'},
    {title:'清除记录',icon:'icoDel' ,disabled:true},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoNote'},
    {title:'清除记录',icon:'icoDel'},
    {title:'粉绿色',icon:'icoIbx'},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoDft'},
    {title:'清除记录',icon:'icoDel' ,disabled:true},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoNote'},
    {title:'清除记录',icon:'icoDel'}
   ]});
   
   cb.select(2);
   cb.setScrollorHeight(150);
   cb = new CCombox({showTo:'auto_combo', autoRender:true,width:300,array:[
    {title:'粉绿色',icon:'icoIbx'},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoIbx'},
    {title:'清除记录',icon:'icoDel' ,disabled:true},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoNote'},
    {title:'清除记录',icon:'icoDel'},
    {title:'粉绿色',icon:'icoIbx'},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoDft'},
    {title:'清除记录',icon:'icoDel' ,disabled:true},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoNote'},
    {title:'清除记录',icon:'icoDel'}
   ]});
   
   //不可编辑
   cb = new CCombox({showTo:'unedit_combo', autoRender:true,uneditable:true, width:300,array:[
    {title:'粉绿色',icon:'icoIbx'},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoIbx'},
    {title:'清除记录',icon:'icoDel' ,disabled:true},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoNote'},
    {title:'清除记录',icon:'icoDel'},
    {title:'粉绿色',icon:'icoIbx'},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoDft'},
    {title:'清除记录',icon:'icoDel' ,disabled:true},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoNote'},
    {title:'清除记录',icon:'icoDel'}
   ]});
   
   //禁用
   cb = new CCombox({showTo:'disable_combo', disabled: true, autoRender:true,uneditable:true, width:300});
   
      // ----------------------------------
      // 下拉为树 
      //自定义过滤
   function treeFilter(matcher, caller){
     var caller = caller || window, fn = arguments.callee;
     if(this.children){
      var cm = true;
         CC.each(this.children, (function(){
             if(!matcher.call(caller,this) && !this.nodes){
                 this.display(0);
                 cm = false;
                 return;
             }
             this.display(1);
             if(this.nodes && this.expanded)
              fn.call(this, matcher, caller);
         }));
       }
   }
      
  var tree = new CTree({title:'tree', showTo:document.body, hidden:true,filter:treeFilter});
   tree.root.fromArray([
    {title:'disabled item',nodes:true,disabled:true},
    {title:'B'},
    {title:'C',nodes:true}
   ]);
   
   tree.root.$(2).fromArray([
    {title:'disabled A',nodes:true,disabled:true},
    {title:'B',disabled:true},
    {title:'items',nodes:true}
   ]);
   tree.render();
    //指定selector
   cb = new CCombox({showTo:'tree_combo', selector:tree, autoRender:true,width:350});
   
   tree = new CTree({title:'tree2', url:'/q?bg_q=test_group', autoConnect:true, showTo:document.body, hidden:true,filter:treeFilter});
   tree.root.fromArray([
     {title:'Back Light 点击展开', nodes:true},
     {title:'BCDEFJDFL;AJSDL'},
    {title:'disabled item',nodes:true,disabled:true},
    {title:'BCDEFJDFL;AJSDL'},
    {title:'C',nodes:true},
    {title:'item something',disabled:true},
    {title:'BCD人物事件'},
    {title:'CDEFASLDFJ', nodes:true},
    {title:'disabled item',nodes:true,disabled:true},
    {title:'AAAAAAAAAAAAAA'},
    {title:'WEB前端'}
   ]);
   
   tree.root.$(2).fromArray([
    {title:'disabled A',nodes:true,disabled:true},
    {title:'B',disabled:true},
    {title:'items',nodes:true}
   ]);
   tree.render();
   tree.root.expand();
    //指定selector
   cb = new CCombox({showTo:'tree_ajax_combo',  autoRender:true,selector:tree, width:350});
   cb.setScrollorHeight(150);
   //
   // 其它基于CSelectedContainer容器的控件
   //
   var foldview = new CFolderView({
    showTo:document.body,
       navKeyEvent : true,
       hidden:true,
       shadow:true,
       title:'请选择其中一项',
       autoRender:true
   });
   //指定selector
   cb = new CCombox({showTo:'other_combo', selector:foldview,  autoRender:true,width:320});
   cb.selector.on('comboxshow', function(){
    //content loaded
    if(this.loaded){
     return;
    }
    //方便显示loading..
    this.setHeight(100);
    this.on('load',(function(){
     this.style('height','');
     this.height = false;
    }));
    
    this.on('final',(function(){
     //更新数量
     this.setTitle(this.title);
    }));
    
    this.connect('http://www.bgscript.com/q?bg_q=foldview_data');
   });
  });
</script>

  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值