关于前端选择器设计原理与实现
1 选择器的概念
选择器其实是一种较小数据源,供我们选择参数值使用,同时提供参数让选择器回显。选择器的表现形式多种多样,可以是模态弹出框,也可以是非模态扁平化弹出层。如我们常见的下拉选择器,下拉树形选择器
地址选择器。表现形式多种多样
2 选择器形式分类
既然选择器是提供一个数据源被用户选择,和回显用户选择的数据项,那么我们可以知道以下几点:
第一:被选中的数据结构,他可以是简单的数据类型,string,bool,或者number,也可以是复杂的array和object类型。对于array无外乎就是多选,object则为我们选中的对象
第二:复杂类型对象,对象的关系一般来说可以有简单对象,即独立性的存在,再有与其他对象组成一个array成为对象数组,或者他是一种树形结构,又或者他有前置条件,也就是说他依赖其他对象
第三:复杂选择器,复杂选择器是说我们选择的对象他是多个对象,依赖关系般存在,比如说地址选择器,他的第一级依赖是省级只有选择了省级才能选择市级,以此内推,呈现出一种前后依赖关系
3 选择器设计
我们并不关注选择器选择了什么具体数据结构,我们只知道我们的选择器统一风格,他需要回显什么对象,选择完成后返回了什么结果就可以了。至于选择器内部诸如列表,树形,前后依赖,查询过滤等等那都是由选择器内部完成。
4 选择器与表单
4.1 表单
我们知道选择器其实绝大多数与表单一起使用而存在,表单又需要具有输入数据的验证性,但是表单原始支持验证的控件只有那么几种,且不可以自定义,比如表单元素有input,select等等。
所以我们的选择器本身仅仅是选择数据源,自然的不能参与到表单内部中去。
4.2 与表单的关系
与表单的关系其实是与表单元素的关系,选择器仅仅是为表单元素 如input提供选择结果,而不参与结果的验证,这点必须要明白,那么有人估计会有疑问,如果我的选择器表现为比如为星级评价,可能就放了5颗星让你选择就是了,那么怎么验证用户没选呢?嗯是的,那么这个时候就要采取迂回的办法,那就是我们隐藏被验证的表单元素,比如我们使用一个input hidden来接收我们星级选择器的结果,那么自然的我们还是按原有表单验证的思路验证即可,两者之间本身是分离的。
5 基于angular的选择器类设计
我们可以封装一个基类
1 /** 2 * Created by Administrator on 2017-07-21. 3 */ 4 import {Component, Input, Output, EventEmitter,AfterViewInit,OnDestroy,HostListener} from '@angular/core'; 5 import {Guid} from "../../utils/guid.util"; 6 7 8 9 @Component({ 10 selector: 'ba-popover', 11 templateUrl: './baPopover.html', 12 }) 13 export class BaPopover implements AfterViewInit,OnDestroy{ 14 15 16 /** 17 * 窗口唯一id标识 18 * */ 19 @Input() myid:string; 20 21 /** 22 * 鼠标点击内容区域是否自动关闭 23 * */ 24 @Input() autoClose:boolean=true; 25 26 /** 27 * 鼠是否关闭指示器 28 * */ 29 isClose:boolean=false; 30 31 /** 32 * 关闭事件 33 * */ 34 @Output() onClosed = new EventEmitter<any>(); 35 36 /** 37 * 选择完成事件 38 * */ 39 @Output() onSelectCompleted=new EventEmitter<any>(); 40 41 /** 42 * 视图初始化完成事件 43 * */ 44 @Output() onAfterViewInit = new EventEmitter(); 45 46 47 48 /** 49 * 构造函数 50 * */ 51 constructor(){ 52 this.myid=Guid.newGuid(); 53 } 54 55 /** 56 * 重写视图初始化完成 57 * */ 58 ngAfterViewInit(): void{ 59 this.onAfterViewInit.emit(); 60 } 61 62 /** 63 * 重写窗口被回收 64 * */ 65 ngOnDestroy():void{ 66 67 } 68 /** 69 * 选择完成 70 * */ 71 selectComplete():void{ 72 73 } 74 /** 75 * 窗口显示 76 * */ 77 public show():void{ 78 this.isClose=true; 79 } 80 81 /** 82 * 窗口关闭 83 * */ 84 public close():void{ 85 this.isClose=!this.isClose; 86 this.onClosed.emit(null); 87 } 88 89 /** 90 * 响应单击事件 91 * */ 92 @HostListener('click') onClick(){ 93 if(this.autoClose) 94 { 95 this.isClose=!this.isClose; 96 } 97 } 98 99 100 101 }
对于子类来说我们仅仅需要关注我们选择了什么进而触发选择完成事件即可,然而有些遗憾的是在angular中对于html模板无法实现继承,所以只好每个模板都套用一个bootstrap的下拉选择容器
1 <div class="dropdown-menu" [ngStyle]="{'display':isClose?'none':''}"> 2 <button class="dropdown-item" href>February 2017</button> 3 <a class="dropdown-item" href>March 2017</a> 4 <a class="dropdown-item" href>April 2017</a> 5 <button (click)="selectChange()" style="width: 400px;height: 200px;">Create danger alert</button> 6 </div>
如何弹出选择器,由于bootstrap提供的下拉菜单,在弹出后什么时候关闭没有接口进行操作,所以采用绑定方式动态显示弹出层,自然也就是上面这段html代码了。你们如何弹出来呢,弹出其实就是将html代码由
隐藏变为显示,我们在需要弹出的地方使用指令来为触发选择器出现的控件添加事件
1 /** 2 * Created by Administrator on 2017-07-27. 3 */ 4 import {Directive, HostBinding,ElementRef,HostListener} from '@angular/core'; 5 import {GlobalState} from "../../../global.state"; 6 7 @Directive({ 8 selector: '[baPop]' 9 }) 10 export class BaPop { 11 12 constructor(private elementRef:ElementRef) { 13 } 14 15 public ngOnInit():void { 16 } 17 18 @HostListener('click') onClick() { 19 var navEle:any=$(this.elementRef.nativeElement); 20 var menus:any=navEle.parent().find(".dropdown-menu"); 21 if(menus.length>0){ 22 var menu=$(menus[0]); 23 menu.css('display','block'); 24 } 25 } 26 27 28 }
自定义一个选择器
1 /** 2 * Created by Administrator on 2017-07-16. 3 */ 4 import {Component, OnInit} from "@angular/core"; 5 import {ActivatedRoute, Router} from "@angular/router"; 6 import {FormArray, FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms"; 7 import {UserDemoInfo} from "../../userdemo.model"; 8 import {UserDemoService} from "../../userdemo.service"; 9 import {BaPopover} from "../../../../theme/components/baPopover/baPopover.component"; 10 11 @Component({ 12 selector: 'mypop', 13 templateUrl: './my.pop.html' 14 }) 15 export class MyPop extends BaPopover{ 16 17 constructor(){ 18 super(); 19 this.autoClose=false; 20 } 21 22 selectChange():void{ 23 this.onSelectCompleted.emit(null); 24 this.close(); 25 } 26 27 }
1 <div class="form-group row" [class.has-danger]="formModel.get('account').touched && (formModel.hasError('minlength','account') || formModel.hasError('required','account'))"> 2 <label class="col-form-label m-r-10">账号</label> 3 <div class="dropdown pull-left m-r-10 hidden-sm-down"> 4 <input class="dropdown-toggle" 5 placeholder="碳储存" value="哈哈哈" baPop 6 data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> 7 <mypop></mypop> 8 </div> 9 </div>