dlegend.js

Summary

dLegend script creates a DHTML legend for a given xml representation of the legend xml generated by mapserver. Multiple legends may exist on a page.

Version: 0.1 Based off of Yahoo! TaskNode example.

Author: brent.pellinen@dnr.state.mn.us


Class Summary
dLegend dLegend class contains legend data and wraps YAHOO treeview class to create a DHTML legend.

/**
 * @fileoverview  dLegend script creates a DHTML legend for a given xml representation of the legend xml generated by mapserver.
 * Multiple legends may exist on a page.
 *
 * @author brent.pellinen@dnr.state.mn.us
 * @version 0.1
 * Based off of Yahoo! TaskNode example.
 *
 */

/**
* Click is the only supported callback.
* @final
* @type int
*/
var DLEGEND_CLICK = 1;

//Determine node behavior
var DLEGEND_CHECKSTYLE = "check";
var DLEGEND_RADIOSTYLE = "radio";


/**
 * @class dLegend class contains legend data and wraps YAHOO treeview class to create a DHTML legend.
 * @constructor
 * @requires YAHOO.js
 * @requires event.js
 * @requires dom.js
 * @requires treeview.js
 * @requires xmldom.js
 * @requires legend.css
 *
 */
function dLegend() {
}
;

/**
 * Method to initialize dLegend. This should be called after page load.
 *
 * @param {string} elementId of div element id.
 * @param {string} xmlContent of xml representation of legend data.
 */
dLegend.prototype.initialize = function(elementId, xmlContent) {
  this.treeLegend = new YAHOO.widget.TreeView(elementId);
  buildMenu(xmlContent, this.treeLegend);
  this.treeLegend.draw();
}

/**
 * Method to activate and inactivate nodes based on current map scale.
 *
 * @param {int} scale of current map scale
 */
dLegend.prototype.update = function(scale) {
  this.treeLegend.update(scale);
}

/**
 * Method to register a handler/callback that will get called when the user selects a legend node. Method should
 * be called after initialzation to be sure that the legend tree exists.
 *
 * @param {int} type constant indicating the handler type.
 * @param {object} function handler of which the first two parameters will be a string layerName and boolean status.
 */
dLegend.prototype.setHandler = function(type, handler) {
  this.treeLegend.setHandler(type, handler);
}

/**
 * Method to programmatically turn a node on or off.
 *
 * @param  {string} layerName is layer name equal to layer name in xml.
 * @param  {boolean} status toggle layer where true/on and false/off.
 */
dLegend.prototype.changeNodeStatus = function(layerName, status) {
  this.treeLegend.changeNodeStatus(layerName, status);
}

/**
 * @private
 */
function buildMenu(xml, legendTree) {

  var objDom = new XMLDoc(xml, this.xmlError)
  //get the root node
  var objDomTree = objDom.docNode;

  var serverUri = objDomTree.getElements("server")[0].getText();

  for (var i = 0; i < objDomTree.getElements("metagroup").length; i++) {

    var xmlMetagroup = objDomTree.getElements("metagroup")[i];
    var metagroupName = xmlMetagroup.getAttribute("name");
    var metagroupStyle = xmlMetagroup.getAttribute("element");
    if (!metagroupStyle) metagroupStyle = DLEGEND_CHECKSTYLE;
    metagroupName += (metagroupStyle == DLEGEND_CHECKSTYLE? "" : " (Select One)");
    var metagroup = new LegendMetagroup(metagroupName, legendTree.getRoot(), metagroupStyle);

    for (var j = 0; j < xmlMetagroup.getElements().length; j++) {
      if (xmlMetagroup.getElements()[j].tagName == "group") {
        var xmlGroup = xmlMetagroup.getElements()[j];
        var groupName = xmlGroup.getAttribute("name");
        if (!groupName) groupName = xmlGroup.getAttribute("id");
        var group = createLayer(xmlGroup, metagroup);

        for (var k = 0; k < xmlGroup.getElements("layer").length; k++) {
          var xmlLayer = xmlGroup.getElements("layer")[k];
          var layer = createLayer(xmlLayer, group, true);
          for (x = 0; x < xmlLayer.getElements("class").length; x++) {
            var legendClass = createClass(xmlLayer.getElements("class")[x], layer);
          }
        }
      } else if (xmlMetagroup.getElements()[j].tagName == "layer") {
        var xmlLayer = xmlMetagroup.getElements()[j];
        var layer = createLayer(xmlLayer, metagroup);
        for (var m = 0; m < xmlLayer.getElements("class").length; m++) {
          var legendClass = createClass(xmlLayer.getElements("class")[m], layer);
        }
      }
    }
  }
  function createLayer(xmlLayer, parent, isGroup) {
    var displayName = xmlLayer.getAttribute("name");
    var layerName = xmlLayer.getAttribute("id");
    var minscale = xmlLayer.getAttribute("minscale");
    var maxscale = xmlLayer.getAttribute("maxscale");
    var icon = iconify(xmlLayer.getAttribute("icon"));
    return new LegendLayer(icon + displayName, parent, layerName, minscale, maxscale, isGroup);
    ;
  }

  function createClass(xmlClass, parent) {
    var icon = iconify(xmlClass.getAttribute("icon"));
    var className = xmlClass.getAttribute("name");
    return new LegendClass(icon + className, parent);

  }

  function iconify(src) {
    if (src) {
      return "<img src='http://" + serverUri + src + "' align='absmiddle' />&nbsp;";
    } else {
      return "";
    }
  }

  function xmlError(e) {
    alert(e);
  }

}

