Commit 08566087 by Kálmán Viktor

Merge branch 'feature-table-sorting' into 'master'

Feature Table Sorting

Sort vm list, template list and lease list.

vm list and template list are sorted with js if it's enabled
parents b55ab2d8 5d820c6e
......@@ -321,7 +321,7 @@ a.hover-black {
}
.template-list-table-thin {
width: 95px;
width: 100px;
}
......@@ -491,3 +491,11 @@ footer a, footer a:hover, footer a:visited {
bottom: 10px;
right: 20px;
}
.vm-list-table th i {
margin-top: 3px;
}
.table-sorting {
display: none;
}
// Stupid jQuery table plugin.
/*
* Source: https://github.com/joequery/Stupid-Table-Plugin
* The Stupid jQuery Plugin is licensed under the MIT license.
* Copyright (c) 2012 Joseph McCullough
*/
// Call on a table
// sortFns: Sort functions for your datatypes.
(function($) {
$.fn.stupidtable = function(sortFns) {
return this.each(function() {
var $table = $(this);
sortFns = sortFns || {};
// Merge sort functions with some default sort functions.
sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns);
// ==================================================== //
// Begin execution! //
// ==================================================== //
// Do sorting when THs are clicked
$table.on("click.stupidtable", "th", function() {
var $this = $(this);
var th_index = 0;
var dir = $.fn.stupidtable.dir;
$table.find("th").slice(0, $this.index()).each(function() {
var cols = $(this).attr("colspan") || 1;
th_index += parseInt(cols,10);
});
// Determine (and/or reverse) sorting direction, default `asc`
var sort_dir = $this.data("sort-default") || dir.ASC;
if ($this.data("sort-dir"))
sort_dir = $this.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
// Choose appropriate sorting function.
var type = $this.data("sort") || null;
// Prevent sorting if no type defined
if (type === null) {
return;
}
// Trigger `beforetablesort` event that calling scripts can hook into;
// pass parameters for sorted column index and sorting direction
$table.trigger("beforetablesort", {column: th_index, direction: sort_dir});
// More reliable method of forcing a redraw
$table.css("display");
// Run sorting asynchronously on a timout to force browser redraw after
// `beforetablesort` callback. Also avoids locking up the browser too much.
setTimeout(function() {
// Gather the elements for this column
var column = [];
var sortMethod = sortFns[type];
var trs = $table.children("tbody").children("tr");
// Extract the data for the column that needs to be sorted and pair it up
// with the TR itself into a tuple
trs.each(function(index,tr) {
var $e = $(tr).children().eq(th_index);
var sort_val = $e.data("sort-value");
var order_by = typeof(sort_val) !== "undefined" ? sort_val : $e.text();
column.push([order_by, tr]);
});
// Sort by the data-order-by value
column.sort(function(a, b) { return sortMethod(a[0], b[0]); });
if (sort_dir != dir.ASC)
column.reverse();
// Replace the content of tbody with the sorted rows. Strangely (and
// conveniently!) enough, .append accomplishes this for us.
trs = $.map(column, function(kv) { return kv[1]; });
$table.children("tbody").append(trs);
// Reset siblings
$table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
$this.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
// Trigger `aftertablesort` event. Similar to `beforetablesort`
$table.trigger("aftertablesort", {column: th_index, direction: sort_dir});
// More reliable method of forcing a redraw
$table.css("display");
}, 10);
});
});
};
// Enum containing sorting directions
$.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"};
$.fn.stupidtable.default_sort_fns = {
"int": function(a, b) {
return parseInt(a, 10) - parseInt(b, 10);
},
"float": function(a, b) {
return parseFloat(a) - parseFloat(b);
},
"string": function(a, b) {
if (a < b) return -1;
if (a > b) return +1;
return 0;
},
"string-ins": function(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
if (a < b) return -1;
if (a > b) return +1;
return 0;
}
};
})(jQuery);
......@@ -20,6 +20,27 @@ $(function() {
});
return false;
});
/* template table sort */
var ttable = $(".template-list-table").stupidtable();
ttable.on("beforetablesort", function(event, data) {
// pass
});
ttable.on("aftertablesort", function(event, data) {
$(".template-list-table thead th i").remove();
var icon_html = '<i class="icon-sort-' + (data.direction == "desc" ? "up" : "down") + ' pull-right" style="position: absolute;"></i>';
$(".template-list-table thead th").eq(data.column).append(icon_html);
});
// only if js is enabled
$(".template-list-table thead th").css("cursor", "pointer");
$(".template-list-table th a").on("click", function(event) {
event.preventDefault();
});
});
......
......@@ -17,14 +17,14 @@ $(function() {
if (ctrlDown) {
setRowColor($(this));
if(!$(this).hasClass('vm-list-selected')) {
selected.splice(selected.indexOf($(this).index()), 1);
selected.splice(getSelectedIndex($(this).index()), 1);
} else {
selected.push($(this).index());
selected.push({'index': $(this).index(), 'vm': $(this).data("vm-pk")});
}
retval = false;
} else if(shiftDown) {
if(selected.length > 0) {
start = selected[selected.length - 1] + 1;
start = selected[selected.length - 1]['index'] + 1;
end = $(this).index();
if(start > end) {
......@@ -32,8 +32,9 @@ $(function() {
}
for(var i = start; i <= end; i++) {
if(selected.indexOf(i) < 0) {
selected.push(i);
var vm = $(".vm-list-table tbody tr").eq(i).data("vm-pk");
if(!isAlreadySelected(vm)) {
selected.push({'index': i, 'vm': vm});
setRowColor($('.vm-list-table tbody tr').eq(i));
}
}
......@@ -42,13 +43,13 @@ $(function() {
} else {
$('.vm-list-selected').removeClass('vm-list-selected');
$(this).addClass('vm-list-selected');
selected = [$(this).index()];
selected = [{'index': $(this).index(), 'vm': $(this).data("vm-pk")}];
}
// reset btn disables
$('.vm-list-table tbody tr .btn').attr('disabled', false);
// show/hide group controls
if(selected.length > 1) {
if(selected.length > 0) {
$('.vm-list-group-control a').attr('disabled', false);
for(var i = 0; i < selected.length; i++) {
$('.vm-list-table tbody tr').eq(selected[i]).find('.btn').attr('disabled', true);
......@@ -61,7 +62,7 @@ $(function() {
$('#vm-list-group-migrate').click(function() {
console.log(collectIds(selected));
// pass?
});
$('.vm-list-details').popover({
......@@ -131,8 +132,9 @@ $(function() {
$('#vm-list-group-select-all').click(function() {
$('.vm-list-table tbody tr').each(function() {
var index = $(this).index();
if(selected.indexOf(index) < 0) {
selected.push(index);
var vm = $(this).data("vm-pk");
if(!isAlreadySelected(vm)) {
selected.push({'index': index, 'vm': vm});
$(this).addClass('vm-list-selected');
}
});
......@@ -154,13 +156,51 @@ $(function() {
);
return false;
});
/* table sort */
var table = $(".vm-list-table").stupidtable();
table.on("beforetablesort", function(event, data) {
$(".table-sorting").show();
});
table.on("aftertablesort", function(event, data) {
// this didn't work ;;
// var th = $("this").find("th");
$(".table-sorting").hide();
$(".vm-list-table thead th i").remove();
var icon_html = '<i class="icon-sort-' + (data.direction == "desc" ? "up" : "down") + ' pull-right"></i>';
$(".vm-list-table thead th").eq(data.column).append(icon_html);
});
// only if js is enabled
$(".vm-list-table thead th").css("cursor", "pointer");
$(".vm-list-table th a").on("click", function(event) {
event.preventDefault();
});
});
function isAlreadySelected(vm) {
for(var i=0; i<selected.length; i++)
if(selected[i].vm == vm)
return true;
return false;
}
function getSelectedIndex(index) {
for(var i=0; i<selected.length; i++)
if(selected[i].index == index)
return i;
return -1;
}
function collectIds(rows) {
var ids = [];
for(var i = 0; i < rows.length; i++) {
var div = $('td:first-child div', $('.vm-list-table tbody tr').eq(rows[i]));
ids.push(div.prop('id').replace('vm-', ''));
ids.push(rows[i].vm);
}
return ids;
}
......
......@@ -220,21 +220,35 @@ class TemplateListTable(Table):
name = LinkColumn(
'dashboard.views.template-detail',
args=[A('pk')],
attrs={'th': {'data-sort': "string"}}
)
num_cores = Column(
verbose_name=_("Cores"),
attrs={'th': {'data-sort': "int"}}
)
ram_size = TemplateColumn(
"{{ record.ram_size }} Mb",
attrs={'th': {'data-sort': "string"}}
)
lease = TemplateColumn(
"{{ record.lease.name }}",
verbose_name=_("Lease"),
attrs={'th': {'data-sort': "string"}}
)
arch = Column(
attrs={'th': {'data-sort': "string"}}
)
system = Column(
attrs={'th': {'data-sort': "string"}}
)
access_method = Column(
attrs={'th': {'data-sort': "string"}}
)
actions = TemplateColumn(
verbose_name=_("Actions"),
template_name="dashboard/template-list/column-template-actions.html",
attrs={'th': {'class': 'template-list-table-thin'}},
orderable=False,
)
class Meta:
......@@ -242,8 +256,9 @@ class TemplateListTable(Table):
attrs = {'class': ('table table-bordered table-striped table-hover'
' template-list-table')}
fields = ('name', 'num_cores', 'ram_size', 'arch',
'system', 'access_method', 'lease', 'state',
'actions', )
'system', 'access_method', 'lease', 'actions', )
prefix = "template-"
class LeaseListTable(Table):
......@@ -252,21 +267,24 @@ class LeaseListTable(Table):
args=[A('pk')],
)
suspend_in = TemplateColumn(
suspend_interval_seconds = TemplateColumn(
"{{ record.get_readable_suspend_time }}"
)
delete_in = TemplateColumn(
delete_interval_seconds = TemplateColumn(
"{{ record.get_readable_delete_time }}"
)
actions = TemplateColumn(
verbose_name=_("Actions"),
template_name="dashboard/template-list/column-lease-actions.html"
template_name="dashboard/template-list/column-lease-actions.html",
orderable=False,
)
class Meta:
model = Lease
attrs = {'class': ('table table-bordered table-striped table-hover'
' lease-list-table')}
fields = ('name', 'suspend_in', 'delete_in', )
fields = ('name', 'suspend_interval_seconds',
'delete_interval_seconds', )
prefix = "lease-"
......@@ -55,5 +55,6 @@
{% endblock %}
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/template-list.js"></script>
<script src="{{ STATIC_URL}}dashboard/template-list.js"></script>
<script src="{{ STATIC_URL}}dashboard/js/stupidtable.min.js"></script>
{% endblock %}
......@@ -9,6 +9,10 @@
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right table-sorting">
{% trans "Sorting ... " %}
<!--<i class="icon-refresh icon-spin icon-2x"></i>-->
</div>
<h3 class="no-margin"><i class="icon-desktop"></i> {% trans "Virtual machines" %}</h3>
</div>
<div class="pull-right" style="max-width: 250px; margin-top: 15px; margin-right: 15px;">
......@@ -32,22 +36,39 @@
<div class="panel-body">
<table class="table table-bordered table-striped table-hover vm-list-table">
<thead><tr>
<th class="orderable pk sortable vm-list-table-thin"><a href="?sort=pk">{% trans "ID" %}</a></th>
<th class="name orderable sortable"><a href="?sort=name">{% trans "Name" %}</a></th>
<th>{% trans "State" %}</th>
<th class="orderable sortable"><a href="?sort=owner">{% trans "Owner" %}</a></th>
{% if user.is_superuser %}<th class="orderable sortable"><a href="?sort=node">{% trans "Node" %}</a></th>{% endif %}
<th data-sort="int" class="orderable pk sortable vm-list-table-thin" style="min-width: 50px;">
{% trans "ID" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="pk" %}
</th>
<th data-sort="string" class="name orderable sortable">
{% trans "Name" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="name" %}
</th>
<th data-sort="string">
{% trans "State" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="status" %}
</th>
<th data-sort="string" class="orderable sortable">
{% trans "Owner" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="owner" %}
</th>
{% if user.is_superuser %}<th data-sort="string" class="orderable sortable">
{% trans "Node" as t %}
{% include "dashboard/vm-list/header-link.html" with name=t sort="node" %}
</th>{% endif %}
</tr></thead><tbody>
{% for i in object_list %}
<tr class="{% cycle 'odd' 'even' %}">
<tr class="{% cycle 'odd' 'even' %}" data-vm-pk="{{ i.pk }}">
<td class="pk"><div id="vm-{{i.pk}}">{{i.pk}}</div> </td>
<td class="name"><a class="real-link" href="{% url "dashboard.views.detail" i.pk %}">{{ i.name }}</a> </td>
<td class="state">{{ i.get_status_display }}</td>
<td>{{ i.owner }}</td>
{% if user.is_superuser %}<td>{{ i.node.name|default:"-" }}</td>{% endif %}
{% if user.is_superuser %}
<td data-sort-value="{{ i.node.normalized_name }}">{{ i.node.name|default:"-" }}</td>
{% endif %}
</tr>
{% empty %}
<tr><td colspan="4">{% trans "You have no virtual machines." %}</td></tr>
<tr><td colspan="5"><strong>{% trans "You have no virtual machines." %}</strong></td></tr>
{% endfor %}
</tbody>
</table>
......@@ -92,4 +113,5 @@
{% block extra_js %}
<script src="{{ STATIC_URL}}dashboard/vm-list.js"></script>
<script src="{{ STATIC_URL}}dashboard/vm-common.js"></script>
<script src="{{ STATIC_URL}}dashboard/js/stupidtable.min.js"></script>
{% endblock %}
<a href="?sort={% if request.GET.sort == sort %}-{% endif %}{{ sort }}">{{ name }}</a>
......@@ -952,7 +952,8 @@ class TemplateList(LoginRequiredMixin, SingleTableView):
def get_context_data(self, *args, **kwargs):
context = super(TemplateList, self).get_context_data(*args, **kwargs)
context['lease_table'] = LeaseListTable(Lease.objects.all())
context['lease_table'] = LeaseListTable(Lease.objects.all(),
request=self.request)
return context
def get_queryset(self):
......@@ -1025,6 +1026,14 @@ class VmList(LoginRequiredMixin, ListView):
s = self.request.GET.get("s")
if s:
queryset = queryset.filter(name__icontains=s)
sort = self.request.GET.get("sort")
# remove "-" that means descending order
# also check if the column name is valid
if (sort and
(sort[1:] if sort[0] == "-" else sort)
in [i.name for i in Instance._meta.fields] + ["pk"]):
queryset = queryset.order_by(sort)
return queryset.select_related('owner', 'node')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment