this.window.$.on( 'click', function( event ){ context.toggleActive( ); } )
			 .on( 'dblclick', function( event ){ context.toggleMaximized( ); } );

При разработке JS приложений часто возникает неудобство ( всё-таки сложно назвать это проблемой ), когда необходимо отделять событие клика от двойного клика из-за особенностей обработки данных событий — браузерами. Думаю многие из Вас сталкивались с тем что во всех браузерах вначале происходит событие onClick а уже после — onDblClick. Не знаю как Вам, но мне это очень мешает спокойному написанию обработки событий подобного рода.

Но проблема вся в том что каких-то простых и удобных решений подобной задачи — нет. Поэтому пришлось взяться за дело, и написать своё решение.

Плагин

Основа плагина — перехват вызова регистрации события ( on, off, click, dblclick ), с последующей заменой на свои события.
$.fn.on = function( ) // ( types, selector, data, fn, one )
{
	var argumentList = $.extend( true, [], arguments );
	var eventType = argumentList[0];
	//....
	case eventType === 'click' || eventType === 'dblclick':
	{
		// Инициируем навешивание текущей функции с дополнительными обработками
		onMethod.call( this, 'reg' + eventType,	eventSelector, eventData,
			function( regEvent, event )
			{
				// Важная проверка, чтобы знать мы ли это в цепи передачи событий
				if( this === regEvent.target )
				{
					// Не даём передать этот сигнал дальше
					regEvent.preventDefault( );
					regEvent.stopPropagation( );
						
					// Сбрасываем параметры оригинального вызова
					event.isDefaultPrevented = function returnFalse( ){ return false; };
					event.isPropagationStopped = function returnFalse( ){ return false; };

					// Вызываем оригинальную функцию
					eventFunc.call( regEvent.target, event );
								
					// Если исходя из параметров сигнал нужно передать дальше - передаём
					if( !event.isPropagationStopped( ) )
					{
						$( event.currentTarget.offsetParent ).trigger( 'click', [ event ] );	
					}
				}
			}
		);

		// Считываем события к событию
		var clickEvents = $._data( this[0], 'events' ) ? $._data( this[0], 'events' )[ 'click' ] : undefined;

		// Если еще не записали ни одного клика - записываем
		if( typeof clickEvents === 'undefined' )
		{
			// Непосредственно клик
			onMethod.call( this, 'click', eventSelector, eventData, singleDoubleClick );
						
			// Для проверки перемещения мыши
			onMethod.call( this, 'mousedown', eventSelector, eventData, startMoving );
			onMethod.call( this, 'mouseup', eventSelector, eventData, endMoving );
		}
	}
	//....
}


После перехвата события — от момента первого клика запускается таймер который считает был ли сдвиг мышки за определённый период.
И если был сдвиг, или не произошло второго клика — то происходит событие onClick, в обратном случае onDblClick.
	var timeOut = 200; // Время ожидания
	var start = { x: 0, y: 0 }; // Точка начала
	var end = { x: 0, y: 0 }; // Точка конца
	var distance = 0; // Расстояние между началом и концом движения
	var maxDistance = 3;

	var onMethod = $.fn.on; // Записываем оригинальный on
	var offMethod = $.fn.off; // Записываем оригинальный off

	var singleDoubleClick = function( event )
	{
		// Для каждого объекта в цепи, по которой от сына отцу идет событие, нужно создать свой таймер и счетчик кликов
		// то есть один глобальный объект нельзя держать, если два сына, то клик стает дабл кликом
		event.currentTarget.clicks = ( event.currentTarget.clicks || 0 ) + 1;
		
		// По умолчанию - не даём сигнал дальше
		event.preventDefault( );
		event.stopPropagation( );
		
		var timeoutCallback = function( event )
		{
			return function( )
			{
				// Очищаем таймер
				clearTimeout( event.currentTarget.timer );
				
				// Учитываем кол-во кликов, дергание мыши и стопинг пропагации
				if( event.currentTarget.clicks === 1 && distance < maxDistance )
				{
					$( event.currentTarget ).trigger( 'regclick', [ event ] );	
				}
				else if( event.currentTarget.clicks > 1 && distance < maxDistance )
				{
					event.type = 'dblclick';
					$( event.currentTarget ).trigger( 'regdblclick', [ event ] );
				}
				
				// Обнуляем счётчик кликов
				event.currentTarget.clicks = 0;
			};
		};

		// Устанавливаем таймер
		event.currentTarget.timer = setTimeout( timeoutCallback( event ), timeOut );
	};

	// Начало движения
	var startMoving = function( event )
	{
		start = $.getMousePosition( event );
	};

	// Конец движения
	var endMoving = function( event )
	{
		end = $.getMousePosition( event );
		var dx = end.x - start.x;
		var dy = end.y - start.y;
		distance = Math.sqrt( dx * dx + dy * dy );
	};


Посмотреть на сам плагин можно вот здесь: GitHub
Надеюсь данное решение будет полезным ещё кому-то.