/**
 * @private
 */
YAHOO.widget.TreeView.prototype.update = function(currentScale) {
  for (var i = 0; i < this.getRoot().children.length; i++) {
    for (var j = 0; j < this.getRoot().children[i].children.length; j++) {
      var hasDisabledNode = false;
      var node = this.getRoot().children[i].children[j];
      if ((node.minscale == 0
              && node.maxscale == 0)) {
        //do nothing
      } else if (currentScale <= node.minscale
              || currentScale >= node.maxscale) {
        hasDisabledNode = true;
        node.disableNode();

      } else if (node.disabled) {
        node.enableNode();
      }
      if (hasDisabledNode)node.collapse();
    }
  }
};

/**
 * @private
 */
YAHOO.widget.TreeView.prototype.onClickHandler;

/**
 * @private
 */
YAHOO.widget.TreeView.prototype.setHandler = function(type, handler) {
  if (type == DLEGEND_CLICK) this.onClickHandler = handler;
};

/**
 * @private
 */
YAHOO.widget.TreeView.prototype.changeNodeStatus = function(layerName, status) {

  for (var i = 0; i < this.getRoot().children.length; i++) {
    for (var j = 0; j < this.getRoot().children[i].children.length; j++) {
      var node = this.getRoot().children[i].children[j];
      if (node.layerName &&
          node.layerName == layerName) {
        if (status) {
          node.check();
        } else {
          node.uncheck();
        }
        break;
      }
    }
  }
};

/**
 * @private
 */
LegendNode = function(oData, oParent, expanded, checked) {

  if (oParent) {
    this.init(oData, oParent, expanded);
    this.setUpLabel(oData);
    this.checked = checked;
    if (checked)  this.check();
  }
};

/**
 * @private
 */
LegendMetagroup = function(oData, oParent, optionStyle, checked) {
  this.optionStyle = optionStyle;
  this.checked = true;
  if (oParent) {
    this.init(oData, oParent, this.checked);
    this.setUpLabel(oData);
  }
};

/**
 * @private
 */
LegendLayer = function(displayData, oParent, layerName, minscale, maxscale, displayCheckbox) {

  this.layerName = layerName;
  if (minscale) this.minscale = minscale;
  if (maxscale) this.maxscale = maxscale;
  this.noCheckbox = displayCheckbox;
  this.disabled = false;
  if (oParent) {
    this.init(displayData, oParent, false);
    this.setUpLabel(displayData);
  }
};

/**
 * @private
 */
LegendClass = function(oData, oParent) {
  this.noCheckbox = true;
  this.disabled = false;
  if (oParent) {
    this.init(oData, oParent, false);
    this.setUpLabel(oData);
    this.checked = false;
  }
};

/**
 * @private
 */
LegendNode.prototype = new YAHOO.widget.TextNode();

/**
 * @private
 */
LegendMetagroup.prototype = new LegendNode();

/**
 * @private
 */
LegendLayer.prototype = new LegendNode();

/**
 * @private
 */
LegendClass.prototype = new LegendNode();


LegendLayer.prototype.minscale = 0;
LegendLayer.prototype.maxscale = 0;


LegendLayer.prototype.toString = function() {
  return  "LegendLayer";
};
LegendClass.prototype.toString = function() {
  return  "LegendClass";
};
LegendMetagroup.prototype.toString = function() {
  return  "LegendMetagroup";
};


/**
 * True if checkstate is 1 (some children checked) or 2 (all children checked),
 * false if 0.
 *
 * @private
 * @type boolean
 */
LegendNode.prototype.checked = false;
LegendNode.prototype.noCheckbox = false;
/**
 * checkState
 * 0=unchecked, * 1=some children checked, 2=all children checked
 *
 * @private
 * @type int
 */
LegendNode.prototype.checkState = 0;

/**
 * The id of the check element
 *
 * @private
 * @type string
 */
LegendNode.prototype.getCheckElId = function() {
  return "ygtvcheck" + this.index;
};

LegendNode.prototype.getRadioElId = function() {
  return "ygtvcheck" + this.index;
};

