##| suggest是什么?

  • suggest(后文统一简称为sug)的中文意思是,’建议,提议;暗示;使想起;启示’。经常被用做用户的输入提示。
  • 也就是说sug的主要功能是提示用户输入,而不是一个结果的展示框,更不是一个实时的下拉列表。(为什么要这样区别,后面会娓娓道来~)
  • 当然,用sug来做数据的校验更是不合适的。

##| sug的逻辑及会涉及的事件?

好的,我们先来梳理一下当我们使用sug时自己的操作习惯和流程。

  1. 首先我们期望,sug可以根据自己在输入框中输入删除字符,而给出不同的提示。也就是说这是一个实时动态变化的。
  2. 当我们可以根据键盘的上、下键来选择自己本来想要输入的内容,这样自己可以节省很多时间和体力嘛(其实就是懒)
  3. 当使用键盘选中后,敲回车时,sug中被选中的内容会自动跑到input输入框里。
  4. 除了使用上下键,我们可能还期望使用鼠标点击某个内容,就可以让被选中的内容跑到输入框里。
  5. 为了满足第3点,就需要当鼠标移上某个内容时,这个内容必须要与其他未选中的内容区分,否则我们也不知道是选的哪一个啊。
  6. 另外呢,我们当然还期望有比较好的用户体验啦。
    • 点击除了sug列表的其他位置时,sug会消失
    • sug列表中默认就选中第一条数据,而不是需要按一下神马键再选中第一条,多此一举。
    • 按上、下键来选择内容时,如果列表中只有1条数据,那无论怎么按上下键,就保持原状啊;如果有多条,那可以循环去按呀,从头->尾,从尾->头。

那么按照上述的逻辑,映射到代码层面上,我们会需要监听和涉及到哪些事件呢?

####|| input

  • 与ie兼容,同时监听propertychange(IE专有,好牛气的赶脚)
  • 为啥不用onchange事件呢?oninput,onpropertychange,onchange究竟有啥区别呢?
    • onpropertychange, IE专有,当前对象的属性(checked,value,selectedIndex)发生变化时,都会触发这个事件
    • oninput, 现代浏览器可用,只有当前对象的value值发生变化时,才会触发该事件
    • onchange, value值发生了变化,并且失去焦点时才会触发;并且是由鼠标或键盘改变,如果是脚本改变的值,那也不能触发该事件。
    • 还有一个onkeyup事件,这个能用来监听用户通过键盘输入的value值变化,但是如果是复制粘贴来的值,就无法检测到。

所以,综上选择oninput & onpropertychange 为监听输入框值变化的方案


####|| click

  • 被选中li节点
    • 将对应的数据添加到input中
  • sug范围外的区域
    • remove所有的sug列表

####|| keydown

在这里,值得注意的是,我们需要设置一个sug类的私有变量selectedNode,用来保存当前被选中的节点。这样在之后通过enter或click才能将被选中的值放入input中。

  • up, down, enter对应的e.keycode分别是38,40,13
  • up,down
    在这儿,需要完成两件事,(1)删除上一次selectedNode的背景,(2)保存当前被选中的节点为selectedNode。
    当然,还需要注意,如果sug列表中节点的数量大于1时,才需要完成上面2件事。否则保持现状即可。
  • enter
    在改写@小生爱 的源码中,有这样一个小bug,如果sug中有值,而sug列表自身被隐藏,enter会把默认选中的第一条选到input中。
    为了解决这个问题,在enter时间中,我首先检测当前的sug列表是否是隐藏着的,如果是,那么就不执行enter事件。也就避免了将本来默认选中的第1条数据误添加到input中。

####|| mouseover

鼠标滑过li节点时,对应的节点变为选中状态,增强用户体验。


####|| 确定sug的位置

一般我们期望的sug位置是在input输入框的正下方,所以可以确定:

  • sug的top = input输入框距当前视口顶部的距离 + input输入框本身的高度
  • sug的left = input输入框距当前视口左边的距离
  • 获得input输入框距当前视口的距离,$(input).offset()
  • input输入框本身的高度,$(input).offsetHeight

最后,附上sug列表的js代码.


##| 可改进的点

目前这样做完,在体验上还是有可改进点的(目前只想到了这一个- -.等以后想到了随时补充)。

  • blur: blur时,检测数据,并remove sug.

##| 具体写码时的坑

  • placeholder兼容
    检测如果文档创建一个新的input,其中是否具有placeholder属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    var placeholder = function (){
    // 判断是否支持placeholder
    if('placeholder' in document.createElement('input')) return false;
    // 要添加holder的表单节点
    var elInputs = $('.js-placeholder', root);
    // 初始化表单
    $.each(elInputs, function (){
    $(this).val($(this).attr('placeholder'));
    });
    // 添加事件
    elInputs.on('focus blur', function (e){
    var type = e.type;
    var elInput = $(this);
    // 表单默认值
    var initInput = elInput.attr('placeholder');
    if(type == 'focus' && elInput.val() == initInput){
    // 获取焦点 表单置空
    elInput.val('');
    }else if(type=='blur' && elInput.val()==''){
    elInput.val(initInput);
    }
    });
    };
  • 纳尼?ie6/7下,背景消失了!!!

    • 这种现象一般是由于代码里同时包含这两句,display:inline-block;text-indent: -999em

    • 原因:ie6/7并未实现真的的inline-block,它只所有具有inline-block的某些特征,是因为元素触发了hasLayout,而因此让元素拥有了inline-block的表症。

    • 如何解决呢? 无非两种,从display来考虑,从与字体相关的考虑
    • 方案一:display:block
    • 方案二:font-size:0;line-height:0
    • 方案三:line-height:900px;overflow:hidden
  • ie7,width: 100%,没宽度了?

  • 如何写出一个合适的input

    • 不要给input设置固定高度
    • 用padding撑开input的高度
    • 不要给input设置行高
    • 如果需要改变光标的高度,改变font-size的值
  • 如何弥补把sug当作查询结果集的缺点?

    • 查询已知的现有数据集,再做一次验证。
  • pm想用sug当结果显示框,结果呢?

    • 之前说到过sug本质上一个提示功能,为用户的输入起到一定的辅助功能。当遇到这样的场景时,就显得特别不合适了。
    • 背景:
      (1)input中的内容来源有两个,从sug中选中的,用户自己输入的;
      (2)当用户输入一个百分百匹配的词时,sug并不会给出提示(你都知道要输入啥了,就没有提示的必要了呀)
    • 场景:
      将用户输入框内的正确内容做为参数传给后端。我们知道sug里的内容,肯定是百分之百正确的,所以根据sug得到的内容可以直接发到后端。但是当用户输入‘望京西’时,这是一个百分百匹配的词,sug中返回的结果是空。
      这时,我们如果分辨不是从sug中得来的input内容是正确的还是错误的呢?
      也就是说,没有直接的办法可以判断‘望京西’和‘望京西乱七八糟’哪个是正确的。
      这时,使用sug作为结果显示框是不妥的,应当像上一条提到的一样,再次查询已知的现有结果集,来做验证。

##| 小技巧

  • flag && a = b
    其实这样等同于,不仅代码变得简洁了,而且更有效率了。

    1
    2
    3
    if(flag){
    a =b
    }
  • 验证是否包含在某个数据集中?Hash,对象
    obj['key'] == 1,不用循环遍历

  • 指定异步回调函数名
    1
    2
    3
    4
    5
    6
    dataType: jsonp,
    jsonpCallback: 回调函数名,
    data:{
    a : a,
    b : b
    }