Browse Source

[feat] Add bulk actions in page listing

Added following
- Add bulk action checkboxes (non functional), actions, filters in page listing (non functional)
- Add search bar in page listing (non functional)
Shohan 4 năm trước cách đây
mục cha
commit
64a1d7299f

+ 59 - 2
client/scss/components/_listing.scss

@@ -82,6 +82,50 @@ ul.listing {
 
     .table-headers {
         border-bottom: 1px solid  $color-grey-4;
+
+        .bulk-actions-filter-checkbox {
+            > div {
+                display: flex;
+                align-items: center;
+            }
+
+            .c-dropdown__button {
+                border: 0;
+                padding-left: 0.3rem;
+            }
+
+            .bulk-actions-choices,
+            .bulk-actions-choices ul {
+                display: flex;
+                align-items: center;
+            }
+
+            .bulk-actions-choices li {
+                margin: 0 0.5em;
+            }
+
+            .bulk-actions-choices span {
+                text-transform: none;
+            }
+        }
+
+        .search {
+            padding-left: 0;
+
+            .nav-search {
+                padding: 0;
+                width: max(50%, 250px);
+            }
+
+            input {
+                background-color: #fff;
+                border: 0;
+            }
+
+            button {
+                color: $color-header-bg;
+            }
+        }
     }
 
     tbody {
@@ -661,6 +705,15 @@ table.listing {
             visibility: visible;
         }
 
+        .bulk-action-checkbox {
+            opacity: 0;
+
+            &.show,
+            &:checked {
+                opacity: 1;
+            }
+        }
+
         .no-children {
             border-color: transparent;
 
@@ -669,8 +722,12 @@ table.listing {
             }
         }
 
-        tr:hover .no-children a {
-            opacity: 1;
+        tr:hover {
+            .no-children a,
+            .bulk-action-checkbox {
+                opacity: 1;
+            }
+
         }
 
         tr:hover .children {

+ 56 - 0
client/src/entrypoints/admin/bulk-actions.js

@@ -0,0 +1,56 @@
+const BULK_ACTION_CHECKBOX_CLASS = 'bulk-action-checkbox';
+const BULK_ACTION_CHECKBOX_FILTER_CLASS = 'bulk-actions-filter-checkbox';
+const BULK_ACTION_CHOICES_CLASS = 'bulk-actions-choices';
+const TABLE_HEADERS_CLASS = 'table-headers';
+
+const checkedState = {
+  checkedPages: new Set(),
+};
+
+function SelectBulkActionsFilter(e) {
+  const changeEvent = new Event('change');
+  for (const el of document.querySelectorAll(`.${BULK_ACTION_CHECKBOX_CLASS}`)) {
+    if (el.checked === e.target.checked) continue;
+    el.checked = e.target.checked;
+    el.dispatchEvent(changeEvent);
+  }
+}
+
+function SelectBulkActionsCheckboxes(e) {
+  const prevLength = checkedState.checkedPages.size;
+  if (e.target.checked) checkedState.checkedPages.add(+e.target.dataset.pageId);
+  else {
+    // unchecks `select all` checkbox as soon as one page is unchecked
+    document.querySelector(`.${BULK_ACTION_CHECKBOX_FILTER_CLASS} input`).checked = false;
+    checkedState.checkedPages.delete(+e.target.dataset.pageId);
+  }
+
+  if (checkedState.checkedPages.size === 0) {
+    // all checboxes are unchecked
+    document.querySelectorAll(`.${TABLE_HEADERS_CLASS} > th`).forEach(el => el.classList.remove('u-hidden'));
+    document.querySelector(`.${BULK_ACTION_CHOICES_CLASS}`).classList.add('u-hidden');
+    document.querySelectorAll(`.${BULK_ACTION_CHECKBOX_CLASS}`).forEach(el => el.classList.remove('show'));
+    document.querySelector(`.${BULK_ACTION_CHECKBOX_FILTER_CLASS}`).setAttribute('colspan', '1');
+  } else if (checkedState.checkedPages.size === document.querySelectorAll(`.${BULK_ACTION_CHECKBOX_CLASS}`).length) {
+    // all checkboxes are checked
+    document.querySelector(`.${BULK_ACTION_CHECKBOX_FILTER_CLASS} input`).checked = true;
+  } else if (checkedState.checkedPages.size === 1 && prevLength === 0) {
+    // 1 checkbox is checked for the first time
+    document.querySelectorAll(`.${BULK_ACTION_CHECKBOX_CLASS}`).forEach(el => {
+      el.classList.remove('show');
+      el.classList.add('show');
+    });
+    document.querySelectorAll(`.${TABLE_HEADERS_CLASS} > th`).forEach(el => el.classList.add('u-hidden'));
+    document.querySelector(`.${BULK_ACTION_CHECKBOX_FILTER_CLASS}`).classList.remove('u-hidden');
+    document.querySelector(`.${BULK_ACTION_CHOICES_CLASS}`).classList.remove('u-hidden');
+    document.querySelector(`.${BULK_ACTION_CHECKBOX_FILTER_CLASS}`).setAttribute('colspan', '6');
+  }
+}
+
+function AddBulkActionCheckboxEventListeners() {
+  document.querySelectorAll(`.${BULK_ACTION_CHECKBOX_CLASS}`)
+    .forEach(el => el.addEventListener('change', SelectBulkActionsCheckboxes));
+  document.querySelector(`.${BULK_ACTION_CHECKBOX_FILTER_CLASS}`).addEventListener('change', SelectBulkActionsFilter);
+}
+
+window.AddBulkActionCheckboxEventListeners = AddBulkActionCheckboxEventListeners;

+ 1 - 0
client/webpack.config.js

@@ -56,6 +56,7 @@ module.exports = function exports() {
       'wagtailadmin',
       'workflow-action',
       'workflow-status',
+      'bulk-actions'
     ],
     'images': [
       'image-chooser',

+ 9 - 6
wagtail/admin/templates/wagtailadmin/pages/listing/_list.html

@@ -2,9 +2,7 @@
 {% load l10n %}
 {% load wagtailadmin_tags %}
 <table class="listing {% if full_width %}full-width{% endif %} {% block table_classname %}{% endblock %}">
-    {% if show_ordering_column %}
-        <col width="65px" />
-    {% endif %}
+    <col width="10px" />
     <col />
     {% if show_parent %}
         <col />
@@ -47,9 +45,9 @@
             {% for page in pages %}
                 {% page_permissions page as page_perms %}
                 <tr {% if ordering == "ord" %}id="page_{{ page.id|unlocalize }}" data-page-title="{{ page.get_admin_display_title }}"{% endif %} class="{% if not page.live %}unpublished{% endif %} {% block page_row_classname %}{% endblock %}">
-                    {% if show_ordering_column %}
-                        <td class="ord">{% if orderable and ordering == "ord" %}<div class="handle icon icon-grip text-replace">{% trans 'Drag' %}</div>{% endif %}</td>
-                    {% endif %}
+                    <td>
+                        <input data-page-id="{{page.id}}" class="bulk-action-checkbox" aria-label="Page select checkbox" type="checkbox" />
+                    </td>
                     <td class="title" valign="top" data-listing-page-title>
                         {% block page_title %}
                         {% endblock %}
@@ -79,3 +77,8 @@
         {% endif %}
     </tbody>
 </table>
+
+<script src="{% versioned_static 'wagtailadmin/js/bulk-actions.js' %}"></script>
+<script>
+    window.AddBulkActionCheckboxEventListeners()
+</script>

+ 17 - 20
wagtail/admin/templates/wagtailadmin/pages/listing/_table_headers_explore.html

@@ -17,24 +17,22 @@ ordering: the current sort parameter
 {% endcomment %}
 
 <tr class="table-headers">
-    {% if show_ordering_column %}
-        <th class="ord{% if orderable and ordering == 'ord' %} ord--active{% endif %}">
-            {% if orderable %}
-                {% if ordering == "ord" %}
-                    <a href="{% url 'wagtailadmin_explore' parent_page.id %}" title="{% trans 'Disable ordering of child pages' %}">
-                        {% icon name="order" %}{% trans 'Sort' %}
-                    </a>
-                {% else %}
-                    <a href="{% url 'wagtailadmin_explore' parent_page.id %}?ordering=ord" title="{% trans 'Enable ordering of child pages' %}">
-                        {% icon name="order" %}{% trans 'Sort' %}
-                    </a>
-                {% endif %}
-            {% endif %}
-        </th>
-    {% endif %}
-    <th class="title">
-        {% trans 'Title' as title_label %}
-        {% page_table_header_label label=title_label sortable=sortable sort_field='title' parent_page_title=parent_page.title %}
+    <th class="bulk-actions-filter-checkbox">
+        <div>
+            <input type="checkbox" aria-label="Bulk action checkbox" />
+            {% bulk_action_filters %}
+            <div class="bulk-actions-choices u-hidden">
+                <span>All 8 on this page selected. Select all 43 in listing |</span>
+                <ul>{% bulk_action_choices %}</ul>
+            </div>
+        </div>
+    </th>
+    <th class="search">
+        <div class="nav-search">
+            <button class="button" type="submit">Search</button>
+            <label for="page-search-q">Search</label>
+            <input aria-label="Search text" type="text" id="page-search" name="q" placeholder="Search">
+        </div>
     </th>
     {% if show_parent %}
         <th class="parent">
@@ -55,9 +53,8 @@ ordering: the current sort parameter
             {% page_table_header_label label=type_label sortable=0 parent_page_title=parent_page.title %}
         {% endif %}
     </th>
-    <th class="status">
+    <th class="status" colspan="2">
         {% trans 'Status' as status_label %}
         {% page_table_header_label label=status_label sortable=sortable sort_field='live' parent_page_title=parent_page.title %}
     </th>
-    <th></th>
 </tr>

+ 28 - 0
wagtail/admin/templatetags/wagtailadmin_tags.py

@@ -486,6 +486,34 @@ def page_listing_buttons(context, page, page_perms, is_parent=False):
     return {'page': page, 'buttons': buttons}
 
 
+@register.inclusion_tag("wagtailadmin/pages/listing/_button_with_dropdown.html",
+                        takes_context=True)
+def bulk_action_filters(context):
+    button_hooks = hooks.get_hooks('register_bulk_action_filters')
+
+    buttons = []
+    for hook in button_hooks:
+        buttons.extend(hook())
+
+    buttons.sort()
+
+    return {'buttons': buttons}
+
+
+@register.inclusion_tag("wagtailadmin/pages/listing/_buttons.html",
+                        takes_context=True)
+def bulk_action_choices(context):
+    button_hooks = hooks.get_hooks('register_bulk_action_choices')
+
+    buttons = []
+    for hook in button_hooks:
+        buttons.extend(hook())
+
+    buttons.sort()
+
+    return {'buttons': buttons}
+
+
 @register.simple_tag
 def message_tags(message):
     level_tag = MESSAGE_TAGS.get(message.level)

+ 52 - 0
wagtail/admin/wagtail_hooks.py

@@ -160,6 +160,58 @@ def register_workflow_tasks_menu_item():
     return WorkflowTasksMenuItem(_('Workflow tasks'), reverse('wagtailadmin_workflows:task_index'), icon_name='thumbtack', order=150)
 
 
+@hooks.register('register_bulk_action_filters')
+def bulk_action_filters():
+    yield Button(
+            _('All'),
+            '',
+            attrs={'aria-label': _("All pages")},
+            priority=10
+        )
+
+    yield Button(
+            _('Status: Draft'),
+            '',
+            attrs={'aria-label': _("Draft pages")},
+            priority=10
+        )
+
+    yield Button(
+            _('Status: Live'),
+            '',
+            attrs={'aria-label': _("Live pages")},
+            priority=10
+        )
+
+
+@hooks.register('register_bulk_action_choices')
+def bulk_action_choices(is_parent=False, next_url=None):
+    yield PageListingButton(
+        _('Move'),
+        '',
+        attrs={'aria-label': _("Move pages")},
+        priority=10
+    )
+    yield PageListingButton(
+        _('Publish'),
+        '',
+        attrs={'aria-label': _("Publish pages")},
+        priority=20
+    )
+    yield PageListingButton(
+        _('Unpublish'),
+        '',
+        attrs={'aria-label': _("Unpublish pages")},
+        priority=30
+    )
+    yield PageListingButton(
+        _('Delete'),
+        '',
+        attrs={'aria-label': _("Delete pages")},
+        priority=40
+    )
+
+
 @hooks.register('register_page_listing_buttons')
 def page_listing_buttons(page, page_perms, is_parent=False, next_url=None):
     if page_perms.can_edit():