/**
 * Returns the check box element
 *
 * @private
 * @return the check html element (img)
 */
LegendNode.prototype.getCheckEl = function() {
  return document.getElementById(this.getCheckElId());
};

LegendNode.prototype.getRadioEl = function() {
  return document.getElementById(this.getRadioElId());
};

/**
 * The style of the check element, derived from its current state
 *
 * @private
 * @return {string} the css style for the current check state
 */
LegendNode.prototype.getCheckStyle = function() {
  return "ygtvcheck" + this.checkState;
};

LegendNode.prototype.getRadioStyle = function() {
  return "ygtvcheck" + this.checkState;
};


/**
 * Returns the link that will invoke this node's check toggle
 *
 * @private
 * @return {string} returns the link required to adjust the checkbox state
 */
LegendNode.prototype.getCheckLink = function() {
  return "YAHOO.widget.TreeView.getNode(\'" + this.tree.id + "\'," +
         this.index + ").checkClick(); ";
};

LegendNode.prototype.getRadioLink = function() {
  return "YAHOO.widget.TreeView.getNode(\'" + this.tree.id + "\'," +
         this.index + ").radioClick()";
};

/**
 * Invoked when the user clicks the check box
 * @private
 */
LegendNode.prototype.checkClick = function() {
  if (this.checkState === 0 && this.disabled == false) {
    this.check();
  } else {
    this.uncheck();
  }
};

LegendNode.prototype.radioClick = function() {
  if (this.checkState === 0 && this.disabled == false) {
    this.radioSelect();
  } else if (this.checkState === 1) {
    this.radioUncheck();
  }
};

/**
 * Refresh the state of this node's parent, and cascade up.
 * @private
 */
LegendNode.prototype.updateCheckParent = function() {

  var p = this.parent;
  if (!p || !p.updateCheckParent) {
    //this.logger.debug("Abort udpate parent: " + this.index);
    return;
  }
  var somethingChecked = false;
  var somethingNotChecked = false;

  for (var i = 0; i < p.children.length; ++i) {
    if (p.children[i].checked) {
      somethingChecked = true;
      // checkState will be 1 if the child node has unchecked children
      if (p.children[i].checkState == 1) {
        somethingNotChecked = true;
      }
    } else {
      somethingNotChecked = true;
    }
  }
  if (somethingChecked) {
    p.setCheckState((somethingNotChecked) ? 1 : 2);
  } else {
    p.setCheckState(0);
  }


  p.updateCheckHtml();
  p.updateCheckParent();
};


LegendNode.prototype.updateRadioParent = function() {

  var p = this.parent;
  if (!p || !p.updateRadioParent) {
    return;
  }
  for (var i = 0; i < p.children.length; ++i) {
    if (p.children[i].checked) {
      p.children[i].radioUncheck();
    }
  }

  p.setCheckState(1);
  p.updateRadioHtml();
  p.updateRadioParent();
};


/**
 * Check the type of node, radio or checkbox
 * @private
 */
LegendLayer.prototype.getRootOptionStyle = function() {
  var p = this.parent;
  if (p && p.getRootOptionStyle) return p.getRootOptionStyle();
  if (p && p.optionStyle) {
    return p.optionStyle;
  }
  if (this.optionStyle) {
    return this.optionStyle;
  }
};

/**
 * If the node has been rendered, update the html to reflect the current
 * state of the node.
 * @private
 */
LegendNode.prototype.updateCheckHtml = function() {
  if (this.parent && this.parent.childrenRendered) {
    if (!this.noCheckbox) {
      this.getCheckEl().className = this.getCheckStyle();
      this.performCallback();
    }
  }
};

LegendNode.prototype.updateRadioHtml = function() {
  if (this.parent && this.parent.childrenRendered) {
    if (!this.noCheckbox) {
      this.getRadioEl().className = this.getRadioStyle();
      this.performCallback();
    }
  }
};

LegendNode.prototype.performCallback = function() {
  if (this.layerName && this.tree.onClickHandler && (this.checkState == 0 || this.checkState == 2)) {
    this.tree.onClickHandler(this.layerName, (this.checkState == 2) ? true:false);
  }
}


/**
 * Updates the state.  The checked property is true if the state is 1 or 2
 *
 * @private
 * @param the new check state
 */
LegendNode.prototype.setCheckState = function(state) {
  this.checkState = state;
  this.checked = (state > 0);
};


LegendNode.prototype.disableNode = function() {

  for (var i = 0; i < this.children.length; ++i) {
    this.children[i].disableNodeStyle();
    this.children[i].uncheck();

  }
  this.disableNodeStyle();
  this.uncheck();

  this.updateCheckParent();

};

LegendNode.prototype.enableNode = function() {
  for (var i = 0; i < this.children.length; ++i) {
    this.children[i].enableNodeStyle();
  }
  this.enableNodeStyle();

};

