使ul列表像选择输入一样工作

时间:2013-03-29 09:46:31

标签: javascript jquery html

出于样式原因,我希望能够将ul列表用作选择表单元素。

我可以用我的代码填充一个隐藏的输入(不包含在这个jsfiddle中),到目前为止一直很好。但是现在我试图让我的ul在按下键盘时表现得像选择输入,或者使用鼠标。

在我之前的问题中,我遇到了键盘控件的一些问题。他们现在已经修好了。请参阅:Autoscroll on keyboard arrow up/down

剩下的问题是按下键盘按钮时不会忽略鼠标。这导致“悬停效果”首先听取键盘输入,但是立即转到鼠标并选择此项目作为选择。

这可以在我的jsfiddle示例中看到:http://jsfiddle.net/JVDXT/3/

我的javascript代码:

// scrollTo plugin 
  $.fn.scrollTo = function( target, options, callback ){
  if(typeof options == 'function' && arguments.length == 2){ callback = options; options = target; }
  var settings = $.extend({
    scrollTarget  : target,
    offsetTop     : 100,
    duration      : 0,
    easing        : 'linear'
  }, options);
  return this.each(function(){
    var scrollPane = $(this);
    var scrollTarget = (typeof settings.scrollTarget == "number") ? settings.scrollTarget : $(settings.scrollTarget);
    var scrollY = (typeof scrollTarget == "number") ? scrollTarget : scrollTarget.offset().top + scrollPane.scrollTop() - parseInt(settings.offsetTop);
    scrollPane.animate({scrollTop : scrollY }, parseInt(settings.duration), settings.easing, function(){
      if (typeof callback == 'function') { callback.call(this); }
    });
  });
}


//My code
//The function that is listing the the mouse
jQuery(".btn-group .dropdown-menu li").mouseover(function() {
        console.log('mousie')
        jQuery(".btn-group .dropdown-menu li").removeClass('selected');
        jQuery(this).addClass('selected');
})  

//What to do when the keyboard is pressed
jQuery(".btn-group").keydown(function(e) {
    if (e.keyCode == 38) { // up
        console.log('keyup pressed');
        var selected = jQuery('.selected');
        jQuery(".btn-group .dropdown-menu li").removeClass('selected');
        if (selected.prev().length == 0) {
            selected.siblings().last().addClass('selected');
        } else {
            selected.prev().addClass('selected');
            jQuery('.btn-group .dropdown-menu').scrollTo('.selected');
        }
    }
    if (e.keyCode == 40) { // down
        console.log('keydown');
        var selected = jQuery('.selected');
        jQuery(".btn-group .dropdown-menu li").removeClass('selected');
        if (selected.next().length == 0) {
            selected.siblings().first().addClass('selected');
        } else {
            selected.next().addClass('selected');
            jQuery('.btn-group .dropdown-menu').scrollTo('.selected');
        }
    }
});

所以任何人都可以教我如何在按下键盘按钮时igonore鼠标,但是当用户再次触摸鼠标时会将鼠标列入鼠标。与默认选择输入表单字段一样。

更新

这是一个新的jsfiddle

7 个答案:

答案 0 :(得分:13)

检查出来:

http://jsfiddle.net/coma/9KvhL/25/

