先说点闲话,熟悉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风格数据双向绑定的单选与多选下拉框,希望大家喜欢。
问题内容: 如果我有一个没有模板的AngularJS指令,并且希望它在当前作用域上设置一个属性,那么最好的方法是什么? 例如,计算按钮点击次数的指令: 使用伪指令将点击计数分配给双向属性中的表达式: 有一个更好的方法吗?从我所读的内容来看,孤立的作用域会过大,但是我需要一个子作用域吗?除了使用以外,还有没有更干净的方法可以写回指令属性中定义的范围变量。我只是觉得我很难过。 这里有完整的柱塞。 问题
问题内容: 我是Angular2的新手,但有一个小问题: 在我的Login-Component-HTML中,我有两个复选框,我想通过两种方式将数据绑定到Login-Component-TypeScript。 这是HTML: 这是Component.ts: 如果单击复选框,则会在控制器(组件)中获得正确的值。 但是,如果我更改了例如组件中的值,则复选框不会“获取”新值。 因此,我无法操作Compon
本文向大家介绍vue实现自定义多选与单选的答题功能,包括了vue实现自定义多选与单选的答题功能的使用技巧和注意事项,需要的朋友参考一下 本来实现多选单选这个功能,vue组件中在表单方面提供了一个v-model指令,非常的善解“猿”意, 能把我们的多选单选功能很完美的且很强大得双向绑定起来,实现多选单选任意选根本不在话下。 但是,凡事都有一个但是! 但是奈何这个项目设计稿的缘故,使用原生的表单组件是
本文向大家介绍jQuery自定义多选下拉框效果,包括了jQuery自定义多选下拉框效果的使用技巧和注意事项,需要的朋友参考一下 项目中需要自定义一个下拉框多选插件,业务问题还是自己实现比较好 通过$.fn 向jQuery添加新的方法 下拉数据通过参数传递进去,通过调用该插件时接收,选择后的确定与取消事件采用事件传递方式 代码如下: 1.效果图 2.代码 3.Jquery插件 4.页面调用示例
问题内容: 我一直在尝试定义指令,以便可以根据字段的类型及其参数(存储在数据库中)以表格形式显示不同的“窗口小部件”。我需要对不同类型的场景做出反应,因此需要使用指令来处理布局。 在玩一些示例时,我想出了 kinda 可以工作的代码: HTML 指示 这似乎可行(尽管明显比* proper * angularJS变量绑定要慢),但我认为必须有更好的方法来做到这一点。谁能阐明这件事? 问题答案: 我
本文向大家介绍vue v-model实现自定义样式多选与单选功能,包括了vue v-model实现自定义样式多选与单选功能的使用技巧和注意事项,需要的朋友参考一下 这两天在玩mpvue,但是下午如果对着文档大眼瞪小眼的话,肯定会睡着的。 想起昨晚的flag,我就想直接用demo上手吧,一举两得 谁想到我好不容易快做完了,v-model在小程序中不起作用! 来不及研究为什么,我先直接在原来项目上