Merge branch 'backgrid' into develop

pull/4/head
Mark McDowall 11 years ago
commit a3d67b47f9

@ -33,6 +33,13 @@ namespace NzbDrone.Api.History
SortDirection = pagingResource.SortDirection SortDirection = pagingResource.SortDirection
}; };
//This is a hack to deal with backgrid setting the sortKey to the column name instead of sortValue
if (pagingSpec.SortKey.Equals("series", StringComparison.InvariantCultureIgnoreCase))
{
pagingSpec.SortKey = "series.title";
}
if (episodeId.HasValue) if (episodeId.HasValue)
{ {
int i = (int)episodeId; int i = (int)episodeId;

@ -1,69 +0,0 @@
'use strict';
define(
[
'backgrid',
'Shared/Grid/HeaderCell'
], function (Backgrid, NzbDroneHeaderCell) {
Backgrid.QualityHeaderCell = NzbDroneHeaderCell.extend({
events: {
'click': 'onClick'
},
onClick: function (e) {
e.preventDefault();
var self = this;
var columnName = this.column.get('name');
if (this.column.get('sortable')) {
if (this.direction() === 'ascending') {
this.sort(columnName, 'descending', function (left, right) {
var leftVal = left.get(columnName);
var rightVal = right.get(columnName);
return self._comparator(leftVal, rightVal);
});
}
else {
this.sort(columnName, 'ascending', function (left, right) {
var leftVal = left.get(columnName);
var rightVal = right.get(columnName);
return self._comparator(rightVal, leftVal);
});
}
}
},
_comparator: function (leftVal, rightVal) {
var leftWeight = leftVal.quality.weight;
var rightWeight = rightVal.quality.weight;
if (!leftWeight && !rightWeight) {
return 0;
}
if (!leftWeight) {
return -1;
}
if (!rightWeight) {
return 1;
}
if (leftWeight === rightWeight) {
return 0;
}
if (leftWeight > rightWeight) {
return -1;
}
return 1;
}
});
return Backgrid.QualityHeaderCell;
});

@ -8,8 +8,11 @@ define(
className : 'season-folder-cell', className : 'season-folder-cell',
render: function () { render: function () {
var seasonFolder = this.model.get('seasonFolder'); this.$el.empty();
var seasonFolder = this.model.get(this.column.get('name'));
this.$el.html(seasonFolder.toString()); this.$el.html(seasonFolder.toString());
return this; return this;
} }
}); });

