KnockoutJS的单击和双击

16
我想要能够将单击和双击事件绑定到文本。我知道可以使用

data-bind ="event: { dblclick: doSomething }

我需要实现双击事件,但同时也需要在单击事件上执行不同的功能。有什么建议?


1
你可以简单地为点击事件附加一个事件处理程序,但这将导致大多数浏览器仅触发点击事件(如果您双击,则会触发两次)。这是JavaScript的一般限制。您需要在单击事件处理程序中执行一些技巧,以“模拟”双击事件:https://dev59.com/i2w15IYBdhLWcg3w4_7k - Niko
5个回答

27
<div data-bind="singleClick: clicked, event : { dblclick: double }">
    Click Me
</div>

这将过滤掉既是单击又是双击的单击事件。

ko.bindingHandlers.singleClick= {
    init: function(element, valueAccessor) {
        var handler = valueAccessor(),
            delay = 200,
            clickTimeout = false;

        $(element).click(function() {
            if(clickTimeout !== false) {
                clearTimeout(clickTimeout);
                clickTimeout = false;
            } else {        
                clickTimeout = setTimeout(function() {
                    clickTimeout = false;
                    handler();
                }, delay);
            }
        });
    }
};

这里有一个演示。


我终于让它工作了,但速度慢得难以置信。有什么想法吗? - user736893
只需将延迟变量更改为一个更可接受的数字即可。 - Ryan Bennett

12

以上答案非常有帮助,但没有给出我认为OP想要的确切解决方案:一种简单的Knockout绑定,允许独占单击和双击事件。我知道这篇文章已经是一年前的,但今天在寻找相同的东西时发现了它,因此我发布了这个答案以防对他人有用。

以下示例似乎符合OP的要求,并可能为某人节省时间(免责声明:有限的跨浏览器测试)。 JSFiddle:http://jsfiddle.net/UxRNy/

此外,还有一些问题,例如是否应首先使用它(移动浏览器,减慢页面速度,可访问性等)- 但这是另一个帖子(例如https://ux.stackexchange.com/questions/7400/should-double-click-be-avoided-in-web-applications

示例视图用法:

<div data-bind="singleOrDoubleClick: { click: singleClick, dblclick: doubleClick }">
    Click or double click me...
</div>

绑定:

ko.bindingHandlers.singleOrDoubleClick= {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var singleHandler   = valueAccessor().click,
            doubleHandler   = valueAccessor().dblclick,
            delay           = valueAccessor().delay || 200,
            clicks          = 0;

        $(element).click(function(event) {
            clicks++;
            if (clicks === 1) {
                setTimeout(function() {
                    if( clicks === 1 ) {
                        // Call the single click handler - passing viewModel as this 'this' object
                        // you may want to pass 'this' explicitly
                        if (singleHandler !== undefined) { 
                            singleHandler.call(viewModel, bindingContext.$data, event); 
                        }
                    } else {
                        // Call the double click handler - passing viewModel as this 'this' object
                        // you may want to pass 'this' explicitly
                        if (doubleHandler !== undefined) { 
                            doubleHandler.call(viewModel, bindingContext.$data, event); 
                        }
                    }
                    clicks = 0;
                }, delay);
            }
        });
    }
};

上面的代码是由上述示例和这里的示例https://gist.github.com/ncr/399624结合而成的 - 我只是合并了两个解决方案。


这个解决方案对我来说存在的问题是,单击直到检测到双击的延迟结束后才被处理。由于我想要单击选择一行并双击打开详细信息,这会导致在选择行时出现0.2秒的延迟,这是不可取的。对于许多情况来说可能还可以接受。 - dhochee
@dhochee - 听起来你只是想要一个带有标志的单击绑定,你并不真正需要双击作为一个独立的事件。这对我非常有效,谢谢Chris。 - Hayden Crocker

5

@madcapnmckay提供了一个很好的答案,下面是一个修改后的版本,使用相同的思路来提供双击功能。通过使用最新版本的knockout,vm作为上下文传递给处理程序。这可以与单击同时工作。

<div data-bind="doubleClick: clicked">
    Double click Me
</div>

--

ko.bindingHandlers.doubleClick= {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var handler = valueAccessor(),
            delay = 200,
            clickTimeout = false;

        $(element).click(function() {
            if(clickTimeout !== false) {
                handler.call(viewModel);
                clickTimeout = false;
            } else {        
                clickTimeout = setTimeout(function() {
                    clickTimeout = false;
                }, delay);
            }
        });
    }
};

