Sorting on all series views is now working

New: Sorting is now persisted on a per page and browser basis
New: Series lists now support sorting on all views
This commit is contained in:
Mark McDowall 2013-12-27 00:31:34 -08:00
parent 4d6d477947
commit 6ba17782aa
13 changed files with 414 additions and 123 deletions

View File

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

View File

@ -1,8 +1,8 @@
'use strict';
define(
['Config'],
function (Config) {
['underscore', 'Config'],
function (_, Config) {
return function () {
@ -22,7 +22,8 @@ define(
_setInitialState.call(this);
this.on('backgrid:sort', _storeState, this);
this.on('backgrid:sort', _storeStateFromBackgrid, this);
this.on('drone:sort', _storeState, this);
if (originalInit) {
originalInit.call(this, options);
@ -38,9 +39,17 @@ define(
this.state.order = order;
};
var _storeState = function (column, sortDirection) {
var _storeStateFromBackgrid = function (column, sortDirection) {
var order = _convertDirectionToInt(sortDirection);
var sortKey = column.has('sortValue') ? column.get('sortValue') : column.get('name');
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);

View File

@ -41,66 +41,65 @@ define(
template: 'Series/Index/SeriesIndexLayoutTemplate',
regions: {
seriesRegion: '#x-series',
toolbar : '#x-toolbar',
footer : '#x-series-footer'
seriesRegion : '#x-series',
toolbar : '#x-toolbar',
footer : '#x-series-footer'
},
columns:
[
{
name : 'statusWeight',
label : '',
cell : SeriesStatusCell
},
{
name : 'title',
label : 'Title',
cell : SeriesTitleCell,
cellValue: 'this'
},
{
name : 'seasonCount',
label: 'Seasons',
cell : 'integer'
},
{
name : 'qualityProfileId',
label: 'Quality',
cell : QualityProfileCell
},
{
name : 'network',
label: 'Network',
cell : 'string'
},
{
name : 'nextAiring',
label : 'Next Airing',
cell : RelativeDateCell,
sortValue : function (model) {
var nextAiring = model.get('nextAiring');
columns: [
{
name : 'statusWeight',
label : '',
cell : SeriesStatusCell
},
{
name : 'title',
label : 'Title',
cell : SeriesTitleCell,
cellValue: 'this'
},
{
name : 'seasonCount',
label: 'Seasons',
cell : 'integer'
},
{
name : 'qualityProfileId',
label: 'Quality',
cell : QualityProfileCell
},
{
name : 'network',
label: 'Network',
cell : 'string'
},
{
name : 'nextAiring',
label : 'Next Airing',
cell : RelativeDateCell,
sortValue : function (model) {
var nextAiring = model.get('nextAiring');
if (!nextAiring) {
return Number.MAX_VALUE;
}
return Moment(nextAiring).unix();
if (!nextAiring) {
return Number.MAX_VALUE;
}
},
{
name : 'percentOfEpisodes',
label : 'Episodes',
cell : EpisodeProgressCell,
className: 'episode-progress-cell'
},
{
name : 'this',
label : '',
sortable: false,
cell : SeriesActionsCell
return Moment(nextAiring).unix();
}
],
},
{
name : 'percentOfEpisodes',
label : 'Episodes',
cell : EpisodeProgressCell,
className: 'episode-progress-cell'
},
{
name : 'this',
label : '',
sortable: false,
cell : SeriesActionsCell
}
],
leftSideButtons: {
type : 'default',
@ -138,25 +137,46 @@ define(
]
},
_showTable: function () {
this.currentView = new Backgrid.Grid({
collection: SeriesCollection,
columns : this.columns,
className : 'table table-hover'
});
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');
this._renderView();
this._fetchCollection();
},
if (!nextAiring) {
return Number.MAX_VALUE;
}
_showList: function () {
this.currentView = new ListCollectionView();
this._fetchCollection();
},
_showPosters: function () {
this.currentView = new PosterCollectionView();
this._fetchCollection();
return Moment(nextAiring).unix();
}
},
{
title: 'Episodes',
name : 'percentOfEpisodes'
}
]
},
initialize: function () {
@ -164,39 +184,8 @@ define(
this.listenTo(SeriesCollection, 'sync', this._renderView);
this.listenTo(SeriesCollection, 'remove', this._renderView);
},
_renderView: function () {
if (SeriesCollection.length === 0) {
this.seriesRegion.show(new EmptyView());
this.toolbar.close();
}
else {
this.currentView.collection = SeriesCollection;
this.seriesRegion.show(this.currentView);
this._showToolbar();
this._showFooter();
}
},
onShow: function () {
this._showToolbar();
this._renderView();
},
_fetchCollection: function () {
SeriesCollection.fetch();
},
_showToolbar: function () {
if (this.toolbar.currentView) {
return;
}
var viewButtons = {
this.viewButtons = {
type : 'radio',
storeState : true,
menuKey : 'seriesViewMode',
@ -226,12 +215,71 @@ define(
}
]
};
},
_showTable: function () {
this.currentView = new Backgrid.Grid({
collection: SeriesCollection,
columns : this.columns,
className : 'table table-hover'
});
this._fetchCollection();
},
_showList: function () {
this.currentView = new ListCollectionView({ collection: SeriesCollection });
this._fetchCollection();
},
_showPosters: function () {
this.currentView = new PosterCollectionView({ collection: SeriesCollection });
this._fetchCollection();
},
_renderView: function () {
if (SeriesCollection.length === 0) {
this.seriesRegion.show(new EmptyView());
this.toolbar.close();
}
else {
this.seriesRegion.show(this.currentView);
this._showToolbar();
this._showFooter();
}
},
onShow: function () {
this._showToolbar();
this._renderView();
},
_fetchCollection: function () {
SeriesCollection.fetch();
},
_showToolbar: function () {
if (this.toolbar.currentView) {
return;
}
var rightButtons = [
this.viewButtons
];
if (this.showSortingButton) {
rightButtons.splice(0, 0, this.sortingOptions);
}
rightButtons.splice(0, 0, this.sortingOptions);
this.toolbar.show(new ToolbarLayout({
right :
[
viewButtons
],
right : rightButtons,
left :
[
this.leftSideButtons

View File

@ -8,7 +8,7 @@ define(
'api!series',
'Mixins/AsPersistedStateCollection'
], function (_, Backbone, PageableCollection, SeriesModel, SeriesData, AsPersistedStateCollection) {
var Collection = Backbone.Collection.extend({
var Collection = PageableCollection.extend({
url : window.NzbDrone.ApiRoot + '/series',
model: SeriesModel,
tableName: 'series',

View File

@ -11,6 +11,14 @@ define(
'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 () {
this.$el.empty();
this.$el.append(this.column.get('label'));
@ -37,6 +45,10 @@ define(
if (key === this.column.get('name')) {
this._setSortIcon(order);
}
else {
this._removeSortIcon();
}
}
return this;

View File

@ -1,13 +1,29 @@
'use strict';
define(
[
'underscore',
'backbone'
], function (Backbone) {
], function (_, Backbone) {
return Backbone.Model.extend({
defaults: {
'target' : '/nzbdrone/route',
'title' : '',
'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);
};
}
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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