基于jQuery实现类似Google+圈子选择功能插件(支持键盘事件)

最近项目中用到的一个效果,类似于Google+的添加圈子功能。本文插件约8成封装,好多功能是依据项目中实际需求写的。若要使用,可根据自身情况扩展修改。
google+ choose
具体原理与调用不详述,可看代码注释及DEMO演示(演示中mod.udata.js里有一个汉字转拼音方法,它并不是插件的一部分,但为插件提供了拼音搜索的功能)。
样例与代码:查看样例下载源码

;(function ($) {
    /*
     * 基于jQuery实现类似Google+圈子选择功能插件(支持键盘事件)@Mr.Think(http://mrthink.net/)
     */
    $.fn.iChoose = function (options) {
        var SELF=this;
        var iset = $.extend({}, $.fn.iChoose.defaults, options);
        var _h,pull=null;
        var main=$(iset.choMain);
        var chose=$(iset.choCls,main);
        var input=$(SELF);
        var tips=$(iset.tips,main);
        var cls=iset.selCls;
        var ids=$(iset.chsIds);
        var _l=iset.limit;
        //若无列表数据则中断
        if(iset.data.length==0){return false}
        //初始化下拉列表
        var tpl='<div class="'+iset.pullCls+'"><ul>';
        var tplArr=[];
        $.each(iset.data,function(k,v){
            tplArr.push('<li data-mid="'+ v.mid+'" data-name="'+ v.filterName.toUpperCase()+'" class="hook_visible hook_'+v.mid+'">'+ v.name+'</li>');
        });
        tpl=tpl+tplArr.join('')+'</ul></div>';
        if(pull==null){
            main.append(tpl);
        }
        pull=$('.'+iset.pullCls,main);
        _h=pull.find('li').outerHeight(true);
        pull.width(main.width()-2);
        //操作函数
        var Action={
            init:function(){
                //列表鼠标划过
                pull.delegate('li.hook_visible','mouseover',function(){
                    $(this).addClass(cls).siblings().removeClass(cls);
                });
                //选择列表
                pull.delegate('li.hook_visible','click',function(){
                    Action.choose($(this));
                });
                //点击弹出列表
                chose.click(function(){
                    Action.show();
                });
                //移除已选择
                chose.delegate('.'+iset.removeEl,'click',function(){
                    Action.undock($(this));
                });
            },
            move: function(dir) {
                //上下移动 - 上::str=up | 下::step=down;
                var index;
                var item=pull.find('li.hook_visible').filter(':visible');
                var cur=item.filter('.'+cls);
                cur.size()==0 ? index=-1 : index=item.index(cur);
                item.eq(dir=='up' ? (index<1 ? 0 : index-1) : index+1).addClass(cls).siblings().removeClass(cls);
                //移动时跟随滚动
                if(item.size()>_l){
                    //下翻滚动
                    if(dir=='down'){
                        pull.scrollTop((index+2-_l)*_h);
                    }
                    //上翻滚动
                    if(dir=='up'){
                        pull.scrollTop((index-1)*_h);
                    }
                }
            },
            choose:function(el){
                //列表选择
                var mid=el.attr('data-mid');
                var name=el.text();
                tips.before('<div class="'+iset.selItemCls+'"><span>'+name+'</span><a href="javascript:;" class="'+iset.removeEl+'" data-mid="'+mid+'">x</a></div>');
                input.focus();
                el.hide().removeClass('hook_visible');
                //返回选中列表id
                ids.val() =='' ? ids.val(mid) : ids.val(ids.val()+','+mid);
                pull.find('li.hook_visible:first').addClass(cls).siblings().removeClass(cls);
                this.upstyle();
            },
            undock:function(el){
                //删除已选
                var mid=el.attr('data-mid');
                var idsArr=ids.val().split(',');
                input.focus();
                el.parent().remove();
                idsArr=$.grep(idsArr,function(v,k){
                   return v != mid;
                });
                ids.val(idsArr.join(','));
                pull.find('li.hook_'+mid).show().addClass('hook_visible');
                this.upstyle();
            },
            show:function(){
                //显示列表
                pull.slideDown(100,function(){Action.match('')});
                pull.find('li.hook_visible:first').addClass(cls).siblings().removeClass(cls);
                $(SELF).focus();
                tips.hide();
                input.addClass(iset.inputWCls);
            },
            hide:function(){
                //隐藏列表
                pull.slideUp(100);
                $(SELF).blur();
                tips.show();
                input.removeClass(iset.inputWCls);
            },
            upstyle:function(type){
                //更新下拉列表样式 - 当已选换行时 --
                var len=pull.find('li.hook_visible').size();
                if(type=='match'){
                    len=pull.find('li:visible').size();
                }
                if(len<_l){
                    pull.height(_h*len);
                    if(len==0){
                        this.hide();
                    }
                }else{
                    pull.height(_h*_l);
                }
                pull.css('top',chose.outerHeight());
            },
            match:function(str){
                //过滤选择 - 拼音选择须插件支持
                var visible=pull.find('li.hook_visible');
                visible.each(function(){
                    var name=$(this).attr('data-name');
                    name.match(str.toUpperCase())!=null ? $(this).show() : $(this).hide();
                });
                if(visible.size()==0 && $('.'+iset.noResCls).size()==0){
                    pull.find('li:first').before('<li class="'+iset.noResCls+'">暂无可选择列表</li>')
                }
                this.upstyle('match');
            },
            blur:function(str){
                //非选择区域点击隐藏
                pull.find('li.hook_visible').each(function(){
                    var name=$(this).text();
                    if(name == str){
                        Action.choose($(this));
                    }
                });
                input.val('');
            }
        }
        Action.init();
        //键盘控制 - 绑定输入表单
        $(this).on({
            //键盘弹起
            'keyup change':function(){
                var val= $.trim(input.val());
                Action.match(val);
            },
            //失去焦点
            'blur':function(){
                var val= $.trim(input.val());
                Action.blur(val);
            },
            //键盘按下 -- 捕获键盘值,以执行对应事件
            /*
            8  - delete;
            27 - esc;
            38 - up;
            40 - down;
            9  - tab;
            13 - enter
             */
            'keydown':function(e){
                switch (e.keyCode) {
                    case 8:
                        if($.trim(input.val())==''){
                            e.preventDefault();
                            var last=chose.find('.'+iset.removeEl+':last');
                            if(last.size()>0){
                                Action.undock(last);
                            }
                        }
                        break;
                    case 27:
                        e.preventDefault();
                        Action.hide();
                        break;
                    case 38:
                        e.preventDefault();
                        Action.move('up');
                        break;
                    case 40:
                        e.preventDefault();
                        Action.move('down')
                        break;
                    case 9:
                    case 13:
                        e.preventDefault();
                        Action.choose(pull.find('.'+cls));
                        break;
                    default:
                        $.noop();
                }
            }
        });
        //点击非当前区域隐藏弹出层
        main.click(function(e){
            e.stopPropagation();
        });
        $(document).click(function(){
           Action.hide();
        });
    }
    $.fn.iChoose.defaults = {
        /*
        data:传入值,可以实时ajax传上,演示中是json值,具体格式可参考mod.udatas.js中的$.map(...)
        limit:每次显示的条数,其他滚动显示
        choMain:载入区域id
        pullCls:弹出列表的class
        choCls:已选择列表的外围class
        selItemCls:已选择的单个元素class
        removeEl:删除元素的class
        selCls:列表高亮的class
        inputWCls:input的class
        tips:默认提示值元素钩子
        noResCls:列表中无结果时的cls
        chsIds:已选择列表的id值,传给后端用的。(此值亦可用回调函数把值从插件中回传出来)
         */
        data:UDatas,
        limit:5,
        choMain:'#iChooseMain',
        pullCls:'icm-list',
        choCls:'.icm-box',
        selItemCls:'icm-item',
        removeEl:'icm-delete',
        selCls:'selected',
        inputWCls:'icm-input-w',
        tips:'.icm-cur-txt',
        noResCls:'hook_noresult',
        chsIds:'#iChooseIds'
    }
})(jQuery);

