| // 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 dinamically 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); |
| } |
| } |
| } |