项目赶着十一上线,老是加班;已经连续个把月单休了。昨天加班今天没事干;居然跑到公司写插件,也是醉了;哈哈哈。废话不多少,下面进入正题;

为大家带来一个小插件叫“联想输入”十分类似jqueryjquery.autocomplete(没用过的赶紧去恶补一下,谢谢。)项目也用到了类似的功能;但是业务强相关的;autocomplete就显得有点不合适了;所以周末没事,写一个;下周刚好能用上。哈哈哈。



先上示例代码;然后讲讲为什么这样设计 

/*
 *联想输入插件
 *示例:
 * var config = {obj:input, 
 *   top:top + eHeight + 2, 上边距
 *   left:left,左边距
 *   width:740,下拉组件的长度
 *   skin:"",//定制皮肤
 *   selectHandler:selectHandler,
 *   changeHandler:changeHandler,//监听对象值变动回调;该函数需要返回对象数组或者以拼接好的html片段(建议使用li标签);
 *   contentType:1,
 *   maxCount:0//最大展示数量,超过时,出现滚动条
 *  }
 * 
 *var autoCompleteDialog = new window.AutoComplete().show(config);
 *
 * 组件支持两种自定义事件,change;select 同时,支持一个事件绑定多个回调函数,如下:
 * autoCompleteDialog.on("select",function(argument) {
 *   console.log(" on select ")
 * }).on("change",function(argument) {
 *  console.log(" on change ")
 *});
 */


Obj,不用多言;就是监听的对象;

Top,联想输入下拉组件的上边距

Left,联想输入下拉组件左边,这个参数主要是针对有些地方,左边距不一定要与输入框做对齐的场景,比如:下图的的场景

wKiom1X-S0Hx0Ac0AACKYcc6p7I248.jpg

width,联想输入下拉组件宽度,这个参数主要是针对有些地方,左边距不一定要与输入框做对齐的场景,比如:input设置了padding-left,或者right;或者如下图这样:(默认监听对象的宽度)

wKioL1X-TY7ypnoxAACKYcc6p7I294.jpg

Skin:为定制皮肤,预留一个接口;防止有需求说默认下拉效果不合理;或者多套皮肤的场景。

selectHandler:null,下拉选中后,回调函数;一般场景都是将选中值,填充到输入框中;但是大家应该见过,类似邮箱的联想输入-下拉展示接收人昵称,选中后,输入框填充对应的邮箱地址。

 

changeHandler:null,//监听对象值变化后,回调函数;一般值变动,需要拿到关键字ajax查询,然后返回数据;这地方,用户可以返回两种数据类型

1.数组对象如:

var data = [{key:1,value:"aaa"},{key:2,value:"bbb"},{key:3,value:"ccc"}]

2.字符串,由于有些业务场景下拉选项要进行一些颜色的强调,或者显示默认的数据;比如,下图所示:

wKioL1X-TdiT8bLfAACKYcc6p7I699.jpg

或者时,需要给每一项添加编号等业务场景,如:

 wKiom1X-S9mxYF9tAAF08INulBc122.jpg

 

contentType:这个属性和changeHandler的返回值一起使用;

结果集拼接方式:

0,自定义拼装;(建议使用li标签)

1,返回对象数组,格式[{key:1,value:"111"},{key:2,value:"222"}]

maxCount/最大展示数量,超过时,出现滚动条(为0,不设置时,无限下拉,永远不出现滚动条)


源码如下:

/*
 *联想输入插件   create by jiaxiangjun   2015-9-20 
 *示例:
 * var config = {obj:input, 
 *   top:top + eHeight + 2, 上边距
 *   left:left,左边距
 *   width:740,下拉组件的长度
 *   skin:"",//定制皮肤
 *   selectHandler:selectHandler,
 *   changeHandler:changeHandler,//监听对象值变动回调;该函数需要返回对象数组或者以拼接好的html片段(建议使用li标签);
 *   contentType:1,
 *   maxCount:0//最大展示数量,超过时,出现滚动条
 *  }
 * 
 *var autoCompleteDialog = new window.AutoComplete().show(config);
 *
 * 组件支持两种自定义事件,change;select 同时,支持一个事件绑定多个回调函数,如下:
 * autoCompleteDialog.on("select",function(argument) {
 *   console.log(" on select ")
 * }).on("change",function(argument) {
 *  console.log(" on change ")
 *});
 */