文本域光标操作(选、添、删、取)的jQuery扩展

最近的项目中,好几次用到操作文本域的方法,比如光标位置、删除光标前字符等。每次都是查阅资料(这部分操作涉及到的js方法都比较生僻),费时费事。于是就封装了一个jQuery扩展。
该针对文本域的扩展实现的功能及使用方法:
1、获取光标位置:$(elem).iGetFieldPos()
2、设置光标位置:$(elem).iSelectField(start)
3、选中指定位置内的字符:$(elem).iSelectField(start,end)
4、选中指定的字符:$(elem).iSelectStr(str)
5、在光标之后插入字符串:$(elem).iAdd(str)
6、删除光标前面(-n)或者后面(n)的n个字符:$(elem).iDel(n)
jQuery扩展代码:

;(function($){
    /*
     * 文本域光标操作(选、添、删、取)的jQuery扩展 @Mr.Think http://mrthink.net/text-field-jquery-extend/
     */
    $.fn.extend({
        /*
         * 获取光标所在位置
         */
        iGetFieldPos:function(){
            var field=this.get(0);
            if(document.selection){
                //IE
                $(this).focus();
                var sel=document.selection;
                var range=sel.createRange();
                var dupRange=range.duplicate();
                dupRange.moveToElementText(field);
                dupRange.setEndPoint('EndToEnd',range);
                field.selectionStart=dupRange.text.length-range.text.length;
                field.selectionEnd=field.selectionStart+ range.text.length;
            }
            return field.selectionStart;
        },
        /*
         * 选中指定位置内字符 || 设置光标位置
         * --- 从start起选中(含第start个),到第end结束(不含第end个)
         * --- 若不输入end值,即为设置光标的位置(第start字符后)
         */
        iSelectField:function(start,end){
            var field=this.get(0);
            //end未定义,则为设置光标位置
            if(arguments[1]==undefined){
                end=start;
            }
            if(document.selection){
                //IE
                var range = field.createTextRange();
                range.moveEnd('character',-$(this).val().length);
                range.moveEnd('character',end);
                range.moveStart('character',start);
                range.select();
            }else{
                //非IE
                field.setSelectionRange(start,end);
                $(this).focus();
            }
        },
        /*
         * 选中指定字符串
         */
        iSelectStr:function(str){
            var field=this.get(0);
            var i=$(this).val().indexOf(str);
            i != -1 ? $(this).iSelectField(i,i+str.length) : false;
        },
        /*
         * 在光标之后插入字符串
         */
        iAddField:function(str){
            var field=this.get(0);
            var v=$(this).val();
            var len=$(this).val().length;
            if(document.selection){
                //IE
                $(this).focus()
                document.selection.createRange().text=str;
            }else{
                //非IE
                var selPos=field.selectionStart;
                $(this).val($(this).val().slice(0,field.selectionStart)+str+$(this).val().slice(field.selectionStart,len));
                this.iSelectField(selPos+str.length);
            };
        },
        /*
         * 删除光标前面(-)或者后面(+)的n个字符
         */
        iDelField:function(n){
            var field=this.get(0);
            var pos=$(this).iGetFieldPos();
            var v=$(this).val();
            //大于0则删除后面,小于0则删除前面
            $(this).val(n>0 ? v.slice(0,pos-n)+v.slice(pos) : v.slice(0,pos)+v.slice(pos-n));
            $(this).iSelectField(pos-(n<0 ? 0 : n));
        }
    });
})(jQuery);

