* Fix some IE (mainly IE6) overflow issues
[citadel.git] / webcit / static / summaryview.js
1 /** Webcit Summary View v2
2     All comments, flowers and death threats to Mathew McBride
3     <matt@mcbridematt.dhs.org> / <matt@comalies>
4 */
5 document.observe("dom:loaded", createMessageView);
6
7 var message_view = null;
8 var loadingMsg = null;
9 var rowArray = null;
10 var currentSortMode = null;
11
12 // Header elements
13 var mlh_date = null;
14 var mlh_subject = null;
15 var mlh_from = null;
16 var currentSorterToggle = null;
17 var query = "";
18 var currentlyMarkedRows = new Object();
19 var markedRowId = null;
20
21 var mouseDownEvent = null;
22 var exitedMouseDown = false;
23
24 var sortModes = {
25   "rdate" : sortRowsByDateDescending,
26   "date" : sortRowsByDateAscending,
27   //  "reverse" : sortRowsByDateDescending,
28   "subj" : sortRowsBySubjectAscending,
29   "rsubj" : sortRowsBySubjectDescending,
30   "sender": sortRowsByFromAscending,
31   "rsender" : sortRowsByFromDescending
32 };
33 var toggles = {};
34
35 var nummsgs = 0;
36 var startmsg = 0;
37 function createMessageView() {
38   message_view = document.getElementById("message_list_body");
39   loadingMsg = document.getElementById("loading");
40   getMessages();
41   mlh_date = $("mlh_date");
42   mlh_subject = $('mlh_subject');
43   mlh_from = $('mlh_from');
44   toggles["rdate"] = mlh_date;
45   toggles["date"] = mlh_date;
46   // toggles["reverse"] = mlh_date;
47   toggles["subj"] = mlh_subject;
48   toggles["rsubj"] = mlh_subject;
49   toggles["sender"] = mlh_from;
50   toggles["rsender"] = mlh_from;
51   mlh_date.observe('click',ApplySort);
52   mlh_subject.observe('click',ApplySort);
53   mlh_from.observe('click',ApplySort);
54   $(document).observe('keyup',CtdlMessageListKeyUp,false);
55   //window.oncontextmenu = function() { return false; };  
56   $('resize_msglist').observe('mousedown', CtdlResizeMouseDown);
57   $('m_refresh').observe('click', getMessages);
58   document.getElementById('m_refresh').setAttribute("href","#");
59   Event.observe(document.onresize ? document : window, "resize", normalizeHeaderTable);
60   Event.observe(document.onresize ? document : window, "resize", sizePreviewPane);
61   $('summpage').observe('change', getPage);
62   takeOverSearchOMatic();
63   setupDragDrop(); // here for now
64 }
65 function getMessages() {
66   if (loadingMsg.parentNode == null) {
67     message_view.innerHTML = "";
68     message_view.appendChild(loadingMsg);
69   }
70 roomName = getTextContent(document.getElementById("rmname"));
71  var parameters = {'room':roomName, 'startmsg': startmsg};
72  if (is_safe_mode) {
73    parameters['maxmsgs'] = 500;
74    if (currentSortMode != null) {
75      var SortBy = currentSortMode[0];
76      if (SortBy.charAt(0) == 'r') {
77        SortBy = SortBy.substr(1);
78        parameters["SortOrder"] = "2";
79      }
80      parameters["SortBy"] = SortBy;
81    }
82  } 
83  if (query.length > 0) {
84    parameters["query"] = query;
85  }
86 new Ajax.Request("roommsgs", {
87     method: 'get',
88         onSuccess: loadMessages,
89         parameters: parameters,
90         sanitize: false,
91       evalJSON: false,
92       onFailure: function(e) { alert("Failure: " + e);}
93         });
94 }
95 function loadMessages(transport) {
96   try {
97   var data = eval('('+transport.responseText+')');
98   if (!!data && transport.responseText.length < 2) {
99     alert("Message loading failed");
100   } 
101   nummsgs = data['nummsgs'];
102   var msgs = data['msgs'];
103   var length = msgs.length;
104   rowArray = new Array(); // store so they can be sorted
105   var start = new Date();
106   for(var i=0; i<length;i++) {
107     var trElement = document.createElement("tr");
108     var data = msgs[i];
109     var msgId = data[0];
110     var rowId = "msg_" + msgId;
111     trElement.setAttribute("id",rowId);
112     //$(trElement).observe('click', CtdlMessageListClick);
113     trElement.ctdlMsgId = msgId;
114     for(var j=1; j<5;j++) { // 1=msgId (hidden), 4 date timestamp (hidden) 6 = isNew etc. 
115       var content = data[j];
116       if(content.length < 1) {
117         content = "(blank)";
118       }
119       if (j==3) {
120         trElement.ctdlDate = content;
121       } else { 
122         try {
123       var tdElement = document.createElement("td");
124       trElement.appendChild(tdElement);
125       var txtContent = document.createTextNode(content);
126       tdElement.appendChild(txtContent);
127       var x=j;
128       if (x==4) x=3;
129       var classStmt = "col"+x;
130       //tdElement.setAttribute("class", classStmt);
131       tdElement.className = classStmt;
132         } catch (e) {
133           WCLog("Error on #"+msgId +" col"+j+":"+e);
134         }
135       }
136     }
137     if (data[5]) {
138       trElement.setAttribute("class", "new_message");
139     }
140     trElement.dropEnabled = true;
141     trElement.ctdlMarked = false;
142     rowArray[i] = trElement; 
143   } 
144   var end = new Date();
145   var delta = end.getTime() - start.getTime();
146     WCLog("loadMessages construct: " + delta);
147   } catch (e) {
148     //window.alert(e+"|"+e.description);
149   }
150   if (currentSortMode == null) {
151   if (sortmode.length < 1) {
152     sortmode = "rdate";
153   }
154   currentSortMode = [sortmode, sortModes[sortmode]];
155   currentSorterToggle = toggles[sortmode];
156   }
157   if (!is_safe_mode) {
158   resortAndDisplay(sortRowsByDateDescending);
159   } else {
160     setupPageSelector();
161     resortAndDisplay(null);
162   }
163   if (loadingMsg.parentNode != null) {
164     loadingMsg.parentNode.removeChild(loadingMsg);
165   }
166   sizePreviewPane();
167 }
168 function resortAndDisplay(sortMode) {
169   var start = new Date();
170   emptyElement(message_view);
171   var fragment = document.createDocumentFragment();
172   if (sortMode != null) {
173     rowArray.sort(sortMode);
174   }
175   var length = rowArray.length;
176   for(var x=0; x<length; ++x) {
177     try {
178       var currentRow = rowArray[x];
179       var className = currentRow.className;
180     className = className.replace("table-alt-row","");
181     className = className.replace("table-row","");
182     if (((x-1) % 2) == 0) {
183       className += " table-alt-row";
184     } else {
185       className += " table-row";
186     }
187     currentRow.className = className;
188     /* Using element.onclick is evil, but until IE 
189        supports addEventListener, it is much faster
190        than prototype observe */
191     currentRow.onclick = CtdlMessageListClick;
192     currentRow.ctdlDnDElement = summaryViewDragAndDropHandler;
193     currentRow.ctdlRowId = x;
194     fragment.appendChild(currentRow);
195     } catch (e) {
196       alert("Exception" + e);
197     }
198   }
199   message_view.appendChild(fragment);
200   var end = new Date();
201     var delta = end.getTime() - start.getTime();
202     WCLog("resortAndDisplay sort and append: " + delta);
203   ApplySorterToggle();
204   normalizeHeaderTable();
205 }
206 function sortRowsByDateAscending(a, b) {
207   var dateOne = a.ctdlDate;
208   var dateTwo = b.ctdlDate;
209   return (dateOne - dateTwo);
210 }
211 function sortRowsByDateDescending(a, b) {
212   var dateOne = a.ctdlDate;
213   var dateTwo = b.ctdlDate;
214   return (dateTwo - dateOne);
215 }
216
217 function sortRowsBySubjectAscending(a, b) {
218   var subjectOne = getTextContent(a.getElementsByTagName("td")[0]).toLowerCase();
219   var subjectTwo = getTextContent(b.getElementsByTagName("td")[0]).toLowerCase();
220   return (subjectOne.charCodeAt(0) - subjectTwo.charCodeAt(0));
221 }
222
223 function sortRowsBySubjectDescending(a, b) {
224   var subjectOne = getTextContent(a.getElementsByTagName("td")[0]).toLowerCase();
225   var subjectTwo = getTextContent(b.getElementsByTagName("td")[0]).toLowerCase();
226   return (subjectTwo.charCodeAt(0) - subjectOne.charCodeAt(0));
227 }
228
229 function sortRowsByFromAscending(a, b) {
230   var fromOne = getTextContent(a.getElementsByTagName("td")[1]).toLowerCase();
231   var fromTwo = getTextContent(b.getElementsByTagName("td")[1]).toLowerCase();
232   return (fromOne.charCodeAt(0) - fromTwo.charCodeAt(0));
233 }
234
235 function sortRowsByFromDescending(a, b) {
236   var fromOne = getTextContent(a.getElementsByTagName("td")[1]).toLowerCase();
237   var fromTwo = getTextContent(b.getElementsByTagName("td")[1]).toLowerCase();
238   return (fromTwo.charCodeAt(0) - fromOne.charCodeAt(0));
239 }
240
241 function CtdlMessageListClick(evt) {
242   /* Since element.onload is used here, test to see if evt is defined */
243   var event = evt ? evt : window.event; 
244   var target = event.target ? event.target: event.srcElement; // and again..
245   var parent = target.parentNode;
246   var msgId = parent.ctdlMsgId;
247   // If the ctrl key modifier wasn't used, unmark all rows and load the message
248   if (!event.shiftKey && !event.ctrlKey && !event.altKey) {
249     unmarkAllRows();
250     markedRowId = parent.ctdlRowId;
251     document.getElementById("preview_pane").innerHTML = "";
252     new Ajax.Updater('preview_pane', 'msg/'+msgId, {method: 'get'});
253     markRow(parent);
254     new Ajax.Request('ajax_servcmd', {
255       method: 'post',
256           parameters: 'g_cmd=SEEN ' + msgId + '|1',
257           onComplete: CtdlMarkRowAsRead(parent)});
258   } else if (event.button != 2 && event.shiftKey) {
259     markRow(parent);
260     var rowId = parent.ctdlRowId;
261     var startMarkingFrom = 0;
262     var finish = 0;
263     if (rowId > markedRowId) {
264       startMarkingFrom = markedRowId+1;
265       finish = rowId;
266     } else if (rowId < markedRowId) {
267       startMarkingFrom = rowId+1;
268       finish = markedRowId;
269     } 
270     for(var x = startMarkingFrom; x<finish; x++) {
271       WCLog("Marking row "+x);
272       markRow(rowArray[x]);
273     }
274   } else if (event.button != 2 && (event.ctrlKey || event.altKey)) {
275     markRow(parent);
276   }
277 }
278 function CtdlMarkRowAsRead(rowElement) {
279   var classes = rowElement.className;
280   classes = classes.replace("new_message","");
281   rowElement.className = classes;
282 }
283 function ApplySort(event) {
284   var target = event.target;
285   var sortId = target.id;
286   removeOldSortClass();
287   currentSorterToggle = target;
288   var sortModes = getSortMode(target); // returns [[key, func],[key,func]]
289   var sortModeToUse = null;
290   if (currentSortMode[0] == sortModes[0][0]) {
291     sortModeToUse = sortModes[1];
292   } else {
293     sortModeToUse = sortModes[0];
294   }
295   currentSortMode = sortModeToUse;
296   if (is_safe_mode) {
297     getMessages(); // in safe mode, we load from server already sorted
298   } else {
299   resortAndDisplay(sortModeToUse[1]);
300   }
301 }
302 function getSortMode(toggleElem) {
303   var forward = null;
304   var reverse = null;
305   for(var key in toggles) {
306     var kr = (key.charAt(0) == 'r');
307     if (toggles[key] == toggleElem && !kr) {
308       forward = [key, sortModes[key]];
309     } else if (toggles[key] == toggleElem && kr) {
310       reverse = [key, sortModes[key]];
311     }
312   }
313   return [forward, reverse];
314 }
315 function removeOldSortClass() {
316   if (currentSorterToggle) {
317     var classes = currentSorterToggle.className;
318     classes = classes.replace("current_sort_mode","");
319     classes = classes.replace("sort_ascending","");
320     classes = classes.replace("sort_descending","");
321     currentSorterToggle.className = classes;
322   }
323 }
324 function markRow( row) {
325   var msgId = row.ctdlMsgId;
326   row.className = row.className += " marked_row";
327   row.ctdlMarked = true;
328   currentlyMarkedRows[msgId] = row;
329 }
330 function unmarkRow(row) {
331   var msgId = row.ctdlMsgId;
332   row.className = row.className.replace("marked_row","");
333   row.ctdlMarked = false;
334   delete currentlyMarkedRows[msgId];
335 }
336 function unmarkAllRows() {
337   for(msgId in currentlyMarkedRows) {
338     unmarkRow(currentlyMarkedRows[msgId]);
339   }
340 }
341 function deleteAllMarkedRows() {
342   for(msgId in currentlyMarkedRows) {
343     var row = currentlyMarkedRows[msgId];
344     var rowArrayId = row.ctdlRowId;
345     row.parentNode.removeChild(row);
346     delete currentlyMarkedRows[msgId];
347     delete rowArray[rowArrayId];
348   }
349   // Now we have to reconstruct rowarray as the array length has changed */
350   var newRowArray = new Array();
351   var x=0;
352   for(var i=0; i<rowArray.length; i++) {
353     var currentRow = rowArray[i];
354     if (currentRow != null) {
355       newRowArray[x] = currentRow;
356       x++;
357     }
358   }
359   rowArray = newRowArray;
360   resortAndDisplay(null);
361 }
362 function CtdlMessageListKeyUp(event) {
363   var key = event.which;
364   if (key == 46) { // DELETE
365     for(msgId in currentlyMarkedRows) {
366       if (!room_is_trash) {
367       new Ajax.Request('ajax_servcmd', 
368                        {method: 'post',
369                            parameters: 'g_cmd=MOVE ' + msgId + '|_TRASH_|0'
370                            });
371       } else {
372         new Ajax.Request('ajax_servcmd', {method: 'post',
373               parameters: 'g_cmd=DELE '+msgId});
374       }
375     }
376     document.getElementById("preview_pane").innerHTML = "";
377     deleteAllMarkedRows();
378   }
379 }
380
381 function clearMessage(msgId) {
382   var row = document.getElementById('msg_'+msgId);
383   row.parentNode.removeChild(row);
384   delete currentlyMarkedRows[msgId];
385 }
386
387 function summaryViewContextMenu() {
388   if (!exitedMouseDown) {
389     var contextSource = document.getElementById("listViewContextMenu");
390     CtdlSpawnContextMenu(mouseDownEvent, contextSource);
391   }
392 }
393
394 function summaryViewDragAndDropHandler() {
395   var element = document.createElement("div");
396   var msgList = document.createElement("ul");
397   element.appendChild(msgList);
398   for(msgId in currentlyMarkedRows) {
399     msgRow = currentlyMarkedRows[msgId];
400     var subject = getTextContent(msgRow.getElementsByTagName("td")[0]);
401     var li = document.createElement("li");
402     msgList.appendChild(li);
403     setTextContent(li, subject);
404     li.ctdlMsgId = msgId;
405   }
406   return element;
407 }
408
409 var saved_y = 0;
410 function CtdlResizeMouseDown(event) {
411   $(document).observe('mousemove', CtdlResizeMouseMove);
412   $(document).observe('mouseup', CtdlResizeMouseUp);
413   saved_y = event.clientY;
414 }
415
416 function sizePreviewPane() {
417   var preview_pane = document.getElementById("preview_pane");
418   var summary_view = document.getElementById("summary_view");
419   var banner = document.getElementById("banner");
420   var message_list_hdr = document.getElementById("message_list_hdr");
421   var content = $('global');  // we'd like to use prototype methods here
422   var childElements = content.childElements();
423   var sizeOfElementsAbove = 0;
424   var heightOfViewPort = document.viewport.getHeight() // prototypejs method
425   var bannerHeight = banner.offsetHeight;
426   var contentViewPortHeight = heightOfViewPort-banner.offsetHeight-message_list_hdr.offsetHeight;
427   contentViewPortHeight = 0.98 * contentViewPortHeight; // leave some error
428   // Set summary_view to 20%;
429   var summary_height = ctdlLocalPrefs.readPref("svheight");
430   if (summary_height == null) {
431     summary_height = 0.20 * contentViewPortHeight;
432   }
433   // Set preview_pane to the remainder
434   var preview_height = contentViewPortHeight - summary_height;
435   
436   summary_view.style.height = (summary_height)+"px";
437   preview_pane.style.height = (preview_height)+"px";
438 }
439 function CtdlResizeMouseMove(event) {
440   var clientX = event.clientX;
441   var clientY = event.clientY;
442   var summary_view = document.getElementById("summary_view");
443   var summaryViewHeight = summary_view.offsetHeight;
444   var increment = clientY-saved_y;
445   var summary_view_height = increment+summaryViewHeight;
446   summary_view.style.height = (summary_view_height)+"px";
447   // store summary view height 
448   ctdlLocalPrefs.setPref("svheight",summary_view_height);
449   var msglist = document.getElementById("preview_pane");
450   var msgListHeight = msglist.offsetHeight;
451   msglist.style.height = (msgListHeight-increment)+"px";
452   saved_y = clientY;
453   /* For some reason the grippy doesn't work without position: absolute
454      so we need to set its top pos manually all the time */
455   var resize = document.getElementById("resize_msglist");
456   var resizePos = resize.offsetTop;
457   resize.style.top = (resizePos+increment)+"px";
458 }
459 function CtdlResizeMouseUp(event) {
460   $(document).stopObserving('mousemove', CtdlResizeMouseMove);
461   $(document).stopObserving('mouseup', CtdlResizeMouseUp);
462 }
463 function ApplySorterToggle() {
464   var className = currentSorterToggle.className;
465   className += " current_sort_mode";
466   if (currentSortMode[1] == sortRowsByDateDescending ||
467       currentSortMode[1] == sortRowsBySubjectDescending ||
468       currentSortMode[1] == sortRowsByFromDescending) {
469     className += " sort_descending";
470   } else {
471     className += " sort_ascending";
472   }
473   currentSorterToggle.className = className;
474 }
475 /** Hack to make the header table line up with the data */
476 function normalizeHeaderTable() {
477   var message_list_hdr = document.getElementById("message_list_hdr");
478   var summary_view = document.getElementById("summary_view");
479   var resize_msglist = document.getElementById("resize_msglist");
480   var headerTable = message_list_hdr.getElementsByTagName("table")[0];
481   var dataTable = summary_view.getElementsByTagName("table")[0];
482   var dataTableWidth = dataTable.offsetWidth;
483   headerTable.style.width = dataTableWidth+"px";
484 }
485
486 function setupPageSelector() {
487   var summpage = document.getElementById("summpage");
488   //var select_page = document.getElementById("selectpage");
489   summpage.innerHTML = "";
490   if (is_safe_mode) {
491     summpage.parentNode.setAttribute("style","display: inline !important"); //override webcit.css
492   } else {
493     return;
494   }
495   var pages = nummsgs / 500;
496   for(var i=0; i<pages; i++) {
497     var opt = document.createElement("option");
498     var startmsg = i * 500;
499     opt.setAttribute("value",startmsg);
500     opt.appendChild(document.createTextNode((i+1)));
501     summpage.appendChild(opt);
502   }
503 }
504 function getPage(event) {
505   var target = event.target;
506   startmsg = target.options.item(target.selectedIndex).value;
507   //query = ""; // We are getting a page from the _entire_ msg list, don't query
508   getMessages();
509 }
510 function takeOverSearchOMatic() {
511   var searchForm = document.getElementById("searchomatic").getElementsByTagName("form")[0];
512   // First disable the form post
513   searchForm.setAttribute("action","javascript:void();");
514   searchForm.removeAttribute("method");
515   $(searchForm).observe('submit', doSearch);
516 }
517 function doSearch() {
518   query = document.getElementById("srchquery").value;
519   getMessages();
520   return false;
521 }