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

先说点闲话,熟悉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上一起完善吧,自定义选项模板,选项分组这两个功能还没有实现。少年,加入开源的大军吧。

时间: 2024-12-19 15:36:14

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

Angular指令封装jQuery日期时间插件datetimepicker实现双向绑定

一放假就高产似母猪了. 00.混乱的前端界 Angular1.x确实是个学习成本很高的框架,刚开始实习那会儿,前端啥也不懂,工头说用Angular,我们这群小弟也只能硬着头皮学.在这之前,前端的东西大部分都用的jQuery,而Angular正好是和jQuery的思维是相反的,开发过程中遇到了不少坑.而Angular团队也放弃了1.x开始开发和React神似的2.0版本,唉,真是沧海桑田啊. 01.Angular vs jQuery Angular模块化和解耦的思路确实值得一学,但是相对于成熟的j

页面多选下拉框 jquery.multiple.select

1.首先引用jquery.multiple.select.js.multiple-select.css 2.在页面select下拉列表里面加一个multiple="multiple" <select name="nimbusHost" id="nimbusHost" multiple="multiple"> <c:forEach items="${stormIps}" var="

自定义SWT控件二之自定义多选下拉框

2.自定义下拉多选框 package com.view.control.select; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CLabel; import or

jquery easyui 多选下拉框的实现

修改官方提供的demo实例,本来是单选的,让她编程多选,完成了勾选和去勾选的所有功能. 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Basic Combo - jQuery EasyUI Demo</title> 6 <link rel="stylesheet" type="text/

jquery下拉框插件心得

想记录下自己在开发这个下拉框插件之后的心得,发现写一篇博客,真是"知之非艰,行之惟艰".所以再有看到好的文章,就不会再吝啬自己的"赞"了. 多次想该如何写比较好呢?最终决定,将以后我可能用得到的东西一条条记录下来,一条条来写,这样比较清晰. 1.写jquery插件需要的一个结构 // 定义jQuery私有作用域 (function ($) { // 插件默认属性 var defaults = { name1: "value1", name2: &

jquery实现下拉框多选

一.说明 本文是利用EasyUI实现下拉框多选功能,在ComboxTree其原有的基础上对样式进行了改进,样式表已上传demo,代码如下 二.代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w

jquery级联下拉框

$(document).ready(function(){     //找到三个下拉框     var carnameSelect = $(".carname").children("select");     var cartypeSelect = $(".cartype").children("select");     var wheeltypeSelect = $(".wheeltype").chi

自定义SWT控件一之自定义单选下拉框

一.自定义下拉控件 自定义的下拉框,是自定义样式的,其中的下拉框使用的是独立的window,非复选框的下拉框双击单机其它区域或选择完之后,独立window构成的下拉框会自动消失. package com.view.control.select; import java.util.ArrayList; import java.util.List; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite;

bootstrap下拉框的例子,提示Error: Bootstrap&#39;s JavaScript requires jQuery

bootstrap很多js依赖jquery,所以需要引入jquery 遇到的问题: 页面访问提示:Error: Bootstrap's JavaScript requires jQuery 解决方法: 在引入bootstrap的js文件之前,先引入jquery.js 导入顺序可以向下面一样 <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script><link rel