具体使用方法就不详述了,加载于扩展代码,然后根据扩展中的方法名调用即可。

基于jQuery的简易手风琴切换插件

最近确实忙碌。
正好接下来的工作中可能需要一个手风琴效果,就动手写了一个。其实有多个现成的jQuery手风琴插件可以用,但对比了一下,总感觉有些笨重,还是自己写的脉络自己最清楚,扩展起来也更容易些。
可用于图片或者容器,使用与常规jQuery插件调用方式无异。实现原理也不难理解,都在代码注释中。想研究的可以看下面的代码,或者样例演示。
jQuery代码:点此查看样列

;(function($){
    /*
     * 基于jQuery的简易手风琴切换插件@Mr.Think(http://mrthink.net/)
     */
    $.fn.iAccordion=function(iSet){
        var self=this;
        iSet=$.extend({Type:'mouseover',Select:'img',Cur:0,InitInterval:100,Interval:500,Easing:''},iSet||{});
        /*
         * Type: 鼠标事件类型,mouseover,click,mouseleave等
         * Select: 选择器,用以获取需要切换的元素集合
         * Cur: 默认展开元素的索引
         * InitInterval: 初始化手风琴效果动画间隔时间
         * Interval: 鼠标事件动画间隔时间
         * Easing: 动画效果,需要jQuery.easing支持,参数可参考jQuery.easing@ http://gsgd.co.uk/sandbox/jquery/easing/
         */
        var item,boxW,selectW,animateW,sIndex,animateL;
        $(self).each(function(){
            //初始化容器样式
            $(this).css({'position':'relative','overflow':'hidden'});
            item=$(this).find(iSet.Select);
            //初始化切换元素样式
            item.css({'position':'absolute','left':0,'top':0});
            boxW=$(this).outerWidth();
            selectW=item.outerWidth();
            animateW=(boxW-selectW)/(item.size()-1);
            //初始化元素排列并为元素data一个索引值
            item.each(function(i){
                $(this).animate({'left':animateW*i+'px'},iSet.InitInterval,iSet.Easing);
                $(this).data('index',i);
            }).on(iSet.Type,function(e){//绑定鼠标事件
                //获取当前元素索引值
                sIndex=$(this).data('index');
                    //鼠标事件动画,通过判断元素索引值与当前元素索引值的大小关系动画显示当前元素并动画排列
                    item.each(function(n){
                        n > sIndex ? animateL=selectW+animateW*(n-1) : animateL=animateW*n;
                        $(this).stop().animate({'left':animateL+'px'},iSet.Interval,iSet.Easing);
                    });
            }).eq(iSet.Cur).trigger(iSet.Type);
        });
    }
})(jQuery);

