自定义Angular指令与jQuery实现的Bootstrap风格数据双向绑定的单选与多选下拉框
先说点闲话,熟悉Angular的猿们会喜欢这个插件的。
00.本末倒置
不得不承认我是一个喜欢本末倒置的人,学生时代就喜欢先把晚交的作业先做,留着马上就要交的作业不做,然后慢悠悠做完不重要的作业,卧槽,XX作业马上要交了,赶紧补补补。如今做这个项目,因为没找到合适的多选下拉Web插件,又不想用html自带的丑陋的<selectmultiple></select>,自己花了一整天时间做了一个。或许这样占用的主要功能开发的时间,开发起来会更有紧迫感吧。感觉自己是个抖M自虐倾向,并且伴有css和代码缩进强迫症的程序猿。
01.画蛇添足
Angular强大的控制器似乎已经可以满足大部分UI上的需求了,但是NodeJS应用往往会使用ejs,jade这样的模板引擎来动态生成html页面,那么问题来了,当我想把后台传给express中res.render()的参数直接显示到界面而且绑定到相应的ng-model怎么办?
解决方法1,不要什么事一次来,Angular的Controller发个post请求再拿数据不就行了
解决方法2,先用模板暂存在html上,再让Controller根据页面上的数据来初始化$scope的值
解决方法3,鄙人对Angular和EJS才疏学浅,谁有好办法教我呗
比如现在要做一个选择下拉框<select>n个<option>xx</option></select>,选项在后台,我不想单独发post拿,也不想放在页面上,Controller单独写逻辑处理,而Angular社区有个ui-select插件,看起来数据是从$scope取的,并不是直接拿的<option/>标签的数据,当时我就火了,不就一个下拉框,自己做呗。
10.乐观的程序猿
思路很明确,定义一个Angulardirective->把选项值拿出来->各种事件加加加->scope数据绑定->完结撒花
我估计的时间是半天,然而实际花了多久只能呵呵了,css强迫症,Angular理解不深(所以很多html操作还是在用jQuery),事件考虑不全导致了最终花了超过两倍的时间做完,
不废话了,简单实用,既可以即时绑定ng-model$scope.xxx,也可以直接调jQuery的$("标签的id").val()也能拿到值,
git传送门duang:https://git.oschina.net/code2life/easy-select.git
demo传送门duang~duang:http://ydxxwb.sinaapp.com/easy-select-demo/ (代码不是最新,有两个fix的bug还没有部署上去)
11.放码
1.使用方法:引入库文件Bootstrap,Angular1.x,引入style.css文件(可以修改css自定义自己想要的样式),easy-select.js,定义Angular的Controller,依赖easySelect模块,像这样↓
angular.module('dataDisplay',['easySelect']).controller('selectController',['$scope','$http',function($scope,$http){ //yourcode}]);
然后参考demo示例的规范定义选择框就行啦,是不是很有html原生select标签的亲切感
2.源码解释:dom操作和事件都是用jQuery实现的,每一步都有简略的注释,实现双向绑定的关键在于取得标签上定义的ng-model,然后在事件中设置scope[ng-model]的值,
并且调用$digest()循环来让Angular根据ng-model更新DOM,$digest是Angular实现双向绑定的核心之一,原理是将变化的scope值同步到所有需要更新的地方,实现暂时还不大明白,有空单独研究一下这些Angular里面$,$$开头的东西。
3.自适应与css,Bootstrap就是自适应的,css可以自己定制不同的风格,style.css都有相关注释
easy-select.js
varcomDirective=angular.module('easySelect',[]);
comDirective.directive("easySelect",function(){
return{
link:function(scope,element,attrs){
varngModel=$(element).attr("ng-model");
if(!ngModel||ngModel.length==0){
ngModel="defaultSelectModel";
}
varstatus=false;//toggleboolean
varvalueMap="";
varoptions=$(element).children();
$(element).attr("style","padding:0");
//hideoriginaloptions
$.each(options,function(opt){
$(options[opt]).attr("style","display:none");
});
//buildul
varhtml="<divid='"+attrs.id+"-root'style='width:100%;position:relative;left:-1px'>"+
"<pid='display-"+attrs.id+"'style='padding:6px12px"+((attrs.multiple!=undefined)?"4px":"7px")+
"12px;margin:0;border:none;width:95%;margin-left:2px;background-color:transparent'>"+
"<spanstyle='display:inline-block;padding-bottom:3px'></span></p>"+//thisisadummyspan
"<ulid='"+attrs.id+
"-container'class='list-groupeasy-select-container'style='display:none'>";//options'container
if(attrs.multiple!=undefined){
$.each(options,function(opt){
html+="<livalue='"+$(options[opt]).val()+"'class='my-li-containerlist-group-itemoption-"+
attrs.id+"'><divstyle='width:100%;display:inline-block'>"+$(options[opt]).html()+
"</div><spanvalue='"+$(options[opt]).val()+"'class='my-li-optionglyphiconglyphicon-ok'></span></li>";
});
}else{
$.each(options,function(opt){
if($(options[opt]).attr("default")!=undefined){
scope[ngModel]=$(options[opt]).val();
valueMap=$(options[opt]).html();
html+="<livalue='"+$(options[opt]).val()+"'class='my-li-containerlist-group-itemoption-"+attrs.id+"'>"
+$(options[opt]).html()+"</li>";
}else{
html+="<livalue='"+$(options[opt]).val()+"'class='my-li-containerlist-group-itemoption-"+attrs.id+"'>"
+$(options[opt]).html()+"</li>";
}
});
}
//ifmultiple,addbutton
if(attrs.multiple!=undefined){
html+="<liclass='list-group-item'for='ensure-li'><buttonclass='btnbtn-default'"+
"for='ensure-btn'style='padding:2px'>确定</button></li>";
}
//renderui
html+="</ul></div>";
$(element).append(html);
$(".my-li-option").each(function(){
$(this).fadeOut(0);
});
if(attrs.multiple==undefined)
$($("#display-"+attrs.id).children()[0]).html(valueMap);
//adjustwidth
$("#"+attrs.id+"-root").width($("#"+attrs.id+"-root").width()+2);
//mouseleaveevent
$(element).mouseleave(function(){
$(".my-li-container").each(function(){
$(this).attr("style","");
});
if(status){
$("#"+attrs.id+"-container").attr("style","display:none");
status=!status;
}
});
//multipleselectseemscomplex
if(attrs.multiple!=undefined){
//clickevent
$(element).click(function(e){
//ifclickontags,removeit
if($(e.target).attr("for")=="option-tag"){
//changevalanddigestchangeiteminangular
scope[ngModel]=$(element).val().replace($(e.target).attr("value"),"").replace(/;+/,";").replace(/^;/,"");
$(element).val(scope[ngModel]);
scope.$digest();
$(e.target).remove();
$(".my-li-option").each(function(){
if($(this).attr("value")==$(e.target).attr("value")){
$(this).css("opacity","0.01");
}
});
}elseif($(this).attr("for")!='ensure-li'){
//toggleul
$("#"+attrs.id+"-container").attr("style",status?"display:none":"");
status=!status;
}
});
$(".option-"+attrs.id).each(function(){
$(this).on('click',function(){
varselectValue=$(element).val();
varcurrentValue=$(this).attr("value");
varselected=false;
//ifoptionisselected,removeit
vartemp=selectValue.split(";");
$.each(temp,function(obj){
if(temp[obj].indexOf(currentValue)!=-1){
selected=true;
}
})
if(selected){
$($(this).children()[1]).fadeTo(300,0.01);
scope[ngModel]=$(element).val().replace(currentValue,"").replace(/;{2}/,";").replace(/^;/,"");
$(element).val(scope[ngModel]);
scope.$digest();
$("#display-"+attrs.id+"span").each(function(){
if($(this).attr("value")==currentValue){
$(this).remove();
}
});
}else{
//addoptiontoval()andui
$($(this).children()[1]).fadeTo(300,1);
scope[ngModel]=($(element).val()+";"+currentValue).replace(/;{2}/,";").replace(/^;/,"");
$(element).val(scope[ngModel]);
scope.$digest();
$("#display-"+attrs.id).append(
"<spanfor='option-tag'value='"+$(this).attr("value")+"'class='p-option-tag'>"
+$(this).children()[0].innerHTML+"</span>");
}
status=!status;//preventbubble
});
//controlbackground
$(this).mouseenter(function(){
$(".my-li-container").each(function(){
$(this).attr("style","");
});
$(this).attr("style","background-color:#eee");
});
});
}else{
$(".option-"+attrs.id).each(function(){
$(this).mouseenter(function(){
$(".my-li-container").each(function(){
$(this).attr("style","");
});
$(this).attr("style","background-color:#eee");
});
});
//singleselect,justaddvalueandremoveul
$(element).click(function(){
$("#"+attrs.id+"-container").attr("style",status?"display:none":"");
status=!status;
});
$(".option-"+attrs.id).each(function(){
$(this).on('click',function(){
scope[ngModel]=$(this).attr("value");
$(element).val(scope[ngModel]);
scope.$digest();
console.log(ngModel);
console.log(element.val());
$($("#display-"+attrs.id).children()[0]).html($(this).html());
});
});
}
}
}
});
100.如果看到了这里,说明对这个小东西有兴趣,git上一起完善吧,自定义选项模板,选项分组这两个功能还没有实现。少年,加入开源的大军吧。
以上所述是小编给大家分享的自定义Angular指令与jQuery实现的Bootstrap风格数据双向绑定的单选与多选下拉框,希望大家喜欢。