Tutorial > Input Events

Because these components are JavaScript objects, they are very easy to extend, even at runtime. But before we focus on extending the canvases, let's first investigate how to implement handlers for abstract parent functions. We will demonstrate this through input events that are triggered by user interaction.

The Traditional Events

There are two sets of user event types, mouse and keyboard events originating from traditional platforms and touch events and gestures originating from mobile platforms. Both sets of input events are handled by the ChemDoodle Web Components library. The abstract Canvas class implements complex handlers for many input events in both sets, and forwards behavior to abstract functions. Those functions can be defined in child classes. If the child class defines the function, then the corresponding input event will be recognized. If it does not, then the corresponding input event will be ignored. A nice effect of this system is that input events are also handled by the browser that contains the component. If a component defines an input event, the parent Canvas class will automatically tell the browser to ignore its default action. For instance, if you use your mouse wheel over a TransformCanvas, then the molecule will scale; the parent Canvas class then tells the browser to ignore the event and prevents the browser from scrolling the webpage, which would be very annoying. Other canvases that don't specify a scroll action will ignore those events, and you can scroll the webpage over them because of this.

The Canvas class defines the following traditional input events:

  1. Mouse
    1. click() - responds to a left mouse click
    2. middleclick() - responds to a middle mouse click
    3. rightclick() - responds to a right mouse click
    4. dblclick() - responds to a double click
    5. mousedown() - responds to the left mouse button being pressed down
    6. middlemousedown() - responds to the middle mouse button being pressed down
    7. rightmousedown() - responds to the right mouse button being pressed down
    8. mouseup() - responds to the left mouse button being released
    9. middlemouseup() - responds to the middle mouse button being released
    10. rightmouseup() - responds to the right mouse button being released
    11. mousemove() - responds to the mouse moving
    12. mouseout() - responds to the mouse leaving the canvas
    13. mouseover() - responds to the mouse hovering over the component
    14. mousewheel() - responds to the mouse wheel being spun
    15. contextmenu() - responds to the right mouse button being pressed down to open the context menu
    16. drag() - responds to the mouse being dragged after the left mouse button has been pressed down
  2. Keyboard
    1. keydown() - responds to a keyboard key being pressed down
    2. keypress() - responds to a keyboard key being pressed and released
    3. keyup() - responds to a keyboard key being released

Events are only sent to a Canvas if the event occurs within the space of the <canvas> element. To clarify, a Canvas click event will only be recognized by a Canvas if a click happened within the bounds of that component, not if it originated somewhere else on the page. The exception to this is if the current event is a drag. If you start a drag event in a canvas and then drag outside of the canvas, that canvas will continue to receive all drag events and keyboard events until the mouse is released; the Canvas will also receive the terminating mouseup event.

All event functions are sent an Event parameter. The parent Canvas class sets a Point variable, called p, in each Event that contains the coordinates of the event in the coordinate space of the receiving canvas. The mousewheel() function also receives a second parameter of a signed magnitude that defines the amount and direction of the mouse wheel event. Keyboard event keys can be recognized by checking the Event.which integer.

The Mobile Events

The Canvas class defines the following mobile input events:

  1. Touch
    1. touchstart() - responds to a finger being placed on the canvas
    2. touchmove() - responds to the finger moving on a canvas
    3. touchend() - responds to a finger being lifted from the canvas
  2. Gesture
    1. gesturestart() - responds to a gesture starting
    2. gesturechange() - responds to a gesture changing
    3. gestureend() - responds to a gesture ending
  3. Abstract
    1. tap() - responds to a finger touching down and releasing within a quarter of a second
    2. dbltap() - responds to two tap events within a half second
    3. touchhold() - responds to a finger touching down and not moving or lifting for 1 second
    4. multitouchmove() - responds to multiple fingers moving across the interface

Touch events can become quite complex, with several fingers down at once. The parent Canvas class will also set a Point variable for these events, called p, but this will correspond to the current position of only the first touch. Gestures are even more complex, as they represent coordinated movements of groups of touches, such as pinching and twisting, but you can just simply call the Event.originalEvent.scale and Event.originalEvent.rotation variables to get magnitudes for those basic gestures.

Using both of these sets of abstract functions, you can create one Canvas child and define a different behavior for that canvas on desktop platforms than on mobile devices. This allows you to create powerful interactive interfaces that are tailored to work across all devices. If you do not set handlers for the mobile events, then they are mapped to the closest traditional event handler (if that traditional event handler is set). For instance, a touchdown event is mapped to a mousedown event. This way your interfaces will automatically work on mobile devices if you set them up to work on traditional platforms.

Implementing a Handler

Implementing a handler for an event is very easy, just set a function to the corresponding abstract name. To demonstrate this, we will initialize a ViewerCanvas and bind a click handler to it that simply displays the message "You clicked on me (x, y)!", where x and y are the click coordinates within the space of the canvas.