如何调用?
1、在页面中引入上面的插件代码;
2、$(selectmain).iAccordion({…});
3、相关参数及功能,请参考插件中的注释说明。

小小的提示,若需要定义Easing,需要导入jQuery.easing插件,Easing的参数即jQuery.easing的方法名称,如easeOutBounce、easeOutQuint等。

统一下拉列表样式并实现select样式高度自定义的jQuery插件

好像很久没有更新过博客了,哈~
发布一款最近写的jQuery小插件,用于统一下拉表样式并实现select样式高度自定义。select样式如同input[type=file]一样,各系统平台各浏览器显示的都不一样,特别是ie~一切影响美感的东西较真的UI设计狮们是不会妥协的。兵来将挡,水来土淹,为了保持最近一个公司项目中部分下拉列表的美观统一,用一种变通的方式为select穿上一套标准的新衣:不论平台不论浏览器样式统一、样式高度自定义、不破坏select原有结构与功能(这意味着后端程序仍可像调用普通select那样取值)。

原理无须多讲,在需要改变样式的select后插入一个div容器,同时隐藏掉select。然后把select的相关信息(optgroup/option/value/selected等)取出来用dl展现,如此一来,改变div容器中元素样式即可。记得前年写过一个用input[type=hidden]存值,用dl模拟下拉列表的插件。两者原理差不多,只是本文中的可能更人性化,使用起来更方便些。
jQuery代码:点此查看样列

