自定義Angular指令與jQuery實現的Bootstrap風格數據雙向綁定的單選&多選下拉框

先說點閒話,熟悉Angular的猿們會喜歡這個插件的。javascript

00.本末倒置

不得不認可我是一個喜歡本末倒置的人,學生時代就喜歡先把晚交的做業先作,留着立刻就要交的做業不作,而後慢悠悠作完不重要的做業,臥槽,XX做業立刻要交了,趕忙補補補。現在作這個項目,由於沒找到合適的多選下拉Web插件,又不想用html自帶的醜陋的<select multiple></select>,本身花了一成天時間作了一個。或許這樣佔用的主要功能開發的時間,開發起來會更有緊迫感吧。感受本身是個抖M自虐傾向,而且伴有css和代碼縮進強迫症的程序猿。css

 

01.多此一舉

Angular強大的控制器彷佛已經能夠知足大部分UI上的需求了,可是NodeJS應用每每會使用ejs,jade這樣的模板引擎來動態生成html頁面,那麼問題來了,當我想把後臺傳給express中res.render()的參數直接顯示到界面並且綁定到相應的ng-model怎麼辦?html

解決方法1,不要什麼事一次來,Angular的Controller發個post請求再拿數據不就好了java

解決方法2,先用模板暫存在html上,再讓Controller根據頁面上的數據來初始化$scope的值git

解決方法3,鄙人對Angular和EJS才疏學淺,誰有好辦法教我唄express

 

好比如今要作一個選擇下拉框<select>n個<option>xx</option></select>,選項在後臺,我不想單獨發post拿,也不想放在頁面上,Controller單獨寫邏輯處理,而Angular社區有個ui-select插件,看起來數據是從$scope取的,並非直接拿的<option />標籤的數據,當時我就火了,不就一個下拉框,本身作唄。app

 

10.樂觀的程序猿

思路很明確,定義一個Angular directive -> 把選項值拿出來 -> 各類事件加加加 -> scope數據綁定 -> 完結撒花dom

我估計的時間是半天,然而實際花了多久只能呵呵了,css強迫症,Angular理解不深(因此不少html操做仍是在用jQuery),事件考慮不全致使了最終花了超過兩倍的時間作完,ide

不廢話了,簡單實用,既能夠即時綁定ng-model $scope.xxx,也能夠直接調jQuery的$("標籤的id").val()也能拿到值,post

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上一塊兒完善吧,自定義選項模板,選項分組這兩個功能尚未實現。少年,加入開源的大軍吧。

相關文章
相關標籤/搜索