<script>
    let viewer = new ChemDoodle.ViewerCanvas('viewer', 100, 100);
    viewer.click = function(e){
    alert('You clicked on me ('+e.p.x+', '+e.p.y+')!');
    }
    viewer.loadMolecule(ChemDoodle.readMOL('Molecule Name\n  CHEMDOOD01011121543D 0   0.00000     0.00000     0\n[Insert Comment Here]\n  6  6  0  0  0  0  0  0  0  0  1 V2000\n    0.0000    1.0000    0.0000   N 0  0  0  0  0  0  0  0  0  0  0  0\n   -0.8660    0.5000    0.0000   C 0  0  0  0  0  0  0  0  0  0  0  0\n   -0.8660   -0.5000    0.0000   C 0  0  0  0  0  0  0  0  0  0  0  0\n    0.0000   -1.0000    0.0000   C 0  0  0  0  0  0  0  0  0  0  0  0\n    0.8660   -0.5000    0.0000   C 0  0  0  0  0  0  0  0  0  0  0  0\n    0.8660    0.5000    0.0000   C 0  0  0  0  0  0  0  0  0  0  0  0\n  1  2  2  0  0  0  0\n  2  3  1  0  0  0  0\n  3  4  2  0  0  0  0\n  4  5  1  0  0  0  0\n  5  6  2  0  0  0  0\n  6  1  1  0  0  0  0\nM  END'));
</script>

Here we implemented a handler for the click event by defining the function click() for our canvas. All event handlers take an Event parameter, as described above, so we just accessed e.p.x and e.p.y to get the coordinates. Simple!

Now be careful, because a given canvas may already have that handler defined, and if you define it again, you will override the original functionality. So lets do one more complex example. We will implement a TransformCanvas, but on top of the canvas, we want to draw the paths that the mouse dragged over it. By default, TransformCanvas already defines a handler for the drag event and we don't want to lose the ability to rotate the molecule. So what we will do is save the old handler to another variable and set a new handler. Then in the new handler, we will call the old one.

