自定义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 相关文章推荐
jQuery拖拽 &amp; 弹出层 介绍与示例
Dec 27 Javascript
Extjs Label的 fieldLabel和html属性值对齐的方法
Jun 15 Javascript
使用CamanJS在Web页面上处理图像的技巧
Aug 18 Javascript
举例讲解JavaScript substring()的使用方法
Nov 09 Javascript
javascript基础知识分享之类与函数化
Feb 13 Javascript
js 递归和定时器的实例解析
Feb 03 Javascript
vue.js中指令Directives详解
Mar 20 Javascript
Vue.js实现微信过渡动画左右切换效果
Jun 13 Javascript
详解在 Angular 项目中添加 clean-blog 模板
Jul 04 Javascript
浅谈关于iview表单验证的问题
Sep 29 Javascript
vue3.0中使用postcss-pxtorem的具体方法
Nov 20 Javascript
JS前端轻量fabric.js系列之画布初始化
Aug 05 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
php遍历数组的方法分享
2012/03/22 PHP
php开发文档 会员收费1期
2012/08/14 PHP
将二维数组转为一维数组的2种方法
2014/05/26 PHP
PHP采集类Snoopy抓取图片实例
2014/06/19 PHP
Nginx下配置codeigniter框架方法
2015/04/07 PHP
简单的无缝滚动程序-仅几行代码
2007/05/08 Javascript
关于Javascript模块化和命名空间管理的问题说明
2010/12/06 Javascript
js 在定义的时候立即执行的函数表达式(function)写法
2013/01/16 Javascript
js设置组合快捷键/tabindex功能的方法
2013/11/21 Javascript
javaScript使用EL表达式的几种方式
2014/05/27 Javascript
Javascript中的apply()方法浅析
2015/03/15 Javascript
JS实现的文字与图片定时切换效果代码
2015/10/06 Javascript
JavaScript动态插入CSS的方法
2015/12/10 Javascript
JS判断键盘是否按的回车键并触发指定按钮点击操作的方法
2017/02/13 Javascript
canvas实现刮刮卡效果
2017/03/14 Javascript
js判断非127开头的IP地址的实例代码
2020/01/05 Javascript
详解详解Python中writelines()方法的使用
2015/05/25 Python
详解Python编程中包的概念与管理
2015/10/16 Python
Python探索之pLSA实现代码
2017/10/25 Python
Python实现进程同步和通信的方法
2018/01/02 Python
使用python将时间转换为指定的格式方法
2018/11/12 Python
Python对接支付宝支付自实现功能
2019/10/10 Python
Python 私有化操作实例分析
2019/11/21 Python
Python Sympy计算梯度、散度和旋度的实例
2019/12/06 Python
灵活运用CSS3特性绘制简易版围棋效果
2016/09/28 HTML / CSS
伦敦时尚生活的缩影:LN-CC
2017/01/24 全球购物
经贸日语专业个人求职信范文
2013/12/28 职场文书
结婚典礼证婚词
2014/01/08 职场文书
证婚人搞笑证婚词
2014/01/10 职场文书
新春文艺演出主持词
2014/03/27 职场文书
讲解员培训方案
2014/05/04 职场文书
物理系毕业生自荐书
2014/06/13 职场文书
民族团结演讲稿范文
2014/08/27 职场文书
法律专业大学生职业生涯规划书:向目标一步步迈进
2014/09/22 职场文书
2014年新教师工作总结
2014/11/08 职场文书
2019年幼儿园管理条例范本!
2019/07/17 职场文书