expandcollapse.js
6.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
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 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);
}
}
}