LegendNode.prototype.disableNodeStyle = function() {
  this.disabled = true;
  label = document.getElementById(this.labelElId);
  if (label) label.className = "disablednode";

}

LegendNode.prototype.enableNodeStyle = function() {
  this.disabled = false;
  label = document.getElementById(this.labelElId);
  if (label) label.className = "ygtvlabel";

}

/**
 * Check this node
 */
LegendNode.prototype.check = function() {
  this.setCheckState(2);
  for (var i = 0; i < this.children.length; ++i) {
    this.children[i].check();
  }
  this.updateCheckHtml();
  this.updateCheckParent();
};

LegendNode.prototype.radioSelect = function() {

  for (var i = 0; i < this.children.length; ++i) {
    this.children[i].radioUncheck();
  }
  this.updateRadioParent();

  if (this.hasChildren()) {
    this.setCheckState(1);
  }
  this.setCheckState(2);
  this.updateRadioHtml();


};

/**
 * Uncheck this node
 */
LegendNode.prototype.uncheck = function() {
  this.setCheckState(0);
  for (var i = 0; i < this.children.length; ++i) {
    this.children[i].uncheck();
  }
  this.updateCheckHtml();
  this.updateCheckParent();
};

LegendNode.prototype.radioUncheck = function() {
  this.setCheckState(0);
  for (var i = 0; i < this.children.length; ++i) {
    this.children[i].radioUncheck();
  }

  this.updateRadioHtml();

};

// Overrides YAHOO.widget.TextNode
LegendNode.prototype.getNodeHtml = function() {


  var sb = new Array();

  sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
  sb[sb.length] = '<tr>';

  for (i = 0; i < this.depth; ++i) {
    sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '">&nbsp;</td>';
  }

  sb[sb.length] = '<td';
  sb[sb.length] = ' id="' + this.getToggleElId() + '"';
  sb[sb.length] = ' class="' + this.getStyle() + '"';
  if (this.hasChildren(true)) {
    sb[sb.length] = ' onmouseover="this.className=';
    sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
    sb[sb.length] = this.tree.id + '\',' + this.index + ').getHoverStyle()"';
    sb[sb.length] = ' onmouseout="this.className=';
    sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
    sb[sb.length] = this.tree.id + '\',' + this.index + ').getStyle()"';
  }
  sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '">&nbsp;';
  sb[sb.length] = '</td>';


  // check box
  if ((this.optionStyle && this.optionStyle == DLEGEND_CHECKSTYLE) ||
      (this.getRootOptionStyle && this.getRootOptionStyle() == DLEGEND_CHECKSTYLE)) {
    if (!this.noCheckbox) {
      sb[sb.length] = '<td';
      sb[sb.length] = ' id="' + this.getCheckElId() + '"';
      sb[sb.length] = ' class="' + this.getCheckStyle() + '"';
      sb[sb.length] = ' onclick="javascript:' + this.getCheckLink() + '">';
      sb[sb.length] = '&nbsp;</td>';
    } else {
      sb[sb.length] = '<td';
      sb[sb.length] = ' id="' + this.getCheckElId() + '"';
      sb[sb.length] = ' >&nbsp;</td>';
    }
  } else {
    //radio button
    if (!this.noCheckbox) {
      sb[sb.length] = '<td';
      sb[sb.length] = ' id="' + this.getRadioElId() + '"';
      sb[sb.length] = ' class="' + this.getRadioStyle() + '"';
      sb[sb.length] = ' onclick="javascript:' + this.getRadioLink() + '">';
      sb[sb.length] = '&nbsp;</td>';
    }
  }


  sb[sb.length] = '<td>';
  sb[sb.length] = '<a';
  sb[sb.length] = ' id="' + this.labelElId + '"';
  sb[sb.length] = ' class="' + this.labelStyle + '"';
  sb[sb.length] = ' href="' + this.href + '"';
  sb[sb.length] = ' target="' + this.target + '"';
  if (this.hasChildren(true)) {
    sb[sb.length] = ' onmouseover="document.getElementById(\'';
    sb[sb.length] = this.getToggleElId() + '\').className=';
    sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
    sb[sb.length] = this.tree.id + '\',' + this.index + ').getHoverStyle()"';
    sb[sb.length] = ' onmouseout="document.getElementById(\'';
    sb[sb.length] = this.getToggleElId() + '\').className=';
    sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
    sb[sb.length] = this.tree.id + '\',' + this.index + ').getStyle()"';
  }
  sb[sb.length] = ' >';
  sb[sb.length] = this.label;
  sb[sb.length] = '</a>';
  sb[sb.length] = '</td>';
  sb[sb.length] = '</tr>';
  sb[sb.length] = '</table>';

  return sb.join("");

};



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