Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I act on cursor activity originating from mouse clicks and not keyboard actions?

I want to do something when a user moves the cursor to another location via a mouse click, but not do it when it's done via a keyboard action (arrows, pageup/pagedown, home/end).

  • I can't just listen to cursorActivity since it triggers on both keyboard and mouse actions.
  • I'm not sure I can listen to mousedown, because it might be the start of something which is not a cursor location change (e.g. selecting, dragging).

What's the best way to catch those mouse-originated cursor movements?

like image 791
Oak Avatar asked Oct 27 '16 11:10

Oak


3 Answers

You can hook into CodeMirror by using a hook, and define your own event that fires when the cursor changes from a mouse click on the editor

CodeMirror.defineInitHook(function (editor) {
    editor.cursorDidChange = false;
    $(editor.getWrapperElement()).on({
        mousedown : function() {
            if (editor.cursorDidChange) CodeMirror.signal(editor, 'cursorClick');
        },
        mouseup : function() {
            editor.cursorDidChange = false;
        }
    });
    editor.on('cursorActivity', function(e) {
        if (e.isSelection) editor.cursorDidChange = true;
    });
    editor.on('beforeSelectionChange', function(e, range) {
        var start = range.ranges[0].anchor, end = range.ranges[0].head;
        e.isSelection = range.origin == '*mouse' && start.line == end.line && start.ch == end.ch;
    })
});

This uses a flag and a timer to catch both events if they happen within a short time of each other, as the click handler fires right after the cursorActivity handler.

Here's a working example of how to use the newly defined event with CodeMirror :

/* Create Hook */
CodeMirror.defineInitHook(function (editor) {
	editor.cursorDidChange = false;
	$(editor.getWrapperElement()).on({
    	mousedown : function() {
            if (editor.cursorDidChange) CodeMirror.signal(editor, 'cursorClick');
        },
        mouseup : function() {
            editor.cursorDidChange = false;
        }
    });
    editor.on('cursorActivity', function(e) {
    	if (e.isSelection) editor.cursorDidChange = true;
    });
    editor.on('beforeSelectionChange', function(e, range) {
        var start = range.ranges[0].anchor, end = range.ranges[0].head;
        e.isSelection = range.origin == '*mouse' && start.line == end.line && start.ch == end.ch;
    })
});
/* -------------- */
/* Create an editor to test it */

var $this = $('.code').eq(0),
    $code = $this.html(),
    $unescaped = $('<div/>').html($code).text();
        
$this.empty();
    
var editor = CodeMirror($this.get(0), {
    value       : $unescaped,
    mode        : 'javascript',
    lineNumbers : true,
    readOnly    : false
});

/* Lets test out the new event */
editor.on('cursorClick', function() {
	$('<div />', {text : 'Cursor moved when clicked !'}).appendTo('#result')
        .show(1).delay(1000).fadeOut(function() {
         	$(this).remove();
        });
});
body {background: #eee;}
.code {margin: 10px 0;}
#result {color: green;}
.CodeMirror {height: auto!important;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://codemirror.net/lib/codemirror.js"></script>
<script src="https://codemirror.net/mode/javascript/javascript.js"></script>
<link href="https://codemirror.net/lib/codemirror.css" rel="stylesheet"/>
<!-- EDITOR -->
<div class="code">test.fn = test.prototype = {
    display : function() {}
        console.log("Move cursor by clicking");
        console.log("Move cursor with keys");
    },
	pushStack: function( elems ) {
		// This is just pseudocode to have something to test
	}
    return false;
}</div>
<!-- EDITOR END -->
<div id="result"></div>
like image 73
adeneo Avatar answered Oct 23 '22 21:10

adeneo


You could listen to these events:

  • mousedown
  • cursorActivity
  • keydown
  • beforeChange

The cursor was moved by a mouse click if these conditions are met:

  • A cursorActivity event is triggered after a mousedown event
  • No movement key was pressed between the two events
  • No change was made to the content between the two events
  • No text is selected

var movedByMouse = false;

var editor = CodeMirror(document.body);

editor.on("mousedown", function () {
    movedByMouse = true;
});

editor.on("cursorActivity", function () {
    if (movedByMouse) {
        movedByMouse = false;
        if (!editor.getSelection()) {
            console.log("Moved by mouse");
        }
    }
});

editor.on("keydown", function () {
    if (isMovementKey(event.which)) {
        movedByMouse = false;
    }
});

editor.on("beforeChange", function () {
    movedByMouse = false;
});

function isMovementKey(keyCode) {
    return 33 <= keyCode && keyCode <= 40;
};
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/codemirror.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/codemirror.min.js"></script>
like image 23
ConnorsFan Avatar answered Oct 23 '22 23:10

ConnorsFan


All Mouse Events (that I know of):

  • onclick
  • oncontextmenu
  • ondbclick
  • onmousedown
  • onmouseenter
  • onmouseleave
  • onmousemove
  • onmouseover
  • onmouseout
  • onmouseup

So, basically, there's no solution for your problem that's in javascript AFAIK. However, you can go with your second suggestion and track events and depending on them to choose whether to execute your action or not.

var editorElement = document.body;
var codeMirror = CodeMirror(editorElement);
var mouseEvents = [
  'click',
  'contextmenu',
  'dbclick',
  'mousedown',
  'mouseenter',
  'mouseleave',
  'mousemove',
  'mouseover',
  'mouseout',
  'mouseup'
];
var mouseEventsLog = [];
var lastCursorPosition = null;
var doAction = function() {
  var previousEvent = mouseEventsLog[mouseEventsLog.length - 2];
  var lastEvent = mouseEventsLog[mouseEventsLog.length - 1];

  if (((previousEvent === 'mousedown' && lastEvent === 'mouseup') || lastEvent === 'click') &&codeMirror.getSelection().length <= 0) {
    if (codeMirror.getCursor() !== lastCursorPosition) {
      console.log('Cursor changed by clicking.');
    }
  }

  lastCursorPosition = codeMirror.getCursor();
};
for (var i = 0; i < mouseEvents.length; i++) {
  editorElement.addEventListener(mouseEvents[i], function(event) {
    mouseEventsLog.push(event.type);
    if (event.type == 'mouseup') {
      doAction();
    }
  }, false);
}
<script src="https://codemirror.net/lib/codemirror.js"></script>
<script src="http://codemirror.net/mode/javascript/javascript.js"></script>
<link href="https://codemirror.net/lib/codemirror.css" rel="stylesheet"/>

Note: I tried combining cursorActivity and mouse events, but cursorActivity is executed before them? I'm actually not sure what happens there =D

like image 4
Ivanka Todorova Avatar answered Oct 23 '22 21:10

Ivanka Todorova