(function(){
  function AutoComplete() {

    if (this instanceof AutoComplete) {
      this.config = {
        obj:null,//监听对象
        top:0,//下拉上边距
        left:0,//下拉左边距
        width:0,//下拉组件宽度
        skin:"",//皮肤
        selectHandler:null,//下拉选中后,回调函数
        changeHandler:null,//监听对象值变化后,回调函数
        contentType:0, //结果集拼接方式:0,自定义拼装;1,返回对象数组,格式[{key:1,value:"111"},{key:2,value:"222"}]
        maxCount:0//最大展示数量,超过时,出现滚动条
        }
    } else {
      return new AutoComplete();
    }
    
  }

  AutoComplete.prototype = new window.Widget();

  AutoComplete.prototype.hide = function(argument) {
    $(".u-autocom-resSelector").hide();
  }
  AutoComplete.prototype.destroy = function(argument) {
    $(".u-autocom-resSelector").remove();
  }
  AutoComplete.prototype.show = function(config) {
      var CFG = $.extend(this.config, config);//合并对象
      if (!CFG.obj) {
        return;
      }
      var searchElement = CFG.obj;
      CFG.width = CFG.width ? CFG.width : CFG.obj.width();//默认宽度为监听对象宽度
      var that = this;
      //input change兼容性处理
     if(window.addEventListener){
          searchElement[0].oninput = resultHandler;
     } else {
       searchElement[0].onpropertychange = resultHandler;
     }

     //点击页面其他部分隐藏
     $("body").on("click", hideResHandler);
     function hideResHandler() {
          var $this = $(this);
          if($this.hasClass("res_li")){
              return;
          }
          $(".u-autocom-resSelector").hide();
     }

    $("body").on("click",".u-autocom-resSelector li" ,selectHandler);
    /*选择下拉选择值*/
   function selectHandler(event) {
      if(CFG.selectHandler && "function" === typeof CFG.selectHandler){ 
          CFG.selectHandler();
          that.fire("select", "select data");
          $(".u-autocom-resSelector").hide();  
          return;
      }
      var $this = $(this);
      var liVal = $this.text();
      CFG.obj.val(liVal);
      $(".u-autocom-resSelector").hide();  
   }


   function resultHandler(event) {
        var $this = $(this);
        var val = $this.val();
        var resStr = "";
        if (0 === CFG.contentType) {
            resStr = CFG.changeHandler && CFG.changeHandler(val);
        }
        if (1 === CFG.contentType) {
            var data = CFG.changeHandler && CFG.changeHandler(val);
            if ("[object Array]" !== Object.prototype.toString.call(data)) {//type=1,时,返回数据必须是数组
              return;
            }
            resStr = assembleData(data);
        }
        that.fire("change", "change data");
        appendResult(resStr);
    }

    function appendResult(resStr) {
        if(!resStr || "" === resStr) {
            return;
        }
        var selector = $(".u-autocom-resSelector");
        if(0 === selector.length) {
            selectorStr = '<div class="u-autocom-resSelector">' + resStr + '</div>';
            $("body").append(selectorStr);
            selector = $(".u-autocom-resSelector");
        } else {
            selector.empty();
            selector.append(resStr);
        }
        var maxHeight = "none";
        var overflowY = "hidden";

        //如果设置了最大展示数量,滚动显示
        if(CFG.maxCount) {
           var liHeight =selector.children("li").eq(0).height();
           maxHeight = liHeight*CFG.maxCount;
           overflowY = "scroll";

        }
        selector.css({
          "top":CFG.top,
          "left":CFG.left,
          "width":CFG.width,
          "maxHeight":maxHeight,
          "overflowY":overflowY
         });

        "" !== CFG.skin && selector.addClass(CFG.skin);
        selector.show();
    }

    //拼装数据成html片段
    function assembleData(data) {
      var len = data.length;
      var resStr = "";
        for(var i = 0; i < len; i++) {
            resStr = resStr + '<li class="res_li" data-value="' + data[i].key + '">' + data[i].value + '</li>'
        }  
        return resStr;
    }

/*    if(CFG.selectHandler){
      this.on("select", CFG.selectHandler);
    }

    if(CFG.selectHandler){
      this.on("change", CFG.changeHandler);
    }*/

    return this;
  };
	  window.AutoComplete = AutoComplete;
})(window);代码一共提供了三个接口:

