diff --git a/django/contrib/admin/static/admin/js/SelectBox.js b/django/contrib/admin/static/admin/js/SelectBox.js index e166cf02fc..ace6d9dfb8 100644 --- a/django/contrib/admin/static/admin/js/SelectBox.js +++ b/django/contrib/admin/static/admin/js/SelectBox.js @@ -13,6 +13,7 @@ redisplay: function(id) { // Repopulate HTML select box from cache const box = document.getElementById(id); + const scroll_value_from_top = box.scrollTop; box.innerHTML = ''; for (const node of SelectBox.cache[id]) { if (node.displayed) { @@ -22,6 +23,7 @@ box.appendChild(new_option); } } + box.scrollTop = scroll_value_from_top; }, filter: function(id, text) { // Redisplay the HTML select box, displaying only the choices containing ALL diff --git a/js_tests/admin/SelectBox.test.js b/js_tests/admin/SelectBox.test.js index 3de7623691..7d127b5d59 100644 --- a/js_tests/admin/SelectBox.test.js +++ b/js_tests/admin/SelectBox.test.js @@ -21,3 +21,27 @@ QUnit.test('filter', function(assert) { assert.equal($('#id option').length, 1); assert.equal($('#id option').text(), "A"); }); + +QUnit.test('preserve scroll position', function(assert) { + const $ = django.jQuery; + const optionsCount = 100; + $('').appendTo('#qunit-fixture'); + $('').appendTo('#qunit-fixture'); + const fromSelectBox = document.getElementById('from_id'); + const toSelectBox = document.getElementById('to_id'); + for (let i = 0; i < optionsCount; i++) { + fromSelectBox.appendChild(new Option()); + } + SelectBox.init('from_id'); + SelectBox.init('to_id'); + const selectedOptions = [97, 98, 99]; + for (const index of selectedOptions) { + fromSelectBox.options[index].selected = true; + fromSelectBox.options[index].scrollIntoView(); + } + assert.equal(fromSelectBox.options.length, optionsCount); + SelectBox.move('from_id', 'to_id'); + assert.equal(fromSelectBox.options.length, optionsCount - selectedOptions.length); + assert.equal(toSelectBox.options.length, selectedOptions.length); + assert.notEqual(fromSelectBox.scrollTop, 0); +});