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/
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.
11
* The Original Code is the Bugzilla Bug Tracking System.
13
* The Initial Developer of the Original Code is Netscape Communications
14
* Corporation. Portions created by Netscape are
15
* Copyright (C) 1998 Netscape Communications Corporation. All
18
* Contributor(s): Christian Reis <kiko@async.com.br>
21
/* this file contains functions to update form controls based on a
22
* collection of javascript arrays containing strings */
24
/* selectClassification reads the selection from f.classification and updates
25
* f.product accordingly
26
* - f: a form containing classification, product, component, varsion and
27
* target_milestone select boxes.
29
* - prods, indexed by classification name
30
* - first_load: boolean, specifying if it is the first time we load
32
* - last_sel: saves our last selection list so we know what has
33
* changed, and optimize for additions.
35
function selectClassification(classfield, product, component, version, milestone) {
36
/* this is to avoid handling events that occur before the form
37
* itself is ready, which could happen in buggy browsers.
43
/* if this is the first load and nothing is selected, no need to
44
* merge and sort all components; perl gives it to us sorted.
46
if ((first_load) && (classfield.selectedIndex == -1)) {
51
/* don't reset first_load as done in selectProduct. That's because we
52
want selectProduct to handle the first_load attribute
55
/* - sel keeps the array of classifications we are selected.
56
* - merging says if it is a full list or just a list of classifications
57
* that were added to the current selection.
62
/* if nothing selected, pick all */
63
var findall = classfield.selectedIndex == -1;
64
sel = get_selection(classfield, findall, false);
66
/* save sel for the next invocation of selectClassification() */
69
/* this is an optimization: if we have just added classifications to an
70
* existing selection, no need to clear the form controls and add
71
* everybody again; just merge the new ones with the existing
74
if ((last_sel.length > 0) && (last_sel.length < sel.length)) {
75
sel = fake_diff_array(sel, last_sel);
80
/* save original options selected */
81
var saved_prods = get_selection(product, false, true);
83
/* do the actual fill/update, reselect originally selected options */
84
updateSelect(prods, sel, product, merging);
85
restoreSelection(product, saved_prods);
86
selectProduct(product, component, version, milestone);
90
/* selectProduct reads the selection from the product control and
91
* updates version, component and milestone controls accordingly.
93
* - product, component, version and milestone: form controls
96
* - cpts, vers, tms: array of arrays, indexed by product name. the
97
* subarrays contain a list of names to be fed to the respective
98
* selectboxes. For bugzilla, these are generated with perl code
100
* - first_load: boolean, specifying if it is the first time we load
102
* - last_sel: saves our last selection list so we know what has
103
* changed, and optimize for additions.
105
function selectProduct(product, component, version, milestone) {
108
/* this is to avoid handling events that occur before the form
109
* itself is ready, which could happen in buggy browsers. */
113
/* if this is the first load and nothing is selected, no need to
114
* merge and sort all components; perl gives it to us sorted. */
115
if ((first_load) && (product.selectedIndex == -1)) {
120
/* turn first_load off. this is tricky, since it seems to be
121
* redundant with the above clause. It's not: if when we first load
122
* the page there is _one_ element selected, it won't fall into that
123
* clause, and first_load will remain 1. Then, if we unselect that
124
* item, selectProduct will be called but the clause will be valid
125
* (since selectedIndex == -1), and we will return - incorrectly -
126
* without merge/sorting. */
129
/* - sel keeps the array of products we are selected.
130
* - merging says if it is a full list or just a list of products that
131
* were added to the current selection. */
135
/* if nothing selected, pick all */
136
var findall = product.selectedIndex == -1;
137
if (useclassification) {
138
/* update index based on the complete product array */
139
sel = get_selection(product, findall, true);
140
for (var i=0; i<sel.length; i++) {
141
sel[i] = prods[sel[i]];
144
sel = get_selection(product, findall, false);
147
/* save sel for the next invocation of selectProduct() */
150
/* this is an optimization: if we have just added products to an
151
* existing selection, no need to clear the form controls and add
152
* everybody again; just merge the new ones with the existing
154
if ((last_sel.length > 0) && (last_sel.length < sel.length)) {
155
sel = fake_diff_array(sel, last_sel);
161
/* do the actual fill/update */
163
var saved_cpts = get_selection(component, false, true);
164
updateSelect(cpts, sel, component, merging);
165
restoreSelection(component, saved_cpts);
169
var saved_vers = get_selection(version, false, true);
170
updateSelect(vers, sel, version, merging);
171
restoreSelection(version, saved_vers);
175
var saved_tms = get_selection(milestone, false, true);
176
updateSelect(tms, sel, milestone, merging);
177
restoreSelection(milestone, saved_tms);
182
/* updateSelect(array, sel, target, merging)
184
* Adds to the target select object all elements in array that
185
* correspond to the elements selected in source.
186
* - array should be a array of arrays, indexed by number. the
187
* array should contain the elements that correspond to that
189
* - sel is a list of selected items, either whole or a diff
190
* depending on merging.
191
* - target should be the target select object.
192
* - merging (boolean) determines if we are mergine in a diff or
193
* substituting the whole selection. a diff is used to optimize adding
196
* Example (compsel is a select form control)
198
* var components = Array();
199
* components[1] = [ 'ComponentA', 'ComponentB' ];
200
* components[2] = [ 'ComponentC', 'ComponentD' ];
202
* updateSelect(components, source, compsel, 0, 0);
204
* would clear compsel and add 'ComponentC' and 'ComponentD' to it.
208
function updateSelect(array, sel, target, merging) {
212
/* If we have no versions/components/milestones */
213
if (array.length < 1) {
214
target.options.length = 0;
219
/* array merging/sorting in the case of multiple selections */
220
/* merge in the current options with the first selection */
221
item = merge_arrays(array[sel[0]], target.options, 1);
223
/* merge the rest of the selection with the results */
224
for (i = 1 ; i < sel.length ; i++) {
225
item = merge_arrays(array[sel[i]], item, 0);
227
} else if ( sel.length > 1 ) {
228
/* here we micro-optimize for two arrays to avoid merging with a
230
item = merge_arrays(array[sel[0]],array[sel[1]], 0);
232
/* merge the arrays. not very good for multiple selections. */
233
for (i = 2; i < sel.length; i++) {
234
item = merge_arrays(item, array[sel[i]], 0);
236
} else { /* single item in selection, just get me the list */
237
item = array[sel[0]];
241
target.options.length = 0;
243
/* load elements of list into select */
244
for (i = 0; i < item.length; i++) {
245
target.options[i] = new Option(item[i], item[i]);
251
/* Selects items in control that have index defined in sel
252
* - control: SELECT control to be restored
253
* - selnames: array of indexes in select form control */
254
function restoreSelection(control, selnames) {
255
/* right. this sucks. but I see no way to avoid going through the
256
* list and comparing to the contents of the control. */
257
for (var j=0; j < selnames.length; j++) {
258
for (var i=0; i < control.options.length; i++) {
259
if (control.options[i].value == selnames[j]) {
260
control.options[i].selected = true;
267
/* Returns elements in a that are not in b.
268
* NOT A REAL DIFF: does not check the reverse.
269
* - a,b: arrays of values to be compare. */
270
function fake_diff_array(a, b) {
271
var newsel = new Array();
274
/* do a boring array diff to see who's new */
277
if (a[ia] == b[ib]) {
282
newsel[newsel.length] = a[ia];
289
/* takes two arrays and sorts them by string, returning a new, sorted
290
* array. the merge removes dupes, too.
291
* - a, b: arrays to be merge.
292
* - b_is_select: if true, then b is actually an optionitem and as
293
* such we need to use item.value on it. */
294
function merge_arrays(a, b, b_is_select) {
297
var ret = new Array();
300
/* iterate through both arrays and add the larger item to the return
301
* list. remove dupes, too. Use toLowerCase to provide
302
* case-insensitivity. */
303
while ((pos_a < a.length) && (pos_b < b.length)) {
305
bitem = b[pos_b].value;
311
/* smaller item in list a */
312
if (aitem.toLowerCase() < bitem.toLowerCase()) {
313
ret[ret.length] = aitem;
316
/* smaller item in list b */
317
if (aitem.toLowerCase() > bitem.toLowerCase()) {
318
ret[ret.length] = bitem;
321
/* list contents are equal, inc both counters. */
322
ret[ret.length] = aitem;
329
/* catch leftovers here. these sections are ugly code-copying. */
330
if (pos_a < a.length) {
331
for (; pos_a < a.length ; pos_a++) {
332
ret[ret.length] = a[pos_a];
336
if (pos_b < b.length) {
337
for (; pos_b < b.length; pos_b++) {
339
bitem = b[pos_b].value;
343
ret[ret.length] = bitem;
349
/* Returns an array of indexes or values from a select form control.
350
* - control: select control from which to find selections
351
* - findall: boolean, store all options when true or just the selected
353
* - want_values: boolean; we store values when true and indexes when
355
function get_selection(control, findall, want_values) {
356
var ret = new Array();
358
if ((!findall) && (control.selectedIndex == -1)) {
362
for (var i=0; i<control.length; i++) {
363
if (findall || control.options[i].selected) {
364
ret[ret.length] = want_values ? control.options[i].value : i;