| /* The contents of this file are subject to the Mozilla Public |
| * License Version 1.1 (the "License"); you may not use this file |
| * except in compliance with the License. You may obtain a copy of |
| * the License at http://www.mozilla.org/MPL/ |
| * |
| * Software distributed under the License is distributed on an "AS |
| * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or |
| * implied. See the License for the specific language governing |
| * rights and limitations under the License. |
| * |
| * The Original Code is the Bugzilla Bug Tracking System. |
| * |
| * The Initial Developer of the Original Code is Netscape Communications |
| * Corporation. Portions created by Netscape are |
| * Copyright (C) 1998 Netscape Communications Corporation. All |
| * Rights Reserved. |
| * |
| * Contributor(s): Christian Reis <kiko@async.com.br> |
| */ |
| |
| /* this file contains functions to update form controls based on a |
| * collection of javascript arrays containing strings */ |
| |
| /* selectClassification reads the selection from f.classification and updates |
| * f.product accordingly |
| * - f: a form containing classification, product, component, varsion and |
| * target_milestone select boxes. |
| * globals (3vil!): |
| * - prods, indexed by classification name |
| * - first_load: boolean, specifying if it is the first time we load |
| * the query page. |
| * - last_sel: saves our last selection list so we know what has |
| * changed, and optimize for additions. |
| */ |
| function selectClassification(classfield, product, component, version, milestone) { |
| /* this is to avoid handling events that occur before the form |
| * itself is ready, which could happen in buggy browsers. |
| */ |
| if (!classfield) { |
| return; |
| } |
| |
| /* if this is the first load and nothing is selected, no need to |
| * merge and sort all components; perl gives it to us sorted. |
| */ |
| if ((first_load) && (classfield.selectedIndex == -1)) { |
| first_load = false; |
| return; |
| } |
| |
| /* don't reset first_load as done in selectProduct. That's because we |
| want selectProduct to handle the first_load attribute |
| */ |
| |
| /* - sel keeps the array of classifications we are selected. |
| * - merging says if it is a full list or just a list of classifications |
| * that were added to the current selection. |
| */ |
| var merging = false; |
| var sel = Array(); |
| |
| /* if nothing selected, pick all */ |
| var findall = classfield.selectedIndex == -1; |
| sel = get_selection(classfield, findall, false); |
| if (!findall) { |
| /* save sel for the next invocation of selectClassification() */ |
| var tmp = sel; |
| |
| /* this is an optimization: if we have just added classifications to an |
| * existing selection, no need to clear the form controls and add |
| * everybody again; just merge the new ones with the existing |
| * options. |
| */ |
| if ((last_sel.length > 0) && (last_sel.length < sel.length)) { |
| sel = fake_diff_array(sel, last_sel); |
| merging = true; |
| } |
| last_sel = tmp; |
| } |
| /* save original options selected */ |
| var saved_prods = get_selection(product, false, true); |
| |
| /* do the actual fill/update, reselect originally selected options */ |
| updateSelect(prods, sel, product, merging); |
| restoreSelection(product, saved_prods); |
| selectProduct(product, component, version, milestone); |
| } |
| |
| |
| /* selectProduct reads the selection from the product control and |
| * updates version, component and milestone controls accordingly. |
| * |
| * - product, component, version and milestone: form controls |
| * |
| * globals (3vil!): |
| * - cpts, vers, tms: array of arrays, indexed by product name. the |
| * subarrays contain a list of names to be fed to the respective |
| * selectboxes. For bugzilla, these are generated with perl code |
| * at page start. |
| * - first_load: boolean, specifying if it is the first time we load |
| * the query page. |
| * - last_sel: saves our last selection list so we know what has |
| * changed, and optimize for additions. |
| */ |
| function selectProduct(product, component, version, milestone) { |
| |
| if (!product) { |
| /* this is to avoid handling events that occur before the form |
| * itself is ready, which could happen in buggy browsers. */ |
| return; |
| } |
| |
| /* if this is the first load and nothing is selected, no need to |
| * merge and sort all components; perl gives it to us sorted. */ |
| if ((first_load) && (product.selectedIndex == -1)) { |
| first_load = false; |
| return; |
| } |
| |
| /* turn first_load off. this is tricky, since it seems to be |
| * redundant with the above clause. It's not: if when we first load |
| * the page there is _one_ element selected, it won't fall into that |
| * clause, and first_load will remain 1. Then, if we unselect that |
| * item, selectProduct will be called but the clause will be valid |
| * (since selectedIndex == -1), and we will return - incorrectly - |
| * without merge/sorting. */ |
| first_load = false; |
| |
| /* - sel keeps the array of products we are selected. |
| * - merging says if it is a full list or just a list of products that |
| * were added to the current selection. */ |
| var merging = false; |
| var sel = Array(); |
| |
| /* if nothing selected, pick all */ |
| var findall = product.selectedIndex == -1; |
| if (useclassification) { |
| /* update index based on the complete product array */ |
| sel = get_selection(product, findall, true); |
| for (var i=0; i<sel.length; i++) { |
| sel[i] = prods[sel[i]]; |
| } |
| } else { |
| sel = get_selection(product, findall, false); |
| } |
| if (!findall) { |
| /* save sel for the next invocation of selectProduct() */ |
| var tmp = sel; |
| |
| /* this is an optimization: if we have just added products to an |
| * existing selection, no need to clear the form controls and add |
| * everybody again; just merge the new ones with the existing |
| * options. */ |
| if ((last_sel.length > 0) && (last_sel.length < sel.length)) { |
| sel = fake_diff_array(sel, last_sel); |
| merging = true; |
| } |
| last_sel = tmp; |
| } |
| |
| /* do the actual fill/update */ |
| if (component) { |
| var saved_cpts = get_selection(component, false, true); |
| updateSelect(cpts, sel, component, merging); |
| restoreSelection(component, saved_cpts); |
| } |
| |
| if (version) { |
| var saved_vers = get_selection(version, false, true); |
| updateSelect(vers, sel, version, merging); |
| restoreSelection(version, saved_vers); |
| } |
| |
| if (milestone) { |
| var saved_tms = get_selection(milestone, false, true); |
| updateSelect(tms, sel, milestone, merging); |
| restoreSelection(milestone, saved_tms); |
| } |
| } |
| |
| |
| /* updateSelect(array, sel, target, merging) |
| * |
| * Adds to the target select object all elements in array that |
| * correspond to the elements selected in source. |
| * - array should be a array of arrays, indexed by number. the |
| * array should contain the elements that correspond to that |
| * product. |
| * - sel is a list of selected items, either whole or a diff |
| * depending on merging. |
| * - target should be the target select object. |
| * - merging (boolean) determines if we are mergine in a diff or |
| * substituting the whole selection. a diff is used to optimize adding |
| * selections. |
| * |
| * Example (compsel is a select form control) |
| * |
| * var components = Array(); |
| * components[1] = [ 'ComponentA', 'ComponentB' ]; |
| * components[2] = [ 'ComponentC', 'ComponentD' ]; |
| * source = [ 2 ]; |
| * updateSelect(components, source, compsel, 0, 0); |
| * |
| * would clear compsel and add 'ComponentC' and 'ComponentD' to it. |
| * |
| */ |
| |
| function updateSelect(array, sel, target, merging) { |
| |
| var i, item; |
| |
| /* If we have no versions/components/milestones */ |
| if (array.length < 1) { |
| target.options.length = 0; |
| return false; |
| } |
| |
| if (merging) { |
| /* array merging/sorting in the case of multiple selections */ |
| /* merge in the current options with the first selection */ |
| item = merge_arrays(array[sel[0]], target.options, 1); |
| |
| /* merge the rest of the selection with the results */ |
| for (i = 1 ; i < sel.length ; i++) { |
| item = merge_arrays(array[sel[i]], item, 0); |
| } |
| } else if ( sel.length > 1 ) { |
| /* here we micro-optimize for two arrays to avoid merging with a |
| * null array */ |
| item = merge_arrays(array[sel[0]],array[sel[1]], 0); |
| |
| /* merge the arrays. not very good for multiple selections. */ |
| for (i = 2; i < sel.length; i++) { |
| item = merge_arrays(item, array[sel[i]], 0); |
| } |
| } else { /* single item in selection, just get me the list */ |
| item = array[sel[0]]; |
| } |
| |
| /* clear select */ |
| target.options.length = 0; |
| |
| /* load elements of list into select */ |
| for (i = 0; i < item.length; i++) { |
| target.options[i] = new Option(item[i], item[i]); |
| } |
| return true; |
| } |
| |
| |
| /* Selects items in control that have index defined in sel |
| * - control: SELECT control to be restored |
| * - selnames: array of indexes in select form control */ |
| function restoreSelection(control, selnames) { |
| /* right. this sucks. but I see no way to avoid going through the |
| * list and comparing to the contents of the control. */ |
| for (var j=0; j < selnames.length; j++) { |
| for (var i=0; i < control.options.length; i++) { |
| if (control.options[i].value == selnames[j]) { |
| control.options[i].selected = true; |
| } |
| } |
| } |
| } |
| |
| |
| /* Returns elements in a that are not in b. |
| * NOT A REAL DIFF: does not check the reverse. |
| * - a,b: arrays of values to be compare. */ |
| function fake_diff_array(a, b) { |
| var newsel = new Array(); |
| var found = false; |
| |
| /* do a boring array diff to see who's new */ |
| for (var ia in a) { |
| for (var ib in b) { |
| if (a[ia] == b[ib]) { |
| found = true; |
| } |
| } |
| if (!found) { |
| newsel[newsel.length] = a[ia]; |
| } |
| found = false; |
| } |
| return newsel; |
| } |
| |
| /* takes two arrays and sorts them by string, returning a new, sorted |
| * array. the merge removes dupes, too. |
| * - a, b: arrays to be merge. |
| * - b_is_select: if true, then b is actually an optionitem and as |
| * such we need to use item.value on it. */ |
| function merge_arrays(a, b, b_is_select) { |
| var pos_a = 0; |
| var pos_b = 0; |
| var ret = new Array(); |
| var bitem, aitem; |
| |
| /* iterate through both arrays and add the larger item to the return |
| * list. remove dupes, too. Use toLowerCase to provide |
| * case-insensitivity. */ |
| while ((pos_a < a.length) && (pos_b < b.length)) { |
| if (b_is_select) { |
| bitem = b[pos_b].value; |
| } else { |
| bitem = b[pos_b]; |
| } |
| aitem = a[pos_a]; |
| |
| /* smaller item in list a */ |
| if (aitem.toLowerCase() < bitem.toLowerCase()) { |
| ret[ret.length] = aitem; |
| pos_a++; |
| } else { |
| /* smaller item in list b */ |
| if (aitem.toLowerCase() > bitem.toLowerCase()) { |
| ret[ret.length] = bitem; |
| pos_b++; |
| } else { |
| /* list contents are equal, inc both counters. */ |
| ret[ret.length] = aitem; |
| pos_a++; |
| pos_b++; |
| } |
| } |
| } |
| |
| /* catch leftovers here. these sections are ugly code-copying. */ |
| if (pos_a < a.length) { |
| for (; pos_a < a.length ; pos_a++) { |
| ret[ret.length] = a[pos_a]; |
| } |
| } |
| |
| if (pos_b < b.length) { |
| for (; pos_b < b.length; pos_b++) { |
| if (b_is_select) { |
| bitem = b[pos_b].value; |
| } else { |
| bitem = b[pos_b]; |
| } |
| ret[ret.length] = bitem; |
| } |
| } |
| return ret; |
| } |
| |
| /* Returns an array of indexes or values from a select form control. |
| * - control: select control from which to find selections |
| * - findall: boolean, store all options when true or just the selected |
| * indexes |
| * - want_values: boolean; we store values when true and indexes when |
| * false */ |
| function get_selection(control, findall, want_values) { |
| var ret = new Array(); |
| |
| if ((!findall) && (control.selectedIndex == -1)) { |
| return ret; |
| } |
| |
| for (var i=0; i<control.length; i++) { |
| if (findall || control.options[i].selected) { |
| ret[ret.length] = want_values ? control.options[i].value : i; |
| } |
| } |
| return ret; |
| } |
| |