自定义Angular指令与jQuery实现的Bootstrap风格数据双向绑定的单选与多选下拉框


Posted in Javascript onDecember 12, 2015

先说点闲话,熟悉Angular的猿们会喜欢这个插件的。

00.本末倒置

不得不承认我是一个喜欢本末倒置的人,学生时代就喜欢先把晚交的作业先做,留着马上就要交的作业不做,然后慢悠悠做完不重要的作业,卧槽,XX作业马上要交了,赶紧补补补。如今做这个项目,因为没找到合适的多选下拉Web插件,又不想用html自带的丑陋的<select multiple></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.乐观的程序猿

思路很明确,定义一个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 = "<div id='" + attrs.id + "-root' style='width:100%;position: relative;left:-1px'>" +
    "<p id='display-"+attrs.id + "' style='padding:6px 12px "+ ((attrs.multiple != undefined)?"4px":"7px")+
    " 12px;margin:0;border:none;width:95%;margin-left:2px;background-color: transparent'>" +
    "<span style='display: inline-block;padding-bottom: 3px'> </span></p>" + //this is a dummy span
    "<ul id='" + attrs.id +
    "-container' class='list-group easy-select-container' style='display:none'>"; //options' container
   if(attrs.multiple != undefined) {
    $.each(options, function (opt) {
     html += "<li value='"+ $(options[opt]).val() +"' class='my-li-container list-group-item option-"+
     attrs.id+ "'><div style='width:100%;display:inline-block'>" + $(options[opt]).html() +
     "</div><span value='"+ $(options[opt]).val() +"' class='my-li-option glyphicon glyphicon-ok'></span></li>";
    });
   } else {
    $.each(options, function (opt) {
     if($(options[opt]).attr("default") != undefined) {
      scope[ngModel] = $(options[opt]).val();
      valueMap = $(options[opt]).html();
      html += "<li value='"+ $(options[opt]).val() +"' class='my-li-container list-group-item option-"+ attrs.id+ "'>"
      + $(options[opt]).html() + "</li>";
     } else {
      html += "<li value='"+ $(options[opt]).val() +"' class='my-li-container list-group-item option-"+ attrs.id+ "'>"
      + $(options[opt]).html() + "</li>";
     }
    });
   }
   //if multiple, add button
   if (attrs.multiple != undefined) {
    html += "<li class='list-group-item ' for='ensure-li'><button class='btn btn-default'" +
    " for='ensure-btn' style='padding: 2px' > 确定 </button></li>";
   }
   //render ui
   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);
   //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(
        "<span for='option-tag' value='"+ $(this).attr("value") +"' class='p-option-tag'>"
        +$(this).children()[0].innerHTML+ "</span>");
      }
      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风格数据双向绑定的单选与多选下拉框,希望大家喜欢。

Javascript 相关文章推荐
javascript 一些用法小结
Sep 11 Javascript
JavaScript Timer实现代码
Feb 17 Javascript
jquery实现用户打分评分特效
May 28 Javascript
Bootstrap入门书籍之(三)栅格系统
Feb 17 Javascript
高效Web开发的10个jQuery代码片段
Jul 22 Javascript
javascript 判断当前浏览器版本并判断ie版本
Feb 17 Javascript
js实现延迟加载的几种方法
Apr 24 Javascript
详解React-Native解决键盘遮挡问题(Keyboard遮挡问题)
Jul 13 Javascript
详解使用Visual Studio Code对Node.js进行断点调试
Sep 14 Javascript
使用vue根据状态添加列表数据和删除列表数据的实例
Sep 29 Javascript
小程序测试后台服务的方法(ngrok)
Mar 08 Javascript
Vue.js数字输入框组件使用方法详解
Oct 19 Javascript
利用jQuery实现WordPress中@的ID悬浮显示评论内容
Dec 11 #Javascript
jQuery入门之层次选择器实例简析
Dec 11 #Javascript
jQuery使用$.ajax提交表单完整实例
Dec 11 #Javascript
jQuery使用$.ajax进行即时验证实例详解
Dec 11 #Javascript
JavaScript基础篇(6)之函数表达式闭包
Dec 11 #Javascript
jQuery弹簧插件编写基础之“又见弹窗”
Dec 11 #Javascript
jQuery点击改变class并toggle及toggleClass()方法定义用法
Dec 11 #Javascript
You might like
sourcesafe管理phpproj文件的补充说明(downmoon)
2009/04/11 PHP
PHP采集相关教程之一 CURL函数库
2010/02/15 PHP
利用PHP将图片转换成base64编码的实现方法
2016/09/13 PHP
PHP数组生成XML格式数据的封装类实例
2016/11/10 PHP
PHP最常用的正则表达式
2017/02/13 PHP
[原创]PHP实现生成vcf vcard文件功能类定义与使用方法详解【附demo源码下载】
2017/09/02 PHP
php中array_fill函数的实例用法
2021/03/02 PHP
jQuery LigerUI 插件介绍及使用之ligerDrag和ligerResizable示例代码打包
2011/04/06 Javascript
详解JavaScript正则表达式中的global属性的使用
2015/06/16 Javascript
Javascript中Date类型和Math类型详解
2016/02/27 Javascript
浅析Node.js实现HTTP文件下载
2016/08/05 Javascript
表单input项使用label同时引用Bootstrap库导致input点击效果区增大问题
2016/10/11 Javascript
JavaScript实现审核流程状态的动态显示进度条
2017/03/15 Javascript
微信小程序开发之实现自定义Toast弹框
2017/06/08 Javascript
Javascript实现base64的加密解密方法示例
2017/06/27 Javascript
JQuery 获取Dom元素的实例讲解
2017/07/08 jQuery
Vue在chrome44偶现点击子元素事件无法冒泡的解决方法
2019/12/15 Javascript
解决vue数据不实时更新的问题(数据更改了,但数据不实时更新)
2020/10/27 Javascript
Ant Design moment对象和字符串之间的相互转化教程
2020/10/27 Javascript
[00:37]2016完美“圣”典风云人物:rOtk宣传片
2016/12/09 DOTA
[01:13:01]2018DOTA2亚洲邀请赛 4.4 淘汰赛 TNC vs VG 第三场
2018/04/05 DOTA
python操作摄像头截图实现远程监控的例子
2014/03/25 Python
Python解析树及树的遍历
2016/02/03 Python
Python基于百度云文字识别API
2018/12/13 Python
python write无法写入文件的解决方法
2019/01/23 Python
PyTorch使用cpu加载模型运算方式
2020/01/13 Python
python实现TCP文件传输
2020/03/20 Python
沃尔玛旗下墨西哥超市:Bodega Aurrera
2020/11/13 全球购物
电脑专业个人求职信范文
2014/02/04 职场文书
校园文化标语
2014/06/18 职场文书
专科生就业求职信
2014/06/22 职场文书
教师个人查摆剖析材料
2014/10/14 职场文书
机关干部四风问题自查报告及整改措施
2014/10/26 职场文书
邀请函模板
2015/02/02 职场文书
2016大一新生军训心得体会
2016/01/11 职场文书
2016年法制宣传月活动总结
2016/04/01 职场文书