192 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			192 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| // 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);
 | |
|     }
 | |
|   }
 | |
| }
 |