Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Simplified the admin changelist multi-sort interface specifically by …

…removing the popup window, adding explicit tooltip help texts, improving the hover visual states and allowing all operations (i.e. removing a column from sorting and toggling the sorting with and without changing the sorting priority) to be actionable with just one click. Many thanks to Idan Gazit for the feedback and direction. Refs #16212.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16899 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit f2ed107b079b050950e2fc5f2b689ca553ae12f5 1 parent b1c3174
Julien Phalip jphalip authored
89 django/contrib/admin/static/admin/css/base.css
View
@@ -309,77 +309,84 @@ tr.alt {
/* SORTABLE TABLES */
+thead th {
+ padding: 0;
+ line-height: normal;
+}
+
thead th a:link, thead th a:visited {
color: #666;
- display: block;
}
thead th.sorted {
background: #c5c5c5 url(../img/nav-bg-selected.gif) top left repeat-x;
}
-table thead th.sorted a {
- padding-right: 13px;
+table thead th .text span {
+ padding: 2px 5px;
+ display:block;
}
-table thead th.ascending a {
- background: url(../img/arrow-up.gif) right .4em no-repeat;
+table thead th .text a {
+ display: block;
+ cursor: pointer;
+ padding: 2px 5px;
}
-table thead th.descending a {
- background: url(../img/arrow-down.gif) right .4em no-repeat;
+table thead th.sortable:hover {
+ background: white url(../img/nav-bg-reverse.gif) 0 -5px repeat-x;
}
-table thead th.sorted a span.text {
- display: block;
- float: left;
- cursor: pointer; /* IE needs this */
+thead th.sorted a.sortremove {
+ visibility: hidden;
}
-table thead th.sorted a span.sortpos {
- display: block;
- float: right;
- font-size: .6em;
- text-align: right;
- cursor: pointer; /* IE needs this */
+table thead th.sorted:hover a.sortremove {
+ visibility: visible;
}
-table thead th.sorted a img {
- vertical-align: top;
+table thead th.sorted .sortoptions {
+ display: block;
+ padding: 4px 5px 0 5px;
+ float: right;
+ text-align: right;
}
-table thead th.sorted a span.clear {
- display: block;
- clear: both;
+table thead th.sorted .sortpriority {
+ font-size: .8em;
+ min-width: 12px;
+ text-align: center;
+ vertical-align: top;
}
-#sorting-popup-div {
- display: none;
- position: absolute;
- background-color: white;
- border: 1px solid #ddd;
- z-index: 2000; /* more than filters on right */
+table thead th.sorted .sortoptions a {
+ width: 14px;
+ height: 12px;
+ display: inline-block;
}
-#sorting-popup-div table {
- border-right: 0px;
- border-left: 0px;
+table thead th.sorted .sortoptions a.sortremove {
+ background: url(../img/sorting-icons.gif) -4px -5px no-repeat;
}
-#sorting-popup-div .reset {
- text-align: center;
+table thead th.sorted .sortoptions a.sortremove:hover {
+ background: url(../img/sorting-icons.gif) -4px -27px no-repeat;
}
-#sorting-popup-div .cancel {
- font-size: 10px;
- background: #e1e1e1 url(../img/nav-bg.gif) 0 50% repeat-x;
- border-top: 1px solid #ddd;
- text-align: center;
+table thead th.sorted .sortoptions a.ascending {
+ background: url(../img/sorting-icons.gif) -5px -50px no-repeat;
}
-#sorting-popup-div .cancel a {
- width: 100%;
- display: block;
+table thead th.sorted .sortoptions a.ascending:hover {
+ background: url(../img/sorting-icons.gif) -5px -72px no-repeat;
+}
+
+table thead th.sorted .sortoptions a.descending {
+ background: url(../img/sorting-icons.gif) -5px -94px no-repeat;
+}
+
+table thead th.sorted .sortoptions a.descending:hover {
+ background: url(../img/sorting-icons.gif) -5px -115px no-repeat;
}
/* ORDERABLE TABLES */
17 django/contrib/admin/static/admin/css/rtl.css
View
@@ -80,22 +80,7 @@ div.breadcrumbs {
/* SORTABLE TABLES */
-
-table thead th.sorted a {
- padding-left: 13px;
- padding-right: 0px;
-}
-
-table thead th.ascending a,
-table thead th.descending a {
- background-position: left;
-}
-
-table thead th.sorted a span.text {
- float: right;
-}
-
-table thead th.sorted a span.sortpos {
+table thead th.sorted .sortoptions {
float: left;
}
BIN  django/contrib/admin/static/admin/img/arrow-down.gif
View
Deleted file not rendered
BIN  django/contrib/admin/static/admin/img/arrow-up.gif
View
Deleted file not rendered
BIN  django/contrib/admin/static/admin/img/icon_cog.gif
View
Deleted file not rendered
BIN  django/contrib/admin/static/admin/img/sorting-icons.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 django/contrib/admin/templates/admin/change_list_results.html
View
@@ -11,15 +11,17 @@
<tr>
{% for header in result_headers %}
<th scope="col" {{ header.class_attrib }}>
- {% if header.sortable %}<a href="{{ header.url_primary }}">{% endif %}
- <span class="text">{{ header.text|capfirst }}</span>
- {% if header.sortable %}
- {% if header.sort_pos > 0 %}<span class="sortpos">
- {% if header.sort_pos == 1 %}<img id="primary-sort-icon" src="{% static "admin/img/icon_cog.gif" %}" alt="" />&nbsp;{% endif %}
- {{ header.sort_pos }}</span>
- {% endif %}
- <span class="clear"></span></a>
- {% endif %}
+ {% if header.sortable %}
+ {% if header.sort_priority > 0 %}
+ <div class="sortoptions">
+ <a class="sortremove" href="{{ header.url_remove }}" title="{% trans "Remove from sorting" %}"></a>
+ {% if num_sorted_fields > 1 %}<span class="sortpriority" title="{% blocktrans with priority_number=header.sort_priority %}Sorting priority: {{ priority_number }}{% endblocktrans %}">{{ header.sort_priority }}</span>{% endif %}
+ <a href="{{ header.url_toggle }}" class="toggle {% if header.ascending %}ascending{% else %}descending{% endif %}" title="{% trans "Toggle sorting" %}"></a>
+ </div>
+ {% endif %}
+ {% endif %}
+ <div class="text">{% if header.sortable %}<a href="{{ header.url_primary }}">{{ header.text|capfirst }}</a>{% else %}<span>{{ header.text|capfirst }}</span>{% endif %}</div>
+ <div class="clear"></div>
</th>{% endfor %}
</tr>
</thead>
@@ -33,90 +35,4 @@
</tbody>
</table>
</div>
-
-{# Sorting popup: #}
-<div id="sorting-popup-div">
-<table>
- <caption>
- {% trans "Sorting by:" %}
- </caption>
- <tbody>
- {% for header in result_headers|dictsort:"sort_pos" %}
- {% if header.sort_pos > 0 %}
- <tr>
- <td>{{ header.sort_pos }}</td>
- <td>{{ header.text|capfirst }}</td>
- <td>{% if header.ascending %}{% trans "ascending" %}{% else %}{% trans "descending" %}{% endif %}</td>
- <td><a href="{{ header.url_toggle }}">{% trans "toggle" %}</a></td>
- <td><a href="{{ header.url_remove }}">{% trans "remove" %}</a></td>
- </tr>
- {% endif %}
- {% endfor %}
- </tbody>
-</table>
-<div class="reset"><a href="{{ reset_sorting_url }}">{% trans "Reset sorting" %}</a></div>
-<div class="cancel"><a href="javascript:void" id="sorting-popup-dismiss">{% trans "Cancel" %}</a></div>
-</div>
-<script type="text/javascript">
-<!--
-(function($) {
- $(document).ready(function() {
- var popup = $('#sorting-popup-div');
- var img = $('#primary-sort-icon');
- /* These next lines seems necessary to prime the popup: */
- popup.offset({left:0, top:-1000});
- popup.show();
- if ($.browser.msie) {
- // Can't find a way to make IE autosize the div.
- popup.width(300);
- }
- var popupWidth = popup.width();
- popup.hide();
-
- var visible = false;
-
- var escHandler = function(ev) {
- if (ev.which == 27) {
- hidePopup();
- ev.preventDefault();
- }
- };
-
- var showPopup = function() {
- var pos = img.offset();
- pos.top += img.height();
- if (pos.left + popupWidth >
- $(window).width()) {
- pos.left -= popupWidth;
- }
- popup.show();
- popup.offset(pos);
- visible = true;
- $(document).bind('keyup', escHandler);
- };
-
- var hidePopup = function() {
- popup.hide();
- visible = false;
- $(document).unbind('keyup', escHandler);
- };
-
- $('#primary-sort-icon').click(function(ev) {
- ev.preventDefault();
- if (visible) {
- hidePopup();
- } else {
- showPopup();
- }
- });
-
- $('#sorting-popup-dismiss').click(function(ev) {
- hidePopup();
- ev.preventDefault()
- });
- });
-})(django.jQuery);
-//-->
-</script>
-
{% endif %}
27 django/contrib/admin/templatetags/admin_list.py
View
@@ -83,7 +83,6 @@ def result_headers(cl):
"""
ordering_field_columns = cl.get_ordering_field_columns()
for i, field_name in enumerate(cl.list_display):
- admin_order_field = None
text, attr = label_for_field(field_name, cl.model,
model_admin = cl.model_admin,
return_attr = True
@@ -95,25 +94,31 @@ def result_headers(cl):
if field_name == 'action_checkbox':
yield {
"text": text,
- "class_attrib": mark_safe(' class="action-checkbox-column"')
+ "class_attrib": mark_safe(' class="action-checkbox-column"'),
+ "sortable": False,
}
continue
admin_order_field = getattr(attr, "admin_order_field", None)
if not admin_order_field:
# Not sortable
- yield {"text": text}
+ yield {
+ "text": text,
+ "sortable": False,
+ }
continue
# OK, it is sortable if we got this far
- th_classes = []
+ th_classes = ['sortable']
order_type = ''
new_order_type = 'asc'
- sort_pos = 0
+ sort_priority = 0
+ sorted = False
# Is it currently being sorted on?
if i in ordering_field_columns:
+ sorted = True
order_type = ordering_field_columns.get(i).lower()
- sort_pos = ordering_field_columns.keys().index(i) + 1
+ sort_priority = ordering_field_columns.keys().index(i) + 1
th_classes.append('sorted %sending' % order_type)
new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type]
@@ -144,8 +149,9 @@ def result_headers(cl):
yield {
"text": text,
"sortable": True,
+ "sorted": sorted,
"ascending": order_type == "asc",
- "sort_pos": sort_pos,
+ "sort_priority": sort_priority,
"url_primary": cl.get_query_string({ORDER_VAR: '.'.join(o_list_primary)}),
"url_remove": cl.get_query_string({ORDER_VAR: '.'.join(o_list_remove)}),
"url_toggle": cl.get_query_string({ORDER_VAR: '.'.join(o_list_toggle)}),
@@ -260,13 +266,14 @@ def result_list(cl):
Displays the headers and data list together
"""
headers = list(result_headers(cl))
+ num_sorted_fields = 0
for h in headers:
- # Sorting in templates depends on sort_pos attribute
- h.setdefault('sort_pos', 0)
+ if h['sortable'] and h['sorted']:
+ num_sorted_fields += 1
return {'cl': cl,
'result_hidden_fields': list(result_hidden_fields(cl)),
'result_headers': headers,
- 'reset_sorting_url': cl.get_query_string(remove=[ORDER_VAR]),
+ 'num_sorted_fields': num_sorted_fields,
'results': list(results(cl))}
@register.inclusion_tag('admin/date_hierarchy.html')
13 docs/releases/1.4.txt
View
@@ -360,13 +360,16 @@ Removed admin icons
~~~~~~~~~~~~~~~~~~~
As part of an effort to improve the performance and usability of the admin's
-vertical and horizontal "filter" widgets, some icon files were removed and
-grouped into a single sprite file (``selector-icons.gif``):
+changelist sorting interface and of the admin's :attr:`horizontal
+<django.contrib.admin.ModelAdmin.filter_horizontal>` and :attr:`vertical
+<django.contrib.admin.ModelAdmin.filter_vertical>` "filter" widgets, some icon
+files were removed and grouped into two sprite files, respectively:
``selector-add.gif``, ``selector-addall.gif``, ``selector-remove.gif``,
``selector-removeall.gif``, ``selector_stacked-add.gif`` and
-``selector_stacked-remove.gif``. If you used those icons to customize the
-admin then you will want to replace them with your own icons or retrieve them
-from a previous release.
+``selector_stacked-remove.gif`` into ``selector-icons.gif``; and
+``arrow-up.gif`` and ``arrow-down.gif`` into ``sorting-icons.gif``. If you used
+those icons to customize the admin then you will want to replace them with your
+own icons or retrieve them from a previous release.
Compatibility with old signed data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Please sign in to comment.
Something went wrong with that request. Please try again.