|
|
@@ -0,0 +1,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 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); |
|
|
} |
|
|
} |
|
|
} |