Distinguish between onClick and onDoubleClick on same element to perform different actions in Dart

≡放荡痞女 提交于 2019-12-05 19:38:53
Günter Zöchbauer

I'm not sure if IE still has the event sequence explained here (no 2nd click event) https://stackoverflow.com/a/5511527/217408

If yes you can use a slightly deviated variant of Roberts solution:

library app_element;

import 'dart:html' as dom;
import 'dart:async' as async;

Duration dblClickDelay = new Duration(milliseconds: 500);
async.Timer clickTimer;

void clickHandler(dom.MouseEvent e, [bool forReal = false]) {
  if(clickTimer == null) {
    clickTimer = new async.Timer(dblClickDelay, () {
      clickHandler(e, true);
      clickTimer = null;
    });
  } else if(forReal){
    print('click');
  }
}

void dblClickHandler(dom.MouseEvent e) {
  if(clickTimer != null) {
    clickTimer.cancel();
    clickTimer = null;
  }
  print('doubleClick');
}

void main() {
  dom.querySelector('button')
    ..onClick.listen(clickHandler)
    ..onDoubleClick.listen(dblClickHandler);
}

or just use Roberts solution with the mouseUp event instead of the click event.

The problem is that your variable is not global.

var eventTypes = new List<String>();

void catchClickEvents(Event e) {
  eventTypes.add(e.type);
  Duration duration = const Duration(milliseconds: 300);
  var timeout = new Timer(duration, () => processEvents(eventTypes));
}

void processEvents(List eTypes) {
  print(eTypes);
}

There also is e.detail that should return the number of the click. You can use that, if you don't need the Internet Explorer. The problem with your list is that it grows and never gets cleared.

Let's take into account what we know: You get click events and at somepoint you have doubleclicks.

You could use a click counter here. (Or use e.detail) to skip the second click event. So you only have click and dblclick.

Now when you get a click event, you create a new timer like you did before and run the click action. If you get the dblclick event you simply run you action. This could like this:

DivElement div = querySelector('#div');
Timer timeout = null;
div.onClick.listen((MouseEvent e) {
  if(e.detail >= 2) {
    e.preventDefault();
  } else {
    if(timeout != null) {
      timeout.cancel();
    }

    timeout = new Timer(new Duration(milliseconds: 150), () => print('click'));
  }
});

div.onDoubleClick.listen((MouseEvent e) {
  if(timeout != null) {
    timeout.cancel();
    timeout = null;
  }
  print('dblclick');
});

This is the example code that works for me. If you can't rely on e.detail just us a counter and reset it after some ms after a click event.

I hope this helps you.

Regards, Robert

Your page should react on user inputs as fast as possible. If you wait to confirm double click - your onClick will become much less responsive. You can hide the problem by changing visual state of the element(for example, playing animation) after first click but it can confuse user. And it gets even worse with handheld. Also if element has to react only on onClick event, you can "cheat" and listen to onmousedown instead - it will make your UI much more responsive.

On top of all this, double click, from client to client, may have noticeably different trigger time interval and it isn't intuitive, for user, that you can double click something. You will have to bloat your interface with unnecessary hints.

edit: Added my solution. It should be fairly extensible and future proof.

import 'dart:html';
import 'dart:async';
import 'dart:math';

//enum. Kinda... https://code.google.com/p/dart/issues/detail?id=88
class UIAction {
  static const NEXT = const UIAction._(0);
  static const FULLSCREEN = const UIAction._(1);
  static const ERROR = const UIAction._(2);
  final int value;
  const UIAction._(this.value);
}

/**
 *[UIEventToUIAction] transforms UIEvents into corresponding  UIActions.
 */
class UIEventToUIAction implements StreamTransformer<UIEvent, UIAction> {

/**
 * I use "guesstimate" value for [doubleClickInterval] but you can calculate
 * comfortable value for the user from his/her previous activity.
 */
  final Duration doubleClickInterval = const Duration(milliseconds: 400);

  final StreamController<UIAction> st = new StreamController();

  Stream<UIAction> bind(Stream<UIEvent> originalStream) {
    int t1 = 0,
        t2 = 0;
    bool isOdd = true;
    Duration deltaTime;
    originalStream.timeout(doubleClickInterval, onTimeout: (_) {
      if ((deltaTime != null) && (deltaTime >= doubleClickInterval)) {
        st.add(UIAction.NEXT);
      }
    }).forEach((UIEvent uiEvent) {
      isOdd ? t1 = uiEvent.timeStamp : t2 = uiEvent.timeStamp;
      deltaTime = new Duration(milliseconds: (t1 - t2).abs());
      if (deltaTime < doubleClickInterval) st.add(UIAction.FULLSCREEN);
      isOdd = !isOdd;
    });
    return st.stream;
  }
}


void main() {

  //Eagerly perform time consuming tasks to decrease the input latency.
  Future NextImageLoaded;
  Future LargeImageLoaded;
  element.onMouseDown.forEach((_) {
    NextImageLoaded = asyncActionStub(
        "load next image. Returns completed future if already loaded");
    LargeImageLoaded = asyncActionStub(
        "load large version of active image to show in fullscreen mode."
            "Returns completed future if already loaded");
  });


  Stream<UIEvent> userInputs = element.onClick as Stream<UIEvent>;

  userInputs.transform(new UIEventToUIAction()).forEach((action) {
    switch (action) {
      case UIAction.FULLSCREEN:
        LargeImageLoaded.then( (_) =>asyncActionStub("fullscreen mode") )
        .then((_) => print("'full screen' finished"));
        break;
      case UIAction.NEXT:
        NextImageLoaded.then( (_) =>asyncActionStub("next image") )
        .then((_) => print("'next image' finished"));
        break;
      default:
        asyncActionStub("???");
    }
  });
}


final DivElement element = querySelector("#element");

final Random rng = new Random();
final Set performed = new Set();
/**
 *[asyncActionStub] Pretends to asynchronously do something usefull.
 * Also pretends to use cache.
 */
Future asyncActionStub(String msg) {
  if (performed.contains(msg)) {
    return new Future.delayed(const Duration(milliseconds: 0));
  }
  print(msg);
  return new Future.delayed(
      new Duration(milliseconds: rng.nextInt(300)),
      () => performed.add(msg));
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!