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