;(function($){
    /*
     * 统一select样式并实现样式高度自定义的jQuery插件@Mr.Think(http://mrthink.net/)
     */
    $.fn.iSimulateSelect=function(iSet){
        iSet=$.extend({
            selectBoxCls:'i_selectbox', //string类型,外围class名
            curSCls:'i_currentselected',//string类型,默认显示class名
            optionCls:'i_selectoption',//string类型,下拉列表class名
            selectedCls:'selected',//string类型,当前选中class名
            width:222,//number类型,模拟select的宽度
            height:300,//number类型,模拟select的最大高度
            zindex:20//层级顺序
        },iSet||{});
        this.hide();
        return this.each(function(){
           var self=this;
           var thisCurVal,thisSelect,cIndex=0;
           //计算模拟select宽度
           if(iSet.width==0){
              iSet.width=$(self).width();
           }
           var html='<div class="'+iSet.selectBoxCls+'" style="z-index:'+iSet.zindex+'"><div class="'+iSet.curSCls+'" style="width:'+iSet.width+'px">'+$(self).find('option:selected').text()+'</div><dl class="'+iSet.optionCls+'" style="display:none;width:'+iSet.width+'px">';
           //判断select中是否有optgroup
           //用dt替代optgroup,用dd替代option
           if($(self).find('optgroup').size()>0){
               $(this).find('optgroup').each(function(){
                   html+='<dt>'+$(this).attr('label')+'</dt>';
                   $(this).find('option').each(function(){
                        if($(this).is(':selected')){
                          html+='<dd class="'+iSet.selectedCls+'">'+$(this).text()+'</dd>';
                       }else{
                          html+='<dd>'+$(this).text()+'</dd>';
                       }
                   });
               });
           }else{
               $(this).find('option').each(function(){
                   if($(this).is(':selected')){
                      html+='<dd class="'+iSet.selectedCls+'">'+$(this).text()+'</dd>';
                   }else{
                      html+='<dd>'+$(this).text()+'</dd>';
                   }
               });
           }
           //将模拟dl插入到select后面
           $(self).after(html);
           //当前模拟select外围box元素
           thisBox=$(self).next('.'+iSet.selectBoxCls);
           //当前模拟select初始值元素
           thisCurVal=thisBox.find('.'+iSet.curSCls);
           //当前模拟select列表
           thisSelect=thisBox.find('.'+iSet.optionCls);
           /*
           若同页面还有其他原生select,请前往https://github.com/brandonaaron/bgiframe下载bgiframe,同时在此处调用thisSelect.bgiframe()
            */
           //thisSelect.bgiframe();

           //弹出模拟下拉列表
           thisCurVal.click(function(){
              $('.'+iSet.optionCls).hide();
              $('.'+iSet.selectBoxCls).css('zIndex',iSet.zindex);
              $(self).next('.'+iSet.selectBoxCls).css('zIndex',iSet.zindex+1);
              thisSelect.show();
           });
           //若模拟select高度超出限定高度,则自动overflow-y:auto
           if(thisSelect.height()>iSet.height){
               thisSelect.height(iSet.height);
           }
           //模拟列表点击事件-赋值-改变y坐标位置-...
           thisSelect.find('dd').click(function(){
               $(this).addClass(iSet.selectedCls).siblings().removeClass(iSet.selectedCls);
               cIndex=thisSelect.find('dd').index(this);
               thisCurVal.text($(this).text());
               $(self).find('option').eq(cIndex).attr('selected','selected');
               $('.'+iSet.selectBoxCls).css('zIndex',iSet.zindex);
               thisSelect.hide();
           });
           //非模拟列表处点击隐藏模拟列表
           //$(document)点击事件不兼容部分移动设备
           $('html,body').click(function(e){
                if(e.target.className.indexOf(iSet.curSCls)==-1){
                  thisSelect.hide();
                  $('.'+iSet.selectBoxCls).css('zIndex',iSet.zindex);
              }
           });
           //取消模块列表处取消默认事件
           thisSelect.click(function(e){
              e.stopPropagation();
           });
        });
    }
})(jQuery);

如何调用?
1、在页面中引入上面的插件代码;
2、$(select).iSimulateSelect({…});
3、相关参数及功能,请参考插件中的注释说明。