(function($, undefined) {

    $.fn.dropdown = function() {

        var widget = $(this);
        var label = widget.find('span.valueOfButton');
        var list = widget.children('ul');
        var selected;
        var highlighted;

        var select = function(i) {

            selected = $(i);
            label.text(selected.text());

        };

        var highlight = function(i) {

            highlighted = $(i);

            highlighted
            .addClass('selected')
            .siblings('.selected')
            .removeClass('selected');
        };

        var scroll = function(event) {

            list.scrollTo('.selected');

        };

        var hover = function(event) {

            highlight(this);

        };

        var rebind = function(event) {

            bind();

        };

        var bind = function() {

            list.on('mouseover', 'li', hover);
            widget.off('mousemove', rebind);

        };

        var unbind = function() {

            list.off('mouseover', 'li', hover);
            widget.on('mousemove', rebind);

        };

        list.on('click', 'li', function(event) {

            select(this);

        });

        widget.keydown(function(event) {

            unbind();

            switch(event.keyCode) {

                case 38:
                    highlight((highlighted && highlighted.prev().length > 0) ? highlighted.prev() : list.children().last());

                    scroll();
                    break;

                case 40:
                    highlight((highlighted && highlighted.next().length > 0) ? highlighted.next() : list.children().first());

                    scroll();
                    break;

                case 13:
                    if(highlighted) {

                        select(highlighted);

                    }
                    break;

            }

        });

        bind();

    };

    $.fn.scrollTo = function(target, options, callback) {

        if(typeof options === 'function' && arguments.length === 2) {

            callback = options;
            options = target;
        }

        var settings = $.extend({
            scrollTarget  : target,
            offsetTop     : 185,
            duration      : 0,
            easing        : 'linear'
        }, options);

        return this.each(function(i) {

            var scrollPane = $(this);
            var scrollTarget = (typeof settings.scrollTarget === 'number') ? settings.scrollTarget : $(settings.scrollTarget);
            var scrollY = (typeof scrollTarget === 'number') ? scrollTarget : scrollTarget.offset().top + scrollPane.scrollTop() - parseInt(settings.offsetTop, 10);

            scrollPane.animate({scrollTop: scrollY}, parseInt(settings.duration, 10), settings.easing, function() {

                if (typeof callback === 'function') {

                    callback.call(this);
                }

            });

        });

    };

})(jQuery);

$('div.btn-group').dropdown();

关键是取消绑定鼠标悬停并在鼠标移动时重新绑定。

我通过使用闭包函数重构了一点,将逻辑添加到名为下拉的jQuery方法中,这样您就可以重复使用它,使用switch而不是一堆if等等。

嗯,有很多插件可以将一个选择转换为一个列表:

http://ivaynberg.github.io/select2/

http://harvesthq.github.io/chosen/

http://meetselva.github.io/combobox/

我也有我的!(使用与http://uniformjs.com相同的技巧为触控设备做好准备)

https://github.com/coma/jquery.select

但是这个问题是关于采用HTML并使其行为像选择避免悬停问题一样吗?

答案 1 :(得分:2)

这是一个解决方案,我正在使用mousemove,因为这样可以确保在鼠标再次开始移动时选择正确的列表项,mouseover它只会开始选择一个列表项在输入新的清单项目时:

使用匿名函数并为其命名:

function mousemove() {
  console.log('mousie')
  jQuery(".btn-group .dropdown-menu li").removeClass('selected');
  jQuery(this).addClass('selected');
}

声明一个全局变量mousemoved,指示鼠标是否已移过文档并将其设置为false,在文档上mousemove上,将其设置为true并且将mousemove函数附加到列表项上的mousemove事件。

var mousemoved = false;

jQuery(document).mousemove(function() {
  if(!mousemoved) {
    $('.btn-group .dropdown-menu li').mousemove(mousemove);  
    mousemoved = true;    
  }
})  

一旦按下某个键(在keydown事件开始时),使用jQuery的.off()方法删除列表项上的mousemove事件(如果存在),并设置{{ 1}}到mousemoved以确保在再次移动鼠标之前不会再次附加false事件。

mousemove

这是 jsFiddle

答案 2 :(得分:1)

我尝试通过阻止自动滚动,在li上添加tabindex,将焦点设置为活动以及使用标志来禁止鼠标来解决您的问题。

固定小提琴: http://jsfiddle.net/8nKJT/ [修复了Chrome中的问题]

http://jsfiddle.net/RDSEt/

问题在于,scroll上触发的自动keydown会再次触发mouseenter混淆li的选择。

  

注意:与其他方法的差异(这里的答案)我注意到滚动每个按键而不是仅在到达顶部或底部后滚动(正常行为) )。当你并排检查演示时,你会感受到不同。

