!import
1 //@line 38 "/home/visbrero/mnt/roisin/rev_control/hg/mozilla/mail/components/compose/content/addressingWidgetOverlay.js"
2
3 top.MAX_RECIPIENTS = 0;
4
5 var inputElementType = "";
6 var selectElementType = "";
7 var selectElementIndexTable = null;
8
9 var gNumberOfCols = 0;
10
11 var gDragService = Components.classes["@mozilla.org/widget/dragservice;1"].getService();
12 gDragService = gDragService.QueryInterface(Components.interfaces.nsIDragService);
13 var gMimeHeaderParser = null;
14
15 var test_addresses_sequence = false;
16
17 try {
18 if (sPrefs)
19 test_addresses_sequence = sPrefs.getBoolPref("mail.debug.test_addresses_sequence");
20 }
21 catch (ex) {}
22
awGetMaxRecipients
23 function awGetMaxRecipients()
24 {
25 return top.MAX_RECIPIENTS;
26 }
27
awGetNumberOfCols
28 function awGetNumberOfCols()
29 {
30 if (gNumberOfCols == 0)
31 {
32 var listbox = document.getElementById('addressingWidget');
33 var listCols = listbox.getElementsByTagName('listcol');
34 gNumberOfCols = listCols.length;
35 if (!gNumberOfCols)
36 gNumberOfCols = 1; /* if no cols defined, that means we have only one! */
37 }
38
39 return gNumberOfCols;
40 }
41
awInputElementName
42 function awInputElementName()
43 {
44 if (inputElementType == "")
45 inputElementType = document.getElementById("addressCol2#1").localName;
46 return inputElementType;
47 }
48
awSelectElementName
49 function awSelectElementName()
50 {
51 if (selectElementType == "")
52 selectElementType = document.getElementById("addressCol1#1").localName;
53 return selectElementType;
54 }
55
56 // TODO: replace awGetSelectItemIndex with recipient type index constants
57
awGetSelectItemIndex
58 function awGetSelectItemIndex(itemData)
59 {
60 if (selectElementIndexTable == null)
61 {
62 selectElementIndexTable = new Object();
63 var selectElem = document.getElementById("addressCol1#1");
64 for (var i = 0; i < selectElem.childNodes[0].childNodes.length; i ++)
65 {
66 var aData = selectElem.childNodes[0].childNodes[i].getAttribute("value");
67 selectElementIndexTable[aData] = i;
68 }
69 }
70
71 return selectElementIndexTable[itemData];
72 }
73
Recipients2CompFields
74 function Recipients2CompFields(msgCompFields)
75 {
76 if (msgCompFields)
77 {
78 var i = 1;
79 var addrTo = "";
80 var addrCc = "";
81 var addrBcc = "";
82 var addrReply = "";
83 var addrNg = "";
84 var addrFollow = "";
85 var addrOther = "";
86 var to_Sep = "";
87 var cc_Sep = "";
88 var bcc_Sep = "";
89 var reply_Sep = "";
90 var ng_Sep = "";
91 var follow_Sep = "";
92
93 gMimeHeaderParser = Components.classes["@mozilla.org/messenger/headerparser;1"].getService(Components.interfaces.nsIMsgHeaderParser);
94
95 var recipientType;
96 var inputField;
97 var fieldValue;
98 var recipient;
99 while ((inputField = awGetInputElement(i)))
100 {
101 fieldValue = inputField.value;
102
103 if (fieldValue == null)
104 fieldValue = inputField.getAttribute("value");
105
106 if (fieldValue != "")
107 {
108 recipientType = awGetPopupElement(i).selectedItem.getAttribute("value");
109 recipient = null;
110
111 switch (recipientType)
112 {
113 case "addr_to" :
114 case "addr_cc" :
115 case "addr_bcc" :
116 case "addr_reply" :
117 try {
118 recipient = gMimeHeaderParser.reformatUnquotedAddresses(fieldValue);
119 } catch (ex) {recipient = fieldValue;}
120 break;
121 }
122
123 switch (recipientType)
124 {
125 case "addr_to" : addrTo += to_Sep + recipient; to_Sep = ","; break;
126 case "addr_cc" : addrCc += cc_Sep + recipient; cc_Sep = ","; break;
127 case "addr_bcc" : addrBcc += bcc_Sep + recipient; bcc_Sep = ","; break;
128 case "addr_reply" : addrReply += reply_Sep + recipient; reply_Sep = ","; break;
129 case "addr_newsgroups" : addrNg += ng_Sep + fieldValue; ng_Sep = ","; break;
130 case "addr_followup" : addrFollow += follow_Sep + fieldValue; follow_Sep = ","; break;
131 // do CRLF, same as PUSH_NEWLINE() in nsMsgSend.h / nsMsgCompUtils.cpp
132 // see bug #195965
133 case "addr_other" : addrOther += awGetPopupElement(i).selectedItem.getAttribute("label") + " " + fieldValue + "\r\n";break;
134 }
135 }
136 i ++;
137 }
138
139 msgCompFields.to = addrTo;
140 msgCompFields.cc = addrCc;
141 msgCompFields.bcc = addrBcc;
142 msgCompFields.replyTo = addrReply;
143 msgCompFields.newsgroups = addrNg;
144 msgCompFields.followupTo = addrFollow;
145 msgCompFields.otherRandomHeaders = addrOther;
146
147 gMimeHeaderParser = null;
148 }
149 else
150 dump("Message Compose Error: msgCompFields is null (ExtractRecipients)");
151 }
152
CompFields2Recipients
153 function CompFields2Recipients(msgCompFields)
154 {
155 if (msgCompFields) {
156 gMimeHeaderParser = Components.classes["@mozilla.org/messenger/headerparser;1"].getService(Components.interfaces.nsIMsgHeaderParser);
157
158 var listbox = document.getElementById('addressingWidget');
159 var newListBoxNode = listbox.cloneNode(false);
160 var listBoxColsClone = listbox.firstChild.cloneNode(true);
161 newListBoxNode.appendChild(listBoxColsClone);
162 var templateNode = listbox.getElementsByTagName("listitem")[0];
163 // dump("replacing child in comp fields 2 recips \n");
164 listbox.parentNode.replaceChild(newListBoxNode, listbox);
165
166 top.MAX_RECIPIENTS = 0;
167 var msgReplyTo = msgCompFields.replyTo;
168 var msgTo = msgCompFields.to;
169 var msgCC = msgCompFields.cc;
170 var msgBCC = msgCompFields.bcc;
171 var msgRandomHeaders = msgCompFields.otherRandomHeaders;
172 var msgNewsgroups = msgCompFields.newsgroups;
173 var msgFollowupTo = msgCompFields.followupTo;
174 var havePrimaryRecipient = false;
175 if(msgReplyTo)
176 awSetInputAndPopupFromArray(msgCompFields.SplitRecipients(msgReplyTo, false),
177 "addr_reply", newListBoxNode, templateNode);
178 if(msgTo)
179 {
180 var rcp = msgCompFields.SplitRecipients(msgTo, false)
181 if (rcp.count)
182 {
183 awSetInputAndPopupFromArray(rcp, "addr_to", newListBoxNode, templateNode);
184 havePrimaryRecipient = true;
185 }
186 }
187 if(msgCC)
188 awSetInputAndPopupFromArray(msgCompFields.SplitRecipients(msgCC, false),
189 "addr_cc", newListBoxNode, templateNode);
190 if(msgBCC)
191 awSetInputAndPopupFromArray(msgCompFields.SplitRecipients(msgBCC, false),
192 "addr_bcc", newListBoxNode, templateNode);
193 if(msgRandomHeaders)
194 awSetInputAndPopup(msgRandomHeaders, "addr_other", newListBoxNode, templateNode);
195 if(msgNewsgroups)
196 {
197 awSetInputAndPopup(msgNewsgroups, "addr_newsgroups", newListBoxNode, templateNode);
198 havePrimaryRecipient = true;
199 }
200 if(msgFollowupTo)
201 awSetInputAndPopup(msgFollowupTo, "addr_followup", newListBoxNode, templateNode);
202
203 // If it's a new message, we need to add an extra empty recipient.
204 if (!havePrimaryRecipient)
205 _awSetInputAndPopup("", "addr_to", newListBoxNode, templateNode);
206 awFitDummyRows(2);
207
208 // CompFields2Recipients is called whenever a user replies or edits an existing message. We want to
209 // add all of the recipients for this message to the ignore list for spell check
210 addRecipientsToIgnoreList((gCurrentIdentity ? gCurrentIdentity.identityName + ', ' : '') + msgTo + ', ' + msgCC + ', ' + msgBCC);
211
212 gMimeHeaderParser = null; //Release the mime parser
213 }
214 }
215
awSetInputAndPopupId
216 function awSetInputAndPopupId(inputElem, popupElem, rowNumber)
217 {
218 popupElem.id = "addressCol1#" + rowNumber;
219 inputElem.id = "addressCol2#" + rowNumber;
220 inputElem.setAttribute("aria-labelledby", popupElem.id);
221 }
222
awSetInputAndPopupValue
223 function awSetInputAndPopupValue(inputElem, inputValue, popupElem, popupValue, rowNumber)
224 {
225 // remove leading spaces
226 while (inputValue && inputValue[0] == " " )
227 inputValue = inputValue.substring(1, inputValue.length);
228
229 inputElem.setAttribute("value", inputValue);
230 inputElem.value = inputValue;
231
232 popupElem.selectedItem = popupElem.childNodes[0].childNodes[awGetSelectItemIndex(popupValue)];
233
234 if (rowNumber >= 0)
235 awSetInputAndPopupId(inputElem, popupElem, rowNumber);
236
237 _awSetAutoComplete(popupElem, inputElem);
238 }
239
_awSetInputAndPopup
240 function _awSetInputAndPopup(inputValue, popupValue, parentNode, templateNode)
241 {
242 top.MAX_RECIPIENTS++;
243
244 var newNode = templateNode.cloneNode(true);
245 parentNode.appendChild(newNode); // we need to insert the new node before we set the value of the select element!
246
247 var input = newNode.getElementsByTagName(awInputElementName());
248 var select = newNode.getElementsByTagName(awSelectElementName());
249
250 if (input && input.length == 1 && select && select.length == 1)
251 awSetInputAndPopupValue(input[0], inputValue, select[0], popupValue, top.MAX_RECIPIENTS)
252 }
253
awSetInputAndPopup
254 function awSetInputAndPopup(inputValue, popupValue, parentNode, templateNode)
255 {
256 if ( inputValue && popupValue )
257 {
258 var addressArray = inputValue.split(",");
259
260 for ( var index = 0; index < addressArray.length; index++ )
261 _awSetInputAndPopup(addressArray[index], popupValue, parentNode, templateNode);
262 }
263 }
264
awSetInputAndPopupFromArray
265 function awSetInputAndPopupFromArray(inputArray, popupValue, parentNode, templateNode)
266 {
267 if ( inputArray && popupValue )
268 {
269 var recipient;
270 for ( var index = 0; index < inputArray.count; index++ )
271 {
272 recipient = null;
273 if (gMimeHeaderParser)
274 try {
275 recipient = gMimeHeaderParser.unquotePhraseOrAddrWString(inputArray.StringAt(index), true);
276 } catch (ex) {};
277 if (!recipient)
278 recipient = inputArray.StringAt(index)
279 _awSetInputAndPopup(recipient, popupValue, parentNode, templateNode);
280 }
281 }
282 }
283
awRemoveRecipients
284 function awRemoveRecipients(msgCompFields, recipientType, recipientsList)
285 {
286 if (!msgCompFields)
287 return;
288
289 var recipientArray = msgCompFields.SplitRecipients(recipientsList, false);
290 if (! recipientArray)
291 return;
292
293 for ( var index = 0; index < recipientArray.count; index++ )
294 for (var row = 1; row <= top.MAX_RECIPIENTS; row ++)
295 {
296 var popup = awGetPopupElement(row);
297 if (popup.selectedItem.getAttribute("value") == recipientType)
298 {
299 var input = awGetInputElement(row);
300 if (input.value == recipientArray.StringAt(index))
301 {
302 awSetInputAndPopupValue(input, "", popup, "addr_to", -1);
303 break;
304 }
305 }
306 }
307 }
308
awAddRecipients
309 function awAddRecipients(msgCompFields, recipientType, recipientsList)
310 {
311 if (!msgCompFields)
312 return;
313
314 var recipientArray = msgCompFields.SplitRecipients(recipientsList, false);
315 if (! recipientArray)
316 return;
317
318 for ( var index = 0; index < recipientArray.count; index++ )
319 awAddRecipient(recipientType, recipientArray.StringAt(index));
320 }
321
322 // this was broken out of awAddRecipients so it can be re-used...adds a new row matching recipientType and
323 // drops in the single address.
awAddRecipient
324 function awAddRecipient(recipientType, address)
325 {
326 for (var row = 1; row <= top.MAX_RECIPIENTS; row ++)
327 {
328 if (awGetInputElement(row).value == "")
329 break;
330 }
331
332 if (row > top.MAX_RECIPIENTS)
333 awAppendNewRow(false);
334
335 awSetInputAndPopupValue(awGetInputElement(row), address, awGetPopupElement(row), recipientType, row);
336
337 /* be sure we still have an empty row left at the end */
338 if (row == top.MAX_RECIPIENTS)
339 {
340 awAppendNewRow(true);
341 awSetInputAndPopupValue(awGetInputElement(top.MAX_RECIPIENTS), "", awGetPopupElement(top.MAX_RECIPIENTS), "addr_to", top.MAX_RECIPIENTS);
342 }
343
344 // add the recipient to our spell check ignore list
345 addRecipientsToIgnoreList(address);
346 }
347
awTestRowSequence
348 function awTestRowSequence()
349 {
350 /*
351 This function is for debug and testing purpose only, normal user should not run it!
352
353 Everytime we insert or delete a row, we must be sure we didn't break the ID sequence of
354 the addressing widget rows. This function will run a quick test to see if the sequence still ok
355
356 You need to define the pref mail.debug.test_addresses_sequence to true in order to activate it
357 */
358
359 if (! test_addresses_sequence)
360 return true;
361
362 /* debug code to verify the sequence still good */
363
364 var listbox = document.getElementById('addressingWidget');
365 var listitems = listbox.getElementsByTagName('listitem');
366 if (listitems.length >= top.MAX_RECIPIENTS )
367 {
368 for (var i = 1; i <= listitems.length; i ++)
369 {
370 var item = listitems [i - 1];
371 var inputID = item.getElementsByTagName(awInputElementName())[0].getAttribute("id").split("#")[1];
372 var popupID = item.getElementsByTagName(awSelectElementName())[0].getAttribute("id").split("#")[1];
373 if (inputID != i || popupID != i)
374 {
375 dump("#ERROR: sequence broken at row " + i + ", inputID=" + inputID + ", popupID=" + popupID + "\n");
376 return false;
377 }
378 dump("---SEQUENCE OK---\n");
379 return true;
380 }
381 }
382 else
383 dump("#ERROR: listitems.length(" + listitems.length + ") < top.MAX_RECIPIENTS(" + top.MAX_RECIPIENTS + ")\n");
384
385 return false;
386 }
387
awResetAllRows
388 function awResetAllRows()
389 {
390 var maxRecipients = top.MAX_RECIPIENTS;
391
392 for (var row = 1; row <= maxRecipients ; row ++)
393 {
394 awGetInputElement(row).value = "";
395 awGetPopupElement(row).selectedIndex = 0;
396 }
397 }
398
awCleanupRows
399 function awCleanupRows()
400 {
401 var maxRecipients = top.MAX_RECIPIENTS;
402 var rowID = 1;
403
404 for (var row = 1; row <= maxRecipients; row ++)
405 {
406 var inputElem = awGetInputElement(row);
407 if (inputElem.value == "" && row < maxRecipients)
408 awRemoveRow(row, 1);
409 else
410 {
411 awSetInputAndPopupId(inputElem, awGetPopupElement(row), rowID);
412 rowID ++;
413 }
414 }
415
416 awTestRowSequence();
417 }
418
awDeleteRow
419 function awDeleteRow(rowToDelete)
420 {
421 /* When we delete a row, we must reset the id of others row in order to not break the sequence */
422 var maxRecipients = top.MAX_RECIPIENTS;
423 awRemoveRow(rowToDelete);
424
425 // assume 2 column update (input and popup)
426 for (var row = rowToDelete + 1; row <= maxRecipients; row ++)
427 awSetInputAndPopupId(awGetInputElement(row), awGetPopupElement(row), (row-1));
428
429 awTestRowSequence();
430 }
431
awClickEmptySpace
432 function awClickEmptySpace(target, setFocus)
433 {
434 if (target == null ||
435 (target.localName != "listboxbody" &&
436 target.localName != "listcell" &&
437 target.localName != "listitem"))
438 return;
439
440 var lastInput = awGetInputElement(top.MAX_RECIPIENTS);
441
442 if ( lastInput && lastInput.value )
443 awAppendNewRow(setFocus);
444 else
445 if (setFocus)
446 awSetFocus(top.MAX_RECIPIENTS, lastInput);
447 }
448
awReturnHit
449 function awReturnHit(inputElement)
450 {
451 var row = awGetRowByInputElement(inputElement);
452 var nextInput = awGetInputElement(row+1);
453
454 if ( !nextInput )
455 {
456 if ( inputElement.value )
457 awAppendNewRow(true);
458 else // No address entered, switch to Subject field
459 {
460 var subjectField = document.getElementById( 'msgSubject' );
461 subjectField.select();
462 subjectField.focus();
463 }
464 }
465 else
466 {
467 nextInput.select();
468 awSetFocus(row+1, nextInput);
469 }
470
471 // be sure to add the user add recipient to our ignore list
472 // when the user hits enter in an autocomplete widget...
473 addRecipientsToIgnoreList(inputElement.value);
474 }
475
awDeleteHit
476 function awDeleteHit(inputElement)
477 {
478 var row = awGetRowByInputElement(inputElement);
479
480 /* 1. don't delete the row if it's the last one remaining, just reset it! */
481 if (top.MAX_RECIPIENTS <= 1)
482 {
483 inputElement.value = "";
484 return;
485 }
486
487 /* 2. Set the focus to the previous field if possible */
488 if (row > 1)
489 awSetFocus(row - 1, awGetInputElement(row - 1))
490 else
491 awSetFocus(1, awGetInputElement(2)) /* We have to cheat a little bit because the focus will */
492 /* be set asynchronusly after we delete the current row, */
493 /* therefore the row number still the same! */
494
495 /* 3. Delete the row */
496 awDeleteRow(row);
497 }
498
awInputChanged
499 function awInputChanged(inputElement)
500 {
501 //Do we need to add a new row?
502 var lastInput = awGetInputElement(top.MAX_RECIPIENTS);
503 if ( lastInput && lastInput.value && !top.doNotCreateANewRow)
504 awAppendNewRow(false);
505 top.doNotCreateANewRow = false;
506 }
507
awAppendNewRow
508 function awAppendNewRow(setFocus)
509 {
510 var listbox = document.getElementById('addressingWidget');
511 var listitem1 = awGetListItem(1);
512
513 if ( listbox && listitem1 )
514 {
515 var lastRecipientType = awGetPopupElement(top.MAX_RECIPIENTS).selectedItem.getAttribute("value");
516
517 var nextDummy = awGetNextDummyRow();
518 var newNode = listitem1.cloneNode(true);
519 if (nextDummy)
520 listbox.replaceChild(newNode, nextDummy);
521 else
522 listbox.appendChild(newNode);
523
524 top.MAX_RECIPIENTS++;
525
526 var input = newNode.getElementsByTagName(awInputElementName());
527 if ( input && input.length == 1 )
528 {
529 input[0].setAttribute("value", "");
530
531 //this copies the autocomplete sessions list from recipient#1
532 input[0].syncSessions(document.getElementById('addressCol2#1'));
533
534 // also clone the showCommentColumn setting
535 //
536 input[0].showCommentColumn =
537 document.getElementById("addressCol2#1").showCommentColumn;
538
539 // We always clone the first row. The problem is that the first row
540 // could be focused. When we clone that row, we end up with a cloned
541 // XUL textbox that has a focused attribute set. Therefore we think
542 // we're focused and don't properly refocus. The best solution to this
543 // would be to clone a template row that didn't really have any presentation,
544 // rather than using the real visible first row of the listbox.
545 //
546 // For now we'll just put in a hack that ensures the focused attribute
547 // is never copied when the node is cloned.
548 if (input[0].getAttribute('focused') != '')
549 input[0].removeAttribute('focused');
550 }
551 var select = newNode.getElementsByTagName(awSelectElementName());
552 if ( select && select.length == 1 )
553 {
554 // It only makes sense to clone some field types; others
555 // should not be cloned, since it just makes the user have
556 // to go to the trouble of selecting something else. In such
557 // cases let's default to 'To' (a reasonable default since
558 // we already default to 'To' on the first dummy field of
559 // a new message).
560 switch (lastRecipientType)
561 {
562 case "addr_reply":
563 case "addr_other":
564 select[0].selectedIndex = awGetSelectItemIndex("addr_to");
565 break;
566 case "addr_followup":
567 select[0].selectedIndex = awGetSelectItemIndex("addr_newsgroups");
568 break;
569 default:
570 // e.g. "addr_to","addr_cc","addr_bcc","addr_newsgroups":
571 select[0].selectedIndex = awGetSelectItemIndex(lastRecipientType);
572 }
573
574 awSetInputAndPopupId(input[0], select[0], top.MAX_RECIPIENTS);
575
576 if (input)
577 _awSetAutoComplete(select[0], input[0]);
578 }
579
580 // focus on new input widget
581 if (setFocus && input[0] )
582 awSetFocus(top.MAX_RECIPIENTS, input[0]);
583 }
584 }
585
586 // functions for accessing the elements in the addressing widget
587
588 function awGetPopupElement(row)
589 {
590 return document.getElementById("addressCol1#" + row);
591 }
592
awGetInputElement
593 function awGetInputElement(row)
594 {
595 return document.getElementById("addressCol2#" + row);
596 }
597
awGetElementByCol
598 function awGetElementByCol(row, col)
599 {
600 var colID = "addressCol" + col + "#" + row;
601 return document.getElementById(colID);
602 }
603
awGetListItem
604 function awGetListItem(row)
605 {
606 var listbox = document.getElementById('addressingWidget');
607
608 if ( listbox && row > 0)
609 {
610 var listitems = listbox.getElementsByTagName('listitem');
611 if ( listitems && listitems.length >= row )
612 return listitems[row-1];
613 }
614 return 0;
615 }
616
awGetRowByInputElement
617 function awGetRowByInputElement(inputElement)
618 {
619 var row = 0;
620 if (inputElement) {
621 var listitem = inputElement.parentNode.parentNode;
622 while (listitem) {
623 if (listitem.localName == "listitem")
624 ++row;
625 listitem = listitem.previousSibling;
626 }
627 }
628 return row;
629 }
630
631
632 // Copy Node - copy this node and insert ahead of the (before) node. Append to end if before=0
awCopyNode
633 function awCopyNode(node, parentNode, beforeNode)
634 {
635 var newNode = node.cloneNode(true);
636
637 if ( beforeNode )
638 parentNode.insertBefore(newNode, beforeNode);
639 else
640 parentNode.appendChild(newNode);
641
642 return newNode;
643 }
644
645 // remove row
646
awRemoveRow
647 function awRemoveRow(row)
648 {
649 var listbox = document.getElementById('addressingWidget');
650
651 awRemoveNodeAndChildren(listbox, awGetListItem(row));
652 awFitDummyRows();
653
654 top.MAX_RECIPIENTS --;
655 }
656
awRemoveNodeAndChildren
657 function awRemoveNodeAndChildren(parent, nodeToRemove)
658 {
659 nodeToRemove.parentNode.removeChild(nodeToRemove);
660 }
661
awSetFocus
662 function awSetFocus(row, inputElement)
663 {
664 top.awRow = row;
665 top.awInputElement = inputElement;
666 top.awFocusRetry = 0;
667 setTimeout("_awSetFocus();", 0);
668 }
669
_awSetFocus
670 function _awSetFocus()
671 {
672 var listbox = document.getElementById('addressingWidget');
673 //try
674 //{
675 var theNewRow = awGetListItem(top.awRow);
676
677 //Warning: firstVisibleRow is zero base but top.awRow is one base!
678 var firstVisibleRow = listbox.getIndexOfFirstVisibleRow();
679 var numOfVisibleRows = listbox.getNumberOfVisibleRows();
680
681 //Do we need to scroll in order to see the selected row?
682 if (top.awRow <= firstVisibleRow)
683 listbox.scrollToIndex(top.awRow - 1);
684 else
685 if (top.awRow - 1 >= (firstVisibleRow + numOfVisibleRows))
686 listbox.scrollToIndex(top.awRow - numOfVisibleRows);
687
688 top.awInputElement.focus();
689 /*}
690 catch(ex)
691 {
692 top.awFocusRetry ++;
693 if (top.awFocusRetry < 3)
694 {
695 dump("_awSetFocus failed, try it again...\n");
696 setTimeout("_awSetFocus();", 0);
697 }
698 else
699 dump("_awSetFocus failed, forget about it!\n");
700 }*/
701 }
702
awTabFromRecipient
703 function awTabFromRecipient(element, event)
704 {
705 //If we are the last element in the listbox, we don't want to create a new row.
706 if (element == awGetInputElement(top.MAX_RECIPIENTS))
707 top.doNotCreateANewRow = true;
708
709 var row = awGetRowByInputElement(element);
710 if (!event.shiftKey && row < top.MAX_RECIPIENTS) {
711 var listBoxRow = row - 1; // listbox row indices are 0-based, ours are 1-based.
712 var listBox = document.getElementById("addressingWidget");
713 listBox.listBoxObject.ensureIndexIsVisible(listBoxRow + 1);
714 }
715
716 // be sure to add the user add recipient to our ignore list
717 // when the user tabs out of an autocomplete line...
718 addRecipientsToIgnoreList(element.value);
719 }
720
721 function awTabFromMenulist(element, event)
722 {
723 var row = awGetRowByInputElement(element);
724 if (event.shiftKey && row > 1) {
725 var listBoxRow = row - 1; // listbox row indices are 0-based, ours are 1-based.
726 var listBox = document.getElementById("addressingWidget");
727 listBox.listBoxObject.ensureIndexIsVisible(listBoxRow - 1);
728 }
729 }
730
awGetNumberOfRecipients
731 function awGetNumberOfRecipients()
732 {
733 return top.MAX_RECIPIENTS;
734 }
735
DragOverAddressingWidget
736 function DragOverAddressingWidget(event)
737 {
738 var validFlavor = false;
739 var dragSession = dragSession = gDragService.getCurrentSession();
740
741 if (dragSession.isDataFlavorSupported("text/x-moz-address"))
742 validFlavor = true;
743
744 if (validFlavor)
745 dragSession.canDrop = true;
746 }
747
DropOnAddressingWidget
748 function DropOnAddressingWidget(event)
749 {
750 var dragSession = gDragService.getCurrentSession();
751
752 var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
753 trans.addDataFlavor("text/x-moz-address");
754
755 for ( var i = 0; i < dragSession.numDropItems; ++i )
756 {
757 dragSession.getData ( trans, i );
758 var dataObj = new Object();
759 var bestFlavor = new Object();
760 var len = new Object();
761 trans.getAnyTransferData ( bestFlavor, dataObj, len );
762 if ( dataObj )
763 dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
764 if ( !dataObj )
765 continue;
766
767 // pull the address out of the data object
768 var address = dataObj.data.substring(0, len.value);
769 if (!address)
770 continue;
771
772 DropRecipient(event.target, address);
773 }
774 }
775
DropRecipient
776 function DropRecipient(target, recipient)
777 {
778 // break down and add each address
779 return parseAndAddAddresses(recipient, awGetPopupElement(top.MAX_RECIPIENTS).selectedItem.getAttribute("value"));
780 }
781
_awSetAutoComplete
782 function _awSetAutoComplete(selectElem, inputElem)
783 {
784 inputElem.disableAutocomplete = selectElem.value == 'addr_newsgroups' || selectElem.value == 'addr_followup' || selectElem.value == 'addr_other';
785 }
786
awSetAutoComplete
787 function awSetAutoComplete(rowNumber)
788 {
789 var inputElem = awGetInputElement(rowNumber);
790 var selectElem = awGetPopupElement(rowNumber);
791 _awSetAutoComplete(selectElem, inputElem)
792 }
793
awRecipientTextCommand
794 function awRecipientTextCommand(userAction, element)
795 {
796 if (userAction == "typing" || userAction == "scrolling")
797 awReturnHit(element);
798 }
799
800 // Called when an autocomplete session item is selected and the status of
801 // the session it was selected from is nsIAutoCompleteStatus::failureItems.
802 //
803 // As of this writing, the only way that can happen is when an LDAP
804 // autocomplete session returns an error to be displayed to the user.
805 //
806 // There are hardcoded messages in here, but these are just fallbacks for
807 // when string bundles have already failed us.
808 //
awRecipientErrorCommand
809 function awRecipientErrorCommand(errItem, element)
810 {
811 // remove the angle brackets from the general error message to construct
812 // the title for the alert. someday we'll pass this info using a real
813 // exception object, and then this code can go away.
814 //
815 var generalErrString;
816 if (errItem.value != "")
817 generalErrString = errItem.value.slice(1, errItem.value.length-1);
818 else
819 generalErrString = "Unknown LDAP server problem encountered";
820
821 // try and get the string of the specific error to contruct the complete
822 // err msg, otherwise fall back to something generic. This message is
823 // handed to us as an nsISupportsString in the param slot of the
824 // autocomplete error item, by agreement documented in
825 // nsILDAPAutoCompFormatter.idl
826 //
827 var specificErrString = "";
828 try
829 {
830 var specificError = errItem.param.QueryInterface(Components.interfaces.nsISupportsString);
831 specificErrString = specificError.data;
832 } catch (ex)
833 {}
834
835 if (specificErrString == "")
836 specificErrString = "Internal error";
837
838 if (gPromptService)
839 gPromptService.alert(window, generalErrString, specificErrString);
840 else
841 window.alert(generalErrString + ": " + specificErrString);
842 }
843
awRecipientKeyPress
844 function awRecipientKeyPress(event, element)
845 {
846 switch(event.keyCode) {
847 case KeyEvent.DOM_VK_UP:
848 awArrowHit(element, -1);
849 break;
850 case KeyEvent.DOM_VK_DOWN:
851 awArrowHit(element, 1);
852 break;
853 case KeyEvent.DOM_VK_RETURN:
854 case KeyEvent.DOM_VK_TAB:
855 // if the user text contains a comma or a line return, ignore
856 if (element.value.search(',') != -1)
857 {
858 var addresses = element.value;
859 element.value = ""; // clear out the current line so we don't try to autocomplete it..
860 parseAndAddAddresses(addresses, awGetPopupElement(awGetRowByInputElement(element)).selectedItem.getAttribute("value"));
861 }
862 else if (event.keyCode == KeyEvent.DOM_VK_TAB)
863 awTabFromRecipient(element, event);
864
865 break;
866 }
867 }
868
awArrowHit
869 function awArrowHit(inputElement, direction)
870 {
871 var row = awGetRowByInputElement(inputElement) + direction;
872 if (row) {
873 var nextInput = awGetInputElement(row);
874
875 if (nextInput)
876 awSetFocus(row, nextInput);
877 else if (inputElement.value)
878 awAppendNewRow(true);
879 }
880 }
881
awRecipientKeyDown
882 function awRecipientKeyDown(event, element)
883 {
884 switch(event.keyCode) {
885 case 46:
886 case 8:
887 /* do not query directly the value of the text field else the autocomplete widget could potentially
888 alter it value while doing some internal cleanup, instead, query the value through the first child
889 */
890 if (!element.value)
891 awDeleteHit(element);
892
893 // We need to stop the event else the listbox will receive it and the
894 // function awKeyDown will be executed!
895 event.stopPropagation();
896 break;
897 }
898 }
899
awKeyDown
900 function awKeyDown(event, listboxElement)
901 {
902 switch(event.keyCode) {
903 case 46:
904 case 8:
905 /* Warning, the listboxElement.selectedItems will change everytime we delete a row */
906 var selItems = listboxElement.selectedItems;
907 var length = listboxElement.selectedItems.length;
908 for (var i = 1; i <= length; i++) {
909 var inputs = listboxElement.selectedItems[0].getElementsByTagName(awInputElementName());
910 if (inputs && inputs.length == 1)
911 awDeleteHit(inputs[0]);
912 }
913 break;
914 }
915 }
916
917 function awMenulistKeyPress(event, element)
918 {
919 switch(event.keyCode) {
920 case 9:
921 awTabFromMenulist(element, event);
922 break;
923 }
924 }
925
926 /* ::::::::::: addressing widget dummy rows ::::::::::::::::: */
927
928 var gAWContentHeight = 0;
929 var gAWRowHeight = 0;
930
awFitDummyRows
931 function awFitDummyRows()
932 {
933 awCalcContentHeight();
934 awCreateOrRemoveDummyRows();
935 }
936
awCreateOrRemoveDummyRows
937 function awCreateOrRemoveDummyRows()
938 {
939 var listbox = document.getElementById("addressingWidget");
940 var listboxHeight = listbox.boxObject.height;
941
942 // remove rows to remove scrollbar
943 var kids = listbox.childNodes;
944 for (var i = kids.length-1; gAWContentHeight > listboxHeight && i >= 0; --i) {
945 if (kids[i].hasAttribute("_isDummyRow")) {
946 gAWContentHeight -= gAWRowHeight;
947 listbox.removeChild(kids[i]);
948 }
949 }
950
951 // add rows to fill space
952 if (gAWRowHeight) {
953 while (gAWContentHeight+gAWRowHeight < listboxHeight) {
954 awCreateDummyItem(listbox);
955 gAWContentHeight += gAWRowHeight;
956 }
957 }
958 }
959
awCalcContentHeight
960 function awCalcContentHeight()
961 {
962 var listbox = document.getElementById("addressingWidget");
963 var items = listbox.getElementsByTagName("listitem");
964
965 gAWContentHeight = 0;
966 if (items.length > 0) {
967 // all rows are forced to a uniform height in xul listboxes, so
968 // find the first listitem with a boxObject and use it as precedent
969 var i = 0;
970 do {
971 gAWRowHeight = items[i].boxObject.height;
972 ++i;
973 } while (i < items.length && !gAWRowHeight);
974 gAWContentHeight = gAWRowHeight*items.length;
975 }
976 }
977
awCreateDummyItem
978 function awCreateDummyItem(aParent)
979 {
980 var titem = document.createElement("listitem");
981 titem.setAttribute("_isDummyRow", "true");
982 titem.setAttribute("class", "dummy-row");
983
984 for (var i = awGetNumberOfCols(); i > 0; i--)
985 awCreateDummyCell(titem);
986
987 if (aParent)
988 aParent.appendChild(titem);
989
990 return titem;
991 }
992
awCreateDummyCell
993 function awCreateDummyCell(aParent)
994 {
995 var cell = document.createElement("listcell");
996 cell.setAttribute("class", "addressingWidgetCell dummy-row-cell");
997 if (aParent)
998 aParent.appendChild(cell);
999
1000 return cell;
1001 }
1002
awGetNextDummyRow
1003 function awGetNextDummyRow()
1004 {
1005 // gets the next row from the top down
1006 var listbox = document.getElementById("addressingWidget");
1007 var kids = listbox.childNodes;
1008 for (var i = 0; i < kids.length; ++i) {
1009 if (kids[i].hasAttribute("_isDummyRow"))
1010 return kids[i];
1011 }
1012 return null;
1013 }
1014
awSizerListen
1015 function awSizerListen()
1016 {
1017 // when splitter is clicked, fill in necessary dummy rows each time the mouse is moved
1018 awCalcContentHeight(); // precalculate
1019 document.addEventListener("mousemove", awSizerMouseMove, true);
1020 document.addEventListener("mouseup", awSizerMouseUp, false);
1021 }
1022
awSizerMouseMove
1023 function awSizerMouseMove()
1024 {
1025 awCreateOrRemoveDummyRows(2);
1026 }
1027
awSizerMouseUp
1028 function awSizerMouseUp()
1029 {
1030 document.removeEventListener("mousemove", awSizerMouseUp, false);
1031 document.removeEventListener("mouseup", awSizerMouseUp, false);
1032 }
1033
awDocumentKeyPress
1034 function awDocumentKeyPress(event)
1035 {
1036 try {
1037 var id = event.target.getAttribute('id');
1038 if (id.substr(0, 11) == 'addressCol1')
1039 awMenulistKeyPress(event, event.target);
1040 } catch (e) { }
1041 }
1042
awRecipientInputCommand
1043 function awRecipientInputCommand(event, inputElement)
1044 {
1045 gContentChanged=true;
1046 setupAutocomplete();
1047 }
1048
1049 // Given an arbitrary block of text like a comma delimited list of names or a names separated by spaces,
1050 // we will try to autocomplete each of the names and then take the FIRST match for each name, adding it the
1051 // addressing widget on the compose window.
1052
1053 var gAutomatedAutoCompleteListener = null;
1054
parseAndAddAddresses
1055 function parseAndAddAddresses(addressText, recipientType)
1056 {
1057 // strip any leading >> characters inserted by the autocomplete widget
1058 var strippedAddresses = addressText.replace(/.* >> /, "");
1059
1060 var hdrParser = Components.classes["@mozilla.org/messenger/headerparser;1"].getService(Components.interfaces.nsIMsgHeaderParser);
1061 var addresses = {};
1062 var names = {};
1063 var fullNames = {};
1064 var numAddresses = hdrParser.parseHeadersWithArray(strippedAddresses, addresses, names, fullNames);
1065
1066 if (numAddresses > 0)
1067 {
1068 // we need to set up our own autocomplete session and search for results
1069
1070 setupAutocomplete(); // be safe, make sure we are setup
1071 if (!gAutomatedAutoCompleteListener)
1072 gAutomatedAutoCompleteListener = new AutomatedAutoCompleteHandler();
1073
1074 gAutomatedAutoCompleteListener.init(fullNames.value, numAddresses, recipientType);
1075 }
1076 }
1077
AutomatedAutoCompleteHandler
1078 function AutomatedAutoCompleteHandler()
1079 {
1080 }
1081
1082 // state driven self contained object which will autocomplete a block of addresses without any UI.
1083 // force picks the first match and adds it to the addressing widget, then goes on to the next
1084 // name to complete.
1085
1086 AutomatedAutoCompleteHandler.prototype =
1087 {
1088 param: this,
1089 sessionName: null,
1090 namesToComplete: {},
1091 numNamesToComplete: 0,
1092 indexIntoNames: 0,
1093
1094 numSessionsToSearch: 0,
1095 numSessionsSearched: 0,
1096 recipientType: null,
1097 searchResults: null,
1098
init
1099 init:function(namesToComplete, numNamesToComplete, recipientType)
1100 {
1101 this.indexIntoNames = 0;
1102 this.numNamesToComplete = numNamesToComplete;
1103 this.namesToComplete = namesToComplete;
1104
1105 this.recipientType = recipientType;
1106
1107 // set up the auto complete sessions to use
1108 setupAutocomplete();
1109 this.autoCompleteNextAddress();
1110 },
1111
autoCompleteNextAddress
1112 autoCompleteNextAddress:function()
1113 {
1114 this.numSessionsToSearch = 0;
1115 this.numSessionsSearched = 0;
1116 this.searchResults = new Array;
1117
1118 if (this.indexIntoNames < this.numNamesToComplete && this.namesToComplete[this.indexIntoNames])
1119 {
1120 if (this.namesToComplete[this.indexIntoNames].search('@') == -1) // don't autocomplete if address has an @ sign in it
1121 {
1122 // make sure total session count is updated before we kick off ANY actual searches
1123 if (gAutocompleteSession)
1124 this.numSessionsToSearch++;
1125
1126 if (gLDAPSession && gCurrentAutocompleteDirectory)
1127 this.numSessionsToSearch++;
1128
1129 if (gAutocompleteSession)
1130 {
1131 gAutocompleteSession.onAutoComplete(this.namesToComplete[this.indexIntoNames], null, this);
1132 // AB searches are actually synchronous. So by the time we get here we have already looked up results.
1133
1134 // if we WERE going to also do an LDAP lookup, then check to see if we have a valid match in the AB, if we do
1135 // don't bother with the LDAP search too just return
1136
1137 if (gLDAPSession && gCurrentAutocompleteDirectory && this.searchResults[0] && this.searchResults[0].defaultItemIndex != -1)
1138 {
1139 this.processAllResults();
1140 return;
1141 }
1142 }
1143
1144 if (gLDAPSession && gCurrentAutocompleteDirectory)
1145 gLDAPSession.onStartLookup(this.namesToComplete[this.indexIntoNames], null, this);
1146 }
1147
1148 if (!this.numSessionsToSearch)
1149 this.processAllResults(); // ldap and ab are turned off, so leave text alone
1150 }
1151 },
1152
onStatus
1153 onStatus:function(aStatus)
1154 {
1155 return;
1156 },
1157
onAutoComplete
1158 onAutoComplete: function(aResults, aStatus)
1159 {
1160 // store the results until all sessions are done and have reported in
1161 if (aResults)
1162 this.searchResults[this.numSessionsSearched] = aResults;
1163
1164 this.numSessionsSearched++; // bump our counter
1165
1166 if (this.numSessionsToSearch <= this.numSessionsSearched)
1167 setTimeout('gAutomatedAutoCompleteListener.processAllResults()', 0); // we are all done
1168 },
1169
processAllResults
1170 processAllResults: function()
1171 {
1172 // Take the first result and add it to the compose window
1173 var addressToAdd;
1174
1175 // loop through the results looking for the non default case (default case is the address book with only one match, the default domain)
1176 var sessionIndex;
1177
1178 var searchResultsForSession;
1179
1180 for (sessionIndex in this.searchResults)
1181 {
1182 searchResultsForSession = this.searchResults[sessionIndex];
1183 if (searchResultsForSession && searchResultsForSession.defaultItemIndex > -1)
1184 {
1185 addressToAdd = searchResultsForSession.items.QueryElementAt(searchResultsForSession.defaultItemIndex, Components.interfaces.nsIAutoCompleteItem).value;
1186 break;
1187 }
1188 }
1189
1190 // still no match? loop through looking for the -1 default index
1191 if (!addressToAdd)
1192 {
1193 for (sessionIndex in this.searchResults)
1194 {
1195 searchResultsForSession = this.searchResults[sessionIndex];
1196 if (searchResultsForSession && searchResultsForSession.defaultItemIndex == -1)
1197 {
1198 addressToAdd = searchResultsForSession.items.QueryElementAt(0, Components.interfaces.nsIAutoCompleteItem).value;
1199 break;
1200 }
1201 }
1202 }
1203
1204 // no matches anywhere...just use what we were given
1205 if (!addressToAdd)
1206 addressToAdd = this.namesToComplete[this.indexIntoNames];
1207
1208 // that will automatically set the focus on a new available row, and make sure it is visible
1209 awAddRecipient(this.recipientType ? this.recipientType : "addr_to", addressToAdd);
1210
1211 this.indexIntoNames++;
1212 this.autoCompleteNextAddress();
1213 },
1214
QueryInterface
1215 QueryInterface : function(iid)
1216 {
1217 if (iid.equals(Components.interfaces.nsIAutoCompleteListener) ||
1218 iid.equals(Components.interfaces.nsISupports))
1219 return this;
1220 throw Components.results.NS_NOINTERFACE;
1221 }
1222 }