Комментарии (14)


  1. ionicman
    18.08.2015 15:35
    +2

    Давным давно на просторах stackoverflow:

    function mouseClickDebounce( o, ev, handler ) {
    	if ( o.getAttribute( "data-_clk_" ) ) {
    		clearTimeout( Number( o.getAttribute( "data-_clk_" ) ) );
    		o.removeAttribute( "data-_clk_" );
    		ev.type = "_dblclick";
    		handler.call( o, ev );
    	}
    	else {
    		o.setAttribute(
    			"data-_clk_",
    			String( setTimeout(
    				function() {
    					o.removeAttribute( "data-_clk_" );
    					ev.type = "_click";
    					handler.call( o, ev );
    				},
    				200
    			) )
    		);
    	}
    }
    


    o — объект для кторого проводится действие
    ev — объект (например event у jQuery, или любой другой — в нем будет меняться свойство type)
    ev.type = "_click" — одно нажатие
    ev.type = "_dblclick" — двойное нажатие

    работать так:

    $( element ).on(
    	"click",
    	function( ev ) {
    		mouseClickDebounce( this, ev, function( ev ) { alert( ev.type ); } );
    	}
    );
    


    У Вас излишне сложно, хотя более правильно. Но это далеко не всегда нужно.


    1. ange007
      18.08.2015 15:40

      Это я конечно видел.
      Но реализация в виде плагина с некоторыми дополнительными нюансами ( обработка передвижения курсора, и бесшовная обработка событий ) — показалась более оправданной.


    1. NikitaKA
      18.08.2015 16:00
      +1

      У Вас излишне сложно, хотя более правильно. Но это далеко не всегда нужно.

      Единственный раз столкнулся с реальной необходимостью двойного клика при работе с canvas. Ключевым моментом было отслеживать не только время между кликами, но и смещение курсора: активно работая с инструментом click per second может просто зашкаливать.

      Что касается dblclick, то всегда стараюсь уходить от использования его при проектировании интерфейсов, т.к. он, как минимум, не очевиден.


      1. ionicman
        18.08.2015 16:08

        У меня за все время работы это событие применялось только два раза, и оба — в очень навороченных гридах. Вполне хватало только времени.

        А так да, полностью согласен, что использовать надо лишь тогда, когда ооочень необходимо, ввиду неочевидности и сложности действия. Юзабилисты всегда говорят на такой ивент аяяй :)


        1. ange007
          18.08.2015 16:21

          Понятное дело что подобный функционал должен применяться только тогда когда он нужен.

          В нашем случае он используется так как общий интерфейс приложения ассоциируется с окнами в ОС, и есть определённые «фишки» при использовании клика и дабл.клика — быстрое переключение режимов отображения ( превью, активен, полный размер ) и подобное.


          1. NikitaKA
            18.08.2015 17:00

            Не совсем понял где на вашем скриншоте нужно кликать дважды. На уменьшенных превьюшках, верно? А что будет, если я просто кликну на превью, без двойного клика?


            1. ange007
              18.08.2015 17:05

              Здесь не отображен режим на два клика.
              Я просто хотел показать пример интерфейса в котором такой функционал чаще востребован чем нет.

              Но режим «полный размер» вот:


              1. NikitaKA
                18.08.2015 17:28

                Самое интересное, что то, куда надо дважды кликнуть для получения режима «полный размер», так и осталось загадкой. :) Просто чисто из любопытства было интересно попытаться понять проблему и решить задачу иным путем. Можно было бы, например, не убирать dblclick, но при этом по одинарному щелчку (по превью) показывать какую-то иконку поверх окна, приглашающую в «полный размер».


                1. ange007
                  18.08.2015 17:36

                  Прошу прощения что не ответил.
                  Кликнуть можно по превью и по области вокруг активного контента ( карпет ) как один раз, так и два:

                  • один раз отображает чуть больше информации и даёт воспроизвести контент ( то-есть быстрый доступ, просмотреть что там к чему и т.д. )
                  • два раза — то-же самое + связанный контент и полную информацию

                  Ну и прочее подобное.


                  1. NikitaKA
                    18.08.2015 17:52

                    Понял, спасибо. Просто было интересно.


                    1. ange007
                      18.08.2015 17:55

                      По поводу вашего предложения относительно иконки — в нашем конкретно случае можно использовать подобное в качестве подсказки до клика — во время периода «обучения» пользователя. Но это уже нюансы, которыми должны заниматься дизайнеры.
                      Так что спасибо за идею.


  1. eme
    18.08.2015 19:25
    +3

    ИМХО, это проблемы проектирования UI. И в последнюю очередь проблема click\dblclick.

    p.s. плюс ко всему dblclick нажатия может регулироваться на уровне ОС.


  1. subzey
    19.08.2015 13:05
    +1

    Кстати, у современных браузеров есть (event).detail, который больше 1, если это даблклик, триплклик или квадруплклик.


  1. Ram0n
    26.08.2015 16:30

    Спасибо, полезным будет Ваш материал.
    Пока еще конечно сталкиваться не приходилось в работе, но думаю еще придется)