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