summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--js/productform.js425
-rw-r--r--template/en/default/admin/flag-type/edit.html.tmpl6
-rw-r--r--template/en/default/admin/flag-type/list.html.tmpl6
-rw-r--r--template/en/default/global/js-products.html.tmpl10
-rw-r--r--template/en/default/request/queue.html.tmpl6
-rw-r--r--template/en/default/search/form.html.tmpl4
6 files changed, 249 insertions, 208 deletions
diff --git a/js/productform.js b/js/productform.js
index 7cf07d732..f9b420c31 100644
--- a/js/productform.js
+++ b/js/productform.js
@@ -18,139 +18,164 @@
* 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.
+// Functions to update form select elements based on a
+// collection of javascript arrays containing strings.
+
+/**
+ * Reads the selected classifications and updates product, component,
+ * version and milestone lists accordingly.
+ *
+ * @param classfield Select element that contains classifications.
+ * @param product Select element that contains products.
+ * @param component Select element that contains components. Can be null if
+ * there is no such element to update.
+ * @param version Select element that contains versions. Can be null if
+ * there is no such element to update.
+ * @param milestone Select element that contains milestones. Can be null if
+ * there is no such element to update.
+ *
+ * @global prods Array of products indexed by classification name.
+ * @global first_load Boolean; true if this is the first time this page loads
+ * or false if not.
+ * @global last_sel Array that contains last list of products 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) {
+ // 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 this is the first load and nothing is selected, no need to
+ // merge and sort all lists; they are created 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;
+ // Don't reset first_load as done in selectProduct. That's because we
+ // want selectProduct to handle the first_load attribute.
+
+ // Stores classifications that are selected.
var sel = Array();
- /* if nothing selected, pick all */
+ // True if sel array has a full list or false if sel contains only
+ // new classifications that are to be merged to the current list.
+ var merging = false;
+
+ // 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() */
+ // 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.
- */
+ // This is an optimization: if we have just added classifications to an
+ // existing selection, no need to clear the form elements and add
+ // everything 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);
+ // Save original options selected.
+ var saved_prods = get_selection(product, false, true, null);
+
+ // Do the actual fill/update, reselect originally selected options.
+ updateSelect(prods, sel, product, merging, null);
restoreSelection(product, saved_prods);
- selectProduct(product, component, version, milestone);
+ selectProduct(product, component, version, milestone, null);
}
-
-/* selectProduct reads the selection from the product control and
- * updates version, component and milestone controls accordingly.
- *
- * - product, component, version and milestone: form controls
+/**
+ * Reads the selected products and updates component, version and milestone
+ * lists accordingly.
+ *
+ * @param product Select element that contains products.
+ * @param component Select element that contains components. Can be null if
+ * there is no such element to update.
+ * @param version Select element that contains versions. Can be null if
+ * there is no such element to update.
+ * @param milestone Select element that contains milestones. Can be null if
+ * there is no such element to update.
+ * @param anyval Value to use for a special "Any" list item. Can be null
+ * to not use any. If used must and will be first item in
+ * the select element.
*
- * 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.
+ * @global cpts Array of arrays, indexed by product name. The subarrays
+ * contain a list of components to be fed to the respective
+ * select element.
+ * @global vers Array of arrays, indexed by product name. The subarrays
+ * contain a list of versions to be fed to the respective
+ * select element.
+ * @global tms Array of arrays, indexed by product name. The subarrays
+ * contain a list of milestones to be fed to the respective
+ * select element.
+ * @global first_load Boolean; true if this is the first time this page loads
+ * or false if not.
+ * @global last_sel Array that contains last list of products so we know what
+ * has changed, and optimize for additions.
*/
-function selectProduct(product, component, version, milestone) {
+function selectProduct(product, component, version, milestone, anyval) {
+ // This is to avoid handling events that occur before the form
+ // itself is ready, which could happen in buggy browsers.
+ if (!product)
+ return;
- if (!product) {
- /* this is to avoid handling events that occur before the form
- * itself is ready, which could happen in buggy browsers. */
+ // Do nothing if no products are defined. This is to avoid the
+ // "a has no properties" error from merge_arrays function.
+ if (product.length == (anyval != null ? 1 : 0))
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 this is the first load and nothing is selected, no need to
+ // merge and sort all lists; they are created 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. */
+ // 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;
+ // Stores products that are selected.
var sel = Array();
- /* if nothing selected, pick all */
- var findall = product.selectedIndex == -1;
+ // True if sel array has a full list or false if sel contains only
+ // new products that are to be merged to the current list.
+ var merging = false;
+
+ // If nothing is selected, or the special "Any" option is selected
+ // which represents all products, then pick all products so we show
+ // all components.
+ var findall = (product.selectedIndex == -1
+ || (anyval != null && product.options[0].selected));
+
if (useclassification) {
- /* update index based on the complete product array */
- sel = get_selection(product, findall, true);
- for (var i=0; i<sel.length; i++) {
+ // Update index based on the complete product array.
+ sel = get_selection(product, findall, true, anyval);
+ for (var i=0; i<sel.length; i++)
sel[i] = prods[sel[i]];
- }
- } else {
- sel = get_selection(product, findall, false);
+ }
+ else {
+ sel = get_selection(product, findall, false, anyval);
}
if (!findall) {
- /* save sel for the next invocation of selectProduct() */
+ // 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. */
+ // 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;
@@ -158,167 +183,180 @@ function selectProduct(product, component, version, milestone) {
last_sel = tmp;
}
- /* do the actual fill/update */
+ // Do the actual fill/update.
if (component) {
- var saved_cpts = get_selection(component, false, true);
- updateSelect(cpts, sel, component, merging);
+ var saved_cpts = get_selection(component, false, true, null);
+ updateSelect(cpts, sel, component, merging, anyval);
restoreSelection(component, saved_cpts);
}
if (version) {
- var saved_vers = get_selection(version, false, true);
- updateSelect(vers, sel, version, merging);
+ var saved_vers = get_selection(version, false, true, null);
+ updateSelect(vers, sel, version, merging, anyval);
restoreSelection(version, saved_vers);
}
if (milestone) {
- var saved_tms = get_selection(milestone, false, true);
- updateSelect(tms, sel, milestone, merging);
+ var saved_tms = get_selection(milestone, false, true, null);
+ updateSelect(tms, sel, milestone, merging, anyval);
restoreSelection(milestone, saved_tms);
}
}
-
-/* updateSelect(array, sel, target, merging)
+/**
+ * Adds to the target select element all elements from array that
+ * correspond to the selected items.
*
- * 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.
+ * @param array An array of arrays, indexed by number. The array should
+ * contain elements for each selection.
+ * @param sel A list of selected items, either whole or a diff depending
+ * on merging parameter.
+ * @param target Select element that is to be updated.
+ * @param merging Boolean that determines if we are merging in a diff or
+ * substituting the whole selection. A diff is used to optimize
+ * adding selections.
+ * @param anyval Name of special "Any" value to add. Can be null if not used.
+ * @return Boolean; true if target contains options or false if target
+ * is empty.
*
- * Example (compsel is a select form control)
+ * Example (compsel is a select form element):
*
* 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.
+ * updateSelect(components, source, compsel, false, null);
*
+ * This would clear compsel and add 'ComponentC' and 'ComponentD' to it.
*/
-
-function updateSelect(array, sel, target, merging) {
-
+function updateSelect(array, sel, target, merging, anyval) {
var i, item;
- /* If we have no versions/components/milestones */
+ // 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 */
+ // 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++) {
+ // 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 */
+ }
+ 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++) {
+ // 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 */
+ }
+ else {
+ // Single item in selection, just get me the list.
item = array[sel[0]];
}
- /* clear select */
+ // Clear current selection.
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]);
- }
+ // Add special "Any" value back to the list.
+ if (anyval != null)
+ target.options[0] = new Option(anyval, "");
+
+ // Load elements of list into select element.
+ for (i = 0; i < item.length; i++)
+ target.options[target.options.length] = 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 */
+/**
+ * Selects items in select element that are defined to be selected.
+ *
+ * @param control Select element of which selected options are to be restored.
+ * @param selnames Array of option names to select.
+ */
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]) {
+ // 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.
+/**
+ * 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. */
+ *
+ * @param a First array to compare.
+ * @param b Second array to compare.
+ * @return Array of elements in a but not in b.
+ */
function fake_diff_array(a, b) {
var newsel = new Array();
var found = false;
- /* do a boring array diff to see who's new */
+ // 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]) {
+ for (var ib in b)
+ if (a[ia] == b[ib])
found = true;
- }
- }
- if (!found) {
+
+ 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. */
+/**
+ * Takes two arrays and sorts them by string, returning a new, sorted
+ * array. The merge removes dupes, too.
+ *
+ * @param a First array to merge.
+ * @param b Second array or an optionitem element to merge.
+ * @param b_is_select Boolean; true if b is an optionitem element (need to
+ * access its value by item.value) or false if b is a
+ * an array.
+ * @return Merged and sorted array.
+ */
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. */
+ // 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) {
+ aitem = a[pos_a];
+ if (b_is_select)
bitem = b[pos_b].value;
- } else {
+ else
bitem = b[pos_b];
- }
- aitem = a[pos_a];
- /* smaller item in list a */
+ // Smaller item in list a.
if (aitem.toLowerCase() < bitem.toLowerCase()) {
ret[ret.length] = aitem;
pos_a++;
- } else {
- /* smaller item in list b */
+ }
+ 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. */
+ }
+ else {
+ // List contents are equal, include both counters.
ret[ret.length] = aitem;
pos_a++;
pos_b++;
@@ -326,44 +364,45 @@ function merge_arrays(a, b, b_is_select) {
}
}
- /* catch leftovers here. these sections are ugly code-copying. */
- if (pos_a < a.length) {
- for (; pos_a < a.length ; pos_a++) {
+ // 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) {
+ if (b_is_select)
bitem = b[pos_b].value;
- } else {
+ 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) {
+/**
+ * Returns an array of indexes or values of options in a select form element.
+ *
+ * @param control Select form element from which to find selections.
+ * @param findall Boolean; true to return all options or false to return
+ * only selected options.
+ * @param want_values Boolean; true to return values and false to return
+ * indexes.
+ * @param anyval Name of a special "Any" value that should be skipped. Can
+ * be null if not used.
+ * @return Array of all or selected indexes or values.
+ */
+function get_selection(control, findall, want_values, anyval) {
var ret = new Array();
- if ((!findall) && (control.selectedIndex == -1)) {
+ if ((!findall) && (control.selectedIndex == -1))
return ret;
- }
- for (var i=0; i<control.length; i++) {
- if (findall || control.options[i].selected) {
+ for (var i = (anyval != null ? 1 : 0); i < control.length; i++)
+ if (findall || control.options[i].selected)
ret[ret.length] = want_values ? control.options[i].value : i;
- }
- }
+
return ret;
}
-
diff --git a/template/en/default/admin/flag-type/edit.html.tmpl b/template/en/default/admin/flag-type/edit.html.tmpl
index 4e2edd5a4..d7add7738 100644
--- a/template/en/default/admin/flag-type/edit.html.tmpl
+++ b/template/en/default/admin/flag-type/edit.html.tmpl
@@ -46,8 +46,8 @@
table#form th { text-align: right; vertical-align: baseline; white-space: nowrap; }
table#form td { text-align: left; vertical-align: baseline; }
"
- onload="selectProduct(document.forms[0], 'product', 'component', '__Any__');"
- javascript_urls=["productmenu.js"]
+ onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, '__Any__');"
+ javascript_urls=["js/productform.js"]
%]
<form method="post" action="editflagtypes.cgi">
@@ -100,7 +100,7 @@
<tr>
<td style="vertical-align: top;">
<b>Product/Component:</b><br>
- <select name="product" onchange="selectProduct(this.form, 'product', 'component', '__Any__');">
+ <select name="product" onchange="selectProduct(this, this.form.component, null, null, '__Any__');">
<option value="">__Any__</option>
[% FOREACH prod = products %]
<option value="[% prod.name FILTER html %]"
diff --git a/template/en/default/admin/flag-type/list.html.tmpl b/template/en/default/admin/flag-type/list.html.tmpl
index 3346f9570..c6046a766 100644
--- a/template/en/default/admin/flag-type/list.html.tmpl
+++ b/template/en/default/admin/flag-type/list.html.tmpl
@@ -31,8 +31,8 @@
.inactive { color: #787878; }
.multiplicable { display: block; }
"
- onload="selectProduct(document.forms[0], 'product', 'component', '__All__');"
- javascript_urls=["productmenu.js"]
+ onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, '__All__');"
+ javascript_urls=["js/productform.js"]
%]
<p>
@@ -60,7 +60,7 @@
<tr>
<th><label for="product">Product:</label></th>
<td>
- <select name="product" onchange="selectProduct(this.form, 'product', 'component', '__Any__');">
+ <select name="product" onchange="selectProduct(this, this.form.component, null, null, '__Any__');">
<option value="">__Any__</option>
[% FOREACH prod = products %]
<option value="[% prod.name FILTER html %]"
diff --git a/template/en/default/global/js-products.html.tmpl b/template/en/default/global/js-products.html.tmpl
index 57126f004..dbc1c2e1e 100644
--- a/template/en/default/global/js-products.html.tmpl
+++ b/template/en/default/global/js-products.html.tmpl
@@ -22,12 +22,14 @@
[%# The javascript block gets used in header.html.tmpl. %]
[% javascript = BLOCK %]
- var usetms = 0; // do we have target milestone?
- var first_load = 1; // is this the first time we load the page?
- var last_sel = []; // caches last selection
+ var useclassification = false; // No classification level in use
+ var first_load = true; // Is this the first time we load the page?
+ var last_sel = []; // Caches last selection
var cpts = new Array();
+ [% n = 1 %]
[% FOREACH prod = products %]
- cpts['[% prod.name FILTER js %]'] = [
+ cpts['[% n %]'] = [
[%- FOREACH comp = prod.components %]'[% comp.name FILTER js %]'[% ", " UNLESS loop.last %] [%- END -%] ];
+ [% n = n+1 %]
[% END %]
[% END %]
diff --git a/template/en/default/request/queue.html.tmpl b/template/en/default/request/queue.html.tmpl
index c80ecae87..ff143725a 100644
--- a/template/en/default/request/queue.html.tmpl
+++ b/template/en/default/request/queue.html.tmpl
@@ -32,8 +32,8 @@
table.requests th { text-align: left; }
table#filtering th { text-align: right; }
"
- onload="selectProduct(document.forms[0], 'product', 'component', 'Any');"
- javascript_urls=["productmenu.js"]
+ onload="var f = document.forms[0]; selectProduct(f.product, f.component, null, null, 'Any');"
+ javascript_urls=["js/productform.js"]
%]
<p>
@@ -53,7 +53,7 @@ to some group are shown by default.
title="Requester's email address"></td>
<th>Product:</th>
<td>
- <select name="product" onchange="selectProduct(this.form, 'product', 'component', 'Any');">
+ <select name="product" onchange="selectProduct(this, this.form.component, null, null, 'Any');">
<option value="">Any</option>
[% FOREACH prod = products %]
<option value="[% prod.name FILTER html %]"
diff --git a/template/en/default/search/form.html.tmpl b/template/en/default/search/form.html.tmpl
index 9669eed40..25c17704d 100644
--- a/template/en/default/search/form.html.tmpl
+++ b/template/en/default/search/form.html.tmpl
@@ -93,12 +93,12 @@ function doOnSelectProduct(selectmode) {
if (useclassification && f.classification.selectedIndex > -1) {
selectClassification(f.classification, f.product, f.component, f.version, milestone);
} else {
- selectProduct(f.product, f.component, f.version, milestone);
+ selectProduct(f.product, f.component, f.version, milestone, null);
}
} else if (selectmode == 1) {
selectClassification(f.classification, f.product, f.component, f.version, milestone);
} else {
- selectProduct(f.product, f.component, f.version, milestone);
+ selectProduct(f.product, f.component, f.version, milestone, null);
}
}