!import
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
23 function awGetMaxRecipients()
24 {
25 return top.MAX_RECIPIENTS;
26 }
27
awGetNumberOfCols
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
42 function awInputElementName()
43 {
44 if (inputElementType == "")
45 inputElementType = document.getElementById("addressCol2#1").localName;
46 return inputElementType;
47 }
48
awSelectElementName
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
588 function awGetPopupElement(row)
589 {
590 return document.getElementById("addressCol1#" + row);
591 }
592
awGetInputElement
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
593 function awGetInputElement(row)
594 {
595 return document.getElementById("addressCol2#" + row);
596 }
597
awGetElementByCol
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
598 function awGetElementByCol(row, col)
599 {
600 var colID = "addressCol" + col + "#" + row;
601 return document.getElementById(colID);
602 }
603
awGetListItem
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
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
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
657 function awRemoveNodeAndChildren(parent, nodeToRemove)
658 {
659 nodeToRemove.parentNode.removeChild(nodeToRemove);
660 }
661
awSetFocus
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
662 function awSetFocus(row, inputElement)
663 {
664 top.awRow = row;
665 top.awInputElement = inputElement;
666 setTimeout(_awSetFocus, 0);
667 }
668
_awSetFocus
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
669 function _awSetFocus()
670 {
671 var listbox = document.getElementById('addressingWidget');
672 var theNewRow = awGetListItem(top.awRow);
673
674 // Warning: firstVisibleRow is zero base but top.awRow is one base!
675 var firstVisibleRow = listbox.getIndexOfFirstVisibleRow();
676 var numOfVisibleRows = listbox.getNumberOfVisibleRows();
677
678 // Do we need to scroll in order to see the selected row?
679 if (top.awRow <= firstVisibleRow)
680 listbox.scrollToIndex(top.awRow - 1);
681 else if (top.awRow - 1 >= (firstVisibleRow + numOfVisibleRows))
682 listbox.scrollToIndex(top.awRow - numOfVisibleRows);
683
684 top.awInputElement.focus();
685 }
686
awTabFromRecipient
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
687 function awTabFromRecipient(element, event)
688 {
689 // If we are the last element in the listbox, we don't want to create a new row.
690 if (element == awGetInputElement(top.MAX_RECIPIENTS))
691 top.doNotCreateANewRow = true;
692
693 var row = awGetRowByInputElement(element);
694 if (!event.shiftKey && row < top.MAX_RECIPIENTS) {
695 var listBoxRow = row - 1; // listbox row indices are 0-based, ours are 1-based.
696 var listBox = document.getElementById("addressingWidget");
697 listBox.listBoxObject.ensureIndexIsVisible(listBoxRow + 1);
698 }
699
700 // be sure to add the user add recipient to our ignore list
701 // when the user tabs out of an autocomplete line...
702 addRecipientsToIgnoreList(element.value);
703 }
704
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
705 function awTabFromMenulist(element, event)
706 {
707 var row = awGetRowByInputElement(element);
708 if (event.shiftKey && row > 1) {
709 var listBoxRow = row - 1; // listbox row indices are 0-based, ours are 1-based.
710 var listBox = document.getElementById("addressingWidget");
711 listBox.listBoxObject.ensureIndexIsVisible(listBoxRow - 1);
712 }
713 }
714
awGetNumberOfRecipients
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
715 function awGetNumberOfRecipients()
716 {
717 return top.MAX_RECIPIENTS;
718 }
719
DragOverAddressingWidget
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
720 function DragOverAddressingWidget(event)
721 {
722 var validFlavor = false;
723 var dragSession = dragSession = gDragService.getCurrentSession();
724
725 if (dragSession.isDataFlavorSupported("text/x-moz-address"))
726 validFlavor = true;
727
728 if (validFlavor)
729 dragSession.canDrop = true;
730 }
731
DropOnAddressingWidget
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
732 function DropOnAddressingWidget(event)
733 {
734 var dragSession = gDragService.getCurrentSession();
735
736 var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
737 trans.addDataFlavor("text/x-moz-address");
738
739 for ( var i = 0; i < dragSession.numDropItems; ++i )
740 {
741 dragSession.getData ( trans, i );
742 var dataObj = new Object();
743 var bestFlavor = new Object();
744 var len = new Object();
745 trans.getAnyTransferData ( bestFlavor, dataObj, len );
746 if ( dataObj )
747 dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
748 if ( !dataObj )
749 continue;
750
751 // pull the address out of the data object
752 var address = dataObj.data.substring(0, len.value);
753 if (!address)
754 continue;
755
756 DropRecipient(event.target, address);
757 }
758 }
759
DropRecipient
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
760 function DropRecipient(target, recipient)
761 {
762 // break down and add each address
763 return parseAndAddAddresses(recipient, awGetPopupElement(top.MAX_RECIPIENTS).selectedItem.getAttribute("value"));
764 }
765
_awSetAutoComplete
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
766 function _awSetAutoComplete(selectElem, inputElem)
767 {
768 inputElem.disableAutoComplete = selectElem.value == 'addr_newsgroups' ||
769 selectElem.value == 'addr_followup' ||
770 selectElem.value == 'addr_other';
771 }
772
awSetAutoComplete
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
773 function awSetAutoComplete(rowNumber)
774 {
775 var inputElem = awGetInputElement(rowNumber);
776 var selectElem = awGetPopupElement(rowNumber);
777 _awSetAutoComplete(selectElem, inputElem)
778 }
779
awRecipientTextCommand
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
780 function awRecipientTextCommand(userAction, element)
781 {
782 if (userAction == "typing" || userAction == "scrolling")
783 awReturnHit(element);
784 }
785
786 // Called when an autocomplete session item is selected and the status of
787 // the session it was selected from is nsIAutoCompleteStatus::failureItems.
788 //
789 // As of this writing, the only way that can happen is when an LDAP
790 // autocomplete session returns an error to be displayed to the user.
791 //
792 // There are hardcoded messages in here, but these are just fallbacks for
793 // when string bundles have already failed us.
794 //
awRecipientErrorCommand
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
795 function awRecipientErrorCommand(errItem, element)
796 {
797 // remove the angle brackets from the general error message to construct
798 // the title for the alert. someday we'll pass this info using a real
799 // exception object, and then this code can go away.
800 //
801 var generalErrString;
802 if (errItem.value != "")
803 generalErrString = errItem.value.slice(1, errItem.value.length-1);
804 else
805 generalErrString = "Unknown LDAP server problem encountered";
806
807 // try and get the string of the specific error to contruct the complete
808 // err msg, otherwise fall back to something generic. This message is
809 // handed to us as an nsISupportsString in the param slot of the
810 // autocomplete error item, by agreement documented in
811 // nsILDAPAutoCompFormatter.idl
812 //
813 var specificErrString = "";
814 try
815 {
816 var specificError = errItem.param.QueryInterface(Components.interfaces.nsISupportsString);
817 specificErrString = specificError.data;
818 } catch (ex)
819 {}
820
821 if (specificErrString == "")
822 specificErrString = "Internal error";
823
824 if (gPromptService)
825 gPromptService.alert(window, generalErrString, specificErrString);
826 else
827 window.alert(generalErrString + ": " + specificErrString);
828 }
829
awRecipientKeyPress
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
830 function awRecipientKeyPress(event, element)
831 {
832 switch(event.keyCode) {
833 case KeyEvent.DOM_VK_UP:
834 awArrowHit(element, -1);
835 break;
836 case KeyEvent.DOM_VK_DOWN:
837 awArrowHit(element, 1);
838 break;
839 case KeyEvent.DOM_VK_RETURN:
840 case KeyEvent.DOM_VK_TAB:
841 // if the user text contains a comma or a line return, ignore
842 if (element.value.search(',') != -1)
843 {
844 var addresses = element.value;
845 element.value = ""; // clear out the current line so we don't try to autocomplete it..
846 parseAndAddAddresses(addresses, awGetPopupElement(awGetRowByInputElement(element)).selectedItem.getAttribute("value"));
847 }
848 else if (event.keyCode == KeyEvent.DOM_VK_TAB)
849 awTabFromRecipient(element, event);
850
851 break;
852 }
853 }
854
awArrowHit
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
855 function awArrowHit(inputElement, direction)
856 {
857 var row = awGetRowByInputElement(inputElement) + direction;
858 if (row) {
859 var nextInput = awGetInputElement(row);
860
861 if (nextInput)
862 awSetFocus(row, nextInput);
863 else if (inputElement.value)
864 awAppendNewRow(true);
865 }
866 }
867
awRecipientKeyDown
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
868 function awRecipientKeyDown(event, element)
869 {
870 switch(event.keyCode) {
871 case 46:
872 case 8:
873 /* do not query directly the value of the text field else the autocomplete widget could potentially
874 alter it value while doing some internal cleanup, instead, query the value through the first child
875 */
876 if (!element.value)
877 awDeleteHit(element);
878
879 // We need to stop the event else the listbox will receive it and the
880 // function awKeyDown will be executed!
881 event.stopPropagation();
882 break;
883 }
884 }
885
awKeyDown
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
886 function awKeyDown(event, listboxElement)
887 {
888 switch(event.keyCode) {
889 case 46:
890 case 8:
891 /* Warning, the listboxElement.selectedItems will change everytime we delete a row */
892 var selItems = listboxElement.selectedItems;
893 var length = listboxElement.selectedItems.length;
894 for (var i = 1; i <= length; i++) {
895 var inputs = listboxElement.selectedItems[0].getElementsByTagName(awInputElementName());
896 if (inputs && inputs.length == 1)
897 awDeleteHit(inputs[0]);
898 }
899 break;
900 }
901 }
902
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
903 function awMenulistKeyPress(event, element)
904 {
905 switch(event.keyCode) {
906 case 9:
907 awTabFromMenulist(element, event);
908 break;
909 }
910 }
911
912 /* ::::::::::: addressing widget dummy rows ::::::::::::::::: */
913
914 var gAWContentHeight = 0;
915 var gAWRowHeight = 0;
916
awFitDummyRows
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
917 function awFitDummyRows()
918 {
919 awCalcContentHeight();
920 awCreateOrRemoveDummyRows();
921 }
922
awCreateOrRemoveDummyRows
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
923 function awCreateOrRemoveDummyRows()
924 {
925 var listbox = document.getElementById("addressingWidget");
926 var listboxHeight = listbox.boxObject.height;
927
928 // remove rows to remove scrollbar
929 var kids = listbox.childNodes;
930 for (var i = kids.length-1; gAWContentHeight > listboxHeight && i >= 0; --i) {
931 if (kids[i].hasAttribute("_isDummyRow")) {
932 gAWContentHeight -= gAWRowHeight;
933 listbox.removeChild(kids[i]);
934 }
935 }
936
937 // add rows to fill space
938 if (gAWRowHeight) {
939 while (gAWContentHeight+gAWRowHeight < listboxHeight) {
940 awCreateDummyItem(listbox);
941 gAWContentHeight += gAWRowHeight;
942 }
943 }
944 }
945
awCalcContentHeight
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
946 function awCalcContentHeight()
947 {
948 var listbox = document.getElementById("addressingWidget");
949 var items = listbox.getElementsByTagName("listitem");
950
951 gAWContentHeight = 0;
952 if (items.length > 0) {
953 // all rows are forced to a uniform height in xul listboxes, so
954 // find the first listitem with a boxObject and use it as precedent
955 var i = 0;
956 do {
957 gAWRowHeight = items[i].boxObject.height;
958 ++i;
959 } while (i < items.length && !gAWRowHeight);
960 gAWContentHeight = gAWRowHeight*items.length;
961 }
962 }
963
awCreateDummyItem
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
964 function awCreateDummyItem(aParent)
965 {
966 var titem = document.createElement("listitem");
967 titem.setAttribute("_isDummyRow", "true");
968 titem.setAttribute("class", "dummy-row");
969
970 for (var i = awGetNumberOfCols(); i > 0; i--)
971 awCreateDummyCell(titem);
972
973 if (aParent)
974 aParent.appendChild(titem);
975
976 return titem;
977 }
978
awCreateDummyCell
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
979 function awCreateDummyCell(aParent)
980 {
981 var cell = document.createElement("listcell");
982 cell.setAttribute("class", "addressingWidgetCell dummy-row-cell");
983 if (aParent)
984 aParent.appendChild(cell);
985
986 return cell;
987 }
988
awGetNextDummyRow
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
989 function awGetNextDummyRow()
990 {
991 // gets the next row from the top down
992 var listbox = document.getElementById("addressingWidget");
993 var kids = listbox.childNodes;
994 for (var i = 0; i < kids.length; ++i) {
995 if (kids[i].hasAttribute("_isDummyRow"))
996 return kids[i];
997 }
998 return null;
999 }
1000
awSizerListen
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1001 function awSizerListen()
1002 {
1003 // when splitter is clicked, fill in necessary dummy rows each time the mouse is moved
1004 awCalcContentHeight(); // precalculate
1005 document.addEventListener("mousemove", awSizerMouseMove, true);
1006 document.addEventListener("mouseup", awSizerMouseUp, false);
1007 }
1008
awSizerMouseMove
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1009 function awSizerMouseMove()
1010 {
1011 awCreateOrRemoveDummyRows(2);
1012 }
1013
awSizerMouseUp
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1014 function awSizerMouseUp()
1015 {
1016 document.removeEventListener("mousemove", awSizerMouseUp, false);
1017 document.removeEventListener("mouseup", awSizerMouseUp, false);
1018 }
1019
awDocumentKeyPress
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1020 function awDocumentKeyPress(event)
1021 {
1022 try {
1023 var id = event.target.getAttribute('id');
1024 if (id.substr(0, 11) == 'addressCol1')
1025 awMenulistKeyPress(event, event.target);
1026 } catch (e) { }
1027 }
1028
awRecipientInputCommand
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1029 function awRecipientInputCommand(event, inputElement)
1030 {
1031 gContentChanged=true;
1032 setupAutocomplete();
1033 }
1034
1035 // Given an arbitrary block of text like a comma delimited list of names or a names separated by spaces,
1036 // we will try to autocomplete each of the names and then take the FIRST match for each name, adding it the
1037 // addressing widget on the compose window.
1038
1039 var gAutomatedAutoCompleteListener = null;
1040
parseAndAddAddresses
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1041 function parseAndAddAddresses(addressText, recipientType)
1042 {
1043 // strip any leading >> characters inserted by the autocomplete widget
1044 var strippedAddresses = addressText.replace(/.* >> /, "");
1045
1046 var hdrParser = Components.classes["@mozilla.org/messenger/headerparser;1"].getService(Components.interfaces.nsIMsgHeaderParser);
1047 var addresses = {};
1048 var names = {};
1049 var fullNames = {};
1050 var numAddresses = hdrParser.parseHeadersWithArray(strippedAddresses, addresses, names, fullNames);
1051
1052 if (numAddresses > 0)
1053 {
1054 // we need to set up our own autocomplete session and search for results
1055
1056 setupAutocomplete(); // be safe, make sure we are setup
1057 if (!gAutomatedAutoCompleteListener)
1058 gAutomatedAutoCompleteListener = new AutomatedAutoCompleteHandler();
1059
1060 gAutomatedAutoCompleteListener.init(fullNames.value, numAddresses, recipientType);
1061 }
1062 }
1063
AutomatedAutoCompleteHandler
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1064 function AutomatedAutoCompleteHandler()
1065 {
1066 }
1067
1068 // state driven self contained object which will autocomplete a block of addresses without any UI.
1069 // force picks the first match and adds it to the addressing widget, then goes on to the next
1070 // name to complete.
1071
1072 AutomatedAutoCompleteHandler.prototype =
1073 {
1074 param: this,
1075 sessionName: null,
1076 namesToComplete: {},
1077 numNamesToComplete: 0,
1078 indexIntoNames: 0,
1079
1080 numSessionsToSearch: 0,
1081 numSessionsSearched: 0,
1082 recipientType: null,
1083 searchResults: null,
1084
init
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1085 init:function(namesToComplete, numNamesToComplete, recipientType)
1086 {
1087 this.indexIntoNames = 0;
1088 this.numNamesToComplete = numNamesToComplete;
1089 this.namesToComplete = namesToComplete;
1090
1091 this.recipientType = recipientType;
1092
1093 // set up the auto complete sessions to use
1094 setupAutocomplete();
1095 this.autoCompleteNextAddress();
1096 },
1097
autoCompleteNextAddress
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1098 autoCompleteNextAddress:function()
1099 {
1100 this.numSessionsToSearch = 0;
1101 this.numSessionsSearched = 0;
1102 this.searchResults = new Array;
1103
1104 if (this.indexIntoNames < this.numNamesToComplete && this.namesToComplete[this.indexIntoNames])
1105 {
1106 if (this.namesToComplete[this.indexIntoNames].search('@') == -1) // don't autocomplete if address has an @ sign in it
1107 {
1108 // make sure total session count is updated before we kick off ANY actual searches
1109 if (gAutocompleteSession)
1110 this.numSessionsToSearch++;
1111
1112 if (gLDAPSession && gCurrentAutocompleteDirectory)
1113 this.numSessionsToSearch++;
1114
1115 if (gAutocompleteSession)
1116 {
1117 gAutocompleteSession.onAutoComplete(this.namesToComplete[this.indexIntoNames], null, this);
1118 // AB searches are actually synchronous. So by the time we get here we have already looked up results.
1119
1120 // 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
1121 // don't bother with the LDAP search too just return
1122
1123 if (gLDAPSession && gCurrentAutocompleteDirectory && this.searchResults[0] && this.searchResults[0].defaultItemIndex != -1)
1124 {
1125 this.processAllResults();
1126 return;
1127 }
1128 }
1129
1130 if (gLDAPSession && gCurrentAutocompleteDirectory)
1131 gLDAPSession.onStartLookup(this.namesToComplete[this.indexIntoNames], null, this);
1132 }
1133
1134 if (!this.numSessionsToSearch)
1135 this.processAllResults(); // ldap and ab are turned off, so leave text alone
1136 }
1137 },
1138
onStatus
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1139 onStatus:function(aStatus)
1140 {
1141 return;
1142 },
1143
onAutoComplete
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1144 onAutoComplete: function(aResults, aStatus)
1145 {
1146 // store the results until all sessions are done and have reported in
1147 if (aResults)
1148 this.searchResults[this.numSessionsSearched] = aResults;
1149
1150 this.numSessionsSearched++; // bump our counter
1151
1152 if (this.numSessionsToSearch <= this.numSessionsSearched)
1153 setTimeout('gAutomatedAutoCompleteListener.processAllResults()', 0); // we are all done
1154 },
1155
processAllResults
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1156 processAllResults: function()
1157 {
1158 // Take the first result and add it to the compose window
1159 var addressToAdd;
1160
1161 // loop through the results looking for the non default case (default case is the address book with only one match, the default domain)
1162 var sessionIndex;
1163
1164 var searchResultsForSession;
1165
1166 for (sessionIndex in this.searchResults)
1167 {
1168 searchResultsForSession = this.searchResults[sessionIndex];
1169 if (searchResultsForSession && searchResultsForSession.defaultItemIndex > -1)
1170 {
1171 addressToAdd = searchResultsForSession.items.QueryElementAt(searchResultsForSession.defaultItemIndex, Components.interfaces.nsIAutoCompleteItem).value;
1172 break;
1173 }
1174 }
1175
1176 // still no match? loop through looking for the -1 default index
1177 if (!addressToAdd)
1178 {
1179 for (sessionIndex in this.searchResults)
1180 {
1181 searchResultsForSession = this.searchResults[sessionIndex];
1182 if (searchResultsForSession && searchResultsForSession.defaultItemIndex == -1)
1183 {
1184 addressToAdd = searchResultsForSession.items.QueryElementAt(0, Components.interfaces.nsIAutoCompleteItem).value;
1185 break;
1186 }
1187 }
1188 }
1189
1190 // no matches anywhere...just use what we were given
1191 if (!addressToAdd)
1192 addressToAdd = this.namesToComplete[this.indexIntoNames];
1193
1194 // that will automatically set the focus on a new available row, and make sure it is visible
1195 awAddRecipient(this.recipientType ? this.recipientType : "addr_to", addressToAdd);
1196
1197 this.indexIntoNames++;
1198 this.autoCompleteNextAddress();
1199 },
1200
QueryInterface
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1201 QueryInterface : function(iid)
1202 {
1203 if (iid.equals(Components.interfaces.nsIAutoCompleteListener) ||
1204 iid.equals(Components.interfaces.nsISupports))
1205 return this;
1206 throw Components.results.NS_NOINTERFACE;
1207 }
1208 }