aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/productform.js295
1 files changed, 295 insertions, 0 deletions
diff --git a/js/productform.js b/js/productform.js
new file mode 100644
index 000000000..0be0d4971
--- /dev/null
+++ b/js/productform.js
@@ -0,0 +1,295 @@
+/* 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 */
+
+/* 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;
+ 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;
+}
+