reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
// expand/collapse button (expander) is added if height of a cell content 
// exceeds CLIP_HEIGHT px.
var CLIP_HEIGHT = 135;

// Height in pixels of an expander image.
var EXPANDER_HEIGHT = 13;

// Path to images for an expander.
var imgPath = "./images/expandcollapse/";

// array[group][cell] of { 'height', 'expanded' }.
// group: a number; cells of the same group belong to the same table row.
// cell: a number; unique index of a cell in a group.
// height: a number, px; original height of a cell in a table.
// expanded: boolean; is a cell expanded or collapsed?
var CellsInfo = [];

// Extracts group and cell indices from an id of the form identifier_group_cell.
function getCellIdx(id) {
  var idx = id.substr(id.indexOf("_") + 1).split("_");
  return { 'group': idx[0], 'cell': idx[1] };
}

// Returns { 'height', 'expanded' } info for a cell with a given id.
function getCellInfo(id) { 
  var idx = getCellIdx(id); 
  return CellsInfo[idx.group][idx.cell]; 
}

// Initialization, add nodes, collect info.
function initExpandCollapse() {
  if (!document.getElementById)
    return;

  var groupCount = 0;

  // Examine all table rows in the document.
  var rows = document.body.getElementsByTagName("tr");
  for (var i=0; i<rows.length; i+=1) {

    var cellCount=0, newGroupCreated = false;

    // Examine all divs in a table row.
    var divs = rows[i].getElementsByTagName("div");
    for (var j=0; j<divs.length; j+=1) {

      var expandableDiv = divs[j];

      if (expandableDiv.className.indexOf("expandable") == -1)
        continue;

      if (expandableDiv.offsetHeight <= CLIP_HEIGHT)
        continue;

      // We found a div wrapping a cell content whose height exceeds 
      // CLIP_HEIGHT.
      var originalHeight = expandableDiv.offsetHeight;
      // Unique postfix for ids for generated nodes for a given cell.
      var idxStr = "_" + groupCount + "_" + cellCount;
      // Create an expander and an additional wrapper for a cell content.
      //
      //                                --- expandableDiv ----
      //  --- expandableDiv ---         | ------ data ------ |
      //  |    cell content   |   ->    | |  cell content  | | 
      //  ---------------------         | ------------------ |
      //                                | ---- expander ---- |
      //                                ----------------------
      var data = document.createElement("div");
      data.className = "data";
      data.id = "data" + idxStr;
      data.innerHTML = expandableDiv.innerHTML;
      with (data.style) { height = (CLIP_HEIGHT - EXPANDER_HEIGHT) + "px";
                          overflow = "hidden" }

      var expander = document.createElement("img");
      with (expander.style) { display = "block"; paddingTop = "5px"; }
      expander.src = imgPath + "ellipses_light.gif";
      expander.id = "expander" + idxStr;

      // Add mouse calbacks to expander.
      expander.onclick = function() {
        expandCollapse(this.id);
        // Hack for Opera - onmouseout callback is not invoked when page 
        // content changes dynamically and mouse pointer goes out of an element.
        this.src = imgPath + 
                   (getCellInfo(this.id).expanded ? "arrows_light.gif"
                                                  : "ellipses_light.gif");
      }
      expander.onmouseover = function() { 
        this.src = imgPath + 
                   (getCellInfo(this.id).expanded ? "arrows_dark.gif"
                                                  : "ellipses_dark.gif");
      }
      expander.onmouseout = function() { 
        this.src = imgPath + 
                   (getCellInfo(this.id).expanded ? "arrows_light.gif"
                                                  : "ellipses_light.gif");
      }

      expandableDiv.innerHTML = "";
      expandableDiv.appendChild(data);
      expandableDiv.appendChild(expander);
      expandableDiv.style.height = CLIP_HEIGHT + "px";
      expandableDiv.id = "cell"+ idxStr;

      // Keep original cell height and its ecpanded/cpllapsed state.
      if (!newGroupCreated) {
        CellsInfo[groupCount] = [];
        newGroupCreated = true;
      }
      CellsInfo[groupCount][cellCount] = { 'height' : originalHeight,
                                           'expanded' : false };
      cellCount += 1;
    }
    groupCount += newGroupCreated ? 1 : 0;
  }
}

function isElemTopVisible(elem) {
  var body = document.body,
      html = document.documentElement,
      // Calculate expandableDiv absolute Y coordinate from the top of body.
      bodyRect = body.getBoundingClientRect(),
      elemRect = elem.getBoundingClientRect(),
      elemOffset = Math.floor(elemRect.top - bodyRect.top),
      // Calculate the absoute Y coordinate of visible area.
      scrollTop = html.scrollTop || body && body.scrollTop || 0;
  scrollTop -= html.clientTop; // IE<8

  
  if (elemOffset < scrollTop)
    return false;

  return true;
}

// Invoked when an expander is pressed; expand/collapse a cell.
function expandCollapse(id) {
  var cellInfo = getCellInfo(id);
  var idx = getCellIdx(id);

  // New height of a row.
  var newHeight;
  // Smart page scrolling may be done after collapse.
  var mayNeedScroll;

  if (cellInfo.expanded) {
    // Cell is expanded - collapse the row height to CLIP_HEIGHT.
    newHeight = CLIP_HEIGHT;
    mayNeedScroll = true;
  }
  else {
    // Cell is collapsed - expand the row height to the cells original height.
    newHeight = cellInfo.height;
    mayNeedScroll = false;
  }

  // Update all cells (height and expanded/collapsed state) in a row according 
  // to the new height of the row.
  for (var i = 0; i < CellsInfo[idx.group].length; i++) {
    var idxStr = "_" + idx.group + "_" + i;
    var expandableDiv = document.getElementById("cell" + idxStr);
    expandableDiv.style.height = newHeight + "px";
    var data = document.getElementById("data" + idxStr);
    var expander = document.getElementById("expander" + idxStr);
    var state = CellsInfo[idx.group][i];

    if (state.height > newHeight) {
      // Cell height exceeds row height - collapse a cell.
      data.style.height = (newHeight - EXPANDER_HEIGHT) + "px";
      expander.src = imgPath + "ellipses_light.gif";
      CellsInfo[idx.group][i].expanded = false;
    } else {
      // Cell height is less then or equal to row height - expand a cell.
      data.style.height = "";
      expander.src = imgPath + "arrows_light.gif";
      CellsInfo[idx.group][i].expanded = true;
    }
  }

  if (mayNeedScroll) {
    var idxStr = "_" + idx.group + "_" + idx.cell;
    var clickedExpandableDiv = document.getElementById("cell" + idxStr);
    // Scroll page up if a row is collapsed and the rows top is above the 
    // viewport. The amount of scroll is the difference between a new and old 
    // row height.
    if (!isElemTopVisible(clickedExpandableDiv)) {
      window.scrollBy(0, newHeight - cellInfo.height);
    }
  }
}