Browse Source

Fixed #31523 -- Removed jQuery dependency from actions.js.

Jon Dufresne 4 years ago
parent
commit
30e59705fc

+ 4 - 0
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 {

+ 0 - 1
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 {

+ 0 - 4
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 {

+ 151 - 135
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);
         }
     });
 }

+ 3 - 3
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 %}

+ 1 - 1
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'));
     }
 });
 

+ 17 - 12
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">