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