展示,隐藏,销毁;不断说,代码一看就懂。


 

另外需要说明的是,为了进一步分层和抽象;也为了后期为多组件开发提供基础,抽象出来Widget类。提供了四个接口:

On绑定自定义事情;可以为一个事件,绑定多个函数

Fire执行绑定自定义事情;一个事件,一次执行为其绑定的函数 

另外的destroybulid等具体插件具体实现;

 

源码如下:

/**
 *Widget抽象类   create by jiaxiangjun   2015-9-20 
 * 
 * @return {[Widget抽象类]}
 */
(function(){

  function Widget() {
    if(this instanceof Widget) {
        this.handlers = {};
    } else {
      return new Widget();
    }
  }

  Widget.prototype = 
  {
    //绑定自定义事件
    on:function(type,handler) {
      if ("undefined" === typeof this.handlers[type]) {
        this.handlers[type] = [];
      }
      this.handlers[type].push(handler);
      return this;
    },
    //依次触发自定义事件
    fire:function(type, data) {
      if ("[object Array]" === Object.prototype.toString.call(this.handlers[type])){
        var handlers = this.handlers[type];
        var len = handlers.length;
        for (var i = 0; i < len; i++) {
          handlers[i]();
        }
        return this;
      }
    },
    //销毁函数
    destroy:function(argument) {
      // to do smething
    },
    hide:function(argument) {
      // to do smething
    },
    build:function(argument) {
      // to do smething
    }

  }

	window.Widget = Widget;
})(window);


调用示例:

	//联想输入
	var input = $("#keywordInput");
	var top = input.offset().top;
    var left = input.offset().left;
    var eHeight =  input.height();
   

	var config = {obj:input, 
		top:top + eHeight + 2, 
		left:left,
		width:740,
		selectHandler:selectHandler,
		changeHandler:changeHandler,
		contentType:1,
		maxCount:7
	}

	var autoCompleteDialog = new window.AutoComplete().show(config);
	autoCompleteDialog.on("select",function(argument) {
		console.log(" on select ")
	}).on("change",function(argument) {
		console.log(" on change ")
	});

	function selectHandler() {
		 var $this = $(this);
		 console.log(" selectHandler ")
		 var e  = event || window.event;
		 var target = e.target || e.srcElement;
		 var liVal = $(target).text();
	     input.val(liVal);
	}
	function  changeHandler(keyword) {
	//ajax请求数据,此处只为演示效果,请自行实现ajax请求过程
		 console.log(" changeHandler ");
		  var resStr = '<ul>'+
                     '<li class="res_li">搜<span class="keyword-tips">' + keyword + '</span></li>'+
                      '<li class="res_li">搜<span class="keyword-tips">' + keyword + '</span></li>'+
                      '<li class="res_li">搜<span class="keyword-tips">' + keyword + '</span></li>'+
                      '<li class="res_li">搜<span class="keyword-tips">' + keyword + '</span></li>'+
                      '<li class="res_li">搜<span class="keyword-tips">' + keyword + '</span></li>'+
                     '<li class="res_li">搜<span class="keyword-tips">' + keyword + '</span></li>'+
                     '<li class="res_li">其他<span class="keyword-tips">@蝶讯网</span>其他</li>'+
                '</ul>';
                var data = [
		                {key:1,value:"aaa"},{key:2,value:"bbb"},{key:3,value:"ccc"},
		                {key:1,value:"aaa"},{key:2,value:"bbb"},{key:3,value:"ccc"},
		                {key:1,value:"aaa"},{key:2,value:"bbb"},{key:3,value:"ccc"}
	                ]
                return data;//返回data或者,注意需要修改对应的contentType
	}

效果如下:wKioL1X-TzDhdyvQAACRh2bYa24401.jpg



自我感觉这个插件还有很多优化的空间,后期不断完善。


对了,还有CSS代码;使用的时less;直接上less代码;没使用过的自己改成css.比较简单不解释。

	//关键字,联想输入样式
			.u-autocom-resSelector{
			    position:absolute;
			    width:210px;
			    min-height:100px;
			    display:none;
			    border:1px #DBDBDB solid;
			    border-top:none;
			    background: white;
			    z-index: 999;
			    li{
			        height:30px;
			        line-height: 30px;
			    }
			    li:hover{
			          background: rgb(225, 225, 225);
			          cursor: pointer;
			    }
			    .keyword-tips{color:#F4AA09;}
			}