Skip to content

Commit 8abd878

Browse files
committed
Sortable lists
1 parent d0738d7 commit 8abd878

5 files changed

Lines changed: 311 additions & 5 deletions

File tree

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
table.sortable span.sign {
2+
display: block;
3+
position: absolute;
4+
top: 50%;
5+
right: 5px;
6+
font-size: 12px;
7+
margin-top: -10px;
8+
color: #bfbfc1;
9+
}
10+
11+
table.sortable th:after {
12+
display: block;
13+
position: absolute;
14+
top: 50%;
15+
right: 5px;
16+
font-size: 12px;
17+
margin-top: -10px;
18+
color: #bfbfc1;
19+
}
20+
21+
table.sortable th.arrow:after {
22+
content: '';
23+
}
24+
25+
table.sortable span.arrow, span.reversed, th.arrow.down:after, th.reversedarrow.down:after, th.arrow.up:after, th.reversedarrow.up:after {
26+
border-style: solid;
27+
border-width: 5px;
28+
font-size: 0;
29+
border-color: #ccc transparent transparent transparent;
30+
line-height: 0;
31+
height: 0;
32+
width: 0;
33+
margin-top: -2px;
34+
}
35+
36+
table.sortable span.arrow.up, th.arrow.up:after {
37+
border-color: transparent transparent #ccc transparent;
38+
margin-top: -7px;
39+
}
40+
41+
table.sortable span.reversed, th.reversedarrow.down:after {
42+
border-color: transparent transparent #ccc transparent;
43+
margin-top: -7px;
44+
}
45+
46+
table.sortable span.reversed.up, th.reversedarrow.up:after {
47+
border-color: #ccc transparent transparent transparent;
48+
margin-top: -2px;
49+
}
50+
51+
table.sortable span.az:before, th.az.down:after {
52+
content: "a .. z";
53+
}
54+
55+
table.sortable span.az.up:before, th.az.up:after {
56+
content: "z .. a";
57+
}
58+
59+
table.sortable th.az.nosort:after, th.AZ.nosort:after, th._19.nosort:after, th.month.nosort:after {
60+
content: "..";
61+
}
62+
63+
table.sortable span.AZ:before, th.AZ.down:after {
64+
content: "A .. Z";
65+
}
66+
67+
table.sortable span.AZ.up:before, th.AZ.up:after {
68+
content: "Z .. A";
69+
}
70+
71+
table.sortable span._19:before, th._19.down:after {
72+
content: "1 .. 9";
73+
}
74+
75+
table.sortable span._19.up:before, th._19.up:after {
76+
content: "9 .. 1";
77+
}
78+
79+
table.sortable span.month:before, th.month.down:after {
80+
content: "jan .. dec";
81+
}
82+
83+
table.sortable span.month.up:before, th.month.up:after {
84+
content: "dec .. jan";
85+
}
86+
87+
table.sortable thead th:not([data-defaultsort=disabled]) {
88+
cursor: pointer;
89+
position: relative;
90+
top: 0;
91+
left: 0;
92+
}
93+
94+
table.sortable thead th:hover:not([data-defaultsort=disabled]) {
95+
background: #efefef;
96+
}
97+
98+
table.sortable thead th div.mozilla {
99+
position: relative;
100+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*! tinysort.js
2+
* version: 2.1.1
3+
* author: Ron Valstar (http://www.sjeiti.com/)
4+
* license: MIT/GPL
5+
* build: 2015-01-05
6+
*/
7+
!function (a, b) { "use strict"; function c() { return b } "function" == typeof define && define.amd ? define("tinysort", c) : a.tinysort = b }(this, function () { "use strict"; function a(a) { function f() { 0 === arguments.length ? j({}) : d(arguments, function (a) { j(c(a) ? { selector: a } : a) }), q = C.length } function j(a) { var b = !!a.selector, c = b && ":" === a.selector[0], d = e(a || {}, t); C.push(e({ bFind: b, bAttr: !(d.attr === i || "" === d.attr), bData: d.data !== i, bFilter: c, mFilter: i, fnSort: d.sortFunction, iAsc: "asc" === d.order ? 1 : -1 }, d)) } function m() { d(a, function (a, b) { x ? x !== a.parentNode && (D = !1) : x = a.parentNode; var c = C[0], d = c.bFilter, e = c.selector, f = !e || d && a.matchesSelector(e) || e && a.querySelector(e), g = f ? A : B, h = { elm: a, pos: b, posn: g.length }; z.push(h), g.push(h) }), w = A.slice(0) } function s() { A.sort(u) } function u(a, e) { var f = 0; for (0 !== r && (r = 0) ; 0 === f && q > r;) { var i = C[r], j = i.ignoreDashes ? o : n; if (d(p, function (a) { var b = a.prepare; b && b(i) }), i.sortFunction) f = i.sortFunction(a, e); else if ("rand" == i.order) f = Math.random() < .5 ? 1 : -1; else { var k = h, m = b(a, i), s = b(e, i); if (!i.forceStrings) { var t = c(m) ? m && m.match(j) : h, u = c(s) ? s && s.match(j) : h; if (t && u) { var v = m.substr(0, m.length - t[0].length), w = s.substr(0, s.length - u[0].length); v == w && (k = !h, m = l(t[0]), s = l(u[0])) } } f = m === g || s === g ? 0 : i.iAsc * (s > m ? -1 : m > s ? 1 : 0) } d(p, function (a) { var b = a.sort; b && (f = b(i, k, m, s, f)) }), 0 === f && r++ } return 0 === f && (f = a.pos > e.pos ? 1 : -1), f } function v() { var a = A.length === z.length; D && a ? (A.forEach(function (a) { y.appendChild(a.elm) }), x.appendChild(y)) : (A.forEach(function (a) { var b = a.elm, c = k.createElement("div"); a.ghost = c, b.parentNode.insertBefore(c, b) }), A.forEach(function (a, b) { var c = w[b].ghost; c.parentNode.insertBefore(a.elm, c), c.parentNode.removeChild(c) })) } c(a) && (a = k.querySelectorAll(a)), 0 === a.length && console.warn("No elements to sort"); var w, x, y = k.createDocumentFragment(), z = [], A = [], B = [], C = [], D = !0; return f.apply(i, Array.prototype.slice.call(arguments, 1)), m(), s(), v(), A.map(function (a) { return a.elm }) } function b(a, b) { var d, e = a.elm; return b.selector && (b.bFilter ? e.matchesSelector(b.selector) || (e = i) : e = e.querySelector(b.selector)), b.bAttr ? d = e.getAttribute(b.attr) : b.useVal ? d = e.value : b.bData ? d = e.getAttribute("data-" + b.data) : e && (d = e.textContent), c(d) && (b.cases || (d = d.toLowerCase()), d = d.replace(/\s+/g, " ")), d } function c(a) { return "string" == typeof a } function d(a, b) { for (var c, d = a.length, e = d; e--;) c = d - e - 1, b(a[c], c) } function e(a, b, c) { for (var d in b) (c || a[d] === g) && (a[d] = b[d]); return a } function f(a, b, c) { p.push({ prepare: a, sort: b, sortBy: c }) } var g, h = !1, i = null, j = window, k = j.document, l = parseFloat, m = Array.prototype.indexOf, n = /(-?\d+\.?\d*)$/g, o = /(\d+\.?\d*)$/g, p = [], q = 0, r = 0, s = "2.1.0", t = { selector: i, order: "asc", attr: i, data: i, useVal: h, place: "start", returns: h, cases: h, forceStrings: h, ignoreDashes: h, sortFunction: i }; return j.Element && function (a) { a.matchesSelector = a.matchesSelector || a.mozMatchesSelector || a.msMatchesSelector || a.oMatchesSelector || a.webkitMatchesSelector || function (a) { for (var b = this, c = (b.parentNode || b.document).querySelectorAll(a), d = -1; c[++d] && c[d] != b;); return !!c[d] } }(Element.prototype), e(f, { indexOf: m, loop: d }), e(a, { plugin: f, version: s, defaults: t }) }());
8+
9+
(function ($) {
10+
11+
var $document = $(document),
12+
signClass,
13+
sortEngine;
14+
15+
$.bootstrapSortable = function (applyLast, sign, customSort) {
16+
17+
// Check if moment.js is available
18+
var momentJsAvailable = (typeof moment !== 'undefined');
19+
20+
// Set class based on sign parameter
21+
signClass = !sign ? "arrow" : sign;
22+
23+
// Set sorting algorithm
24+
if (customSort == 'default')
25+
customSort = defaultSortEngine;
26+
sortEngine = customSort || sortEngine || defaultSortEngine;
27+
28+
// Set attributes needed for sorting
29+
$('table.sortable').each(function () {
30+
var $this = $(this),
31+
context = lookupSortContext($this),
32+
bsSort = context.bsSort;
33+
applyLast = (applyLast === true);
34+
$this.find('span.sign').remove();
35+
36+
// Add placeholder cells for colspans
37+
$this.find('thead [colspan]').each(function () {
38+
var colspan = parseFloat($(this).attr('colspan'));
39+
for (var i = 1; i < colspan; i++) {
40+
$(this).after('<th class="colspan-compensate">');
41+
}
42+
});
43+
44+
// Add placeholder cells for rowspans
45+
$this.find('thead [rowspan]').each(function () {
46+
var $cell = $(this);
47+
var rowspan = parseFloat($cell.attr('rowspan'));
48+
for (var i = 1; i < rowspan; i++) {
49+
var parentRow = $cell.parent('tr');
50+
var nextRow = parentRow.next('tr');
51+
var index = parentRow.children().index($cell);
52+
nextRow.children().eq(index).before('<th class="rowspan-compensate">');
53+
}
54+
});
55+
56+
// Set indexes to header cells
57+
$this.find('thead tr').each(function (rowIndex) {
58+
$(this).find('th').each(function (columnIndex) {
59+
var $this = $(this);
60+
$this.addClass('nosort').removeClass('up down');
61+
$this.attr('data-sortcolumn', columnIndex);
62+
$this.attr('data-sortkey', columnIndex + '-' + rowIndex);
63+
});
64+
});
65+
66+
// Cleanup placeholder cells
67+
$this.find('thead .rowspan-compensate, .colspan-compensate').remove();
68+
69+
// Initialize sorting values
70+
$this.find('td').each(function () {
71+
var $this = $(this);
72+
if ($this.attr('data-dateformat') !== undefined && momentJsAvailable) {
73+
$this.attr('data-value', moment($this.text(), $this.attr('data-dateformat')).format('YYYY/MM/DD/HH/mm/ss'));
74+
}
75+
else {
76+
$this.attr('data-value') === undefined && $this.attr('data-value', $this.text());
77+
}
78+
});
79+
$this.find('thead th[data-defaultsort!="disabled"]').each(function (index) {
80+
var $this = $(this);
81+
var $sortTable = $this.closest('table.sortable');
82+
$this.data('sortTable', $sortTable);
83+
var sortKey = $this.attr('data-sortkey');
84+
var thisLastSort = applyLast ? context.lastSort : -1;
85+
bsSort[sortKey] = applyLast ? bsSort[sortKey] : $this.attr('data-defaultsort');
86+
if (bsSort[sortKey] !== undefined && (applyLast === (sortKey === thisLastSort))) {
87+
bsSort[sortKey] = bsSort[sortKey] === 'asc' ? 'desc' : 'asc';
88+
doSort($this, $sortTable);
89+
}
90+
});
91+
$this.trigger('sorted');
92+
});
93+
};
94+
95+
// Add click event to table header
96+
$document.on('click', 'table.sortable thead th[data-defaultsort!="disabled"]', function (e) {
97+
var $this = $(this), $table = $this.data('sortTable') || $this.closest('table.sortable');
98+
$table.trigger('before-sort');
99+
doSort($this, $table);
100+
$table.trigger('sorted');
101+
});
102+
103+
// Look up sorting data appropriate for the specified table (jQuery element).
104+
// This allows multiple tables on one page without collisions.
105+
function lookupSortContext($table) {
106+
var context = $table.data("bootstrap-sortable-context");
107+
if (context == null) {
108+
context = { bsSort: [], lastSort: null };
109+
$table.data("bootstrap-sortable-context", context);
110+
}
111+
return context;
112+
}
113+
114+
function defaultSortEngine(rows, sortingParams) {
115+
tinysort(rows, sortingParams);
116+
}
117+
118+
// Sorting mechanism separated
119+
function doSort($this, $table) {
120+
var sortColumn = parseFloat($this.attr('data-sortcolumn')),
121+
context = lookupSortContext($table),
122+
bsSort = context.bsSort;
123+
124+
var colspan = $this.attr('colspan');
125+
if (colspan) {
126+
var mainSort = Math.min(colspan - 1, parseFloat($this.data('mainsort')) || 0);
127+
var rowIndex = parseFloat($this.data('sortkey').split('-').pop());
128+
129+
// If there is one more row in header, delve deeper
130+
if ($table.find('thead tr').length - 1 > rowIndex) {
131+
doSort($table.find('[data-sortkey="' + (sortColumn + mainSort) + '-' + (rowIndex + 1) + '"]'), $table);
132+
return;
133+
}
134+
// Otherwise, just adjust the sortColumn
135+
sortColumn = sortColumn + mainSort;
136+
}
137+
138+
var localSignClass = $this.attr('data-defaultsign') || signClass;
139+
140+
// update arrow icon
141+
$table.find('th').each(function () {
142+
$(this).removeClass('up').removeClass('down').addClass('nosort');
143+
});
144+
145+
if ($.browser.mozilla) {
146+
var moz_arrow = $table.find('div.mozilla');
147+
if (moz_arrow !== undefined) {
148+
moz_arrow.find('.sign').remove();
149+
moz_arrow.parent().html(moz_arrow.html());
150+
}
151+
$this.wrapInner('<div class="mozilla"></div>');
152+
$this.children().eq(0).append('<span class="sign ' + localSignClass + '"></span>');
153+
}
154+
else {
155+
$table.find('span.sign').remove();
156+
$this.append('<span class="sign ' + localSignClass + '"></span>');
157+
}
158+
159+
// sort direction
160+
var sortKey = $this.attr('data-sortkey');
161+
var initialDirection = $this.attr('data-firstsort') !== 'desc' ? 'desc' : 'asc';
162+
163+
context.lastSort = sortKey;
164+
bsSort[sortKey] = (bsSort[sortKey] || initialDirection) === 'asc' ? 'desc' : 'asc';
165+
if (bsSort[sortKey] === 'desc') {
166+
$this.find('span.sign').addClass('up');
167+
$this.addClass('up').removeClass('down nosort');
168+
} else {
169+
$this.addClass('down').removeClass('up nosort');
170+
}
171+
172+
// sort rows
173+
var rows = $table.children('tbody').children('tr');
174+
sortEngine(rows, { selector: 'td:nth-child(' + (sortColumn + 1) + ')', order: bsSort[sortKey], data: 'value' });
175+
176+
// add class to sorted column cells
177+
$table.find('td.sorted, th.sorted').removeClass('sorted');
178+
rows.find('td:eq(' + sortColumn + ')').addClass('sorted');
179+
$this.addClass('sorted');
180+
}
181+
182+
// jQuery 1.9 removed this object
183+
if (!$.browser) {
184+
$.browser = { chrome: false, mozilla: false, opera: false, msie: false, safari: false };
185+
var ua = navigator.userAgent;
186+
$.each($.browser, function (c) {
187+
$.browser[c] = ((new RegExp(c, 'i').test(ua))) ? true : false;
188+
if ($.browser.mozilla && c === 'mozilla') { $.browser.mozilla = ((new RegExp('firefox', 'i').test(ua))) ? true : false; }
189+
if ($.browser.chrome && c === 'safari') { $.browser.safari = false; }
190+
});
191+
}
192+
193+
// Initialise on DOM ready
194+
$($.bootstrapSortable);
195+
196+
}(jQuery));

cloudbot/web/static/js/moment.min.js

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cloudbot/web/templates/commands.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88
<h2 id="container">Commands</h2>
99
</div>
1010
<!--<p class="lead">How to use {{ bot_name }} commands.</p>-->
11-
<table class="table table-striped table-hover ">
11+
<table class="table table-striped sortable">
1212
<thead>
1313
<tr>
14-
<th>Command</th>
15-
<th>Description</th>
14+
<th class='az' data-defaultsign="nospan" data-defaultsort="asc"><i class="fa fa-map-marker fa-fw"></i>Command</th>
15+
<th data-defaultsort='disabled'>Description</th>
1616
</tr>
1717
</thead>
1818
{% for cmd, doc in commands %}
1919
<tr>
20-
<td>{{ cmd }}</td>
21-
<td>{{ doc }}</td>
20+
<td>{{ cmd | e }}</td>
21+
<td>{{ doc | e }}</td>
2222
</tr>
2323
{% endfor %}
2424
</table>

cloudbot/web/templates/layout.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
<title>{% block title %}{{ bot_name }}{% endblock %}</title>
1919

2020
<link href="/s/css/bootstrap.min.css" rel="stylesheet">
21+
<link href="/s/css/bootstrap-sortable.css" rel="stylesheet">
22+
2123
<link href="/s/css/style.css" rel="stylesheet">
2224

2325
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
@@ -84,6 +86,8 @@ <h4 class="modal-title">About Me</h4>
8486
<!-- javascript -->
8587
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
8688
<script src="/s/js/bootstrap.min.js"></script>
89+
<script src="/s/js/moment.min.js"></script>
90+
<script src="/s/js/bootstrap-sortable.js"></script>
8791
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
8892
<script src="/s/js/ie10-viewport-bug-workaround.js"></script>
8993
</body>

0 commit comments

Comments
 (0)