Re-fix Bug 26950: Make the summary and alias fields support click-to-edit <https...
[WebKit-https.git] / Websites / bugs.webkit.org / js / field.js
1 /* The contents of this file are subject to the Mozilla Public
2  * License Version 1.1 (the "License"); you may not use this file
3  * except in compliance with the License. You may obtain a copy of
4  * the License at http://www.mozilla.org/MPL/
5  *
6  * Software distributed under the License is distributed on an "AS
7  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
8  * implied. See the License for the specific language governing
9  * rights and limitations under the License.
10  *
11  * The Original Code is the Bugzilla Bug Tracking System.
12  *
13  * The Initial Developer of the Original Code is Everything Solved, Inc.
14  * Portions created by Everything Solved are Copyright (C) 2007 Everything
15  * Solved, Inc. All Rights Reserved.
16  *
17  * Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
18  *                 Guy Pyrzak <guy.pyrzak@gmail.com>
19  *                 Reed Loden <reed@reedloden.com>
20  */
21
22 /* This library assumes that the needed YUI libraries have been loaded 
23    already. */
24
25 var bz_no_validate_enter_bug = false;
26 function validateEnterBug(theform) {
27     // This is for the "bookmarkable templates" button.
28     if (bz_no_validate_enter_bug) {
29         // Set it back to false for people who hit the "back" button
30         bz_no_validate_enter_bug = false;
31         return true;
32     }
33
34     var component = theform.component;
35     var short_desc = theform.short_desc;
36     var version = theform.version;
37     var bug_status = theform.bug_status;
38     var description = theform.comment;
39     var attach_data = theform.data;
40     var attach_desc = theform.description;
41
42     var current_errors = YAHOO.util.Dom.getElementsByClassName(
43         'validation_error_text', null, theform);
44     for (var i = 0; i < current_errors.length; i++) {
45         current_errors[i].parentNode.removeChild(current_errors[i]);
46     }
47     var current_error_fields = YAHOO.util.Dom.getElementsByClassName(
48         'validation_error_field', null, theform);
49     for (var i = 0; i < current_error_fields.length; i++) {
50         var field = current_error_fields[i];
51         YAHOO.util.Dom.removeClass(field, 'validation_error_field');
52     }
53
54     var focus_me;
55
56     // These are checked in the reverse order that they appear on the page,
57     // so that the one closest to the top of the form will be focused.
58     if (attach_data.value && YAHOO.lang.trim(attach_desc.value) == '') {
59         _errorFor(attach_desc, 'attach_desc');
60         focus_me = attach_desc;
61     }
62     var check_description = status_comment_required[bug_status.value];
63     if (check_description && YAHOO.lang.trim(description.value) == '') {
64         _errorFor(description, 'description');
65         focus_me = description;
66     }
67     if (YAHOO.lang.trim(short_desc.value) == '') {
68         _errorFor(short_desc);
69         focus_me = short_desc;
70     }
71     if (version.selectedIndex < 0) {
72         _errorFor(version);
73         focus_me = version;
74     }
75     if (component.selectedIndex < 0) {
76         _errorFor(component);
77         focus_me = component;
78     }
79
80     if (focus_me) {
81         focus_me.focus();
82         return false;
83     }
84
85     return true;
86 }
87
88 function _errorFor(field, name) {
89     if (!name) name = field.id;
90     var string_name = name + '_required';
91     var error_text = BUGZILLA.string[string_name];
92     var new_node = document.createElement('div');
93     YAHOO.util.Dom.addClass(new_node, 'validation_error_text');
94     new_node.innerHTML = error_text;
95     YAHOO.util.Dom.insertAfter(new_node, field);
96     YAHOO.util.Dom.addClass(field, 'validation_error_field');
97 }
98
99 function createCalendar(name) {
100     var cal = new YAHOO.widget.Calendar('calendar_' + name, 
101                                         'con_calendar_' + name);
102     YAHOO.bugzilla['calendar_' + name] = cal;
103     var field = document.getElementById(name);
104     cal.selectEvent.subscribe(setFieldFromCalendar, field, false);
105     updateCalendarFromField(field);
106     cal.render();
107 }
108
109 /* The onclick handlers for the button that shows the calendar. */
110 function showCalendar(field_name) {
111     var calendar  = YAHOO.bugzilla["calendar_" + field_name];
112     var field     = document.getElementById(field_name);
113     var button    = document.getElementById('button_calendar_' + field_name);
114
115     bz_overlayBelow(calendar.oDomContainer, field);
116     calendar.show();
117     button.onclick = function() { hideCalendar(field_name); };
118
119     // Because of the way removeListener works, this has to be a function
120     // attached directly to this calendar.
121     calendar.bz_myBodyCloser = function(event) {
122         var container = this.oDomContainer;
123         var target    = YAHOO.util.Event.getTarget(event);
124         if (target != container && target != button
125             && !YAHOO.util.Dom.isAncestor(container, target))
126         {
127             hideCalendar(field_name);
128         }
129     };
130
131     // If somebody clicks outside the calendar, hide it.
132     YAHOO.util.Event.addListener(document.body, 'click', 
133                                  calendar.bz_myBodyCloser, calendar, true);
134
135     // Make Esc close the calendar.
136     calendar.bz_escCal = function (event) {
137         var key = YAHOO.util.Event.getCharCode(event);
138         if (key == 27) {
139             hideCalendar(field_name);
140         }
141     };
142     YAHOO.util.Event.addListener(document.body, 'keydown', calendar.bz_escCal);
143 }
144
145 function hideCalendar(field_name) {
146     var cal = YAHOO.bugzilla["calendar_" + field_name];
147     cal.hide();
148     var button = document.getElementById('button_calendar_' + field_name);
149     button.onclick = function() { showCalendar(field_name); };
150     YAHOO.util.Event.removeListener(document.body, 'click',
151                                     cal.bz_myBodyCloser);
152     YAHOO.util.Event.removeListener(document.body, 'keydown', cal.bz_escCal);
153 }
154
155 /* This is the selectEvent for our Calendar objects on our custom 
156  * DateTime fields.
157  */
158 function setFieldFromCalendar(type, args, date_field) {
159     var dates = args[0];
160     var setDate = dates[0];
161
162     // We can't just write the date straight into the field, because there 
163     // might already be a time there.
164     var timeRe = /\b(\d{1,2}):(\d\d)(?::(\d\d))?/;
165     var currentTime = timeRe.exec(date_field.value);
166     var d = new Date(setDate[0], setDate[1] - 1, setDate[2]);
167     if (currentTime) {
168         d.setHours(currentTime[1], currentTime[2]);
169         if (currentTime[3]) {
170             d.setSeconds(currentTime[3]);
171         }
172     }
173
174     var year = d.getFullYear();
175     // JavaScript's "Date" represents January as 0 and December as 11.
176     var month = d.getMonth() + 1;
177     if (month < 10) month = '0' + String(month);
178     var day = d.getDate();
179     if (day < 10) day = '0' + String(day);
180     var dateStr = year + '-' + month  + '-' + day;
181
182     if (currentTime) {
183         var minutes = d.getMinutes();
184         if (minutes < 10) minutes = '0' + String(minutes);
185         var seconds = d.getSeconds();
186         if (seconds > 0 && seconds < 10) {
187             seconds = '0' + String(seconds);
188         }
189
190         dateStr = dateStr + ' ' + d.getHours() + ':' + minutes;
191         if (seconds) dateStr = dateStr + ':' + seconds;
192     }
193
194     date_field.value = dateStr;
195     hideCalendar(date_field.id);
196 }
197
198 /* Sets the calendar based on the current field value. 
199  */ 
200 function updateCalendarFromField(date_field) {
201     var dateRe = /(\d\d\d\d)-(\d\d?)-(\d\d?)/;
202     var pieces = dateRe.exec(date_field.value);
203     if (pieces) {
204         var cal = YAHOO.bugzilla["calendar_" + date_field.id];
205         cal.select(new Date(pieces[1], pieces[2] - 1, pieces[3]));
206         var selectedArray = cal.getSelectedDates();
207         var selected = selectedArray[0];
208         cal.cfg.setProperty("pagedate", (selected.getMonth() + 1) + '/' 
209                                         + selected.getFullYear());
210         cal.render();
211     }
212 }
213
214 function setupEditLink(id) {
215     var link_container = 'container_showhide_' + id;
216     var input_container = 'container_' + id;
217     var link = 'showhide_' + id;
218     hideEditableField(link_container, input_container, link);
219 }
220
221 /* Hide input/select fields and show the text with (edit) next to it */
222 function hideEditableField( container, input, action, field_array, original_value, new_value ) {
223     var field_id = field_array instanceof Array ? field_array.shift() : field_array;
224     YAHOO.util.Dom.removeClass(container, 'bz_default_hidden');
225     YAHOO.util.Dom.addClass(input, 'bz_default_hidden');
226     YAHOO.util.Event.addListener(action, 'click', showEditableField,
227                                  new Array(container, input, field_id, new_value));
228     if(field_id != ""){
229         YAHOO.util.Event.addListener(field_id + '_nonedit_display', 'click', showEditableField,
230                                      new Array(container, input, undefined, undefined, field_id));
231         YAHOO.util.Event.addListener(window, 'load', checkForChangedFieldValues,
232                         new Array(container, input, field_id, original_value));
233     }
234     for (var field2_id in field_array){
235         if(field2_id != ""){
236             YAHOO.util.Event.addListener(field2_id + '_nonedit_display', 'click', showEditableField,
237                                          new Array(container, input, undefined, undefined, field2_id));
238         }
239     }
240 }
241
242 /* showEditableField (e, ContainerInputArray)
243  * Function hides the (edit) link and the text and displays the input/select field
244  *
245  * var e: the event
246  * var ContainerInputArray: An array containing the (edit) and text area and the input being displayed
247  * var ContainerInputArray[0]: the container that will be hidden usually shows the (edit) or (take) text
248  * var ContainerInputArray[1]: the input area and label that will be displayed
249  * var ContainerInputArray[2]: the input/select field id for which the new value must be set
250  * var ContainerInputArray[3]: the new value to set the input/select field to when (take) is clicked
251  * var ContainerInputArray[4]: [optional] the id of the input element to focus
252  */
253 function showEditableField (e, ContainerInputArray) {
254     var inputs = new Array();
255     var inputArea = YAHOO.util.Dom.get(ContainerInputArray[1]);    
256     if ( ! inputArea ){
257         YAHOO.util.Event.preventDefault(e);
258         return;
259     }
260     YAHOO.util.Dom.addClass(ContainerInputArray[0], 'bz_default_hidden');
261     YAHOO.util.Dom.removeClass(inputArea, 'bz_default_hidden');
262     if ( inputArea.tagName.toLowerCase() == "input" ) {
263         inputs.push(inputArea);
264     } else if (ContainerInputArray[2]) {
265         inputs.push(document.getElementById(ContainerInputArray[2]));
266     } else {
267         inputs = inputArea.getElementsByTagName('input');
268     }
269     if ( inputs.length > 0 ) {
270         // Change the first field's value to ContainerInputArray[2]
271         // if present before focusing.
272         var type = inputs[0].tagName.toLowerCase();
273         if (ContainerInputArray[3]) {
274             if ( type == "input" ) {
275                 inputs[0].value = ContainerInputArray[3];
276             } else {
277                 for (var i = 0; inputs[0].length; i++) {
278                     if ( inputs[0].options[i].value == ContainerInputArray[3] ) {
279                         inputs[0].options[i].selected = true;
280                         break;
281                     }
282                 }
283             }
284         }
285         var elementToFocus = YAHOO.util.Dom.get(ContainerInputArray[4]);
286         if (elementToFocus) {
287             // focus on the requested field
288             elementToFocus.focus();
289             if ( elementToFocus.tagName.toLowerCase() == "input" ) {
290                 elementToFocus.select();
291             }
292         } else {
293             // focus on the first field, this makes it easier to edit
294             inputs[0].focus();
295             if ( type == "input" ) {
296                 inputs[0].select();
297             }
298         }
299     }
300     YAHOO.util.Event.preventDefault(e);
301 }
302
303
304 /* checkForChangedFieldValues(e, array )
305  * Function checks if after the autocomplete by the browser if the values match the originals.
306  *   If they don't match then hide the text and show the input so users don't get confused.
307  *
308  * var e: the event
309  * var ContainerInputArray: An array containing the (edit) and text area and the input being displayed
310  * var ContainerInputArray[0]: the conainer that will be hidden usually shows the (edit) text
311  * var ContainerInputArray[1]: the input area and label that will be displayed
312  * var ContainerInputArray[2]: the field that is on the page, might get changed by browser autocomplete 
313  * var ContainerInputArray[3]: the original value from the page loading.
314  *
315  */  
316 function checkForChangedFieldValues(e, ContainerInputArray ) {
317     var el = document.getElementById(ContainerInputArray[2]);
318     var unhide = false;
319     if ( el ) {
320         if ( el.value != ContainerInputArray[3] ||
321             ( el.value == "" && el.id != "alias") ) {
322             unhide = true;
323         }
324         else {
325             var set_default = document.getElementById("set_default_" +
326                                                       ContainerInputArray[2]);
327             if ( set_default ) {
328                 if(set_default.checked){
329                     unhide = true;
330                 }              
331             }
332         }
333     }
334     if(unhide){
335         YAHOO.util.Dom.addClass(ContainerInputArray[0], 'bz_default_hidden');
336         YAHOO.util.Dom.removeClass(ContainerInputArray[1], 'bz_default_hidden');
337     }
338
339 }
340
341 function hideAliasAndSummary(short_desc_value, alias_value) {
342     // check the short desc field
343     hideEditableField( 'summary_alias_container','summary_alias_input',
344                        'editme_action', new Array('short_desc', 'alias'), short_desc_value);  
345     // check that the alias hasn't changed
346     var bz_alias_check_array = new Array('summary_alias_container',
347                                      'summary_alias_input', 'alias', alias_value);
348     YAHOO.util.Event.addListener( window, 'load', checkForChangedFieldValues,
349                                  bz_alias_check_array);
350 }
351
352 function showPeopleOnChange( field_id_list ) {
353     for(var i = 0; i < field_id_list.length; i++) {
354         YAHOO.util.Event.addListener( field_id_list[i],'change', showEditableField,
355                                       new Array('bz_qa_contact_edit_container',
356                                                 'bz_qa_contact_input'));
357         YAHOO.util.Event.addListener( field_id_list[i],'change',showEditableField,
358                                       new Array('bz_assignee_edit_container',
359                                                 'bz_assignee_input'));
360     }
361 }
362
363 function assignToDefaultOnChange(field_id_list) {
364     showPeopleOnChange( field_id_list );
365     for(var i = 0; i < field_id_list.length; i++) {
366         YAHOO.util.Event.addListener( field_id_list[i],'change', setDefaultCheckbox,
367                                       'set_default_assignee');
368         YAHOO.util.Event.addListener( field_id_list[i],'change',setDefaultCheckbox,
369                                       'set_default_qa_contact');    
370     }
371 }
372
373 function initDefaultCheckbox(field_id){
374     YAHOO.util.Event.addListener( 'set_default_' + field_id,'change', boldOnChange,
375                                   'set_default_' + field_id);
376     YAHOO.util.Event.addListener( window,'load', checkForChangedFieldValues,
377                                   new Array( 'bz_' + field_id + '_edit_container',
378                                              'bz_' + field_id + '_input',
379                                              'set_default_' + field_id ,'1'));
380     
381     YAHOO.util.Event.addListener( window, 'load', boldOnChange,
382                                  'set_default_' + field_id ); 
383 }
384
385 function showHideStatusItems(e, dupArrayInfo) {
386     var el = document.getElementById('bug_status');
387     // finish doing stuff based on the selection.
388     if ( el ) {
389         showDuplicateItem(el);
390
391         // Make sure that fields whose visibility or values are controlled
392         // by "resolution" behave properly when resolution is hidden.
393         var resolution = document.getElementById('resolution');
394         if (resolution && resolution.options[0].value != '') {
395             resolution.bz_lastSelected = resolution.selectedIndex;
396             var emptyOption = new Option('', '');
397             resolution.insertBefore(emptyOption, resolution.options[0]);
398             emptyOption.selected = true;
399         }
400         YAHOO.util.Dom.addClass('resolution_settings', 'bz_default_hidden');
401         if (document.getElementById('resolution_settings_warning')) {
402             YAHOO.util.Dom.addClass('resolution_settings_warning',
403                                     'bz_default_hidden');
404         }
405         YAHOO.util.Dom.addClass('duplicate_display', 'bz_default_hidden');
406
407
408         if ( (el.value == dupArrayInfo[1] && dupArrayInfo[0] == "is_duplicate")
409              || bz_isValueInArray(close_status_array, el.value) ) 
410         {
411             YAHOO.util.Dom.removeClass('resolution_settings', 
412                                        'bz_default_hidden');
413             YAHOO.util.Dom.removeClass('resolution_settings_warning', 
414                                        'bz_default_hidden');
415
416             // Remove the blank option we inserted.
417             if (resolution && resolution.options[0].value == '') {
418                 resolution.removeChild(resolution.options[0]);
419                 resolution.selectedIndex = resolution.bz_lastSelected;
420             }
421         }
422
423         if (resolution) {
424             bz_fireEvent(resolution, 'change');
425         }
426     }
427 }
428
429 function showDuplicateItem(e) {
430     var resolution = document.getElementById('resolution');
431     var bug_status = document.getElementById('bug_status');
432     var dup_id = document.getElementById('dup_id');
433     if (resolution) {
434         if (resolution.value == 'DUPLICATE' && bz_isValueInArray( close_status_array, bug_status.value) ) {
435             // hide resolution show duplicate
436             YAHOO.util.Dom.removeClass('duplicate_settings', 
437                                        'bz_default_hidden');
438             YAHOO.util.Dom.addClass('dup_id_discoverable', 'bz_default_hidden');
439             // check to make sure the field is visible or IE throws errors
440             if( ! YAHOO.util.Dom.hasClass( dup_id, 'bz_default_hidden' ) ){
441                 dup_id.focus();
442                 dup_id.select();
443             }
444         }
445         else {
446             YAHOO.util.Dom.addClass('duplicate_settings', 'bz_default_hidden');
447             YAHOO.util.Dom.removeClass('dup_id_discoverable', 
448                                        'bz_default_hidden');
449             dup_id.blur();
450         }
451     }
452     YAHOO.util.Event.preventDefault(e); //prevents the hyperlink from going to the url in the href.
453 }
454
455 function setResolutionToDuplicate(e, duplicate_or_move_bug_status) {
456     var status = document.getElementById('bug_status');
457     var resolution = document.getElementById('resolution');
458     YAHOO.util.Dom.addClass('dup_id_discoverable', 'bz_default_hidden');
459     status.value = duplicate_or_move_bug_status;
460     bz_fireEvent(status, 'change');
461     resolution.value = "DUPLICATE";
462     bz_fireEvent(resolution, 'change');
463     YAHOO.util.Event.preventDefault(e);
464 }
465
466 function setDefaultCheckbox(e, field_id ) { 
467     var el = document.getElementById(field_id);
468     var elLabel = document.getElementById(field_id + "_label");
469     if( el && elLabel ) {
470         el.checked = "true";
471         YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'bold');
472     }
473 }
474
475 function boldOnChange(e, field_id){
476     var el = document.getElementById(field_id);
477     var elLabel = document.getElementById(field_id + "_label");
478     if( el && elLabel ) {
479         if( el.checked ){
480             YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'bold');
481         }
482         else{
483             YAHOO.util.Dom.setStyle(elLabel, 'font-weight', 'normal');
484         }
485     }
486 }
487
488 function updateCommentTagControl(checkbox, field) {
489     if (checkbox.checked) {
490         YAHOO.util.Dom.addClass(field, 'bz_private');
491     } else {
492         YAHOO.util.Dom.removeClass(field, 'bz_private');
493     }
494 }
495
496 /**
497  * Reset the value of the classification field and fire an event change
498  * on it.  Called when the product changes, in case the classification
499  * field (which is hidden) controls the visibility of any other fields.
500  */
501 function setClassification() {
502     var classification = document.getElementById('classification');
503     var product = document.getElementById('product');
504     var selected_product = product.value; 
505     var select_classification = all_classifications[selected_product];
506     classification.value = select_classification;
507     bz_fireEvent(classification, 'change');
508 }
509
510 /**
511  * Says that a field should only be displayed when another field has
512  * a certain value. May only be called after the controller has already
513  * been added to the DOM.
514  */
515 function showFieldWhen(controlled_id, controller_id, values) {
516     var controller = document.getElementById(controller_id);
517     // Note that we don't get an object for "controlled" here, because it
518     // might not yet exist in the DOM. We just pass along its id.
519     YAHOO.util.Event.addListener(controller, 'change',
520         handleVisControllerValueChange, [controlled_id, controller, values]);
521 }
522
523 /**
524  * Called by showFieldWhen when a field's visibility controller 
525  * changes values. 
526  */
527 function handleVisControllerValueChange(e, args) {
528     var controlled_id = args[0];
529     var controller = args[1];
530     var values = args[2];
531
532     var label_container = 
533         document.getElementById('field_label_' + controlled_id);
534     var field_container =
535         document.getElementById('field_container_' + controlled_id);
536     var selected = false;
537     for (var i = 0; i < values.length; i++) {
538         if (bz_valueSelected(controller, values[i])) {
539             selected = true;
540             break;
541         }
542     }
543
544     if (selected) {
545         YAHOO.util.Dom.removeClass(label_container, 'bz_hidden_field');
546         YAHOO.util.Dom.removeClass(field_container, 'bz_hidden_field');
547     }
548     else {
549         YAHOO.util.Dom.addClass(label_container, 'bz_hidden_field');
550         YAHOO.util.Dom.addClass(field_container, 'bz_hidden_field');
551     }
552 }
553
554 function showValueWhen(controlled_field_id, controlled_value_ids, 
555                        controller_field_id, controller_value_id)
556 {
557     var controller_field = document.getElementById(controller_field_id);
558     // Note that we don't get an object for the controlled field here, 
559     // because it might not yet exist in the DOM. We just pass along its id.
560     YAHOO.util.Event.addListener(controller_field, 'change',
561         handleValControllerChange, [controlled_field_id, controlled_value_ids,
562                                     controller_field, controller_value_id]);
563 }
564
565 function handleValControllerChange(e, args) {
566     var controlled_field = document.getElementById(args[0]);
567     var controlled_value_ids = args[1];
568     var controller_field = args[2];
569     var controller_value_id = args[3];
570
571     var controller_item = document.getElementById(
572         _value_id(controller_field.id, controller_value_id));
573
574     for (var i = 0; i < controlled_value_ids.length; i++) {
575         var item = getPossiblyHiddenOption(controlled_field,
576                                            controlled_value_ids[i]);
577         if (item.disabled && controller_item && controller_item.selected) {
578             item = showOptionInIE(item, controlled_field);
579             YAHOO.util.Dom.removeClass(item, 'bz_hidden_option');
580             item.disabled = false;
581         }
582         else if (!item.disabled && controller_item && !controller_item.selected) {
583             YAHOO.util.Dom.addClass(item, 'bz_hidden_option');
584             if (item.selected) {
585                 item.selected = false;
586                 bz_fireEvent(controlled_field, 'change');
587             }
588             item.disabled = true;
589             hideOptionInIE(item, controlled_field);
590         }
591     }
592 }
593
594 // A convenience function to generate the "id" tag of an <option>
595 // based on the numeric id that Bugzilla uses for that value.
596 function _value_id(field_name, id) {
597     return 'v' + id + '_' + field_name;
598 }
599
600 /*********************************/
601 /* Code for Hiding Options in IE */
602 /*********************************/
603
604 /* IE 7 and below (and some other browsers) don't respond to "display: none"
605  * on <option> tags. However, you *can* insert a Comment Node as a
606  * child of a <select> tag. So we just insert a Comment where the <option>
607  * used to be. */
608 var ie_hidden_options = new Array();
609 function hideOptionInIE(anOption, aSelect) {
610     if (browserCanHideOptions(aSelect)) return;
611
612     var commentNode = document.createComment(anOption.value);
613     commentNode.id = anOption.id;
614     // This keeps the interface of Comments and Options the same for
615     // our other functions.
616     commentNode.disabled = true;
617     // replaceChild is very slow on IE in a <select> that has a lot of
618     // options, so we use replaceNode when we can.
619     if (anOption.replaceNode) {
620         anOption.replaceNode(commentNode);
621     }
622     else {
623         aSelect.replaceChild(commentNode, anOption);
624     }
625
626     // Store the comment node for quick access for getPossiblyHiddenOption
627     if (!ie_hidden_options[aSelect.id]) {
628         ie_hidden_options[aSelect.id] = new Array();
629     }
630     ie_hidden_options[aSelect.id][anOption.id] = commentNode;
631 }
632
633 function showOptionInIE(aNode, aSelect) {
634     if (browserCanHideOptions(aSelect)) return aNode;
635
636     // We do this crazy thing with innerHTML and createElement because
637     // this is the ONLY WAY that this works properly in IE.
638     var optionNode = document.createElement('option');
639     optionNode.innerHTML = aNode.data;
640     optionNode.value = aNode.data;
641     optionNode.id = aNode.id;
642     // replaceChild is very slow on IE in a <select> that has a lot of
643     // options, so we use replaceNode when we can.
644     if (aNode.replaceNode) {
645         aNode.replaceNode(optionNode);
646     }
647     else {
648         aSelect.replaceChild(optionNode, aNode);
649     }
650     delete ie_hidden_options[aSelect.id][optionNode.id];
651     return optionNode;
652 }
653
654 function initHidingOptionsForIE(select_name) {
655     var aSelect = document.getElementById(select_name);
656     if (browserCanHideOptions(aSelect)) return;
657
658     for (var i = 0; ;i++) {
659         var item = aSelect.options[i];
660         if (!item) break;
661         if (item.disabled) {
662           hideOptionInIE(item, aSelect);
663           i--; // Hiding an option means that the options array has changed.
664         }
665     }
666 }
667
668 function getPossiblyHiddenOption(aSelect, optionId) {
669     // Works always for <option> tags, and works for commentNodes
670     // in IE (but not in Webkit).
671     var id = _value_id(aSelect.id, optionId);
672     var val = document.getElementById(id);
673
674     // This is for WebKit and other browsers that can't "display: none"
675     // an <option> and also can't getElementById for a commentNode.
676     if (!val && ie_hidden_options[aSelect.id]) {
677         val = ie_hidden_options[aSelect.id][id];
678     }
679
680     return val;
681 }
682
683 var browser_can_hide_options;
684 function browserCanHideOptions(aSelect) {
685     /* As far as I can tell, browsers that don't hide <option> tags
686      * also never have a X position for <option> tags, even if
687      * they're visible. This is the only reliable way I found to
688      * differentiate browsers. So we create a visible option, see
689      * if it has a position, and then remove it. */
690     if (typeof(browser_can_hide_options) == "undefined") {
691         var new_opt = bz_createOptionInSelect(aSelect, '', '');
692         var opt_pos = YAHOO.util.Dom.getX(new_opt);
693         aSelect.removeChild(new_opt);
694         if (opt_pos) {
695             browser_can_hide_options = true;
696         }
697         else {
698             browser_can_hide_options = false;
699         }
700     }
701     return browser_can_hide_options;
702 }
703
704 /* (end) option hiding code */
705
706 /**
707  * The Autoselect
708  */
709 YAHOO.bugzilla.userAutocomplete = {
710     counter : 0,
711     dataSource : null,
712     generateRequest : function ( enteredText ){ 
713       YAHOO.bugzilla.userAutocomplete.counter = 
714                                    YAHOO.bugzilla.userAutocomplete.counter + 1;
715       YAHOO.util.Connect.setDefaultPostHeader('application/json', true);
716       var json_object = {
717           method : "User.get",
718           id : YAHOO.bugzilla.userAutocomplete.counter,
719           params : [ { 
720             match : [ decodeURIComponent(enteredText) ],
721             include_fields : [ "name", "real_name" ]
722           } ]
723       };
724       var stringified =  YAHOO.lang.JSON.stringify(json_object);
725       var debug = { msg: "json-rpc obj debug info", "json obj": json_object, 
726                     "param" : stringified}
727       YAHOO.bugzilla.userAutocomplete.debug_helper( debug );
728       return stringified;
729     },
730     resultListFormat : function(oResultData, enteredText, sResultMatch) {
731         return ( YAHOO.lang.escapeHTML(oResultData.real_name) + " ("
732                  + YAHOO.lang.escapeHTML(oResultData.name) + ")");
733     },
734     debug_helper : function ( ){
735         /* used to help debug any errors that might happen */
736         if( typeof(console) !== 'undefined' && console != null && arguments.length > 0 ){
737             console.log("debug helper info:", arguments);
738         }
739         return true;
740     },    
741     init_ds : function(){
742         this.dataSource = new YAHOO.util.XHRDataSource("jsonrpc.cgi");
743         this.dataSource.connTimeout = 30000;
744         this.dataSource.connMethodPost = true;
745         this.dataSource.connXhrMode = "cancelStaleRequests";
746         this.dataSource.maxCacheEntries = 5;
747         this.dataSource.responseSchema = {
748             resultsList : "result.users",
749             metaFields : { error: "error", jsonRpcId: "id"},
750             fields : [
751                 { key : "name" },
752                 { key : "real_name"}
753             ]
754         };
755     },
756     init : function( field, container, multiple ) {
757         if( this.dataSource == null ){
758             this.init_ds();  
759         }            
760         var userAutoComp = new YAHOO.widget.AutoComplete( field, container, 
761                                 this.dataSource );
762         // other stuff we might want to do with the autocomplete goes here
763         userAutoComp.maxResultsDisplayed = BUGZILLA.param.maxusermatches;
764         userAutoComp.generateRequest = this.generateRequest;
765         userAutoComp.formatResult = this.resultListFormat;
766         userAutoComp.doBeforeLoadData = this.debug_helper;
767         userAutoComp.minQueryLength = 3;
768         userAutoComp.autoHighlight = false;
769         // this is a throttle to determine the delay of the query from typing
770         // set this higher to cause fewer calls to the server
771         userAutoComp.queryDelay = 0.05;
772         userAutoComp.useIFrame = true;
773         userAutoComp.resultTypeList = false;
774         if( multiple == true ){
775             userAutoComp.delimChar = [","];
776         }
777         
778     }
779 };
780
781 YAHOO.bugzilla.keywordAutocomplete = {
782     dataSource : null,
783     init_ds : function(){
784         this.dataSource = new YAHOO.util.LocalDataSource( YAHOO.bugzilla.keyword_array );
785     },
786     init : function( field, container ) {
787         if( this.dataSource == null ){
788             this.init_ds();
789         }
790         var keywordAutoComp = new YAHOO.widget.AutoComplete(field, container, this.dataSource);
791         keywordAutoComp.maxResultsDisplayed = YAHOO.bugzilla.keyword_array.length;
792         keywordAutoComp.formatResult = keywordAutoComp.formatEscapedResult;
793         keywordAutoComp.minQueryLength = 0;
794         keywordAutoComp.useIFrame = true;
795         keywordAutoComp.delimChar = [","," "];
796         keywordAutoComp.resultTypeList = false;
797         keywordAutoComp.queryDelay = 0;
798         /*  Causes all the possibilities in the keyword to appear when a user 
799          *  focuses on the textbox 
800          */
801         keywordAutoComp.textboxFocusEvent.subscribe( function(){
802             var sInputValue = YAHOO.util.Dom.get('keywords').value;
803             if( sInputValue.length === 0 ){
804                 this.sendQuery(sInputValue);
805                 this.collapseContainer();
806                 this.expandContainer();
807             }
808         });
809         keywordAutoComp.dataRequestEvent.subscribe( function(type, args) {
810             args[0].autoHighlight = args[1] != '';
811         });
812     }
813 };