192 lines · javascript
1// expand/collapse button (expander) is added if height of a cell content 2// exceeds CLIP_HEIGHT px.3var CLIP_HEIGHT = 135;4 5// Height in pixels of an expander image.6var EXPANDER_HEIGHT = 13;7 8// Path to images for an expander.9var 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?16var CellsInfo = [];17 18// Extracts group and cell indices from an id of the form identifier_group_cell.19function 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.25function getCellInfo(id) { 26 var idx = getCellIdx(id); 27 return CellsInfo[idx.group][idx.cell]; 28}29 30// Initialization, add nodes, collect info.31function 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 dynamically 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 119function 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<8129 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.138function 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