@ -46,6 +46,17 @@
.page-toolbar { .page-toolbar {
margin-top : 10px; margin-top : 10px;
margin-bottom : 30px; margin-bottom : 30px;
.toolbar-group {
display: inline-block;
}
.sorting-buttons {
.sorting-title {
display: inline-block;
width: 110px;
}
}
} }
.page-container { .page-container {

@ -47,7 +47,7 @@ define(
this.model = options.model; this.model = options.model;
this.series = options.series; this.series = options.series;
this.collection = new HistoryCollection({ episodeId: this.model.id }); this.collection = new HistoryCollection({ episodeId: this.model.id, tableName: 'episodeActivity' });
this.collection.fetch(); this.collection.fetch();
this.listenTo(this.collection, 'sync', this._showTable); this.listenTo(this.collection, 'sync', this._showTable);
}, },

@ -6,10 +6,8 @@ define(
'Cells/FileSizeCell', 'Cells/FileSizeCell',
'Cells/QualityCell', 'Cells/QualityCell',
'Cells/ApprovalStatusCell', 'Cells/ApprovalStatusCell',
'Release/DownloadReportCell', 'Release/DownloadReportCell'
'Cells/Header/QualityHeaderCell' ], function (Marionette, Backgrid, FileSizeCell, QualityCell, ApprovalStatusCell, DownloadReportCell) {
], function (Marionette, Backgrid, FileSizeCell, QualityCell, ApprovalStatusCell, DownloadReportCell, QualityHeaderCell) {
return Marionette.Layout.extend({ return Marionette.Layout.extend({
template: 'Episode/Search/ManualLayoutTemplate', template: 'Episode/Search/ManualLayoutTemplate',
@ -49,7 +47,9 @@ define(
label : 'Quality', label : 'Quality',
sortable : true, sortable : true,
cell : QualityCell, cell : QualityCell,
headerCell: QualityHeaderCell sortValue : function (model) {
return model.get('quality').quality.weight;
}
}, },
{ {

@ -2,9 +2,10 @@
define( define(
[ [
'History/HistoryModel', 'History/HistoryModel',
'backbone.pageable' 'backbone.pageable',
], function (HistoryModel, PageableCollection) { 'Mixins/AsPersistedStateCollection'
return PageableCollection.extend({ ], function (HistoryModel, PageableCollection, AsPersistedStateCollection) {
var collection = PageableCollection.extend({
url : window.NzbDrone.ApiRoot + '/history', url : window.NzbDrone.ApiRoot + '/history',
model: HistoryModel, model: HistoryModel,
@ -48,4 +49,6 @@ define(
return resp; return resp;
} }
}); });
return AsPersistedStateCollection.apply(collection);
}); });

@ -45,7 +45,8 @@ define(
{ {
name : 'series', name : 'series',
label: 'Series', label: 'Series',
cell : SeriesTitleCell cell : SeriesTitleCell,
sortValue: 'series.title'
}, },
{ {
name : 'episode', name : 'episode',
@ -80,7 +81,7 @@ define(
initialize: function () { initialize: function () {
this.collection = new HistoryCollection(); this.collection = new HistoryCollection({ tableName: 'history' });
this.listenTo(this.collection, 'sync', this._showTable); this.listenTo(this.collection, 'sync', this._showTable);
}, },

File diff suppressed because it is too large Load Diff

@ -5,122 +5,271 @@
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT @license. Licensed under the MIT @license.
*/ */
(function (factory) {
(function ($, _, Backbone, Backgrid) {
// CommonJS
if (typeof exports == "object") {
module.exports = factory(require("underscore"),
require("backbone"),
require("backgrid"),
require("backbone-pageable"));
}
// Browser
else if (typeof _ !== "undefined" &&
typeof Backbone !== "undefined" &&
typeof Backgrid !== "undefined") {
factory(_, Backbone, Backgrid);
}
}(function (_, Backbone, Backgrid) {
"use strict"; "use strict";
/** /**
Paginator is a Backgrid extension that renders a series of configurable PageHandle is a class that renders the actual page handles and reacts to
pagination handles. This extension is best used for splitting a large data click events for pagination.
set across multiple pages. If the number of pages is larger then a
threshold, which is set to 10 by default, the page handles are rendered This class acts in two modes - control or discrete page handle modes. If
within a sliding window, plus the fast forward, fast backward, previous and one of the `is*` flags is `true`, an instance of this class is under
next page handles. The fast forward, fast backward, previous and next page control page handle mode. Setting a `pageIndex` to an instance of this
handles can be turned off. class under control mode has no effect and the correct page index will
always be inferred from the `is*` flag. Only one of the `is*` flags should
@class Backgrid.Extension.Paginator be set to `true` at a time. For example, an instance of this class cannot
simultaneously be a rewind control and a fast forward control. A `label`
and a `title` template or a string are required to be passed to the
constuctor under this mode. If a `title` template is provided, it __MUST__
accept a parameter `label`. When the `label` is provided to the `title`
template function, its result will be used to render the generated anchor's
title attribute.
If all of the `is*` flags is set to `false`, which is the default, an
instance of this class will be in discrete page handle mode. An instance
under this mode requires the `pageIndex` to be passed from the constructor
as an option and it __MUST__ be a 0-based index of the list of page numbers
to render. The constuctor will normalize the base to the same base the
underlying PageableCollection collection instance uses. A `label` is not
required under this mode, which will default to the equivalent 1-based page
index calculated from `pageIndex` and the underlying PageableCollection
instance. A provided `label` will still be honored however. The `title`
parameter is also not required under this mode, in which case the default
`title` template will be used. You are encouraged to provide your own
`title` template however if you wish to localize the title strings.
If this page handle represents the current page, an `active` class will be
placed on the root list element.
if this page handle is at the border of the list of pages, a `disabled`
class will be placed on the root list element.
Only page handles that are neither `active` nor `disabled` will respond to
click events and triggers pagination.
@class Backgrid.Extension.PageHandle
*/ */
Backgrid.Extension.Paginator = Backbone.View.extend({ var PageHandle = Backgrid.Extension.PageHandle = Backbone.View.extend({
/** @property */ /** @property */
className: "backgrid-paginator", tagName: "li",
/** @property */ /** @property */
windowSize: 10, events: {
"click a": "changePage"
},
/** /**
@property {Object} fastForwardHandleLabels You can disable specific @property {string|function(Object.<string, string>): string} title
handles by setting its value to `null`. The title to use for the `title` attribute of the generated page handle
anchor elements. It can be a string or an Underscore template function
that takes a mandatory `label` parameter.
*/ */
fastForwardHandleLabels: { title: _.template('Page <%- label %>', null, {variable: null}),
first: "《",
prev: "〈",
next: "〉",
last: "》"
},
/** @property */ /**
template: _.template('<ul><% _.each(handles, function (handle) { %><li <% if (handle.className) { %>class="<%= handle.className %>"<% } %>><a href="#" <% if (handle.title) {%> title="<%= handle.title %>"<% } %>><%= handle.label %></a></li><% }); %></ul>'), @property {boolean} isRewind Whether this handle represents a rewind
control
*/
isRewind: false,
/** @property */ /**
events: { @property {boolean} isBack Whether this handle represents a back
"click a": "changePage" control
}, */
isBack: false,
/**
@property {boolean} isForward Whether this handle represents a forward
control
*/
isForward: false,
/**
@property {boolean} isFastForward Whether this handle represents a fast
forward control
*/
isFastForward: false,
/** /**
Initializer. Initializer.
@param {Object} options @param {Object} options
@param {Backbone.Collection} options.collection @param {Backbone.Collection} options.collection
@param {boolean} [options.fastForwardHandleLabels] Whether to render fast forward buttons. @param {number} pageIndex 0-based index of the page number this handle
handles. This parameter will be normalized to the base the underlying
PageableCollection uses.
@param {string} [options.label] If provided it is used to render the
anchor text, otherwise the normalized pageIndex will be used
instead. Required if any of the `is*` flags is set to `true`.
@param {string} [options.title]
@param {boolean} [options.isRewind=false]
@param {boolean} [options.isBack=false]
@param {boolean} [options.isForward=false]
@param {boolean} [options.isFastForward=false]
*/ */
initialize: function (options) { initialize: function (options) {
Backgrid.requireOptions(options, ["collection"]); Backbone.View.prototype.initialize.apply(this, arguments);
var collection = this.collection; var collection = this.collection;
var fullCollection = collection.fullCollection; var state = collection.state;
if (fullCollection) { var currentPage = state.currentPage;
this.listenTo(fullCollection, "add", this.render); var firstPage = state.firstPage;
this.listenTo(fullCollection, "remove", this.render); var lastPage = state.lastPage;
this.listenTo(fullCollection, "reset", this.render);
} _.extend(this, _.pick(options,
["isRewind", "isBack", "isForward", "isFastForward"]));
var pageIndex;
if (this.isRewind) pageIndex = firstPage;
else if (this.isBack) pageIndex = Math.max(firstPage, currentPage - 1);
else if (this.isForward) pageIndex = Math.min(lastPage, currentPage + 1);
else if (this.isFastForward) pageIndex = lastPage;
else { else {
this.listenTo(collection, "add", this.render); pageIndex = +options.pageIndex;
this.listenTo(collection, "remove", this.render); pageIndex = (firstPage ? pageIndex + 1 : pageIndex);
this.listenTo(collection, "reset", this.render);
} }
this.pageIndex = pageIndex;
if (((this.isRewind || this.isBack) && currentPage == firstPage) ||
((this.isForward || this.isFastForward) && currentPage == lastPage)) {
this.$el.addClass("disabled");
}
else if (!(this.isRewind ||
this.isBack ||
this.isForward ||
this.isFastForward) &&
currentPage == pageIndex) {
this.$el.addClass("active");
}
this.label = (options.label || (firstPage ? pageIndex : pageIndex + 1)) + '';
var title = options.title || this.title;
this.title = _.isFunction(title) ? title({label: this.label}) : title;
}, },
/** /**
jQuery event handler for the page handlers. Goes to the right page upon Renders a clickable anchor element under a list item.
clicking. */
render: function () {
this.$el.empty();
var anchor = document.createElement("a");
anchor.href = '#';
if (this.title) anchor.title = this.title;
anchor.innerHTML = this.label;
this.el.appendChild(anchor);
this.delegateEvents();
return this;
},
@param {Event} e /**
*/ jQuery click event handler. Goes to the page this PageHandle instance
represents. No-op if this page handle is currently active or disabled.
*/
changePage: function (e) { changePage: function (e) {
e.preventDefault(); e.preventDefault();
var $el = this.$el;
if (!$el.hasClass("active") && !$el.hasClass("disabled")) {
this.collection.getPage(this.pageIndex);
}
return this;
}
var $li = $(e.target).parent(); });
if (!$li.hasClass("active") && !$li.hasClass("disabled")) {
/**
var label = $(e.target).text(); Paginator is a Backgrid extension that renders a series of configurable
var ffLabels = this.fastForwardHandleLabels; pagination handles. This extension is best used for splitting a large data
set across multiple pages. If the number of pages is larger then a
var collection = this.collection; threshold, which is set to 10 by default, the page handles are rendered
within a sliding window, plus the rewind, back, forward and fast forward
if (ffLabels) { control handles. The individual control handles can be turned off.
switch (label) {
case ffLabels.first: @class Backgrid.Extension.Paginator
collection.getFirstPage(); */
return; Backgrid.Extension.Paginator = Backbone.View.extend({
case ffLabels.prev:
collection.getPreviousPage(); /** @property */
return; className: "backgrid-paginator",
case ffLabels.next:
collection.getNextPage(); /** @property */
return; windowSize: 10,
case ffLabels.last:
collection.getLastPage();
return;
}
}
var state = collection.state; /**
var pageIndex = +label; @property {Object.<string, Object.<string, string>>} controls You can
collection.getPage(state.firstPage === 0 ? pageIndex - 1 : pageIndex); disable specific control handles by omitting certain keys.
*/
controls: {
rewind: {
label: "《",
title: "First"
},
back: {
label: "〈",
title: "Previous"
},
forward: {
label: "〉",
title: "Next"
},
fastForward: {
label: "》",
title: "Last"
} }
}, },
/** /**
Internal method to create a list of page handle objects for the template @property {Backgrid.Extension.PageHandle} pageHandle. The PageHandle
to render them. class to use for rendering individual handles
*/
pageHandle: PageHandle,
@return {Array.<Object>} an array of page handle objects hashes /** @property */
*/ goBackFirstOnSort: true,
makeHandles: function () {
/**
Initializer.
@param {Object} options
@param {Backbone.Collection} options.collection
@param {boolean} [options.controls]
@param {boolean} [options.pageHandle=Backgrid.Extension.PageHandle]
@param {boolean} [options.goBackFirstOnSort=true]
*/
initialize: function (options) {
this.controls = options.controls || this.controls;
this.pageHandle = options.pageHandle || this.pageHandle;
var handles = []; var collection = this.collection;
this.listenTo(collection, "add", this.render);
this.listenTo(collection, "remove", this.render);
this.listenTo(collection, "reset", this.render);
if ((options.goBackFirstOnSort || this.goBackFirstOnSort) &&
collection.fullCollection) {
this.listenTo(collection.fullCollection, "sort", function () {
collection.getFirstPage();
});
}
},
_calculateWindow: function () {
var collection = this.collection; var collection = this.collection;
var state = collection.state; var state = collection.state;
@ -132,48 +281,44 @@
currentPage = firstPage ? currentPage - 1 : currentPage; currentPage = firstPage ? currentPage - 1 : currentPage;
var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize; var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize;
var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize); var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize);
return [windowStart, windowEnd];
},
if (collection.mode !== "infinite") { /**
for (var i = windowStart; i < windowEnd; i++) { Creates a list of page handle objects for rendering.
handles.push({
label: i + 1,
title: "No. " + (i + 1),
className: currentPage === i ? "active" : undefined
});
}
}
var ffLabels = this.fastForwardHandleLabels; @return {Array.<Object>} an array of page handle objects hashes
if (ffLabels) { */
makeHandles: function () {
if (ffLabels.prev) { var handles = [];
handles.unshift({ var collection = this.collection;
label: ffLabels.prev,
className: collection.hasPrevious() ? void 0 : "disabled"
});
}
if (ffLabels.first) { var window = this._calculateWindow();
handles.unshift({ var winStart = window[0], winEnd = window[1];
label: ffLabels.first,
className: collection.hasPrevious() ? void 0 : "disabled"
});
}
if (ffLabels.next) { for (var i = winStart; i < winEnd; i++) {
handles.push({ handles.push(new this.pageHandle({
label: ffLabels.next, collection: collection,
className: collection.hasNext() ? void 0 : "disabled" pageIndex: i
}); }));
} }
if (ffLabels.last) { var controls = this.controls;
handles.push({ _.each(["back", "rewind", "forward", "fastForward"], function (key) {
label: ffLabels.last, var value = controls[key];
className: collection.hasNext() ? void 0 : "disabled" if (value) {
}); var handleCtorOpts = {
collection: collection,
title: value.title,
label: value.label
};
handleCtorOpts["is" + key.slice(0, 1).toUpperCase() + key.slice(1)] = true;
var handle = new this.pageHandle(handleCtorOpts);
if (key == "rewind" || key == "back") handles.unshift(handle);
else handles.push(handle);
} }
} }, this);
return handles; return handles;
}, },
@ -184,15 +329,24 @@
render: function () { render: function () {
this.$el.empty(); this.$el.empty();
this.$el.append(this.template({ if (this.handles) {
handles: this.makeHandles() for (var i = 0, l = this.handles.length; i < l; i++) {
})); this.handles[i].remove();
}
}
this.delegateEvents(); var handles = this.handles = this.makeHandles();
var ul = document.createElement("ul");
for (var i = 0; i < handles.length; i++) {
ul.appendChild(handles[i].render().el);
}
this.el.appendChild(ul);
return this; return this;
} }
}); });
}(jQuery, _, Backbone, Backgrid)); }));

@ -1,5 +1,5 @@
/* /*
backbone-pageable 1.3.2 backbone-pageable 1.4.1
http://github.com/wyuenho/backbone-pageable http://github.com/wyuenho/backbone-pageable
Copyright (c) 2013 Jimmy Yuen Ho Wong Copyright (c) 2013 Jimmy Yuen Ho Wong
@ -83,7 +83,7 @@
for (var i = 0, l = kvps.length; i < l; i++) { for (var i = 0, l = kvps.length; i < l; i++) {
var param = kvps[i]; var param = kvps[i];
kvp = param.split('='), k = kvp[0], v = kvp[1] || true; kvp = param.split('='), k = kvp[0], v = kvp[1] || true;
k = decode(k), ls = params[k]; k = decode(k), v = decode(v), ls = params[k];
if (_isArray(ls)) ls.push(v); if (_isArray(ls)) ls.push(v);
else if (ls) params[k] = [ls, v]; else if (ls) params[k] = [ls, v];
else params[k] = v; else params[k] = v;
@ -91,6 +91,29 @@
return params; return params;
} }
// hack to make sure the whatever event handlers for this event is run
// before func is, and the event handlers that func will trigger.
function runOnceAtLastHandler (col, event, func) {
var eventHandlers = col._events[event];
if (eventHandlers && eventHandlers.length) {
var lastHandler = eventHandlers[eventHandlers.length - 1];
var oldCallback = lastHandler.callback;
lastHandler.callback = function () {
try {
oldCallback.apply(this, arguments);
func();
}
catch (e) {
throw e;
}
finally {
lastHandler.callback = oldCallback;
}
};
}
else func();
}
var PARAM_TRIM_RE = /[\s'"]/g; var PARAM_TRIM_RE = /[\s'"]/g;
var URL_TRIM_RE = /[<>\s'"]/g; var URL_TRIM_RE = /[<>\s'"]/g;
@ -256,7 +279,7 @@
*/ */
constructor: function (models, options) { constructor: function (models, options) {
Backbone.Collection.apply(this, arguments); BBColProto.constructor.apply(this, arguments);
options = options || {}; options = options || {};
@ -299,7 +322,7 @@
var fullCollection = this.fullCollection; var fullCollection = this.fullCollection;
if (comparator && options.full) { if (comparator && options.full) {
delete this.comparator; this.comparator = null;
fullCollection.comparator = comparator; fullCollection.comparator = comparator;
} }
@ -308,6 +331,7 @@
// make sure the models in the current page and full collection have the // make sure the models in the current page and full collection have the
// same references // same references
if (models && !_isEmpty(models)) { if (models && !_isEmpty(models)) {
this.reset([].slice.call(models), _extend({silent: true}, options));
this.getPage(state.currentPage); this.getPage(state.currentPage);
models.splice.apply(models, [0, models.length].concat(this.models)); models.splice.apply(models, [0, models.length].concat(this.models));
} }
@ -412,22 +436,10 @@
pageCol.at(pageSize) : pageCol.at(pageSize) :
null; null;
if (modelToRemove) { if (modelToRemove) {
var addHandlers = collection._events.add || [], var popOptions = {onAdd: true};
popOptions = {onAdd: true}; runOnceAtLastHandler(collection, event, function () {
if (addHandlers.length) { pageCol.remove(modelToRemove, popOptions);
var lastAddHandler = addHandlers[addHandlers.length - 1]; });
var oldCallback = lastAddHandler.callback;
lastAddHandler.callback = function () {
try {
oldCallback.apply(this, arguments);
pageCol.remove(modelToRemove, popOptions);
}
finally {
lastAddHandler.callback = oldCallback;
}
};
}
else pageCol.remove(modelToRemove, popOptions);
} }
} }
} }
@ -442,20 +454,25 @@
} }
else { else {
var totalPages = state.totalPages = ceil(state.totalRecords / pageSize); var totalPages = state.totalPages = ceil(state.totalRecords / pageSize);
state.lastPage = firstPage === 0 ? totalPages - 1 : totalPages; state.lastPage = firstPage === 0 ? totalPages - 1 : totalPages || firstPage;
if (state.currentPage > totalPages) state.currentPage = state.lastPage; if (state.currentPage > totalPages) state.currentPage = state.lastPage;
} }
pageCol.state = pageCol._checkState(state); pageCol.state = pageCol._checkState(state);
var nextModel, removedIndex = options.index; var nextModel, removedIndex = options.index;
if (collection == pageCol) { if (collection == pageCol) {
if (nextModel = fullCol.at(pageEnd)) pageCol.push(nextModel); if (nextModel = fullCol.at(pageEnd)) {
runOnceAtLastHandler(pageCol, event, function () {
pageCol.push(nextModel);
});
}
fullCol.remove(model); fullCol.remove(model);
} }
else if (removedIndex >= pageStart && removedIndex < pageEnd) { else if (removedIndex >= pageStart && removedIndex < pageEnd) {
pageCol.remove(model); pageCol.remove(model);
nextModel = fullCol.at(currentPage * (pageSize + removedIndex)); var at = removedIndex + 1
if (nextModel) pageCol.push(nextModel); nextModel = fullCol.at(at) || fullCol.last();
if (nextModel) pageCol.add(nextModel, {at: at});
} }
} }
else delete options.onAdd; else delete options.onAdd;
@ -466,13 +483,13 @@
collection = model; collection = model;
// Reset that's not a result of getPage // Reset that's not a result of getPage
if (collection === pageCol && options.from == null && if (collection == pageCol && options.from == null &&
options.to == null) { options.to == null) {
var head = fullCol.models.slice(0, pageStart); var head = fullCol.models.slice(0, pageStart);
var tail = fullCol.models.slice(pageStart + pageCol.models.length); var tail = fullCol.models.slice(pageStart + pageCol.models.length);
fullCol.reset(head.concat(pageCol.models).concat(tail), options); fullCol.reset(head.concat(pageCol.models).concat(tail), options);
} }
else if (collection === fullCol) { else if (collection == fullCol) {
if (!(state.totalRecords = fullCol.models.length)) { if (!(state.totalRecords = fullCol.models.length)) {
state.totalRecords = null; state.totalRecords = null;
state.totalPages = null; state.totalPages = null;
@ -551,7 +568,7 @@
throw new RangeError("`firstPage must be 0 or 1`"); throw new RangeError("`firstPage must be 0 or 1`");
} }
state.lastPage = firstPage === 0 ? max(0, totalPages - 1) : totalPages; state.lastPage = firstPage === 0 ? max(0, totalPages - 1) : totalPages || firstPage;
if (mode == "infinite") { if (mode == "infinite") {
if (!links[currentPage + '']) { if (!links[currentPage + '']) {
@ -561,6 +578,8 @@
else if (currentPage < firstPage || else if (currentPage < firstPage ||
(totalPages > 0 && (totalPages > 0 &&
(firstPage ? currentPage > totalPages : currentPage >= totalPages))) { (firstPage ? currentPage > totalPages : currentPage >= totalPages))) {
var op = firstPage ? ">=" : ">";
throw new RangeError("`currentPage` must be firstPage <= currentPage " + throw new RangeError("`currentPage` must be firstPage <= currentPage " +
(firstPage ? ">" : ">=") + (firstPage ? ">" : ">=") +
" totalPages if " + firstPage + "-based. Got " + " totalPages if " + firstPage + "-based. Got " +
@ -681,7 +700,7 @@
var fullCollection = this.fullCollection; var fullCollection = this.fullCollection;
var handlers = this._handlers = this._handlers || {}, handler; var handlers = this._handlers = this._handlers || {}, handler;
if (mode != "server" && !fullCollection) { if (mode != "server" && !fullCollection) {
fullCollection = this._makeFullCollection(options.models || []); fullCollection = this._makeFullCollection(options.models || [], options);
fullCollection.pageableCollection = this; fullCollection.pageableCollection = this;
this.fullCollection = fullCollection; this.fullCollection = fullCollection;
var allHandler = this._makeCollectionEventHandler(this, fullCollection); var allHandler = this._makeCollectionEventHandler(this, fullCollection);
@ -856,7 +875,8 @@
[]; [];
if ((mode == "client" || (mode == "infinite" && !_isEmpty(pageModels))) && if ((mode == "client" || (mode == "infinite" && !_isEmpty(pageModels))) &&
!options.fetch) { !options.fetch) {
return this.reset(pageModels, _omit(options, "fetch")); this.reset(pageModels, _omit(options, "fetch"));
return this;
} }
if (mode == "infinite") options.url = this.links[pageNum]; if (mode == "infinite") options.url = this.links[pageNum];
@ -1310,8 +1330,8 @@
this.comparator = comparator; this.comparator = comparator;
} }
if (delComp) delete this.comparator; if (delComp) this.comparator = null;
if (delFullComp && fullCollection) delete fullCollection.comparator; if (delFullComp && fullCollection) fullCollection.comparator = null;
return this; return this;
} }

@ -2,11 +2,13 @@
define( define(
[ [
'Series/EpisodeModel', 'Series/EpisodeModel',
'backbone.pageable' 'backbone.pageable',
], function (EpisodeModel, PagableCollection) { 'Mixins/AsPersistedStateCollection'
return PagableCollection.extend({ ], function (EpisodeModel, PagableCollection, AsPersistedStateCollection) {
var collection = PagableCollection.extend({
url : window.NzbDrone.ApiRoot + '/missing', url : window.NzbDrone.ApiRoot + '/missing',
model: EpisodeModel, model: EpisodeModel,
tableName: 'missing',
state: { state: {
pageSize: 15, pageSize: 15,
@ -38,4 +40,6 @@ define(
return resp; return resp;
} }
}); });
return AsPersistedStateCollection.call(collection);
}); });

@ -4,7 +4,7 @@ define(
'underscore', 'underscore',
'marionette', 'marionette',
'backgrid', 'backgrid',
'Missing/Collection', 'Missing/MissingCollection',
'Cells/SeriesTitleCell', 'Cells/SeriesTitleCell',
'Cells/EpisodeNumberCell', 'Cells/EpisodeNumberCell',
'Cells/EpisodeTitleCell', 'Cells/EpisodeTitleCell',
@ -121,7 +121,6 @@ define(
] ]
}; };
this.toolbar.show(new ToolbarLayout({ this.toolbar.show(new ToolbarLayout({
left : left :
[ [

@ -0,0 +1,69 @@
'use strict';
define(
['underscore', 'Config'],
function (_, Config) {
return function () {
var originalInit = this.prototype.initialize;
this.prototype.initialize = function (options) {
options = options || {};
if (options.tableName) {
this.tableName = options.tableName;
}
if (!this.tableName && !options.tableName) {
throw 'tableName is required';
}
_setInitialState.call(this);
this.on('backgrid:sort', _storeStateFromBackgrid, this);
this.on('drone:sort', _storeState, this);
if (originalInit) {
originalInit.call(this, options);
}
};
var _setInitialState = function () {
var key = Config.getValue('{0}.sortKey'.format(this.tableName), this.state.sortKey);
var direction = Config.getValue('{0}.sortDirection'.format(this.tableName), this.state.order);
var order = parseInt(direction, 10);
this.state.sortKey = key;
this.state.order = order;
};
var _storeStateFromBackgrid = function (column, sortDirection) {
var order = _convertDirectionToInt(sortDirection);
var sortKey = column.has('sortValue') && _.isString(column.get('sortValue')) ? column.get('sortValue') : column.get('name');
Config.setValue('{0}.sortKey'.format(this.tableName), sortKey);
Config.setValue('{0}.sortDirection'.format(this.tableName), order);
};
var _storeState = function (sortModel, sortDirection) {
var order = _convertDirectionToInt(sortDirection);
var sortKey = sortModel.get('name');
Config.setValue('{0}.sortKey'.format(this.tableName), sortKey);
Config.setValue('{0}.sortDirection'.format(this.tableName), order);
};
var _convertDirectionToInt = function (dir) {
if (dir === 'ascending') {
return '-1';
}
return '1';
};
return this;
};
}
);

@ -94,6 +94,8 @@ define(
model.set('rootFolderPath', rootFolderPath.get('path')); model.set('rootFolderPath', rootFolderPath.get('path'));
} }
model.edited = true;
}); });
SeriesCollection.save(); SeriesCollection.save();
@ -150,6 +152,7 @@ define(
SeriesCollection.each(function (model) { SeriesCollection.each(function (model) {
model.trigger('backgrid:select', model, false); model.trigger('backgrid:select', model, false);
model.edited = false;
}); });
} }
}); });

@ -68,7 +68,7 @@ define(
cell : QualityProfileCell cell : QualityProfileCell
}, },
{ {
name : 'monitored', name : 'seasonFolder',
label: 'Season Folder', label: 'Season Folder',
cell : SeasonFolderCell cell : SeasonFolderCell
}, },

@ -13,12 +13,12 @@ define(
'Cells/QualityProfileCell', 'Cells/QualityProfileCell',
'Cells/EpisodeProgressCell', 'Cells/EpisodeProgressCell',
'Cells/SeriesActionsCell', 'Cells/SeriesActionsCell',
'Shared/Grid/DateHeaderCell',
'Cells/SeriesStatusCell', 'Cells/SeriesStatusCell',
'Series/Index/FooterView', 'Series/Index/FooterView',
'Series/Index/FooterModel', 'Series/Index/FooterModel',
'Shared/Toolbar/ToolbarLayout', 'Shared/Toolbar/ToolbarLayout',
'underscore' 'underscore',
'moment'
], function (Marionette, ], function (Marionette,
Backgrid, Backgrid,
PosterCollectionView, PosterCollectionView,
@ -31,68 +31,75 @@ define(
QualityProfileCell, QualityProfileCell,
EpisodeProgressCell, EpisodeProgressCell,
SeriesActionsCell, SeriesActionsCell,
DateHeaderCell,
SeriesStatusCell, SeriesStatusCell,
FooterView, FooterView,
FooterModel, FooterModel,
ToolbarLayout, ToolbarLayout,
_) { _,
Moment) {
return Marionette.Layout.extend({ return Marionette.Layout.extend({
template: 'Series/Index/SeriesIndexLayoutTemplate', template: 'Series/Index/SeriesIndexLayoutTemplate',
regions: { regions: {
seriesRegion: '#x-series', seriesRegion : '#x-series',
toolbar : '#x-toolbar', toolbar : '#x-toolbar',
footer : '#x-series-footer' footer : '#x-series-footer'
}, },
columns: columns: [
[ {
{ name : 'statusWeight',
name : 'statusWeight', label : '',
label : '', cell : SeriesStatusCell
cell : SeriesStatusCell },
}, {
{ name : 'title',
name : 'title', label : 'Title',
label : 'Title', cell : SeriesTitleCell,
cell : SeriesTitleCell, cellValue: 'this'
cellValue: 'this' },
}, {
{ name : 'seasonCount',
name : 'seasonCount', label: 'Seasons',
label: 'Seasons', cell : 'integer'
cell : 'integer' },
}, {
{ name : 'qualityProfileId',
name : 'qualityProfileId', label: 'Quality',
label: 'Quality', cell : QualityProfileCell
cell : QualityProfileCell },
}, {
{ name : 'network',
name : 'network', label: 'Network',
label: 'Network', cell : 'string'
cell : 'string' },
}, {
{ name : 'nextAiring',
name : 'nextAiring', label : 'Next Airing',
label : 'Next Airing', cell : RelativeDateCell,
cell : RelativeDateCell, sortValue : function (model) {
headerCell: DateHeaderCell var nextAiring = model.get('nextAiring');
},
{ if (!nextAiring) {
name : 'percentOfEpisodes', return Number.MAX_VALUE;
label : 'Episodes', }
cell : EpisodeProgressCell,
className: 'episode-progress-cell' return Moment(nextAiring).unix();
},
{
name : 'this',
label : '',
sortable: false,
cell : SeriesActionsCell
} }
], },
{
name : 'percentOfEpisodes',
label : 'Episodes',
cell : EpisodeProgressCell,
className: 'episode-progress-cell'
},
{
name : 'this',
label : '',
sortable: false,
cell : SeriesActionsCell
}
],
leftSideButtons: { leftSideButtons: {
type : 'default', type : 'default',
@ -130,6 +137,86 @@ define(
] ]
}, },
sortingOptions: {
type : 'sorting',
storeState : false,
viewCollection: SeriesCollection,
items :
[
{
title: 'Title',
name : 'title'
},
{
title: 'Seasons',
name : 'seasonCount'
},
{
title: 'Quality',
name : 'qualityProfileId'
},
{
title: 'Network',
name : 'network'
},
{
title : 'Next Airing',
name : 'nextAiring',
sortValue : function (model) {
var nextAiring = model.get('nextAiring');
if (!nextAiring) {
return Number.MAX_VALUE;
}
return Moment(nextAiring).unix();
}
},
{
title: 'Episodes',
name : 'percentOfEpisodes'
}
]
},
initialize: function () {
this.seriesCollection = SeriesCollection;
this.listenTo(SeriesCollection, 'sync', this._renderView);
this.listenTo(SeriesCollection, 'remove', this._renderView);
this.viewButtons = {
type : 'radio',
storeState : true,
menuKey : 'seriesViewMode',
defaultAction: 'listView',
items :
[
{
key : 'posterView',
title : '',
tooltip : 'Posters',
icon : 'icon-th-large',
callback: this._showPosters
},
{
key : 'listView',
title : '',
tooltip : 'Overview List',
icon : 'icon-th-list',
callback: this._showList
},
{
key : 'tableView',
title : '',
tooltip : 'Table',
icon : 'icon-table',
callback: this._showTable
}
]
};
},
_showTable: function () { _showTable: function () {
this.currentView = new Backgrid.Grid({ this.currentView = new Backgrid.Grid({
collection: SeriesCollection, collection: SeriesCollection,
@ -137,25 +224,19 @@ define(
className : 'table table-hover' className : 'table table-hover'
}); });
this._renderView();
this._fetchCollection(); this._fetchCollection();
}, },
_showList: function () { _showList: function () {
this.currentView = new ListCollectionView(); this.currentView = new ListCollectionView({ collection: SeriesCollection });
this._fetchCollection();
},
_showPosters: function () {
this.currentView = new PosterCollectionView();
this._fetchCollection(); this._fetchCollection();
}, },
initialize: function () { _showPosters: function () {
this.seriesCollection = SeriesCollection; this.currentView = new PosterCollectionView({ collection: SeriesCollection });
this.listenTo(SeriesCollection, 'sync', this._renderView); this._fetchCollection();
this.listenTo(SeriesCollection, 'remove', this._renderView);
}, },
_renderView: function () { _renderView: function () {
@ -165,7 +246,6 @@ define(
this.toolbar.close(); this.toolbar.close();
} }
else { else {
this.currentView.collection = SeriesCollection;
this.seriesRegion.show(this.currentView); this.seriesRegion.show(this.currentView);
this._showToolbar(); this._showToolbar();
@ -188,42 +268,18 @@ define(
return; return;
} }
var viewButtons = { var rightButtons = [
type : 'radio', this.viewButtons
storeState : true, ];
menuKey : 'seriesViewMode',
defaultAction: 'listView', if (this.showSortingButton) {
items : rightButtons.splice(0, 0, this.sortingOptions);
[ }
{
key : 'posterView', rightButtons.splice(0, 0, this.sortingOptions);
title : '',
tooltip : 'Posters',
icon : 'icon-th-large',
callback: this._showPosters
},
{
key : 'listView',
title : '',
tooltip : 'Overview List',
icon : 'icon-th-list',
callback: this._showList
},
{
key : 'tableView',
title : '',
tooltip : 'Table',
icon : 'icon-table',
callback: this._showTable
}
]
};
this.toolbar.show(new ToolbarLayout({ this.toolbar.show(new ToolbarLayout({
right : right : rightButtons,
[
viewButtons
],
left : left :
[ [
this.leftSideButtons this.leftSideButtons

@ -3,22 +3,24 @@ define(
[ [
'underscore', 'underscore',
'backbone', 'backbone',
'backbone.pageable',
'Series/SeriesModel', 'Series/SeriesModel',
'api!series' 'api!series',
], function (_, Backbone, SeriesModel, SeriesData) { 'Mixins/AsPersistedStateCollection'
var Collection = Backbone.Collection.extend({ ], function (_, Backbone, PageableCollection, SeriesModel, SeriesData, AsPersistedStateCollection) {
var Collection = PageableCollection.extend({
url : window.NzbDrone.ApiRoot + '/series', url : window.NzbDrone.ApiRoot + '/series',
model: SeriesModel, model: SeriesModel,
tableName: 'series',
comparator: function (model) {
return model.get('title');
},
state: { state: {
sortKey: 'title', sortKey: 'title',
order : -1 order : -1,
pageSize: 1000
}, },
mode: 'client',
save: function () { save: function () {
var self = this; var self = this;
@ -31,7 +33,7 @@ define(
toJSON: function() toJSON: function()
{ {
return self.filter(function (model) { return self.filter(function (model) {
return model.hasChanged(); return model.edited;
}); });
} }
}); });
@ -45,6 +47,7 @@ define(
} }
}); });
var collection = new Collection(SeriesData); var MixedIn = AsPersistedStateCollection.call(Collection);
var collection = new MixedIn(SeriesData);
return collection; return collection;
}); });

@ -1,66 +0,0 @@
'use strict';
define(
[
'backgrid',
'Shared/Grid/HeaderCell'
], function (Backgrid, NzbDroneHeaderCell) {
Backgrid.DateHeaderCell = NzbDroneHeaderCell.extend({
events: {
'click': 'onClick'
},
onClick: function (e) {
e.preventDefault();
var self = this;
var columnName = this.column.get('name');
if (this.column.get('sortable')) {
if (this.direction() === 'ascending') {
this.sort(columnName, 'descending', function (left, right) {
var leftVal = left.get(columnName);
var rightVal = right.get(columnName);
return self._comparator(leftVal, rightVal);
});
}
else {
this.sort(columnName, 'ascending', function (left, right) {
var leftVal = left.get(columnName);
var rightVal = right.get(columnName);
return self._comparator(rightVal, leftVal);
});
}
}
},
_comparator: function (leftVal, rightVal) {
if (!leftVal && !rightVal) {
return 0;
}
if (!leftVal) {
return -1;
}
if (!rightVal) {
return 1;
}
if (leftVal === rightVal) {
return 0;
}
if (leftVal > rightVal) {
return -1;
}
return 1;
}
});
return Backgrid.DateHeaderCell;
});

@ -6,95 +6,134 @@ define(
], function (Backgrid) { ], function (Backgrid) {
Backgrid.NzbDroneHeaderCell = Backgrid.HeaderCell.extend({ Backgrid.NzbDroneHeaderCell = Backgrid.HeaderCell.extend({
events: { events: {
'click': 'onClick' 'click': 'onClick'
}, },
_originalInit: Backgrid.HeaderCell.prototype.initialize,
initialize: function (options) {
this._originalInit.call(this, options);
this.listenTo(this.collection, 'drone:sort', this.render);
},
render: function () { render: function () {
this.$el.empty(); this.$el.empty();
this.$el.append(this.column.get('label')); this.$el.append(this.column.get('label'));
if (this.column.get('sortable')) { var column = this.column;
var sortable = Backgrid.callByNeed(column.sortable(), column, this.collection);
if (sortable)
{
this.$el.addClass('sortable'); this.$el.addClass('sortable');
this.$el.append(' <i class="pull-right"></i>'); this.$el.append(' <i class="pull-right"></i>');
}
//Do we need this?
this.$el.addClass(column.get('name'));
if (this.collection.state) { this.delegateEvents();
var sortKey = this.collection.state.sortKey; this.direction(column.get('direction'));
var sortDir = this._convertIntToDirection(this.collection.state.order);
if (this.collection.state) {
var key = this.collection.state.sortKey;
var order = this.collection.state.order;
if (key === this.column.get('name')) {
this._setSortIcon(order);
}
if (sortKey === this.column.get('name')) { else {
this.$el.children('i').addClass(this._convertDirectionToIcon(sortDir)); this._removeSortIcon();
this._direction = sortDir;
}
} }
} }
this.delegateEvents();
return this; return this;
}, },
direction: function (dir) { direction: function (dir) {
this.$el.children('i').removeClass('icon-sort-up icon-sort-down');
if (arguments.length) { if (arguments.length) {
if (this._direction) { if (dir)
this.$el.children('i').removeClass(this._convertDirectionToIcon(this._direction)); {
this._setSortIcon(dir);
} }
if (dir) {
this.$el.children('i').addClass(this._convertDirectionToIcon(dir)); this.column.set('direction', dir);
}
var columnDirection = this.column.get('direction');
if (!columnDirection && this.collection.state) {
var key = this.collection.state.sortKey;
var order = this.collection.state.order;
if (key === this.column.get('name')) {
columnDirection = order;
} }
this._direction = dir;
} }
return this._direction; return columnDirection;
}, },
onClick: function (e) { onClick: function (e) {
e.preventDefault(); e.preventDefault();
var columnName = this.column.get('name'); var collection = this.collection;
var event = 'backgrid:sort';
if (this.column.get('sortable')) {
if (this.direction() === 'ascending') { function toggleSort(header, col) {
this.sort(columnName, 'descending', function (left, right) { collection.state.sortKey = col.get('name');
var leftVal = left.get(columnName); var direction = header.direction();
var rightVal = right.get(columnName); if (direction === 'ascending' || direction === -1)
if (leftVal === rightVal) { {
return 0; collection.state.order = 'descending';
} collection.trigger(event, col, 'descending');
else if (leftVal > rightVal) {
return -1;
}
return 1;
});
} }
else { else
this.sort(columnName, 'ascending', function (left, right) { {
var leftVal = left.get(columnName); collection.state.order = 'ascending';
var rightVal = right.get(columnName); collection.trigger(event, col, 'ascending');
if (leftVal === rightVal) {
return 0;
}
else if (leftVal < rightVal) {
return -1;
}
return 1;
});
} }
} }
var column = this.column;
var sortable = Backgrid.callByNeed(column.sortable(), column, this.collection);
if (sortable) {
toggleSort(this, column);
}
},
_resetCellDirection: function (columnToSort, direction) {
if (columnToSort !== this.column)
{
this.direction(null);
}
else
{
this.direction(direction);
}
}, },
_convertDirectionToIcon: function (dir) { _convertDirectionToIcon: function (dir) {
if (dir === 'ascending') { if (dir === 'ascending' || dir === -1) {
return 'icon-sort-up'; return 'icon-sort-up';
} }
return 'icon-sort-down'; return 'icon-sort-down';
}, },
_convertIntToDirection: function (dir) { _setSortIcon: function (dir) {
if (dir === '-1') { this._removeSortIcon();
return 'ascending'; this.$el.children('i').addClass(this._convertDirectionToIcon(dir));
} },
return 'descending'; _removeSortIcon: function () {
this.$el.children('i').removeClass('icon-sort-up icon-sort-down');
} }
}); });

@ -1,13 +1,29 @@
'use strict'; 'use strict';
define( define(
[ [
'underscore',
'backbone' 'backbone'
], function (Backbone) { ], function (_, Backbone) {
return Backbone.Model.extend({ return Backbone.Model.extend({
defaults: { defaults: {
'target' : '/nzbdrone/route', 'target' : '/nzbdrone/route',
'title' : '', 'title' : '',
'active' : false, 'active' : false,
'tooltip': undefined } 'tooltip': undefined
},
sortValue: function () {
var sortValue = this.get('sortValue');
if (_.isString(sortValue)) {
return this[sortValue];
}
else if (_.isFunction(sortValue)) {
return sortValue;
}
return function (model, colName) {
return model.get(colName);
};
}
}); });
}); });

@ -13,7 +13,6 @@ define(
'click': 'onClick' 'click': 'onClick'
}, },
initialize: function () { initialize: function () {
this.storageKey = this.model.get('menuKey') + ':' + this.model.get('key'); this.storageKey = this.model.get('menuKey') + ':' + this.model.get('key');
@ -53,7 +52,6 @@ define(
callback.call(this.model.ownerContext); callback.call(this.model.ownerContext);
} }
} }
}); });
}); });

@ -0,0 +1,87 @@
'use strict';
define(
[
'backbone.pageable',
'marionette',
'Shared/Toolbar/Sorting/SortingButtonView'
], function (PageableCollection, Marionette, ButtonView) {
return Marionette.CompositeView.extend({
itemView : ButtonView,
template : 'Shared/Toolbar/Sorting/SortingButtonCollectionViewTemplate',
itemViewContainer: '.dropdown-menu',
initialize: function (options) {
this.viewCollection = options.viewCollection;
this.listenTo(this.viewCollection, 'drone:sort', this.sort);
},
itemViewOptions: function () {
return {
viewCollection: this.viewCollection
};
},
sort: function (sortModel, sortDirection) {
var collection = this.viewCollection;
var order;
if (sortDirection === 'ascending') {
order = -1;
}
else if (sortDirection === 'descending') {
order = 1;
}
else {
order = null;
}
var comparator = this.makeComparator(sortModel.get('name'), order,
order ?
sortModel.sortValue() :
function (model) {
return model.cid;
});
if (PageableCollection &&
collection instanceof PageableCollection) {
collection.setSorting(order && sortModel.get('name'), order,
{sortValue: sortModel.sortValue()});
if (collection.mode === 'client') {
if (collection.fullCollection.comparator === null) {
collection.fullCollection.comparator = comparator;
}
collection.fullCollection.sort();
}
else {
collection.fetch({reset: true});
}
}
else {
collection.comparator = comparator;
collection.sort();
}
return this;
},
makeComparator: function (attr, order, func) {
return function (left, right) {
// extract the values from the models
var l = func(left, attr), r = func(right, attr), t;
// if descending order, swap left and right
if (order === 1) t = l, l = r, r = t;
// compare as usual
if (l === r) return 0;
else if (l < r) return -1;
return 1;
};
}
});
});

@ -0,0 +1,8 @@
<div class="btn-group sorting-buttons">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
Sort <span class="caret"></span>
</a>
<ul class="dropdown-menu">
</ul>
</div>

@ -0,0 +1,84 @@
'use strict';
define(
[
'backbone',
'marionette',
'underscore'
], function (Backbone, Marionette, _) {
return Marionette.ItemView.extend({
template : 'Shared/Toolbar/Sorting/SortingButtonViewTemplate',
tagName : 'li',
ui: {
icon: 'i'
},
events: {
'click': 'onClick'
},
initialize: function (options) {
this.viewCollection = options.viewCollection;
this.listenTo(this.viewCollection, 'drone:sort', this.render);
this.listenTo(this.viewCollection, 'backgrid:sort', this.render);
},
onRender: function () {
if (this.viewCollection.state) {
var key = this.viewCollection.state.sortKey;
var order = this.viewCollection.state.order;
if (key === this.model.get('name')) {
this._setSortIcon(order);
}
else {
this._removeSortIcon();
}
}
},
onClick: function (e) {
e.preventDefault();
var collection = this.viewCollection;
var event = 'drone:sort';
collection.state.sortKey = this.model.get('name');
var direction = collection.state.order;
if (direction === 'ascending' || direction === -1)
{
collection.state.order = 'descending';
collection.trigger(event, this.model, 'descending');
}
else
{
collection.state.order = 'ascending';
collection.trigger(event, this.model, 'ascending');
}
},
_convertDirectionToIcon: function (dir) {
if (dir === 'ascending' || dir === -1) {
return 'icon-sort-up';
}
return 'icon-sort-down';
},
_setSortIcon: function (dir) {
this._removeSortIcon();
this.ui.icon.addClass(this._convertDirectionToIcon(dir));
},
_removeSortIcon: function () {
this.ui.icon.removeClass('icon-sort-up icon-sort-down');
}
});
});

@ -0,0 +1,4 @@
<a href="#">
<span class="sorting-title">{{title}}</span>
<i class=""></i>
</a>

@ -6,8 +6,9 @@ define(
'Shared/Toolbar/ButtonModel', 'Shared/Toolbar/ButtonModel',
'Shared/Toolbar/Radio/RadioButtonCollectionView', 'Shared/Toolbar/Radio/RadioButtonCollectionView',
'Shared/Toolbar/Button/ButtonCollectionView', 'Shared/Toolbar/Button/ButtonCollectionView',
'Shared/Toolbar/Sorting/SortingButtonCollectionView',
'underscore' 'underscore'
], function (Marionette, ButtonCollection, ButtonModel, RadioButtonCollectionView, ButtonCollectionView,_) { ], function (Marionette, ButtonCollection, ButtonModel, RadioButtonCollectionView, ButtonCollectionView, SortingButtonCollectionView, _) {
return Marionette.Layout.extend({ return Marionette.Layout.extend({
template: 'Shared/Toolbar/ToolbarLayoutTemplate', template: 'Shared/Toolbar/ToolbarLayoutTemplate',
@ -78,6 +79,15 @@ define(
}); });
break; break;
} }
case 'sorting':
{
buttonGroupView = new SortingButtonCollectionView({
collection : groupCollection,
menu : buttonGroup,
viewCollection: buttonGroup.viewCollection
});
break;
}
default : default :
{ {
buttonGroupView = new ButtonCollectionView({ buttonGroupView = new ButtonCollectionView({

@ -1,8 +1,8 @@
<div class="pull-left page-toolbar"> <div class="pull-left page-toolbar">
<div class="x-toolbar-left-1"/> <div class="toolbar-group x-toolbar-left-1"/>
<div class="x-toolbar-left-2"/> <div class="toolbar-group x-toolbar-left-2"/>
</div> </div>
<div class="pull-right page-toolbar"> <div class="pull-right page-toolbar">
<div class="x-toolbar-right-1"/> <div class="toolbar-group x-toolbar-right-1"/>
<div class="x-toolbar-right-2"/> <div class="toolbar-group x-toolbar-right-2"/>
</div> </div>

@ -1,10 +1,16 @@
'use strict'; 'use strict';
define(['backbone.pageable', 'System/Logs/LogsModel'], define(
function (PagableCollection, LogsModel) { [
return PagableCollection.extend({ 'backbone.pageable',
'System/Logs/LogsModel',
'Mixins/AsPersistedStateCollection'
],
function (PagableCollection, LogsModel, AsPersistedStateCollection) {
var collection = PagableCollection.extend({
url : window.NzbDrone.ApiRoot + '/log', url : window.NzbDrone.ApiRoot + '/log',
model: LogsModel, model: LogsModel,
tableName: 'logs',
state: { state: {
pageSize: 50, pageSize: 50,
@ -36,4 +42,6 @@ define(['backbone.pageable', 'System/Logs/LogsModel'],
return resp; return resp;
} }
}); });
return AsPersistedStateCollection.call(collection);
}); });

@ -165,7 +165,8 @@ require.config({
renderable: true, renderable: true,
formatter : undefined, formatter : undefined,
cell : undefined, cell : undefined,
headerCell: 'NzbDrone' headerCell: 'NzbDrone',
sortType : 'toggle'
}; };
}); });

Loading…
Cancel
Save