diff --git a/django/contrib/admin/static/admin/js/SelectBox.js b/django/contrib/admin/static/admin/js/SelectBox.js
index ace6d9dfb8..14c7b8bda7 100644
a
|
b
|
|
1 | 1 | 'use strict'; |
2 | 2 | { |
| 3 | const getOptionGroupName = (option) => option.parentElement.label; |
3 | 4 | const SelectBox = { |
4 | 5 | cache: {}, |
5 | 6 | init: function(id) { |
… |
… |
|
7 | 8 | SelectBox.cache[id] = []; |
8 | 9 | const cache = SelectBox.cache[id]; |
9 | 10 | for (const node of box.options) { |
10 | | cache.push({value: node.value, text: node.text, displayed: 1}); |
| 11 | const group = getOptionGroupName(node); |
| 12 | cache.push({group, value: node.value, text: node.text, displayed: 1}); |
11 | 13 | } |
| 14 | SelectBox.sort(id); |
12 | 15 | }, |
13 | 16 | redisplay: function(id) { |
14 | 17 | // Repopulate HTML select box from cache |
15 | 18 | const box = document.getElementById(id); |
16 | 19 | const scroll_value_from_top = box.scrollTop; |
17 | 20 | box.innerHTML = ''; |
18 | | for (const node of SelectBox.cache[id]) { |
19 | | if (node.displayed) { |
20 | | const new_option = new Option(node.text, node.value, false, false); |
| 21 | let node = box; |
| 22 | let group = null; |
| 23 | for (const option of SelectBox.cache[id]) { |
| 24 | if (option.group && option.group !== group && option.displayed) { |
| 25 | group = option.group; |
| 26 | node = document.createElement('optgroup'); |
| 27 | node.setAttribute('label', option.group); |
| 28 | box.appendChild(node); |
| 29 | } |
| 30 | if (option.displayed) { |
| 31 | const new_option = new Option(option.text, option.value, false, false); |
21 | 32 | // Shows a tooltip when hovering over the option |
22 | | new_option.title = node.text; |
23 | | box.appendChild(new_option); |
| 33 | new_option.title = option.text; |
| 34 | node.appendChild(new_option); |
24 | 35 | } |
25 | 36 | } |
26 | 37 | box.scrollTop = scroll_value_from_top; |
… |
… |
|
53 | 64 | cache.splice(delete_index, 1); |
54 | 65 | }, |
55 | 66 | add_to_cache: function(id, option) { |
56 | | SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); |
| 67 | SelectBox.cache[id].push({group: option.group, value: option.value, text: option.text, displayed: 1}); |
| 68 | SelectBox.sort(id); |
57 | 69 | }, |
58 | 70 | cache_contains: function(id, value) { |
59 | 71 | // Check if an item is contained in the cache |
… |
… |
|
69 | 81 | for (const option of from_box.options) { |
70 | 82 | const option_value = option.value; |
71 | 83 | if (option.selected && SelectBox.cache_contains(from, option_value)) { |
72 | | SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1}); |
| 84 | const group = getOptionGroupName(option); |
| 85 | SelectBox.add_to_cache(to, {group, value: option_value, text: option.text, displayed: 1}); |
73 | 86 | SelectBox.delete_from_cache(from, option_value); |
74 | 87 | } |
75 | 88 | } |
… |
… |
|
81 | 94 | for (const option of from_box.options) { |
82 | 95 | const option_value = option.value; |
83 | 96 | if (SelectBox.cache_contains(from, option_value)) { |
84 | | SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1}); |
| 97 | const group = getOptionGroupName(option); |
| 98 | SelectBox.add_to_cache(to, {group, value: option_value, text: option.text, displayed: 1}); |
85 | 99 | SelectBox.delete_from_cache(from, option_value); |
86 | 100 | } |
87 | 101 | } |
… |
… |
|
90 | 104 | }, |
91 | 105 | sort: function(id) { |
92 | 106 | SelectBox.cache[id].sort(function(a, b) { |
93 | | a = a.text.toLowerCase(); |
94 | | b = b.text.toLowerCase(); |
| 107 | a = (a.group && a.group.toLowerCase() || '') + a.text.toLowerCase(); |
| 108 | b = (b.group && b.group.toLowerCase() || '') + b.text.toLowerCase(); |
95 | 109 | if (a > b) { |
96 | 110 | return 1; |
97 | 111 | } |
diff --git a/js_tests/admin/SelectBox.test.js b/js_tests/admin/SelectBox.test.js
index 7d127b5d59..2808dbcacb 100644
a
|
b
|
QUnit.test('preserve scroll position', function(assert) {
|
45 | 45 | assert.equal(toSelectBox.options.length, selectedOptions.length); |
46 | 46 | assert.notEqual(fromSelectBox.scrollTop, 0); |
47 | 47 | }); |
| 48 | |
| 49 | QUnit.test('retain optgroups', function(assert) { |
| 50 | const $ = django.jQuery; |
| 51 | $('<select id="id"></select>').appendTo('#qunit-fixture'); |
| 52 | const grp = $('<optgroup label="group one">').appendTo('#id'); |
| 53 | $('<option value="0">A</option>').appendTo(grp); |
| 54 | $('</optgroup>').appendTo('#id'); |
| 55 | $('<option value="1">B</option>').appendTo('#id'); |
| 56 | SelectBox.init('id'); |
| 57 | SelectBox.redisplay('id'); |
| 58 | assert.equal($('#id option').length, 2); |
| 59 | assert.equal($('#id optgroup').length, 1); |
| 60 | }); |