Events

Lecture Notes for CS 142
Spring 2013
John Ousterhout

  • Additional reading for this topic:
    • Pages 161-194 of the online supplement to Dynamic HTML: The Definitive Reference, by Danny Goodman.
  • Event types:
    • Mouse-related: mouse movement, enter/leave element, button click
    • Keyboard-related: down, up, press
    • Focus-related: focus in, focus out (blur)
    • Timer events
    • Miscellaneous:
      • Content of an element has changed.
      • Page loaded/unloaded.
      • Uncaught error.
  • Creating an event handler: must specify 3 things:
    • What happened: the event of interest.
    • Where it happened: an element of interest.
    • What to do: Javascript to invoke when the event occurs on the element.
  • Option #1: in the HTML:
    <div onclick="mouseClick('id42');">...</div>
    
  • Option #2: from Javascript using the DOM:
    element.onclick = mouseClick;
    
  • When invoking a handler, the browser provides an event object containing various information about the event:
    • button: mouse button that was pressed
    • charCode: integer Unicode value corresponding to key, if there is one.
    • keyCode: identifier for the keyboard key that was pressed (not necessarily an ASCII character!)
    • clientX, clientY: mouse position relative to the browser window
    • screenX, screenY: mouse position in screen coordinates
  • How is event object passed to the event code?
    • HTML: event variable refers to event:
      <div onclick="mouseClick(event);">
      
    • DOM (Firefox, Chrome, etc.): event passed as argument to function:
      element.onclick = mouseClick;
      function mouseClick(evt) {
        ...
        x = evt.clientX;
        ...
      }
      
    • DOM (IE) a global variable event holds the current event:
      element.onclick = mouseClick;
      function mouseClick() {
        ...
        x = window.event.clientX;
        ...
       }
      
  • Example: dragExample.html:
    <body>
      <div id="div1" onmousedown="mouseDown(event);"
          onmousemove="mouseMove(event);"
          onmouseup="mouseUp(event);">Drag Me!</div>
      <script type="text/javascript">
      //<![CDATA[
          isMouseDown = false;
          function mouseDown(event) {
              oldX = event.clientX;
              oldY = event.clientY;
              isMouseDown = true;
          }
          function mouseMove(event) {
              if (!isMouseDown) {
                  return;
              }
              element = document.getElementById("div1");
              element.style.left = (element.offsetLeft +
                      (event.clientX - oldX)) + "px";
              element.style.top = (element.offsetTop +
                      (event.clientY - oldY)) + "px";
              oldX = event.clientX;
              oldY = event.clientY;
          }
          function mouseUp(event) {
              isMouseDown = false;
          }
      //]]>
      </script>
     </body>
    
  • A cleaner version of the same functionality: dragExample2.html
    <body>
      <div id="div1">Drag Me!</div>
      <div id="div2">Drag Me Too!</div>
      <script type="text/javascript" src="dragger.js"></script>
      <script type="text/javascript">
      //<![CDATA[
          new Dragger("div1");
          new Dragger("div2");
      //]]>
      </script>
    </body>
    
  • Put the Javascript code in a separate file dragger.js:
    function Dragger(id) {
        this.isMouseDown = false;
        this.element = document.getElementById(id);
        var obj = this;
        this.element.onmousedown = function(event) {
            obj.mouseDown(event);
        }
    }
    
    Dragger.prototype.mouseDown = function(event) {
        var obj = this;
        this.oldMoveHandler = document.body.onmousemove;
        document.body.onmousemove = function(event) {
            obj.mouseMove(event);
        }
        this.oldUpHandler = document.body.onmouseup;
        document.body.onmouseup = function(event) {
            obj.mouseUp(event);
        }
        this.oldX = event.clientX;
        this.oldY = event.clientY;
        this.isMouseDown = true;
    }
    
    Dragger.prototype.mouseMove = function(event) {
        if (!this.isMouseDown) {
            return;
        }
        this.element.style.left = (this.element.offsetLeft
                + (event.clientX - this.oldX)) + "px";
        this.element.style.top = (this.element.offsetTop
                + (event.clientY - this.oldY)) + "px";
        this.oldX = event.clientX;
        this.oldY = event.clientY;
    }
    
    Dragger.prototype.mouseUp = function(event) {
        this.isMouseDown = false;
        document.body.onmousemove = this.oldMoveHandler;
        document.body.onmouseup = this.oldUpHandler;
    }
    
  • How does the browser decide which handler(s) are invoked for each event?
    • Complicating factor: elements can contain or overlap other elements (containment more common than overlap in the Web). Suppose I click with the mouse on "xyz" in the following example:
      <body>
        <table>
          <tr>
            <td>xyz</td>
          </tr>
        </table>
      </body>
      
    • Sometimes only the innermost element should handle the event
    • Sometimes it's more convenient for an outer element to handle the event
    • Approach #1: bubbling:
      • Invoke handlers on the innermost nested element under the mouse;
      • Then repeat on its parent, grandparent, etc.
      • Any given element can stop the bubbling, so that no ancestors will see the event (set cancelBubble on the event object).
    • Approach #2: capture (or "trickle-down"):
      • Start at the outermost element and work down to the innermost nested element.
      • Each element can stop the capture, so that its children never see the event.
    • The official DOM standard: first trickle-down, then bubble up.
      • {@onclick} give you only bubble-up behavior
      • The addEventListener function can be used to get either behavior.
  • Timer events:
    • Used for animations, automatic page refreshes.
    • Run myfunc once, 50 milliseconds from now:
      setTimeout("myfunc()", 50);
      
    • Run myfunc every 50 milliseconds:
      token = setInterval("myfunc()", 50);
      
    • Cancel a repeating timer:
      clearInterval(token);
      
  • Concurrency model for events:
    • Events are serialized and processed one-at-a-time
      • Event handling does not interleave with other Javascript execution.
      • No multi-threading.
  • Watch out for browser incompatibilities related to events.

Custom form elements with Javascript and events

  • Use HTML to display custom controls (images, tables, etc.).
  • Respond to events with Javascript.
  • Store the form data in hidden elements.

Wrapup

  • Event-based programming is different from traditional imperative programming:
    • Must wait for someone to invoke your code.
    • Must return quickly from the handler (otherwise the application will lock up).
    • Key is to maintain control through events: make sure you have declared enough handlers; last resort is a timer.