productform.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  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 Netscape Communications
  14. * Corporation. Portions created by Netscape are
  15. * Copyright (C) 1998 Netscape Communications Corporation. All
  16. * Rights Reserved.
  17. *
  18. * Contributor(s): Christian Reis <kiko@async.com.br>
  19. */
  20. // Functions to update form select elements based on a
  21. // collection of javascript arrays containing strings.
  22. /**
  23. * Reads the selected classifications and updates product, component,
  24. * version and milestone lists accordingly.
  25. *
  26. * @param classfield Select element that contains classifications.
  27. * @param product Select element that contains products.
  28. * @param component Select element that contains components. Can be null if
  29. * there is no such element to update.
  30. * @param version Select element that contains versions. Can be null if
  31. * there is no such element to update.
  32. * @param milestone Select element that contains milestones. Can be null if
  33. * there is no such element to update.
  34. *
  35. * @global prods Array of products indexed by classification name.
  36. * @global first_load Boolean; true if this is the first time this page loads
  37. * or false if not.
  38. * @global last_sel Array that contains last list of products so we know what
  39. * has changed, and optimize for additions.
  40. */
  41. function selectClassification(classfield, product, component, version, milestone) {
  42. // This is to avoid handling events that occur before the form
  43. // itself is ready, which could happen in buggy browsers.
  44. if (!classfield)
  45. return;
  46. // If this is the first load and nothing is selected, no need to
  47. // merge and sort all lists; they are created sorted.
  48. if ((first_load) && (classfield.selectedIndex == -1)) {
  49. first_load = false;
  50. return;
  51. }
  52. // Don't reset first_load as done in selectProduct. That's because we
  53. // want selectProduct to handle the first_load attribute.
  54. // Stores classifications that are selected.
  55. var sel = Array();
  56. // True if sel array has a full list or false if sel contains only
  57. // new classifications that are to be merged to the current list.
  58. var merging = false;
  59. // If nothing selected, pick all.
  60. var findall = classfield.selectedIndex == -1;
  61. sel = get_selection(classfield, findall, false);
  62. if (!findall) {
  63. // Save sel for the next invocation of selectClassification().
  64. var tmp = sel;
  65. // This is an optimization: if we have just added classifications to an
  66. // existing selection, no need to clear the form elements and add
  67. // everything again; just merge the new ones with the existing
  68. // options.
  69. if ((last_sel.length > 0) && (last_sel.length < sel.length)) {
  70. sel = fake_diff_array(sel, last_sel);
  71. merging = true;
  72. }
  73. last_sel = tmp;
  74. }
  75. // Save original options selected.
  76. var saved_prods = get_selection(product, false, true, null);
  77. // Do the actual fill/update, reselect originally selected options.
  78. updateSelect(prods, sel, product, merging, null);
  79. restoreSelection(product, saved_prods);
  80. selectProduct(product, component, version, milestone, null);
  81. }
  82. /**
  83. * Reads the selected products and updates component, version and milestone
  84. * lists accordingly.
  85. *
  86. * @param product Select element that contains products.
  87. * @param component Select element that contains components. Can be null if
  88. * there is no such element to update.
  89. * @param version Select element that contains versions. Can be null if
  90. * there is no such element to update.
  91. * @param milestone Select element that contains milestones. Can be null if
  92. * there is no such element to update.
  93. * @param anyval Value to use for a special "Any" list item. Can be null
  94. * to not use any. If used must and will be first item in
  95. * the select element.
  96. *
  97. * @global cpts Array of arrays, indexed by product name. The subarrays
  98. * contain a list of components to be fed to the respective
  99. * select element.
  100. * @global vers Array of arrays, indexed by product name. The subarrays
  101. * contain a list of versions to be fed to the respective
  102. * select element.
  103. * @global tms Array of arrays, indexed by product name. The subarrays
  104. * contain a list of milestones to be fed to the respective
  105. * select element.
  106. * @global first_load Boolean; true if this is the first time this page loads
  107. * or false if not.
  108. * @global last_sel Array that contains last list of products so we know what
  109. * has changed, and optimize for additions.
  110. */
  111. function selectProduct(product, component, version, milestone, anyval) {
  112. // This is to avoid handling events that occur before the form
  113. // itself is ready, which could happen in buggy browsers.
  114. if (!product)
  115. return;
  116. // Do nothing if no products are defined. This is to avoid the
  117. // "a has no properties" error from merge_arrays function.
  118. if (product.length == (anyval != null ? 1 : 0))
  119. return;
  120. // If this is the first load and nothing is selected, no need to
  121. // merge and sort all lists; they are created sorted.
  122. if ((first_load) && (product.selectedIndex == -1)) {
  123. first_load = false;
  124. return;
  125. }
  126. // Turn first_load off. This is tricky, since it seems to be
  127. // redundant with the above clause. It's not: if when we first load
  128. // the page there is _one_ element selected, it won't fall into that
  129. // clause, and first_load will remain 1. Then, if we unselect that
  130. // item, selectProduct will be called but the clause will be valid
  131. // (since selectedIndex == -1), and we will return - incorrectly -
  132. // without merge/sorting.
  133. first_load = false;
  134. // Stores products that are selected.
  135. var sel = Array();
  136. // True if sel array has a full list or false if sel contains only
  137. // new products that are to be merged to the current list.
  138. var merging = false;
  139. // If nothing is selected, or the special "Any" option is selected
  140. // which represents all products, then pick all products so we show
  141. // all components.
  142. var findall = (product.selectedIndex == -1
  143. || (anyval != null && product.options[0].selected));
  144. if (useclassification) {
  145. // Update index based on the complete product array.
  146. sel = get_selection(product, findall, true, anyval);
  147. for (var i=0; i<sel.length; i++)
  148. sel[i] = prods[sel[i]];
  149. }
  150. else {
  151. sel = get_selection(product, findall, false, anyval);
  152. }
  153. if (!findall) {
  154. // Save sel for the next invocation of selectProduct().
  155. var tmp = sel;
  156. // This is an optimization: if we have just added products to an
  157. // existing selection, no need to clear the form controls and add
  158. // everybody again; just merge the new ones with the existing
  159. // options.
  160. if ((last_sel.length > 0) && (last_sel.length < sel.length)) {
  161. sel = fake_diff_array(sel, last_sel);
  162. merging = true;
  163. }
  164. last_sel = tmp;
  165. }
  166. // Do the actual fill/update.
  167. if (component) {
  168. var saved_cpts = get_selection(component, false, true, null);
  169. updateSelect(cpts, sel, component, merging, anyval);
  170. restoreSelection(component, saved_cpts);
  171. }
  172. if (version) {
  173. var saved_vers = get_selection(version, false, true, null);
  174. updateSelect(vers, sel, version, merging, anyval);
  175. restoreSelection(version, saved_vers);
  176. }
  177. if (milestone) {
  178. var saved_tms = get_selection(milestone, false, true, null);
  179. updateSelect(tms, sel, milestone, merging, anyval);
  180. restoreSelection(milestone, saved_tms);
  181. }
  182. }
  183. /**
  184. * Adds to the target select element all elements from array that
  185. * correspond to the selected items.
  186. *
  187. * @param array An array of arrays, indexed by number. The array should
  188. * contain elements for each selection.
  189. * @param sel A list of selected items, either whole or a diff depending
  190. * on merging parameter.
  191. * @param target Select element that is to be updated.
  192. * @param merging Boolean that determines if we are merging in a diff or
  193. * substituting the whole selection. A diff is used to optimize
  194. * adding selections.
  195. * @param anyval Name of special "Any" value to add. Can be null if not used.
  196. * @return Boolean; true if target contains options or false if target
  197. * is empty.
  198. *
  199. * Example (compsel is a select form element):
  200. *
  201. * var components = Array();
  202. * components[1] = [ 'ComponentA', 'ComponentB' ];
  203. * components[2] = [ 'ComponentC', 'ComponentD' ];
  204. * source = [ 2 ];
  205. * updateSelect(components, source, compsel, false, null);
  206. *
  207. * This would clear compsel and add 'ComponentC' and 'ComponentD' to it.
  208. */
  209. function updateSelect(array, sel, target, merging, anyval) {
  210. var i, item;
  211. // If we have no versions/components/milestones.
  212. if (array.length < 1) {
  213. target.options.length = 0;
  214. return false;
  215. }
  216. if (merging) {
  217. // Array merging/sorting in the case of multiple selections
  218. // merge in the current options with the first selection.
  219. item = merge_arrays(array[sel[0]], target.options, 1);
  220. // Merge the rest of the selection with the results.
  221. for (i = 1 ; i < sel.length ; i++)
  222. item = merge_arrays(array[sel[i]], item, 0);
  223. }
  224. else if (sel.length > 1) {
  225. // Here we micro-optimize for two arrays to avoid merging with a
  226. // null array.
  227. item = merge_arrays(array[sel[0]],array[sel[1]], 0);
  228. // Merge the arrays. Not very good for multiple selections.
  229. for (i = 2; i < sel.length; i++)
  230. item = merge_arrays(item, array[sel[i]], 0);
  231. }
  232. else {
  233. // Single item in selection, just get me the list.
  234. item = array[sel[0]];
  235. }
  236. // Clear current selection.
  237. target.options.length = 0;
  238. // Add special "Any" value back to the list.
  239. if (anyval != null)
  240. target.options[0] = new Option(anyval, "");
  241. // Load elements of list into select element.
  242. for (i = 0; i < item.length; i++)
  243. target.options[target.options.length] = new Option(item[i], item[i]);
  244. return true;
  245. }
  246. /**
  247. * Selects items in select element that are defined to be selected.
  248. *
  249. * @param control Select element of which selected options are to be restored.
  250. * @param selnames Array of option names to select.
  251. */
  252. function restoreSelection(control, selnames) {
  253. // Right. This sucks but I see no way to avoid going through the
  254. // list and comparing to the contents of the control.
  255. for (var j = 0; j < selnames.length; j++)
  256. for (var i = 0; i < control.options.length; i++)
  257. if (control.options[i].value == selnames[j])
  258. control.options[i].selected = true;
  259. }
  260. /**
  261. * Returns elements in a that are not in b.
  262. * NOT A REAL DIFF: does not check the reverse.
  263. *
  264. * @param a First array to compare.
  265. * @param b Second array to compare.
  266. * @return Array of elements in a but not in b.
  267. */
  268. function fake_diff_array(a, b) {
  269. var newsel = new Array();
  270. var found = false;
  271. // Do a boring array diff to see who's new.
  272. for (var ia in a) {
  273. for (var ib in b)
  274. if (a[ia] == b[ib])
  275. found = true;
  276. if (!found)
  277. newsel[newsel.length] = a[ia];
  278. found = false;
  279. }
  280. return newsel;
  281. }
  282. /**
  283. * Takes two arrays and sorts them by string, returning a new, sorted
  284. * array. The merge removes dupes, too.
  285. *
  286. * @param a First array to merge.
  287. * @param b Second array or an optionitem element to merge.
  288. * @param b_is_select Boolean; true if b is an optionitem element (need to
  289. * access its value by item.value) or false if b is a
  290. * an array.
  291. * @return Merged and sorted array.
  292. */
  293. function merge_arrays(a, b, b_is_select) {
  294. var pos_a = 0;
  295. var pos_b = 0;
  296. var ret = new Array();
  297. var bitem, aitem;
  298. // Iterate through both arrays and add the larger item to the return
  299. // list. Remove dupes, too. Use toLowerCase to provide
  300. // case-insensitivity.
  301. while ((pos_a < a.length) && (pos_b < b.length)) {
  302. aitem = a[pos_a];
  303. if (b_is_select)
  304. bitem = b[pos_b].value;
  305. else
  306. bitem = b[pos_b];
  307. // Smaller item in list a.
  308. if (aitem.toLowerCase() < bitem.toLowerCase()) {
  309. ret[ret.length] = aitem;
  310. pos_a++;
  311. }
  312. else {
  313. // Smaller item in list b.
  314. if (aitem.toLowerCase() > bitem.toLowerCase()) {
  315. ret[ret.length] = bitem;
  316. pos_b++;
  317. }
  318. else {
  319. // List contents are equal, include both counters.
  320. ret[ret.length] = aitem;
  321. pos_a++;
  322. pos_b++;
  323. }
  324. }
  325. }
  326. // Catch leftovers here. These sections are ugly code-copying.
  327. if (pos_a < a.length)
  328. for (; pos_a < a.length ; pos_a++)
  329. ret[ret.length] = a[pos_a];
  330. if (pos_b < b.length) {
  331. for (; pos_b < b.length; pos_b++) {
  332. if (b_is_select)
  333. bitem = b[pos_b].value;
  334. else
  335. bitem = b[pos_b];
  336. ret[ret.length] = bitem;
  337. }
  338. }
  339. return ret;
  340. }
  341. /**
  342. * Returns an array of indexes or values of options in a select form element.
  343. *
  344. * @param control Select form element from which to find selections.
  345. * @param findall Boolean; true to return all options or false to return
  346. * only selected options.
  347. * @param want_values Boolean; true to return values and false to return
  348. * indexes.
  349. * @param anyval Name of a special "Any" value that should be skipped. Can
  350. * be null if not used.
  351. * @return Array of all or selected indexes or values.
  352. */
  353. function get_selection(control, findall, want_values, anyval) {
  354. var ret = new Array();
  355. if ((!findall) && (control.selectedIndex == -1))
  356. return ret;
  357. for (var i = (anyval != null ? 1 : 0); i < control.length; i++)
  358. if (findall || control.options[i].selected)
  359. ret[ret.length] = want_values ? control.options[i].value : i;
  360. return ret;
  361. }