<script>
  let transformer = new ChemDoodle.TransformCanvas('transformer', 200, 200, true);
  //a little styling
  transformer.styles.atoms_useJMOLColors = true;
  transformer.styles.atoms_circles_2D = true;
  transformer.styles.atoms_HBlack_2D = false;
  transformer.styles.bonds_symmetrical_2D = true;
  transformer.styles.backgroundColor = '#E4FFC2';
  //an array of Point that will keep track of the path
  transformer.dragPath = [];
  //save the old handler
  transformer.oldDrag = transformer.drag;
  //define the new handler
  transformer.drag = function(e){
  //notice that you can use the "this" keyword in an object's function to access its variables
  this.dragPath[transformer.dragPath.length] = e.p;
  //call the old handler
  this.oldDrag(e);
  }
  //draw on top of the canvas, covered on the next page
  transformer.drawChildExtras = function(ctx) {
    if(this.dragPath.length!=0){
        ctx.strokeStyle = 'red';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(this.dragPath[0].x, this.dragPath[0].y);
        for(let i = 1, ii = this.dragPath.length; i<ii; i++){
        ctx.lineTo(this.dragPath[i].x, this.dragPath[i].y);
        }
        ctx.stroke();
    }
  }
  transformer.loadMolecule(ChemDoodle.readMOL('Molecule Name\n  CHEMDOOD12250908183D 0   0.00000     0.00000     0\n[Insert Comment Here]\n 40 44  0  0  0  0  0  0  0  0  1 V2000\n   -2.4201   -1.3169    0.4885   C 0  0  0  1  0  0  0  0  0  0  0  0\n   -2.4007   -0.2197    0.6870   C 0  0  0  1  0  0  0  0  0  0  0  0\n   -3.1630   -1.7585    0.5832   H 0  0  0  1  0  0  0  0  0  0  0  0\n   -1.4920   -1.8472    0.1276   C 0  0  0  1  0  0  0  0  0  0  0  0\n   -3.3129    0.2911    0.9878   O 0  0  0  1  0  0  0  0  0  0  0  0\n   -1.4550    0.3261    0.5477   C 0  0  0  1  0  0  0  0  0  0  0  0\n   -0.5339   -1.2844   -0.0016   C 0  0  0  1  0  0  0  0  0  0  0  0\n   -1.5355   -2.6937   -0.0688   H 0  0  0  1  0  0  0  0  0  0  0  0\n   -3.1138    1.0345    1.1162   H 0  0  0  1  0  0  0  0  0  0  0  0\n   -0.5385   -0.2088    0.2615   C 0  0  0  1  0  0  0  0  0  0  0  0\n   -1.3162    1.4114    0.6041   O 0  0  0  1  0  0  0  0  0  0  0  0\n    0.4574   -1.7528   -0.5024   C 0  0  0  1  0  0  0  0  0  0  0  0\n    0.3884    0.5420    0.1963   C 0  0  0  1  0  0  0  0  0  0  0  0\n   -0.2569    1.5925    0.1057   C 0  0  0  1  0  0  0  0  0  0  0  0\n    0.6675   -2.4994   -0.0963   H 0  0  0  1  0  0  0  0  0  0  0  0\n    1.4380   -0.9689   -0.5556   C 0  0  0  1  0  0  0  0  0  0  0  0\n    0.2503   -1.9697   -1.3283   H 0  0  0  1  0  0  0  0  0  0  0  0\n    1.0495    0.4819    1.2355   C 0  0  0  1  0  0  0  0  0  0  0  0\n    1.0919    0.2175   -0.7577   C 0  0  0  1  0  0  0  0  0  0  0  0\n    0.1184    2.2301    0.5784   H 0  0  0  1  0  0  0  0  0  0  0  0\n   -0.3921    2.0169   -1.0582   C 0  0  0  1  0  0  0  0  0  0  0  0\n    1.9358   -1.2202   -1.2354   H 0  0  0  1  0  0  0  0  0  0  0  0\n    2.1438   -0.9618    0.4030   N 0  0  0  1  0  0  0  0  0  0  0  0\n    1.6007   -0.6068    1.3817   C 0  0  0  1  0  0  0  0  0  0  0  0\n    0.5521    0.6440    1.9410   H 0  0  0  1  0  0  0  0  0  0  0  0\n    1.6648    1.1088    1.2152   H 0  0  0  1  0  0  0  0  0  0  0  0\n    0.5456    0.4220   -1.8161   C 0  0  0  1  0  0  0  0  0  0  0  0\n    1.8191    0.7140   -0.7698   H 0  0  0  1  0  0  0  0  0  0  0  0\n   -1.4411    2.4257   -1.2400   O 0  0  0  1  0  0  0  0  0  0  0  0\n   -0.1380    1.2410   -1.9394   C 0  0  0  1  0  0  0  0  0  0  0  0\n    0.1565    2.6937   -1.1652   H 0  0  0  1  0  0  0  0  0  0  0  0\n    2.6986   -1.9758    0.5733   C 0  0  0  1  0  0  0  0  0  0  0  0\n    2.1879   -0.5234    2.0289   H 0  0  0  1  0  0  0  0  0  0  0  0\n    1.0187   -1.2041    1.6545   H 0  0  0  1  0  0  0  0  0  0  0  0\n    0.7435   -0.0833   -2.4955   H 0  0  0  1  0  0  0  0  0  0  0  0\n   -1.9229    1.9705   -0.8287   H 0  0  0  1  0  0  0  0  0  0  0  0\n   -0.4917    1.3949   -2.7191   H 0  0  0  1  0  0  0  0  0  0  0  0\n    3.1158   -2.2288   -0.1548   H 0  0  0  1  0  0  0  0  0  0  0  0\n    3.3129   -1.8835    1.1918   H 0  0  0  1  0  0  0  0  0  0  0  0\n    2.1618   -2.6206    0.8240   H 0  0  0  1  0  0  0  0  0  0  0  0\n  1  2  2  0  0  0  0\n  1  3  1  0  0  0  0\n  1  4  1  0  0  0  0\n  2  5  1  0  0  0  0\n  2  6  1  0  0  0  0\n  7  4  2  0  0  0  0\n  4  8  1  0  0  0  0\n  5  9  1  0  0  0  0\n  6 10  2  0  0  0  0\n  6 11  1  0  0  0  0\n 10  7  1  0  0  0  0\n  7 12  1  0  0  0  0\n 10 13  1  0  0  0  0\n 14 11  1  0  0  0  0\n 12 15  1  0  0  0  0\n 16 12  1  0  0  0  0\n 12 17  1  0  0  0  0\n 13 18  1  0  0  0  0\n 13 14  1  0  0  0  0\n 13 19  1  0  0  0  0\n 14 20  1  0  0  0  0\n 21 14  1  0  0  0  0\n 16 22  1  0  0  0  0\n 19 16  1  0  0  0  0\n 16 23  1  0  0  0  0\n 18 24  1  0  0  0  0\n 18 25  1  0  0  0  0\n 18 26  1  0  0  0  0\n 19 27  1  0  0  0  0\n 19 28  1  0  0  0  0\n 21 29  1  0  0  0  0\n 30 21  1  0  0  0  0\n 21 31  1  0  0  0  0\n 23 32  1  0  0  0  0\n 23 24  1  0  0  0  0\n 24 33  1  0  0  0  0\n 24 34  1  0  0  0  0\n 27 35  1  0  0  0  0\n 27 30  2  0  0  0  0\n 29 36  1  0  0  0  0\n 30 37  1  0  0  0  0\n 32 38  1  0  0  0  0\n 32 39  1  0  0  0  0\n 32 40  1  0  0  0  0\nM  END'));
</script>

And so now you can drag all over the TransformCanvas above, see the molecule rotate, and also see the path that was dragged over it, highlighted in red. You may have also noticed that we overrode the drawChildExtras() function. This allows us to draw on top of the canvas and is covered on the next page.

Testing

A black ViewerCanvas is presented here with all events defined to listen to any input. You will see a log of the event data in the text area below.
Continue to Drawing on Canvases →

Get your work done with our popular desktop software.