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