下面是更改说明列表和一个解释如何修复的小型演示,

  • 使用e.preventDefault() http://jsfiddle.net/TRkAb/ [按ul li上的向上/向下按下箭头/向下箭头触发的防止自动滚动,现在在{{3}上尝试相同} [不再滚动]
  • keydown上添加了一个标记以禁止keydown上的mouseevents,此标志已重置onmousemove
  • tabindex添加到li,您可以使用.focus功能设置焦点。 [更多信息:http://jsfiddle.net/TRkAb/1/]
  • 调用.focus会自动滚动到所需位置。 (不需要scrollTo插件)https://stackoverflow.com/a/6809236/297641 - [查看它如何滚动到焦点上的li]

查看演示和代码更改(添加了一些改进)并告诉我。

还要感谢你的问题,我在我写的一个插件中注意到了这个问题以及其他一些问题。

几个月前我写了一个插件来过滤选项,也就像下拉一样。

DEMO:http://jsfiddle.net/39h3J/ [将filterType更改为''以关闭过滤功能]

原始插件页面为http://jsfiddle.net/nxmBQ/

..更多

答案 3 :(得分:0)

如果最近在窗口小部件上按下了keydown,则可以使用全局忽略mouseover事件。例如:

var last_key_event = 0;

jQuery(".btn-group .dropdown-menu li").mouseover(function() {
    if ((new Date).getTime() > last_key_event + 1000) {
        console.log('mousie')
        jQuery(".btn-group .dropdown-menu li").removeClass('selected');
        jQuery(this).addClass('selected');
    }
});

然后keydown处理程序可以设置处理时间以避免与鼠标交互:

//What to do when the keyboard is pressed
jQuery(".btn-group").keydown(function(e) {
    last_key_event = (new Date).getTime();
    ...
});

last_key_event变量分别用于每个小部件而不是全局小部件可能是有意义的。

答案 4 :(得分:0)

你可以尝试这个解决方案。如果坐标未更改(自上次mousemove事件以来),它将忽略mousemove事件

//The function that is listing the the mouse
var lastOffsets = "";

jQuery(".btn-group .dropdown-menu li").mouseover(function(e) {
        var curOffsets = e.clientX+":"+e.clientY;
        if(curOffsets == lastOffsets) {
           // mouse did not really move
            return false;
        }

        lastOffsets = curOffsets;

        ///// rest of your code
}

更新了小提琴以验证这是否是您所追求的: http://jsfiddle.net/pdW75/1/

答案 5 :(得分:0)

方法合理的解决方案应该模仿其他用于相似目的的UI元素的行为。在所有选中的系统(Windows,Linux,主要浏览器)上,下拉框的行为如下:

  

将鼠标悬停在项目上会突出显示它。按箭头键可更改所选元素,并按比例滚动。移动鼠标选择下面的元素。如果选择为空,则按 down 选择第一个元素。按向上选择最后一个元素。

解决方案code说明了我模仿所述行为的方法。这有点酷,试试吧......

其他注意事项还有许多其他选项可以抑制不需要的鼠标移动以更改所选元素。其中包括:

  • 保持最后输入法的状态。如果最后一次选择使用键盘,则将鼠标悬停在元素上将不会选择它,只需单击
  • 如果坐标没有改变指定距离,则
  • 忽略mouseover事件,例如10像素
  • 如果用户曾使用过键盘,则忽略mouseover

但是,至少对于公众可以访问的应用程序而言,始终最好坚持使用已建立的UI模式。

答案 6 :(得分:0)

显示的问题是,当鼠标悬停在扩展列表的一部分上时,使用键选择无效,因为键盘所做的选择会立即恢复到恰好位于鼠标下方的项目。 / p>

您可以解决此问题并保留所有功能,而无需执行任何复杂的条件行为或删除事件处理程序。

只需将鼠标悬停事件处理程序更改为mousemove事件处理程序即可。这样,任何键盘导航和选择都会被监听,并且只要用户使用键盘选择,就会忽略鼠标位置。只要用鼠标选择,就会听到鼠标。

这听起来微不足道,但似乎让你的JS Fiddle行为完美,鼠标和键盘之间没有任何冲突行为。像这样:

//The function that is listening to the mouse
jQuery(".btn-group .dropdown-menu li").mousemove...

(您的代码继续保持不变,只用mousemove替换mouseover)