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
This commit is contained in:
Julien Phalip 2011-09-24 06:17:53 +00:00
parent b1c3174cfa
commit f2ed107b07
9 changed files with 104 additions and 186 deletions

View File

@ -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.descending a {
background: url(../img/arrow-down.gif) right .4em no-repeat;
}
table thead th.sorted a span.text {
display: block;
float: left;
cursor: pointer; /* IE needs this */
}
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 a img {
vertical-align: top;
}
table thead th.sorted a span.clear {
display: block;
clear: both;
}
#sorting-popup-div {
display: none;
position: absolute;
background-color: white;
border: 1px solid #ddd;
z-index: 2000; /* more than filters on right */
}
#sorting-popup-div table {
border-right: 0px;
border-left: 0px;
}
#sorting-popup-div .reset {
text-align: center;
}
#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;
}
#sorting-popup-div .cancel a {
width: 100%;
table thead th .text a {
display: block;
cursor: pointer;
padding: 2px 5px;
}
table thead th.sortable:hover {
background: white url(../img/nav-bg-reverse.gif) 0 -5px repeat-x;
}
thead th.sorted a.sortremove {
visibility: hidden;
}
table thead th.sorted:hover a.sortremove {
visibility: visible;
}
table thead th.sorted .sortoptions {
display: block;
padding: 4px 5px 0 5px;
float: right;
text-align: right;
}
table thead th.sorted .sortpriority {
font-size: .8em;
min-width: 12px;
text-align: center;
vertical-align: top;
}
table thead th.sorted .sortoptions a {
width: 14px;
height: 12px;
display: inline-block;
}
table thead th.sorted .sortoptions a.sortremove {
background: url(../img/sorting-icons.gif) -4px -5px no-repeat;
}
table thead th.sorted .sortoptions a.sortremove:hover {
background: url(../img/sorting-icons.gif) -4px -27px no-repeat;
}
table thead th.sorted .sortoptions a.ascending {
background: url(../img/sorting-icons.gif) -5px -50px no-repeat;
}
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 */

View File

@ -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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

View File

@ -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 %}

View File

@ -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')

View File

@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~