若在ie6下,同页面中有未调用插件元素,为避免被原生select遮盖,请下载bgiframe插件,并在代码中关于bgiframe调用的公位置取消注释。另外,在调用插件的select后面插入的是一个相对定位元素,如果select前后还有其他行内元素,尽量使用float定位,或者给select外围加一个容器再进行绝对定位。

点击加减或手动改变表单值(支持键盘控制)的jQuery插件

大多电子商务网站购物车页面,都有通过点击来增减商品数量的功能,特地根据常规功能写了一个jQuery插件。
购物车常用功能
实现的功能:
1、可通过点击增加或减小定义的input值;
2、可手动输入input值,且支持键盘控制:上右为加,下左为减;
3、可设定表单的最大最小值,并对input值进行了非负整数判断;
4、定义了回调函数,输出表单最后的input值和所改变input的索引值。也可在插件中稍稍修改一下,把想返回的值传到一个hidden input中;
点此查看样列
jQuery代码:

$.fn.iVaryVal=function(iSet,CallBack){
	/*
	 * Minus:点击元素--减小
	 * Add:点击元素--增加
	 * Input:表单元素
	 * Min:表单的最小值,非负整数
	 * Max:表单的最大值,正整数
	 */
	iSet=$.extend({Minus:$('.J_minus'),Add:$('.J_add'),Input:$('.J_input'),Min:0,Max:20},iSet);
	var C=null,O=null;
	//插件返回值
	var $CB={};
	//增加
	iSet.Add.each(function(i){
		$(this).click(function(){
			O=parseInt(iSet.Input.eq(i).val());
			(O+1<=iSet.Max) || (iSet.Max==null) ? iSet.Input.eq(i).val(O+1) : iSet.Input.eq(i).val(iSet.Max);
			//输出当前改变后的值
			$CB.val=iSet.Input.eq(i).val();
			$CB.index=i;
			//回调函数
			if (typeof CallBack == 'function') {
                CallBack($CB.val,$CB.index);
            }
		});
	});
	//减少
	iSet.Minus.each(function(i){
		$(this).click(function(){
			O=parseInt(iSet.Input.eq(i).val());
			O-1<iSet.Min ? iSet.Input.eq(i).val(iSet.Min) : iSet.Input.eq(i).val(O-1);
			$CB.val=iSet.Input.eq(i).val();
			$CB.index=i;
			//回调函数
			if (typeof CallBack == 'function') {
				CallBack($CB.val,$CB.index);
		  	}
		});
	});
	//手动
	iSet.Input.bind({
		'click':function(){
			O=parseInt($(this).val());
			$(this).select();
		},
		'keyup':function(e){
			if($(this).val()!=''){
				C=parseInt($(this).val());
				//非负整数判断
				if(/^[1-9]\d*|0$/.test(C)){
					$(this).val(C);
					O=C;
				}else{
					$(this).val(O);
				}
			}
			//键盘控制:上右--加,下左--减
			if(e.keyCode==38 || e.keyCode==39){
				iSet.Add.eq(iSet.Input.index(this)).click();
			}
			if(e.keyCode==37 || e.keyCode==40){
				iSet.Minus.eq(iSet.Input.index(this)).click();
			}
			//输出当前改变后的值
			$CB.val=$(this).val();
			$CB.index=iSet.Input.index(this);
			//回调函数
			if (typeof CallBack == 'function') {
                CallBack($CB.val,$CB.index);
            }
		},
		'blur':function(){
			$(this).trigger('keyup');
			if($(this).val()==''){
				$(this).val(O);
			}
			//判断输入值是否超出最大最小值
			if(iSet.Max){
				if(O>iSet.Max){
					$(this).val(iSet.Max);
				}
			}
			if(O<iSet.Min){
				$(this).val(iSet.Min);
			}
			//输出当前改变后的值
			$CB.val=$(this).val();
			$CB.index=iSet.Input.index(this);
			//回调函数
			if (typeof CallBack == 'function') {
                CallBack($CB.val,$CB.index);
            }
		}
	});
}

若有bug,请以评论形式及时反馈~


渔夫科技