dbox.js

Summary

dBox class provides support functions for advanced web clients using the MapServer (although this code is NOT MapServer specific. Functions supported include drag panning, rubber-band box drawing, measure and area tools. Basically the dBox class handles user interaction (via a mouse) with a map.


Class Summary
dBox This is the basic dBox class.

Method Summary
static void clear_coords()
           function to be called on mouse exit events.
static void main_area(a, d, l, n)
           function to be called on area events.
static void main_measure(s, t, n, a)
           function to be called on measure events.
static void main_mousemove(x, y)
           function to be called on mouse move events.
static void main_setbox(minx, miny, maxx, maxy)
           function to be called on mouse click/box draw events.

/**
 * @fileoverview dBox class provides support functions for advanced
 * web clients using the MapServer (although this code is NOT MapServer
 * specific. Functions supported include drag panning, rubber-band box
 * drawing, measure and area tools. Basically the dBox class handles
 * user interaction (via a mouse) with a map.
 */

var DBOX_MOUSEENTER = 1;
var DBOX_MOUSEEXIT = 2;
var DBOX_MOUSEMOVE = 3;
var DBOX_SETBOX = 4;
var DBOX_RESET = 5;
var DBOX_MEASURE = 6;
var DBOX_AREA = 7;

var DEFAULT_BUSY_LOADING_IMG_SRC = "busy.gif";
var DEFAULT_BUSY_LOADING_IMG_WIDTH = 180;
var DEFAULT_BUSY_LOADING_IMG_HEIGHT = 20;

/**
 * Construct a new dBox object.
 * @class This is the basic dBox class.
 * 
 * @constructor
 * @param {String} name The name of the anchor element (img or div).
 * @return A new mapserv object
 */
function dBox(name) {
  this.name = name;

  /**
   * color (either name or hexidecimal) of any drawing elements. (default: red)
   * @type String
   */
  this.color = 'red';

  /**
   * thickness (in pixels) of lines for any drawing elements. (default: 1)
   * @type Integer
   */
  this.thickness = 1;

  this.width = 0;
  this.height = 0;

  this.box = true;
  this.line = false;
  this.drag = false;
  this.poly = false;
  this.verbose = false;

  this.anchor = null;

  // DHTML elements
  this.container = null;
  this.canvas = null;
  this.image = null;

  /**
   * cursor to be displayed when over the dBox. (default: crosshair).
   * @type String
   */
  this.cursor = 'crosshair';

  /**
   * minimum size (in pixels_ of a mouse event resulting in a point. (default: 10).
   * @type Integer
   */
  this.jitter = 10;

  this.x1 = this.y1 = this.x2 = this.y2 = -1;
  this.offsetx = this.offsety = 0;

  this.x = new Array(); // arrays to hold coordinates
  this.y = new Array();
  this.length = 0;
  this.area = 0;

  this.graphics;

  this.dragging = false;
  this.waiting = false; // are we waiting for a new image?

  // handlers/callbacks

  /**
   * function to be called on mouse enter events.

   * <p>Must be defined and set (via {@link #setHandler}) elsewhere in
   * the application, or nothing happens with these events.</p>

   * @type Function
   */
  this.mouseEnterHandler = null;

  /**
   * function to be called on mouse exit events.

   * <p>Must be defined and set (via {@link #setHandler}) elsewhere in
   * the application, or nothing happens with these events.</p>

   * <pre>main.setHandler(DBOX_MOUSEEXIT, clear_coords);	
   * function clear_coords() { 
   *   var e = document.getElementById("coords"); 
   *   if(e) e.innerHTML = '&nbsp;'; 
   * }</pre>

   * @type Function
   */
  this.mouseExitHandler = null;

  /**
   * function to be called on mouse move events.

   * <p>Must be defined and set (via {@link #setHandler}) elsewhere in
   * the application, or nothing happens with these events.</p>

   * <pre>main.setHandler(DBOX_MOUSEMOVE, main_mousemove);
   * function main_mousemove(x, y) {
   *   var text = '';
   *   var utmx = Number(ms.extent[0] + x*ms.cellsize);
   *   var utmy = Number(ms.extent[3] - y*ms.cellsize);
   *
   *   text = "&nbsp;UTM Coordinates:  x =" + Math.round(utmx) + " and y = " + Math.round(utmy);
   * 
	 * 	 var e = document.getElementById("coords");
   *   if(e) e.innerHTML = text;
   * }</pre>

   * @type Function
   */
  this.mouseMoveHandler = null;

  /**
   * function to be called on mouse click/box draw events.

   * <p>Must be defined and set (via {@link #setHandler}) elsewhere in
   * the application, or nothing happens with these events.</p>

   * <p>typically this is where you would trigger draw or query behaviour
   * within your application.</p> 

	 * <pre>main.setHandler(DBOX_SETBOX, main_setbox);
   * function main_setbox(minx, miny, maxx, maxy) {
   *   if(ms.mode == 'map') {
   *     if(minx != maxx && miny != maxy)
   *       ms.applyBox(minx, miny, maxx, maxy);
   *     else
   *       ms.applyZoom(minx, miny);       
   *     ms.draw(); // builds draw URL and calls draw callback          
   *   } else if(ms.mode != 'map') {
   *     ms.applyBoxQuery(minx, miny, maxx, maxy);
   *     ms.applyPointQuery(minx, miny);
   *     ms.query(); // builds query URL and calls query callback          
   *   }
   * }</pre>

   * @type Function
   */
  this.setBoxHandler = null;
  
  this.resetHandler = null;

  /**
   * function to be called on measure events.

   * <p>Must be defined and set (via {@link #setHandler}) elsewhere in
   * the application, or nothing happens with these events.</p>

   * <p>typically this is where you would do something with the coordinate
   * information generated by dBox. For example, you might convert from
   * pixels to map coordinates and update a page element.</p>

   * <pre>main.setHandler(DBOX_MEASURE, main_measure);
   * function main_measure(s, t, n, a) {    
   *   var text = '&nbsp;Distance: ' + Math.round(t*ms.cellsize) + " meters (" + n + " points)";
   *   var e = document.getElementById("measure");
   *   if(e) e.innerHTML = text;
   * }</pre>

   * @type Function
   */
  this.measureHandler = null;

  /**
   * function to be called on area events.

   * <p>Must be defined and set (via {@link #setHandler}) elsewhere in
   * the application, or nothing happens with these events.</p>

   * <p>typically this is where you would do something with the coordinate
   * information generated by dBox. For example, you might convert from
   * pixels to map coordinates and update a page element. You can also do
   * simple feature digitizing using this feature</p>

   * <pre>main.setHandler(DBOX_AREA, main_area);
   * function main_area(a, d, l, n) {    
   *   var text = '&nbsp;Area: ' + Math.round(a*ms.cellsize) + " sq. m. Distance: " + Math.round(l*ms.cellsize) +  " m. (" + n + " points)";
   *   var e = document.getElementById("measure");
   *   if(e) e.innerHTML = text;
   * }</pre>

   * @type Function
   */
  this.areaHandler = null;
  
  this.busyMessage = null; // busy message/image
  this.busyImage = null;

  var self = this; // object reference to avoid "this" conflicts with callbacks and event handlers

  function distance(x1, y1, x2, y2) {
    return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
  }

  function area(x, y) {
    var area = 0;

    for (var i = 0; i < x.length; i++) {
      var j = (i + 1) % x.length;
      area += x[i] * y[j] - x[j] * y[i];
    }

    return (area < 0 ? -area / 2.0:area / 2.0);
  }

  /**
   * set a handler function for draw/query operations.

   * @param {Enumerated} type constant for handler type to be set.
   * Valid type values are:

   * <ul>
   * <li> DBOX_MOUSEENTER (0)</li>
   * <li> DBOX_MOUSEEXIT (1)</li>
   * <li> DBOX_MOUSEMOVE (2)</li>
   * <li> DBOX_SETBOX (3)</li>
   * <li> DBOX_MEASURE (5)</li>
   * <li> DBOX_AREA (6)</li>
   * </ul>

   * @param {Function} handler function to be called
   */
  this.setHandler = function(type, handler) {
    if (type == DBOX_MOUSEENTER)
      self.mouseEnterHandler = handler;
    else if (type == DBOX_MOUSEEXIT)
      self.mouseExitHandler = handler;
    else if (type == DBOX_MOUSEMOVE)
      self.mouseMoveHandler = handler;
    else if (type == DBOX_SETBOX)
      self.setBoxHandler = handler;
    else if (type == DBOX_RESET)
      self.resetHandler = handler;
    else if (type == DBOX_MEASURE)
      self.measureHandler = handler;
    else if (type == DBOX_AREA)
      self.areaHandler = handler;
  }

  /**
   * Method to initialize dBox. This should be called after page load.
   * 
   */
  this.initialize = function() {
    self.anchor = xGetElementById(self.name); // anchor *must* exist in the document, either as an image or a div

    self.width = xWidth(self.anchor);
    self.height = xHeight(self.anchor);

    self.container = document.createElement('div'); // holds the image and graphics canvas
    self.container.style.position = 'absolute';
    self.container.id = self.name + "_container";
    document.body.appendChild(self.container);

    xResizeTo(self.container, self.width, self.height);
    xMoveTo(self.container, xPageX(self.anchor), xPageY(self.anchor)); // align to anchor
    xClip(self.container, 0, self.width, self.height, 0);
    xShow(self.container);

    self.image = document.createElement('img');

    self.image.style.position = 'absolute';
    self.image.id = self.name + "_image";
    self.container.appendChild(self.image);

    self.image.onload = self.reset;

    xResizeTo(self.image, self.width, self.height);
    xMoveTo(self.image, 0, 0);
    xShow(self.image);

    self.canvas = document.createElement('div');

    self.canvas.style.position = 'absolute';
    self.canvas.id = self.name + "_canvas";
    self.container.appendChild(self.canvas);

    xResizeTo(self.canvas, self.width, self.height);
    xMoveTo(self.canvas, 0, 0);
    xClip(self.canvas, 0, self.width, self.height, 0);
    xShow(self.canvas);

    self.offsetx = xLeft(self.container);
    self.offsety = xTop(self.container);

    // event handling
    xAddEventListener(self.container, 'mousedown', self.mouseDown, true);
    xAddEventListener(self.container, 'mousemove', self.mouseMove, true);
    xAddEventListener(self.container, 'mouseup', self.mouseUp, true);
    xAddEventListener(self.container, 'mouseover', self.mouseEnter, true);
    xAddEventListener(self.container, 'mouseout', self.mouseExit, true);
    xEnableDrag(self.container, self.mouseDrag, self.mouseDrag, self.mouseDrag);

    // create the graphics object to use the canvas
    self.graphics = new jsGraphics(self.canvas.id);
    self.graphics.setColor(self.color);
    self.graphics.setStroke(self.thickness);
  }

  /**
   * Reposition the dBox object relative to the anchor.
   */
  this.sync = function() {
    xMoveTo(self.container, xPageX(self.anchor), xPageY(self.anchor));
    self.offsetx = xLeft(self.container);
    self.offsety = xTop(self.container);
  }

  /**
   * Turn drag panning on.
   */
  this.dragOn = function() {
    self.box = self.line = self.poly = false;
    self.drag = true;
  }

  /**
   * Turn drag panning off.
   */
  this.dragOff = function() {
    self.boxOff();
  }

  /**
   * Turn box dragging on.
   */
  this.boxOn = function() {
    self.box = true;
    self.line = self.drag = self.poly = false;
  }

  /**
   * Turn box dragging off.
   */
  this.boxOff = function() {
    self.line = self.box = self.drag = self.poly = false;
    self.x1 = self.x2;
    self.y1 = self.y2;
    self.paint();

    // user SHOULD provide this handler
    if (self.resetHandler) self.resetHandler(Math.min(self.x1, self.x2) - self.offsetx, Math.min(self.y1, self.y2) - self.offsety, Math.max(self.x1, self.x2) - self.offsetx, Math.max(self.y1, self.y2) - self.offsety);
  }

  /**
   * Turn line drawing on.
   */
  this.lineOn = function() {
    self.line = true;
    self.box = self.drag = false;
    self.x = new Array();
    self.y = new Array();
    self.area = self.length = 0;
    self.paint();
  }

  /**
   * Turn line drawing off.
   */
  this.lineOff = function() {
    self.boxOff();
  }

  /**
   * Turn polygon drawing on.
   */
  this.polyOn = function() {
    self.poly = true;
    self.box = self.drag = self.line = false;
    self.x = new Array();
    self.y = new Array();
    self.area = self.length = 0;
    self.paint();
  }

  /**
   * Turn polygon drawing off.
   */
  this.polyOff = function() {
    self.boxOff();
  }

  this.reset = function() {
    self.x1 = self.x2 = (self.width - 1) / 2 + self.offsetx; // center cursor
    self.y1 = self.y2 = (self.height - 1) / 2 + self.offsety;

    if (self.resetHandler) self.resetHandler(self.x1, self.y1, self.x1, self.y1);

    xMoveTo(self.image, 0, 0); // reposition the image

    self.sync();
    self.paint();
    self.waiting = false;
    if (self.busyMessage || self.busyImage) self.busyOff();

  }

  /**
   * Change the image displayed in the dBox object.
   * @param {String} url for the new image
   */
  this.setImage = function(url) {
    self.waiting = true;

    if (self.busyMessage || self.busyImage) self.busyOn();

    self.x1 = self.x2 = (self.width - 1) / 2 + self.offsetx; // center cursor
    self.y1 = self.y2 = (self.height - 1) / 2 + self.offsety;

    self.image.src = url;
  }

  this.paint = function() {
    var x, y, w, h;

    self.graphics.clear();

    if (self.drag) {
      xMoveTo(self.image, (self.x2 - self.x1), (self.y2 - self.y1));
    } else {

      if (self.x1 == self.x2 && self.y1 == self.y2) {
        if (self.line) {
          for (var i = 1; i < self.x.length; i++)
            self.graphics.drawLine(self.x[i - 1] - self.offsetx, self.y[i - 1] - self.offsety, self.x[i] - self.offsetx, self.y[i] - self.offsety);
        } else if (self.poly) {
          for (var i = 1; i < self.x.length; i++)
            self.graphics.drawLine(self.x[i - 1] - self.offsetx, self.y[i - 1] - self.offsety, self.x[i] - self.offsetx, self.y[i] - self.offsety);
          if (self.x.length > 2) // close the polygon
            self.graphics.drawLine(self.x[0] - self.offsetx, self.y[0] - self.offsety, self.x[self.x.length - 1] - self.offsetx, self.y[self.x.length - 1] - self.offsety);
        }
      } else {
        if (self.box) {
          w = Math.abs(self.x1 - self.x2);
          h = Math.abs(self.y1 - self.y2);
          x = Math.min(self.x1, self.x2) - self.offsetx;
          // UL corner of box
          y = Math.min(self.y1, self.y2) - self.offsety;
          self.graphics.drawRect(x, y, w, h);
        } else if (self.line) {
          for (var i = 1; i < self.x.length; i++)
            self.graphics.drawLine(self.x[i - 1] - self.offsetx, self.y[i - 1] - self.offsety, self.x[i] - self.offsetx, self.y[i] - self.offsety);
        } else if (self.poly) {
          for (var i = 1; i < self.x.length; i++)
            self.graphics.drawLine(self.x[i - 1] - self.offsetx, self.y[i - 1] - self.offsety, self.x[i] - self.offsetx, self.y[i] - self.offsety);
          if (self.x.length > 2) // close the polygon
            self.graphics.drawLine(self.x[0] - self.offsetx, self.y[0] - self.offsety, self.x[self.x.length - 1] - self.offsetx, self.y[self.x.length - 1] - self.offsety);
        }
      }
    }

    self.graphics.paint();
  }

  this.mouseDown = function(event) {
    var e = new xEvent(event);

    self.dragging = true;
    self.x1 = self.x2 = e.pageX;
    self.y1 = self.y2 = e.pageY;
  }

  this.mouseMove = function(event) {
    var e = new xEvent(event);
    var x = e.pageX;
    var y = e.pageY;

    if (self.dragging && !self.line && !self.poly) {
      self.x2 = x;
      self.y2 = y;
      if (!self.box && !self.drag) {
        self.x1 = self.x2;
        self.y1 = self.y2;
      } else
        self.paint();
    }

    if (!self.waiting && self.verbose && self.mouseMoveHandler) self.mouseMoveHandler(x - self.offsetx, y - self.offsety);
  }

  this.mouseUp = function(event) {
    var e = new xEvent(event);
    var x1, x2, y1, y2;

    self.dragging = false;

    if (self.box || self.line || self.drag || self.poly) {
      self.x2 = e.pageX;
      self.y2 = e.pageY;

      if ((Math.abs(self.x1 - self.x2) <= self.jitter) || (Math.abs(self.y1 - self.y2) <= self.jitter)) {
        self.x2 = self.x1;
        self.y2 = self.y1;
      }

      if (self.drag) {
        if (self.setBoxHandler) {
          if (self.x1 == self.x2 && self.y1 == self.y2) {
            x1 = x2 = self.x1 - self.offsetx;
            y1 = y2 = self.y1 - self.offsety;
          } else {
            x1 = x2 = self.width / 2 - (self.x2 - self.x1);
            y1 = y2 = self.height / 2 - (self.y2 - self.y1);
          }
          self.setBoxHandler(x1, y1, x2, y2);
        }
      } else if (self.box) {
        if (self.setBoxHandler) {
          x1 = Math.min(self.x1, self.x2) - self.offsetx;
          x2 = Math.max(self.x1, self.x2) - self.offsetx;
          y1 = Math.min(self.y1, self.y2) - self.offsety;
          y2 = Math.max(self.y1, self.y2) - self.offsety;
          self.setBoxHandler(x1, y1, x2, y2);
        }
      } else if (self.line) {
        self.x.push(self.x2);
        self.y.push(self.y2);
        if (self.x.length > 1) {
          var d = distance(self.x[self.x.length - 1], self.y[self.y.length - 1], self.x[self.x.length - 2], self.y[self.y.length - 2]);
          self.length += d;
          if (self.measureHandler) self.measureHandler(d, self.length, self.x.length); // length
          self.paint();
        } else if (self.x.length == 1) {
          if (self.measureHandler) self.measureHandler(0, 0, 1); // no length, 1 point
        }
      } else if (self.poly) {
        self.x.push(self.x2);
        self.y.push(self.y2);
        if (self.x.length > 2) {
          self.area = area(self.x, self.y);
          var d = distance(self.x[self.x.length - 1], self.y[self.y.length - 1], self.x[self.x.length - 2], self.y[self.y.length - 2]);
          self.length += d;
          if (self.areaHandler) self.areaHandler(self.area, d, self.length, self.x.length); // area and length
          self.paint();
        } else if (self.x.length == 2) {
          self.length = distance(self.x[self.x.length - 1], self.y[self.y.length - 1], self.x[self.x.length - 2], self.y[self.y.length - 2]);
          if (self.areaHandler) self.areaHandler(0, self.length, self.length, self.x.length); // no area, but length, 2 points
          self.paint();
        } else if (self.x.length == 1) {
          if (self.areaHandler) self.areaHandler(0, 0, 0, 1); // no area, no length, 1 point
        }
      }
    } else {
      self.x2 = self.x1;
      self.y2 = self.y1;

      if (self.setBoxHandler) {
        x1 = Math.min(self.x1, self.x2) - self.offsetx;
        x2 = Math.max(self.x1, self.x2) - self.offsetx;
        y1 = Math.min(self.y1, self.y2) - self.offsety;
        y2 = Math.max(self.y1, self.y2) - self.offsety;
        self.setBoxHandler(x1, y1, x2, y2);
      }
    }

  }

  this.mouseEnter = function(event) {
    self.anchor.style.cursor = self.container.style.cursor = self.cursor;
    if (self.verbose && self.mouseEnterHandler) self.mouseEnterHandler();
  }

  this.mouseExit = function(event) {
    self.anchor.style.cursor = self.container.style.cursor = "default";
    if (self.verbose && self.mouseExitHandler) self.mouseExitHandler();
  }

  this.mouseDrag = function(event) {
  }

/**
* Method will cause dbox to display a busy message during the onload event handler of the main image.
* This method can be called in one of three ways:
* 1) <string> e.g., "Loading" will display a "Google"-style red box in the upper right corner of the page.
* 2) <Image> e.g., Create an Image with the height, width and src properties set.  The image will display in the center of the main image.
* 3) <null> e.g.,  Calling this method with anything else will use a busy image using the default image set in the 
* following global variables: DEFAULT_BUSY_LOADING_IMG_SRC, DEFAULT_BUSY_LOADING_WIDTH, DEFAULT_BUSY_LOADING_HEIGHT.
*/

  this.useBusyMessage = function(obj) {

    if (typeof obj == "string") {
      self.busyMessage = obj;
    } else if (typeof obj == "object" && obj.src && obj.src.search(/gif|png|jpg/i)) {
      self.busyImage = obj;
    } else {
      //use default image
      var image = new Image();
      image.src = DEFAULT_BUSY_LOADING_IMG_SRC;
      image.width = DEFAULT_BUSY_LOADING_IMG_WIDTH;
      image.height = DEFAULT_BUSY_LOADING_IMG_HEIGHT;
      self.busyImage = image;
    }
  }
}

dBox.prototype.busyImage = null;
dBox.prototype.busyMessage = null;

/**
* Method that controls the style of the busy message. This can be overridden.
* When the busy message/image is displayed a disaabled zone is placed over the entire
* page, essentially preeeventing a users interaction with the page during the loading 
* process. 
*/
dBox.prototype.busyOn = function() {

  var disabledZone = xGetElementById("disabled_zone");
  if (!disabledZone) {
    disabledZone = document.createElement('div');
    disabledZone.setAttribute('id', 'disabled_zone');
    disabledZone.style.position = "absolute";
    disabledZone.style.zIndex = "1000";
    disabledZone.style.left = "0px";
    disabledZone.style.top = "0px";
    disabledZone.style.width = "100%";
    disabledZone.style.height = "100%";
    document.body.appendChild(disabledZone);
    var messageZone = document.createElement('div');
    messageZone.setAttribute('id', 'message_zone');
    messageZone.style.position = "absolute";
    disabledZone.appendChild(messageZone);

    if (this.busyMessage) {

      messageZone.style.top = "0px";
      messageZone.style.right = "0px";
      messageZone.style.padding = "4px";
      messageZone.style.background = "red";
      messageZone.style.color = "white";
      messageZone.style.fontFamily = "Arial,Helvetica,sans-serif";
      var text = document.createTextNode(this.busyMessage);
      messageZone.appendChild(text);

    } else {
      var image = document.createElement('img');
      image.src = this.busyImage.src;
      image.width = this.busyImage.width;
      image.height = this.busyImage.height;
      xHide(disabledZone);
      messageZone.appendChild(image);
      xMoveTo(messageZone, ((this.width - image.width) / 2) + this.offsetx,
              ((this.height - image.height) / 2) + this.offsety);

    }

    xShow(disabledZone);
  } else {
    xShow(disabledZone);
  }
}

/**
* Method to turn off busy image/message.  It gets called after the image has loaded.
*/
dBox.prototype.busyOff = function() {
  xHide('disabled_zone');
}


Documentation generated by JSDoc on Fri May 5 14:01:20 2006