Allowed ability to set default sort_by/order on listings

- improved performance of listings (less refresh of items)
- fixed sorting issue where the order would not be reversed
This commit is contained in:
Jonathan Labreuille
2016-06-17 17:26:17 +02:00
parent 4bb1acf493
commit 7af2775972
4 changed files with 879 additions and 895 deletions

View File

@@ -1,21 +1,15 @@
define([
'react',
'classnames',
'mailpoet'
], function(
React,
classNames,
MailPoet
) {
import MailPoet from 'mailpoet'
import React from 'react'
import classNames from 'classnames'
var ListingHeader = React.createClass({
const ListingHeader = React.createClass({
handleSelectItems: function() {
return this.props.onSelectItems(
this.refs.toggle.checked
);
},
render: function() {
var columns = this.props.columns.map(function(column, index) {
const columns = this.props.columns.map(function(column, index) {
column.is_primary = (index === 0);
column.sorted = (this.props.sort_by === column.name)
? this.props.sort_order
@@ -29,7 +23,7 @@ define([
);
}.bind(this));
var checkbox = false;
let checkbox;
if(this.props.is_selectable === true) {
checkbox = (
@@ -55,27 +49,27 @@ define([
</tr>
);
}
});
});
var ListingColumn = React.createClass({
const ListingColumn = React.createClass({
handleSort: function() {
var sort_by = this.props.column.name,
sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
const sort_by = this.props.column.name;
const sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
this.props.onSort(sort_by, sort_order);
},
render: function() {
var classes = classNames(
const classes = classNames(
'manage-column',
{ 'column-primary': this.props.column.is_primary },
{ 'sortable': this.props.column.sortable },
this.props.column.sorted,
{ 'sorted': (this.props.sort_by === this.props.column.name) }
);
var label;
let label;
if(this.props.column.sortable === true) {
label = (
<a onClick={this.handleSort}>
<a onClick={ this.handleSort }>
<span>{ this.props.column.label }</span>
<span className="sorting-indicator"></span>
</a>
@@ -92,7 +86,6 @@ define([
>{label}</th>
);
}
});
return ListingHeader;
});
module.exports = ListingHeader;

View File

@@ -1,33 +1,16 @@
define(
[
'mailpoet',
'jquery',
'react',
'react-router',
'classnames',
'listing/bulk_actions.jsx',
'listing/header.jsx',
'listing/pages.jsx',
'listing/search.jsx',
'listing/groups.jsx',
'listing/filters.jsx'
],
function(
MailPoet,
jQuery,
React,
Router,
classNames,
ListingBulkActions,
ListingHeader,
ListingPages,
ListingSearch,
ListingGroups,
ListingFilters
) {
var Link = Router.Link;
import MailPoet from 'mailpoet'
import jQuery from 'jquery'
import React from 'react'
import { Router, Link } from 'react-router'
import classNames from 'classnames'
import ListingBulkActions from 'listing/bulk_actions.jsx'
import ListingHeader from 'listing/header.jsx'
import ListingPages from 'listing/pages.jsx'
import ListingSearch from 'listing/search.jsx'
import ListingGroups from 'listing/groups.jsx'
import ListingFilters from 'listing/filters.jsx'
var ListingItem = React.createClass({
const ListingItem = React.createClass({
getInitialState: function() {
return {
toggled: true
@@ -56,7 +39,7 @@ define(
render: function() {
var checkbox = false;
if(this.props.is_selectable === true) {
if (this.props.is_selectable === true) {
checkbox = (
<th className="check-column" scope="row">
<label className="screen-reader-text">{
@@ -77,18 +60,18 @@ define(
const custom_actions = this.props.item_actions;
let item_actions = false;
if(custom_actions.length > 0) {
if (custom_actions.length > 0) {
let is_first = true;
item_actions = custom_actions.map(function(action, index) {
if(action.display !== undefined) {
if(action.display(this.props.item) === false) {
if (action.display !== undefined) {
if (action.display(this.props.item) === false) {
return;
}
}
let custom_action = null;
if(action.name === 'trash') {
if (action.name === 'trash') {
custom_action = (
<span key={ 'action-'+index } className="trash">
{(!is_first) ? ' | ' : ''}
@@ -102,7 +85,7 @@ define(
</a>
</span>
);
} else if(action.refresh) {
} else if (action.refresh) {
custom_action = (
<span
onClick={ this.props.onRefreshItems }
@@ -111,7 +94,7 @@ define(
{ action.link(this.props.item) }
</span>
);
} else if(action.link) {
} else if (action.link) {
custom_action = (
<span
key={ 'action-'+index } className={ action.name }>
@@ -136,7 +119,7 @@ define(
);
}
if(custom_action !== null && is_first === true) {
if (custom_action !== null && is_first === true) {
is_first = false;
}
@@ -150,8 +133,10 @@ define(
);
}
if(this.props.group === 'trash') {
var actions = (
let actions;
if (this.props.group === 'trash') {
actions = (
<div>
<div className="row-actions">
<span>
@@ -183,7 +168,7 @@ define(
</div>
);
} else {
var actions = (
actions = (
<div>
<div className="row-actions">
{ item_actions }
@@ -197,7 +182,7 @@ define(
);
}
var row_classes = classNames({ 'is-expanded': !this.state.toggled })
const row_classes = classNames({ 'is-expanded': !this.state.toggled });
return (
<tr className={ row_classes }>
@@ -206,12 +191,12 @@ define(
</tr>
);
}
});
});
var ListingItems = React.createClass({
const ListingItems = React.createClass({
render: function() {
if(this.props.items.length === 0) {
if (this.props.items.length === 0) {
return (
<tbody>
<tr className="no-items">
@@ -231,8 +216,7 @@ define(
</tbody>
);
} else {
var selectAllClasses = classNames(
const select_all_classes = classNames(
'mailpoet_select_all',
{ 'mailpoet_hidden': (
this.props.selection === false
@@ -243,7 +227,7 @@ define(
return (
<tbody>
<tr className={ selectAllClasses }>
<tr className={ select_all_classes }>
<td colSpan={
this.props.columns.length
+ (this.props.is_selectable ? 1 : 0)
@@ -292,9 +276,9 @@ define(
);
}
}
});
});
var Listing = React.createClass({
const Listing = React.createClass({
contextTypes: {
router: React.PropTypes.object.isRequired
},
@@ -305,8 +289,8 @@ define(
page: 1,
count: 0,
limit: 10,
sort_by: 'id',
sort_order: 'desc',
sort_by: null,
sort_order: null,
items: [],
groups: [],
group: 'all',
@@ -318,7 +302,7 @@ define(
},
componentDidUpdate: function(prevProps, prevState) {
// reset group to "all" if trash gets emptied
if(
if (
// we were viewing the trash
(prevState.group === 'trash' && prevState.count > 0)
&&
@@ -332,15 +316,15 @@ define(
}
},
getParam: function(param) {
var regex = /(.*)\[(.*)\]/
var matches = regex.exec(param)
const regex = /(.*)\[(.*)\]/;
const matches = regex.exec(param);
return [matches[1], matches[2]]
},
initWithParams: function(params) {
let state = this.state || {}
let original_state = state
let state = this.state || {};
let original_state = state;
// check for url params
if(params.splat !== undefined) {
if (params.splat !== undefined) {
params.splat.split('/').map(param => {
let [key, value] = this.getParam(param);
switch(key) {
@@ -350,27 +334,38 @@ define(
let [k, v] = pair.split('=')
filters[k] = v
}
)
);
state.filter = filters
state.filter = filters;
break;
default:
state[key] = value
state[key] = value;
}
})
});
}
// default overrides
if(this.props.limit !== undefined) {
// defaults override
// limit per page
if (this.props.limit !== undefined) {
state.limit = Math.abs(~~this.props.limit);
}
// sort by
if (state.sort_by === null && this.props.sort_by !== undefined) {
state.sort_by = this.props.sort_by;
}
// sort order
if (state.sort_order === null && this.props.sort_order !== undefined) {
state.sort_order = this.props.sort_order;
}
this.setState(state, function() {
this.getItems();
}.bind(this));
},
setParams: function() {
var params = Object.keys(this.state)
let params = Object.keys(this.state)
.filter(key => {
return (
[
@@ -385,32 +380,33 @@ define(
})
.map(key => {
let value = this.state[key]
if(value === Object(value)) {
if (value === Object(value)) {
value = jQuery.param(value)
} else if(value === Boolean(value)) {
} else if (value === Boolean(value)) {
value = value.toString()
}
if(value !== '') {
if (value !== '') {
return `${key}[${value}]`
}
})
.filter(key => { return (key !== undefined) })
.join('/');
params = '/'+params
if(this.props.location) {
if(this.props.location.pathname !== params) {
params = '/' + params;
if (this.props.location) {
if (this.props.location.pathname !== params) {
this.context.router.push(`${params}`);
}
}
},
componentDidMount: function() {
if(this.isMounted()) {
if (this.isMounted()) {
const params = this.props.params || {}
this.initWithParams(params)
this.initWithParams(params);
if(this.props.auto_refresh) {
if (this.props.auto_refresh) {
jQuery(document).on('heartbeat-tick.mailpoet', function(e, data) {
this.getItems();
}.bind(this));
@@ -422,7 +418,7 @@ define(
this.initWithParams(params)
},
getItems: function() {
if(this.isMounted()) {
if (this.isMounted()) {
this.setState({ loading: true });
this.clearSelection();
@@ -448,7 +444,7 @@ define(
count: response.count || 0,
loading: false
}, function() {
if(this.props['onGetItems'] !== undefined) {
if (this.props['onGetItems'] !== undefined) {
this.props.onGetItems(
~~(this.state.groups[0]['count'])
);
@@ -468,7 +464,7 @@ define(
action: 'restore',
data: id
}).done(function(response) {
if(
if (
this.props.messages !== undefined
&& this.props.messages['onRestore'] !== undefined
) {
@@ -488,7 +484,7 @@ define(
action: 'trash',
data: id
}).done(function(response) {
if(
if (
this.props.messages !== undefined
&& this.props.messages['onTrash'] !== undefined
) {
@@ -508,7 +504,7 @@ define(
action: 'delete',
data: id
}).done(function(response) {
if(
if (
this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined
) {
@@ -528,7 +524,7 @@ define(
});
},
handleBulkAction: function(selected_ids, params, callback) {
if(
if (
this.state.selection === false
&& this.state.selected_ids.length === 0
&& selected_ids !== 'all'
@@ -547,7 +543,7 @@ define(
group: this.state.group,
search: this.state.search
}
if(selected_ids !== 'all') {
if (selected_ids !== 'all') {
data.listing.selection = selected_ids;
}
@@ -568,7 +564,6 @@ define(
selected_ids: []
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
handleSort: function(sort_by, sort_order = 'asc') {
@@ -577,17 +572,16 @@ define(
sort_order: sort_order,
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
handleSelectItem: function(id, is_checked) {
var selected_ids = this.state.selected_ids,
selection = false;
if(is_checked) {
if (is_checked) {
selected_ids = jQuery.merge(selected_ids, [ id ]);
// check whether all items on the page are selected
if(
if (
jQuery('tbody .check-column :checkbox:not(:checked)').length === 0
) {
selection = 'page';
@@ -602,7 +596,7 @@ define(
});
},
handleSelectItems: function(is_checked) {
if(is_checked === false) {
if (is_checked === false) {
this.clearSelection();
} else {
var selected_ids = this.state.items.map(function(item) {
@@ -616,7 +610,7 @@ define(
}
},
handleSelectAll: function() {
if(this.state.selection === 'all') {
if (this.state.selection === 'all') {
this.clearSelection();
} else {
this.setState({
@@ -637,7 +631,6 @@ define(
page: 1
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
handleGroup: function(group) {
@@ -651,7 +644,6 @@ define(
page: 1
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
handleSetPage: function(page) {
@@ -661,31 +653,30 @@ define(
selected_ids: []
}, function() {
this.setParams();
this.getItems();
}.bind(this));
},
handleRenderItem: function(item, actions) {
var render = this.props.onRenderItem(item, actions);
const render = this.props.onRenderItem(item, actions);
return render.props.children;
},
handleRefreshItems: function() {
this.getItems();
},
render: function() {
var items = this.state.items,
sort_by = this.state.sort_by,
sort_order = this.state.sort_order;
const items = this.state.items;
const sort_by = this.state.sort_by;
const sort_order = this.state.sort_order;
// columns
var columns = this.props.columns || [];
let columns = this.props.columns || [];
columns = columns.filter(function(column) {
return (column.display === undefined || !!(column.display) === true);
});
// bulk actions
var bulk_actions = this.props.bulk_actions || [];
let bulk_actions = this.props.bulk_actions || [];
if(this.state.group === 'trash' && bulk_actions.length > 0) {
if (this.state.group === 'trash' && bulk_actions.length > 0) {
bulk_actions = [
{
name: 'restore',
@@ -701,9 +692,9 @@ define(
}
// item actions
var item_actions = this.props.item_actions || [];
const item_actions = this.props.item_actions || [];
var table_classes = classNames(
const table_classes = classNames(
'mailpoet_listing_table',
'wp-list-table',
'widefat',
@@ -713,25 +704,25 @@ define(
);
// search
var search = (
let search = (
<ListingSearch
onSearch={ this.handleSearch }
search={ this.state.search }
/>
);
if(this.props.search === false) {
if (this.props.search === false) {
search = false;
}
// groups
var groups = (
let groups = (
<ListingGroups
groups={ this.state.groups }
group={ this.state.group }
onSelectGroup={ this.handleGroup }
/>
);
if(this.props.groups === false) {
if (this.props.groups === false) {
groups = false;
}
@@ -818,8 +809,6 @@ define(
</div>
);
}
});
});
return Listing;
}
);
module.exports = Listing;

View File

@@ -244,6 +244,8 @@ const SegmentList = React.createClass({
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
sort_by="name"
sort_order="asc"
/>
</div>
);

View File

@@ -27,8 +27,8 @@ class Handler {
// searching
'search' => (isset($data['search']) ? $data['search'] : null),
// sorting
'sort_by' => (isset($data['sort_by']) ? $data['sort_by'] : 'id'),
'sort_order' => (isset($data['sort_order']) ? $data['sort_order'] : 'asc'),
'sort_by' => (!empty($data['sort_by']) ? $data['sort_by'] : 'id'),
'sort_order' => (!empty($data['sort_order']) ? $data['sort_order'] : 'asc'),
// grouping
'group' => (isset($data['group']) ? $data['group'] : null),
// filters