先说点闲话,熟悉angular的猿们会喜欢这个插件的。
00.本末倒置
不得不承认我是一个喜欢本末倒置的人,学生时代就喜欢先把晚交的作业先做,留着马上就要交的作业不做,然后慢悠悠做完不重要的作业,卧槽,xx作业马上要交了,赶紧补补补。如今做这个项目,因为没找到合适的多选下拉web插件,又不想用html自带的丑陋的,自己花了一整天时间做了一个。或许这样占用的主要功能开发的时间,开发起来会更有紧迫感吧。感觉自己是个抖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才疏学浅,谁有好办法教我呗
比如现在要做一个选择下拉框n个xx,选项在后台,我不想单独发post拿,也不想放在页面上,controller单独写逻辑处理,而angular社区有个ui-select插件,看起来数据是从$scope取的,并不是直接拿的标签的数据,当时我就火了,不就一个下拉框,自己做呗。
10.乐观的程序猿
思路很明确,定义一个angular directive -> 把选项值拿出来 -> 各种事件加加加 -> 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) { // your code }]);
然后参考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
var comdirective = angular.module('easyselect', []);comdirective.directive(easyselect, function () { return { link: function (scope, element, attrs) { var ngmodel = $(element).attr(ng-model); if(!ngmodel || ngmodel.length == 0) { ngmodel = defaultselectmodel; } var status = false; //toggle boolean var valuemap = ; var options = $(element).children(); $(element).attr(style, padding:0); //hide original options $.each(options, function (opt) { $(options[opt]).attr(style, display:none); }); //build ul var html = + +
+ //this is a dummy span ; //options' container if(attrs.multiple != undefined) { $.each(options, function (opt) { html += + $(options[opt]).html() +
; }); } else { $.each(options, function (opt) { if($(options[opt]).attr(default) != undefined) { scope[ngmodel] = $(options[opt]).val(); valuemap = $(options[opt]).html(); html += + $(options[opt]).html() + ; } else { html += + $(options[opt]).html() + ; } }); } //if multiple, add button if (attrs.multiple != undefined) { html += 确定 ; } //render ui html +=
; $(element).append(html); $(.my-li-option).each(function(){ $(this).fadeout(0); }); if(attrs.multiple == undefined) $($(#display-+attrs.id).children()[0]).html(valuemap); //adjust width $(# + attrs.id + -root).width($(# + attrs.id + -root).width() + 2); //mouse leave event $(element).mouseleave(function(){ $(.my-li-container).each(function(){ $(this).attr(style,); }); if(status) { $(# + attrs.id + -container).attr(style, display:none); status = !status; } }); //multiple select seems complex if (attrs.multiple != undefined) { //click event $(element).click(function (e) { //if click on tags, remove it if($(e.target).attr(for) == option-tag) { // change val and digest change item in angular 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); } }); } else if($(this).attr(for) != 'ensure-li') { //toggle ul $(# + attrs.id + -container).attr(style, status ? display:none : ); status = !status; } }); $(.option-+attrs.id).each(function(){ $(this).on('click',function(){ var selectvalue = $(element).val(); var currentvalue = $(this).attr(value); var selected = false; //if option is selected ,remove it var temp = 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 { //add option to val() and ui $($(this).children()[1]).fadeto(300,1); scope[ngmodel] = ($(element).val()+;+currentvalue).replace(/;{2}/,;).replace(/^;/,); $(element).val(scope[ngmodel]); scope.$digest(); $(#display-+attrs.id).append( +$(this).children()[0].innerhtml+ ); } status = !status; // prevent bubble }); //control background $(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); }); }); //single select ,just add value and remove ul $(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风格数据双向绑定的单选与多选下拉框,希望大家喜欢。