diff options
| -rw-r--r-- | django/contrib/admin/static/admin/css/base.css | 4 | ||||
| -rw-r--r-- | django/contrib/admin/static/admin/css/changelists.css | 1 | ||||
| -rw-r--r-- | django/contrib/admin/static/admin/css/forms.css | 4 | ||||
| -rw-r--r-- | django/contrib/admin/static/admin/js/actions.js | 286 | ||||
| -rw-r--r-- | django/contrib/admin/templates/admin/actions.html | 6 | ||||
| -rw-r--r-- | js_tests/admin/actions.test.js | 2 | ||||
| -rw-r--r-- | js_tests/tests.html | 29 |
7 files changed, 176 insertions, 156 deletions
diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index c4285195fc..76316dbb1f 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -211,6 +211,10 @@ p img, h1 img, h2 img, h3 img, h4 img, td img { white-space: nowrap; } +.hidden { + display: none; +} + /* TABLES */ table { diff --git a/django/contrib/admin/static/admin/css/changelists.css b/django/contrib/admin/static/admin/css/changelists.css index abd2640b4a..477429df14 100644 --- a/django/contrib/admin/static/admin/css/changelists.css +++ b/django/contrib/admin/static/admin/css/changelists.css @@ -300,7 +300,6 @@ #changelist .actions span.question { font-size: 13px; margin: 0 0.5em; - display: none; } #changelist .actions:last-child { diff --git a/django/contrib/admin/static/admin/css/forms.css b/django/contrib/admin/static/admin/css/forms.css index 06acd4290d..700cb34a42 100644 --- a/django/contrib/admin/static/admin/css/forms.css +++ b/django/contrib/admin/static/admin/css/forms.css @@ -22,10 +22,6 @@ form .form-row p { padding-left: 0; } -.hidden { - display: none; -} - /* FORM LABELS */ label { diff --git a/django/contrib/admin/static/admin/js/actions.js b/django/contrib/admin/static/admin/js/actions.js index dae69920b2..3e76ff962a 100644 --- a/django/contrib/admin/static/admin/js/actions.js +++ b/django/contrib/admin/static/admin/js/actions.js @@ -1,154 +1,170 @@ /*global gettext, interpolate, ngettext*/ 'use strict'; { - const $ = django.jQuery; - let lastChecked; + function show(selector) { + document.querySelectorAll(selector).forEach(function(el) { + el.classList.remove('hidden'); + }); + } - $.fn.actions = function(opts) { - const options = $.extend({}, $.fn.actions.defaults, opts); - const actionCheckboxes = $(this); - let list_editable_changed = false; - const showQuestion = function() { - $(options.acrossClears).hide(); - $(options.acrossQuestions).show(); - $(options.allContainer).hide(); - }, - showClear = function() { - $(options.acrossClears).show(); - $(options.acrossQuestions).hide(); - $(options.actionContainer).toggleClass(options.selectedClass); - $(options.allContainer).show(); - $(options.counterContainer).hide(); - }, - reset = function() { - $(options.acrossClears).hide(); - $(options.acrossQuestions).hide(); - $(options.allContainer).hide(); - $(options.counterContainer).show(); - }, - clearAcross = function() { - reset(); - $(options.acrossInput).val(0); - $(options.actionContainer).removeClass(options.selectedClass); - }, - checker = function(checked) { - if (checked) { - showQuestion(); - } else { - reset(); - } - $(actionCheckboxes).prop("checked", checked) - .parent().parent().toggleClass(options.selectedClass, checked); - }, - updateCounter = function() { - const sel = $(actionCheckboxes).filter(":checked").length; - // data-actions-icnt is defined in the generated HTML - // and contains the total amount of objects in the queryset - const actions_icnt = $('.action-counter').data('actionsIcnt'); - $(options.counterContainer).html(interpolate( - ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { - sel: sel, - cnt: actions_icnt - }, true)); - $(options.allToggle).prop("checked", function() { - let value; - if (sel === actionCheckboxes.length) { - value = true; - showQuestion(); - } else { - value = false; - clearAcross(); - } - return value; - }); - }; - // Show counter by default - $(options.counterContainer).show(); - // Check state of checkboxes and reinit state if needed - $(this).filter(":checked").each(function(i) { - $(this).parent().parent().toggleClass(options.selectedClass); - updateCounter(); - if ($(options.acrossInput).val() === 1) { - showClear(); - } + function hide(selector) { + document.querySelectorAll(selector).forEach(function(el) { + el.classList.add('hidden'); }); - $(options.allToggle).show().on('click', function() { - checker($(this).prop("checked")); - updateCounter(); + } + + function showQuestion(options) { + hide(options.acrossClears); + show(options.acrossQuestions); + hide(options.allContainer); + } + + function showClear(options) { + show(options.acrossClears); + hide(options.acrossQuestions); + document.querySelector(options.actionContainer).classList.remove(options.selectedClass); + show(options.allContainer); + hide(options.counterContainer); + } + + function reset(options) { + hide(options.acrossClears); + hide(options.acrossQuestions); + hide(options.allContainer); + show(options.counterContainer); + } + + function clearAcross(options) { + reset(options); + document.querySelector(options.acrossInput).value = 0; + document.querySelector(options.actionContainer).classList.remove(options.selectedClass); + } + + function checker(actionCheckboxes, options, checked) { + if (checked) { + showQuestion(options); + } else { + reset(options); + } + actionCheckboxes.forEach(function(el) { + el.checked = checked; + el.closest('tr').classList.toggle(options.selectedClass, checked); }); - $("a", options.acrossQuestions).on('click', function(event) { - event.preventDefault(); - $(options.acrossInput).val(1); - showClear(); + } + + function updateCounter(actionCheckboxes, options) { + const sel = Array.from(actionCheckboxes).filter(function(el) { + return el.checked; + }).length; + const counter = document.querySelector(options.counterContainer); + // data-actions-icnt is defined in the generated HTML + // and contains the total amount of objects in the queryset + const actions_icnt = Number(counter.dataset.actionsIcnt); + counter.textContent = interpolate( + ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { + sel: sel, + cnt: actions_icnt + }, true); + const allToggle = document.getElementById(options.allToggleId); + allToggle.checked = sel === actionCheckboxes.length; + if (allToggle.checked) { + showQuestion(options); + } else { + clearAcross(options); + } + } + + const defaults = { + actionContainer: "div.actions", + counterContainer: "span.action-counter", + allContainer: "div.actions span.all", + acrossInput: "div.actions input.select-across", + acrossQuestions: "div.actions span.question", + acrossClears: "div.actions span.clear", + allToggleId: "action-toggle", + selectedClass: "selected" + }; + + window.Actions = function(actionCheckboxes, options) { + options = Object.assign({}, defaults, options); + let list_editable_changed = false; + + document.getElementById(options.allToggleId).addEventListener('click', function(event) { + checker(actionCheckboxes, options, this.checked); + updateCounter(actionCheckboxes, options); }); - $("a", options.acrossClears).on('click', function(event) { - event.preventDefault(); - $(options.allToggle).prop("checked", false); - clearAcross(); - checker(0); - updateCounter(); + + document.querySelectorAll(options.acrossQuestions + " a").forEach(function(el) { + el.addEventListener('click', function(event) { + event.preventDefault(); + const acrossInput = document.querySelector(options.acrossInput); + acrossInput.value = 1; + showClear(options); + }); }); - lastChecked = null; - $(actionCheckboxes).on('click', function(event) { - if (!event) { event = window.event; } - const target = event.target ? event.target : event.srcElement; - if (lastChecked && $.data(lastChecked) !== $.data(target) && event.shiftKey === true) { - let inrange = false; - $(lastChecked).prop("checked", target.checked) - .parent().parent().toggleClass(options.selectedClass, target.checked); - $(actionCheckboxes).each(function() { - if ($.data(this) === $.data(lastChecked) || $.data(this) === $.data(target)) { - inrange = (inrange) ? false : true; - } - if (inrange) { - $(this).prop("checked", target.checked) - .parent().parent().toggleClass(options.selectedClass, target.checked); - } - }); - } - $(target).parent().parent().toggleClass(options.selectedClass, target.checked); - lastChecked = target; - updateCounter(); + + document.querySelectorAll(options.acrossClears + " a").forEach(function(el) { + el.addEventListener('click', function(event) { + event.preventDefault(); + document.getElementById(options.allToggleId).checked = false; + clearAcross(options); + checker(actionCheckboxes, options, false); + updateCounter(actionCheckboxes, options); + }); }); - $('form#changelist-form table#result_list tr').on('change', 'td:gt(0) :input', function() { - list_editable_changed = true; + + Array.from(document.getElementById('result_list').tBodies).forEach(function(el) { + el.addEventListener('change', function(event) { + const target = event.target; + if (target.classList.contains('action-select')) { + target.closest('tr').classList.toggle(options.selectedClass, target.checked); + updateCounter(actionCheckboxes, options); + } else { + list_editable_changed = true; + } + }); }); - $('form#changelist-form button[name="index"]').on('click', function(event) { + + document.querySelector('#changelist-form button[name=index]').addEventListener('click', function() { if (list_editable_changed) { - return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost.")); + const confirmed = confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost.")); + if (!confirmed) { + event.preventDefault(); + } } }); - $('form#changelist-form input[name="_save"]').on('click', function(event) { - let action_changed = false; - $('select option:selected', options.actionContainer).each(function() { - if ($(this).val()) { - action_changed = true; + + const el = document.querySelector('#changelist-form input[name=_save]'); + // The button does not exist if no fields are editable. + if (el) { + el.addEventListener('click', function(event) { + if (document.querySelector('[name=action]').value) { + const text = list_editable_changed + ? gettext("You have selected an action, but you haven’t saved your changes to individual fields yet. Please click OK to save. You’ll need to re-run the action.") + : gettext("You have selected an action, and you haven’t made any changes on individual fields. You’re probably looking for the Go button rather than the Save button."); + if (!confirm(text)) { + event.preventDefault(); + } } }); - if (action_changed) { - if (list_editable_changed) { - return confirm(gettext("You have selected an action, but you haven’t saved your changes to individual fields yet. Please click OK to save. You’ll need to re-run the action.")); - } else { - return confirm(gettext("You have selected an action, and you haven’t made any changes on individual fields. You’re probably looking for the Go button rather than the Save button.")); - } - } - }); - }; - /* Setup plugin defaults */ - $.fn.actions.defaults = { - actionContainer: "div.actions", - counterContainer: "span.action-counter", - allContainer: "div.actions span.all", - acrossInput: "div.actions input.select-across", - acrossQuestions: "div.actions span.question", - acrossClears: "div.actions span.clear", - allToggle: "#action-toggle", - selectedClass: "selected" + } }; - $(document).ready(function() { - const $actionsEls = $('tr input.action-select'); - if ($actionsEls.length > 0) { - $actionsEls.actions(); + + // Call function fn when the DOM is loaded and ready. If it is already + // loaded, call the function now. + // http://youmightnotneedjquery.com/#ready + function ready(fn) { + if (document.readyState !== 'loading') { + fn(); + } else { + document.addEventListener('DOMContentLoaded', fn); + } + } + + ready(function() { + const actionsEls = document.querySelectorAll('tr input.action-select'); + if (actionsEls.length > 0) { + Actions(actionsEls); } }); } diff --git a/django/contrib/admin/templates/admin/actions.html b/django/contrib/admin/templates/admin/actions.html index b912d37396..cbb25bfbd2 100644 --- a/django/contrib/admin/templates/admin/actions.html +++ b/django/contrib/admin/templates/admin/actions.html @@ -11,11 +11,11 @@ {% if actions_selection_counter %} <span class="action-counter" data-actions-icnt="{{ cl.result_list|length }}">{{ selection_note }}</span> {% if cl.result_count != cl.result_list|length %} - <span class="all">{{ selection_note_all }}</span> - <span class="question"> + <span class="all hidden">{{ selection_note_all }}</span> + <span class="question hidden"> <a href="#" title="{% translate "Click here to select the objects across all pages" %}">{% blocktranslate with cl.result_count as total_count %}Select all {{ total_count }} {{ module_name }}{% endblocktranslate %}</a> </span> - <span class="clear"><a href="#">{% translate "Clear selection" %}</a></span> + <span class="clear hidden"><a href="#">{% translate "Clear selection" %}</a></span> {% endif %} {% endif %} {% endblock %} diff --git a/js_tests/admin/actions.test.js b/js_tests/admin/actions.test.js index b2eb3c25d8..0077dd6ff3 100644 --- a/js_tests/admin/actions.test.js +++ b/js_tests/admin/actions.test.js @@ -11,7 +11,7 @@ QUnit.module('admin.actions', { const $ = django.jQuery; $('#qunit-fixture').append($('#result-table').text()); - $('tr input.action-select').actions(); + Actions(document.querySelectorAll('tr input.action-select')); } }); diff --git a/js_tests/tests.html b/js_tests/tests.html index c247abc755..72a6eb4fcb 100644 --- a/js_tests/tests.html +++ b/js_tests/tests.html @@ -11,18 +11,23 @@ <div id="qunit-fixture"> </div> <script type="text/html" id="result-table"> - <table id="result_list"> - <tr> - <th> - <input type="checkbox" id="action-toggle"> - </th> - </tr> - <tr> - <td class="action-checkbox"> - <input class="action-select" type="checkbox" value="618"> - </td> - </tr> - </table> + <form id="changelist-form"> + <button type="submit" class="button" name="index" value="0">Go</button> + <span class="action-counter" data-actions-icnt="100"></span> + <table id="result_list"> + <tr> + <th> + <input type="checkbox" id="action-toggle"> + </th> + </tr> + <tr> + <td class="action-checkbox"> + <input class="action-select" type="checkbox" value="618"> + </td> + </tr> + </table> + <input type="submit" name="_save" value="Save"> + </form> </script> <script type="text/html" id="tabular-formset"> <input id="id_first-TOTAL_FORMS" value="1"> |
