Home | History | Annotate | Download | only in scripts
      1 // expand/collapse button (expander) is added if height of a cell content 
      2 // exceeds CLIP_HEIGHT px.
      3 var CLIP_HEIGHT = 135;
      4 
      5 // Height in pixels of an expander image.
      6 var EXPANDER_HEIGHT = 13;
      7 
      8 // Path to images for an expander.
      9 var imgPath = "./images/expandcollapse/";
     10 
     11 // array[group][cell] of { 'height', 'expanded' }.
     12 // group: a number; cells of the same group belong to the same table row.
     13 // cell: a number; unique index of a cell in a group.
     14 // height: a number, px; original height of a cell in a table.
     15 // expanded: boolean; is a cell expanded or collapsed?
     16 var CellsInfo = [];
     17 
     18 // Extracts group and cell indices from an id of the form identifier_group_cell.
     19 function getCellIdx(id) {
     20   var idx = id.substr(id.indexOf("_") + 1).split("_");
     21   return { 'group': idx[0], 'cell': idx[1] };
     22 }
     23 
     24 // Returns { 'height', 'expanded' } info for a cell with a given id.
     25 function getCellInfo(id) { 
     26   var idx = getCellIdx(id); 
     27   return CellsInfo[idx.group][idx.cell]; 
     28 }
     29 
     30 // Initialization, add nodes, collect info.
     31 function initExpandCollapse() {
     32   if (!document.getElementById)
     33     return;
     34 
     35   var groupCount = 0;
     36 
     37   // Examine all table rows in the document.
     38   var rows = document.body.getElementsByTagName("tr");
     39   for (var i=0; i<rows.length; i+=1) {
     40 
     41     var cellCount=0, newGroupCreated = false;
     42 
     43     // Examine all divs in a table row.
     44     var divs = rows[i].getElementsByTagName("div");
     45     for (var j=0; j<divs.length; j+=1) {
     46 
     47       var expandableDiv = divs[j];
     48 
     49       if (expandableDiv.className.indexOf("expandable") == -1)
     50         continue;
     51 
     52       if (expandableDiv.offsetHeight <= CLIP_HEIGHT)
     53         continue;
     54 
     55       // We found a div wrapping a cell content whose height exceeds 
     56       // CLIP_HEIGHT.
     57       var originalHeight = expandableDiv.offsetHeight;
     58       // Unique postfix for ids for generated nodes for a given cell.
     59       var idxStr = "_" + groupCount + "_" + cellCount;
     60       // Create an expander and an additional wrapper for a cell content.
     61       //
     62       //                                --- expandableDiv ----
     63       //  --- expandableDiv ---         | ------ data ------ |
     64       //  |    cell content   |   ->    | |  cell content  | | 
     65       //  ---------------------         | ------------------ |
     66       //                                | ---- expander ---- |
     67       //                                ----------------------
     68       var data = document.createElement("div");
     69       data.className = "data";
     70       data.id = "data" + idxStr;
     71       data.innerHTML = expandableDiv.innerHTML;
     72       with (data.style) { height = (CLIP_HEIGHT - EXPANDER_HEIGHT) + "px";
     73                           overflow = "hidden" }
     74 
     75       var expander = document.createElement("img");
     76       with (expander.style) { display = "block"; paddingTop = "5px"; }
     77       expander.src = imgPath + "ellipses_light.gif";
     78       expander.id = "expander" + idxStr;
     79 
     80       // Add mouse calbacks to expander.
     81       expander.onclick = function() {
     82         expandCollapse(this.id);
     83         // Hack for Opera - onmouseout callback is not invoked when page 
     84         // content changes dinamically and mouse pointer goes out of an element.
     85         this.src = imgPath + 
     86                    (getCellInfo(this.id).expanded ? "arrows_light.gif"
     87                                                   : "ellipses_light.gif");
     88       }
     89       expander.onmouseover = function() { 
     90         this.src = imgPath + 
     91                    (getCellInfo(this.id).expanded ? "arrows_dark.gif"
     92                                                   : "ellipses_dark.gif");
     93       }
     94       expander.onmouseout = function() { 
     95         this.src = imgPath + 
     96                    (getCellInfo(this.id).expanded ? "arrows_light.gif"
     97                                                   : "ellipses_light.gif");
     98       }
     99 
    100       expandableDiv.innerHTML = "";
    101       expandableDiv.appendChild(data);
    102       expandableDiv.appendChild(expander);
    103       expandableDiv.style.height = CLIP_HEIGHT + "px";
    104       expandableDiv.id = "cell"+ idxStr;
    105 
    106       // Keep original cell height and its ecpanded/cpllapsed state.
    107       if (!newGroupCreated) {
    108         CellsInfo[groupCount] = [];
    109         newGroupCreated = true;
    110       }
    111       CellsInfo[groupCount][cellCount] = { 'height' : originalHeight,
    112                                            'expanded' : false };
    113       cellCount += 1;
    114     }
    115     groupCount += newGroupCreated ? 1 : 0;
    116   }
    117 }
    118 
    119 function isElemTopVisible(elem) {
    120   var body = document.body,
    121       html = document.documentElement,
    122       // Calculate expandableDiv absolute Y coordinate from the top of body.
    123       bodyRect = body.getBoundingClientRect(),
    124       elemRect = elem.getBoundingClientRect(),
    125       elemOffset = Math.floor(elemRect.top - bodyRect.top),
    126       // Calculate the absoute Y coordinate of visible area.
    127       scrollTop = html.scrollTop || body && body.scrollTop || 0;
    128   scrollTop -= html.clientTop; // IE<8
    129 
    130   
    131   if (elemOffset < scrollTop)
    132     return false;
    133 
    134   return true;
    135 }
    136 
    137 // Invoked when an expander is pressed; expand/collapse a cell.
    138 function expandCollapse(id) {
    139   var cellInfo = getCellInfo(id);
    140   var idx = getCellIdx(id);
    141 
    142   // New height of a row.
    143   var newHeight;
    144   // Smart page scrolling may be done after collapse.
    145   var mayNeedScroll;
    146 
    147   if (cellInfo.expanded) {
    148     // Cell is expanded - collapse the row height to CLIP_HEIGHT.
    149     newHeight = CLIP_HEIGHT;
    150     mayNeedScroll = true;
    151   }
    152   else {
    153     // Cell is collapsed - expand the row height to the cells original height.
    154     newHeight = cellInfo.height;
    155     mayNeedScroll = false;
    156   }
    157 
    158   // Update all cells (height and expanded/collapsed state) in a row according 
    159   // to the new height of the row.
    160   for (var i = 0; i < CellsInfo[idx.group].length; i++) {
    161     var idxStr = "_" + idx.group + "_" + i;
    162     var expandableDiv = document.getElementById("cell" + idxStr);
    163     expandableDiv.style.height = newHeight + "px";
    164     var data = document.getElementById("data" + idxStr);
    165     var expander = document.getElementById("expander" + idxStr);
    166     var state = CellsInfo[idx.group][i];
    167 
    168     if (state.height > newHeight) {
    169       // Cell height exceeds row height - collapse a cell.
    170       data.style.height = (newHeight - EXPANDER_HEIGHT) + "px";
    171       expander.src = imgPath + "ellipses_light.gif";
    172       CellsInfo[idx.group][i].expanded = false;
    173     } else {
    174       // Cell height is less then or equal to row height - expand a cell.
    175       data.style.height = "";
    176       expander.src = imgPath + "arrows_light.gif";
    177       CellsInfo[idx.group][i].expanded = true;
    178     }
    179   }
    180 
    181   if (mayNeedScroll) {
    182     var idxStr = "_" + idx.group + "_" + idx.cell;
    183     var clickedExpandableDiv = document.getElementById("cell" + idxStr);
    184     // Scroll page up if a row is collapsed and the rows top is above the 
    185     // viewport. The amount of scroll is the difference between a new and old 
    186     // row height.
    187     if (!isElemTopVisible(clickedExpandableDiv)) {
    188       window.scrollBy(0, newHeight - cellInfo.height);
    189     }
    190   }
    191 }
    192