这对我不起作用。在handler.call()上我得到了类似“未定义的没有方法:call()”的东西。 - user736893
我喜欢这个解决方案,但它没有将普通模型和事件参数传递给您的函数。 - Grandizer
好的,我找到了解决办法,让模型和事件属性像普通KO事件一样显示出来。只需要将 $(element).click(function() { 更改为 $(element).click(function(e) { 请注意e,然后将 handler.call(viewModel); 更改为 handler.call(this, bindingContext.$data, e); 从KO 3.0开始, bindingContext.$data 是KO首选的方式,可以参考这里 - Grandizer

4

首先,我不建议完全使用click绑定。相反,您应该使用jQuery中的"click""dblclick"处理程序:

$(someParentElement).on('click', 'your span selector', function (event) {
    var myViewModelFragment = ko.dataFor(this);
    // your code here
});

$(someParentElement).on('dblclick', 'your span selector', function (event) {
    var myViewModelFragment = ko.dataFor(this);
    // your code here
});

编辑:参见Niko的建议,关于支持单击和双击。基本上,您需要手动计算点击次数,并相应地调用不同的函数。我以为jQuery会为您处理这个问题,但不幸的是,它没有。


3
为什么你不推荐那个? - Niko
主要是因为它的行为类似于普通的JS onclick 属性。它直接将事件处理程序附加到元素上。这有两个问题。首先,如果你有许多这样的元素,你最终会得到许多处理程序。JS运行时必须单独优化它们,并增加内存使用量。其次,如果你通过Knockout click 将处理程序附加到某个元素上,而该元素具有带有_live_ click 事件的子元素,则ko-click将遮盖live-click。Live click不会被调用,你将无法获得正确的行为。 - Andrei Андрей Листочкин
8
@Andrew - 我不会说Steve推荐这样做。他为你提供了两种方法的工具。但是,他反对内联函数类型。我接受你关于处理程序数量的说法。然而,你的技术缺点是它会破坏视图模型和视图之间的关注点分离。我建议使用点击事件处理单个项目并编写自定义委托绑定,该绑定可以放置在父DOM元素上。这样我们就能兼顾两者并保持分离。 - madcapnmckay
2
我不下投票的唯一原因是它基于较旧版本的knockout。如果jquery可用,新版本默认使用jquery事件。还有一个设置可以将系统切换到本机事件。 - bug-a-lot
@bug-a-lot 嗯,随意编辑答案。我不再使用 Knockout,所以我不知道团队做出的最新改进。 - Andrei Андрей Листочкин
显示剩余6条评论

0
这是我对问题的编程解决方案:

var ViewModel = function() {
  var self = this;
  this.onSingleClick = function() {
    self.lastTimeClicked = undefined;
    console.log('Single click');
  };

  this.onDoubleClick = function() {
    console.log('Double click');
  };

  this.timeoutID = undefined;

  this.lastTimeClicked = undefined;
  this.clickDelay = 500;
  this.clickedParagraph = function(viewModel, mouseEvent) {
    var currentTime = new Date().getTime();
    var timeBetweenClicks = currentTime - (viewModel.lastTimeClicked || currentTime);
    var timeToReceiveSecondClick = viewModel.clickDelay - timeBetweenClicks;

    if (timeBetweenClicks > 0 && timeBetweenClicks < viewModel.clickDelay) {
      window.clearTimeout(viewModel.timeoutID); // Interrupt "onSingleClick"
      viewModel.lastTimeClicked = undefined;
      viewModel.onDoubleClick();
    } else {
      viewModel.lastTimeClicked = currentTime;
      viewModel.timeoutID = window.setTimeout(viewModel.onSingleClick, timeToReceiveSecondClick);
    }
  };
};

ko.applyBindings(new ViewModel(), document.getElementById("myParagraph"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-debug.js"></script>
<p id="myParagraph" data-bind="click: clickedParagraph">Click me</p>

每次“双击”都是由两次单击创建的。如果存在“双击”,我们必须确保第一次单击的事件处理程序不会被执行(这就是为什么我使用window.setTimeoutwindow.clearTimeout)。在设置计时器时,我们还必须考虑到元素上的第一次单击可能已经是双击。

在我的代码中,我将clickDelay设置为500毫秒。因此,在500毫秒内进行两次点击将被识别为“双击”。您也可以增加此值以测试我的clickedParagraph函数的行为。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接