Major refactoring of listing/router/model relation

- updated Subscribers listing
- udpated Segments listing
- added Forms router
This commit is contained in:
Jonathan Labreuille
2015-10-29 15:30:24 +01:00
parent 103da61d45
commit a4cf2f9c76
20 changed files with 662 additions and 643 deletions

View File

@ -1,6 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom'
import Router from 'react-router'
import { Router, History } from 'react-router'
import MailPoet from 'mailpoet'
import Form from 'form/form.jsx'
@ -29,7 +29,7 @@ const messages = {
const FormForm = React.createClass({
mixins: [
Router.History
History
],
render() {
return (

View File

@ -1,6 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute, Link } from 'react-router'
import { Router, Route, IndexRoute } from 'react-router'
import FormList from 'forms/list.jsx'
import FormForm from 'forms/form.jsx'
import createHashHistory from 'history/lib/createHashHistory'

View File

@ -1,202 +1,178 @@
define(
[
'react',
'react-router',
'listing/listing.jsx',
'classnames',
'mailpoet'
],
function(
React,
Router,
Listing,
classNames,
MailPoet
) {
var columns = [
{
name: 'name',
label: 'Name',
sortable: true
},
{
name: 'created_at',
label: 'Created on',
sortable: true
}
];
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Link, History } from 'react-router'
import Listing from 'listing/listing.jsx'
import classNames from 'classnames'
import MailPoet from 'mailpoet'
var messages = {
onDelete: function(response) {
var count = ~~response.segments;
var message = null;
if(count === 1 || response === true) {
message = (
'1 segment was moved to the trash.'
);
} else if(count > 1) {
message = (
'%$1d segments were moved to the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onConfirmDelete: function(response) {
var count = ~~response.segments;
var message = null;
if(count === 1 || response === true) {
message = (
'1 segment was permanently deleted.'
);
} else if(count > 1) {
message = (
'%$1d segments were permanently deleted.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onRestore: function(response) {
var count = ~~response.segments;
var message = null;
if(count === 1 || response === true) {
message = (
'1 segment has been restored from the trash.'
);
} else if(count > 1) {
message = (
'%$1d segments have been restored from the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
};
var Link = Router.Link;
var item_actions = [
{
name: 'edit',
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>Edit</Link>
);
}
},
{
name: 'duplicate_segment',
refresh: true,
link: function(item) {
return (
<a
href="javascript:;"
onClick={ this.onDuplicate.bind(null, item) }
>Duplicate</a>
);
},
onDuplicate: function(item) {
MailPoet.Ajax.post({
endpoint: 'segments',
action: 'duplicate',
data: item.id
}).done(function() {
MailPoet.Notice.success(
('List "%$1s" has been duplicated.').replace('%$1s', item.name)
);
});
}
},
{
name: 'view_subscribers',
link: function(item) {
return (
<a href={ item.subscribers_url }>View subscribers</a>
);
}
}
];
var bulk_actions = [
{
name: 'trash',
label: 'Trash',
getData: function() {
return {
confirm: false
}
},
onSuccess: messages.onDelete
}
];
var Link = Router.Link;
var SegmentList = React.createClass({
renderItem: function(segment, actions) {
var rowClasses = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
return (
<div>
<td className={ rowClasses }>
<strong>
<a>{ segment.name }</a>
</strong>
{ actions }
</td>
<td className="column-date" data-colname="Description">
<abbr>{ segment.description }</abbr>
</td>
<td className="column-date" data-colname="Subscribed">
<abbr>{ segment.subscribed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Unconfirmed">
<abbr>{ segment.unconfirmed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Unsubscribed">
<abbr>{ segment.unsubscribed || 0 }</abbr>
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ segment.created_at }</abbr>
</td>
</div>
);
},
render: function() {
return (
<div>
<h2 className="title">
Segments <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
messages={ messages }
search={ false }
limit={ 1000 }
endpoint="segments"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>
);
}
});
return SegmentList;
const columns = [
{
name: 'name',
label: 'Name',
sortable: true
},
{
name: 'created_at',
label: 'Created on',
sortable: true
}
);
];
const messages = {
onTrash: function(response) {
let count = ~~response.forms;
let message = null;
if(count === 1 || response === true) {
message = (
'1 form was moved to the trash.'
);
} else if(count > 1) {
message = (
'%$1d forms were moved to the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onDelete: function(response) {
let count = ~~response.forms;
let message = null;
if(count === 1 || response === true) {
message = (
'1 form was permanently deleted.'
);
} else if(count > 1) {
message = (
'%$1d forms were permanently deleted.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
},
onRestore: function(response) {
let count = ~~response.forms;
let message = null;
if(count === 1 || response === true) {
message = (
'1 form has been restored from the trash.'
);
} else if(count > 1) {
message = (
'%$1d forms have been restored from the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
}
}
};
const item_actions = [
{
name: 'edit',
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>Edit</Link>
);
}
},
{
name: 'duplicate_form',
refresh: true,
link: function(item) {
return (
<a
href="javascript:;"
onClick={ this.onDuplicate.bind(null, item) }
>Duplicate</a>
);
},
onDuplicate: function(item) {
MailPoet.Ajax.post({
endpoint: 'forms',
action: 'duplicate',
data: item.id
}).done(function() {
MailPoet.Notice.success(
('List "%$1s" has been duplicated.').replace('%$1s', item.name)
);
});
}
}
];
const bulk_actions = [
{
name: 'trash',
label: 'Trash',
getData: function() {
return {
confirm: false
}
},
onSuccess: messages.onDelete
}
];
const FormList = React.createClass({
renderItem: function(form, actions) {
let row_classes = classNames(
'manage-column',
'column-primary',
'has-row-actions'
);
let segments = mailpoet_segments.filter(function(segment) {
return (jQuery.inArray(segment.id, form.segments) !== -1);
}).map(function(segment) {
return segment.name;
}).join(', ');
return (
<div>
<td className={ row_classes }>
<strong>
<a>{ form.name }</a>
</strong>
{ actions }
</td>
<td className="column-format" data-colname="Lists">
{ segments }
</td>
<td className="column-date" data-colname="Created on">
<abbr>{ form.created_at }</abbr>
</td>
</div>
);
},
render() {
return (
<div>
<h2 className="title">
Forms <Link className="add-new-h2" to="/new">New</Link>
</h2>
<Listing
messages={ messages }
search={ false }
limit={ 1000 }
endpoint="forms"
onRenderItem={ this.renderItem }
columns={ columns }
bulk_actions={ bulk_actions }
item_actions={ item_actions }
/>
</div>
);
}
});
module.exports = FormList;

View File

@ -45,12 +45,13 @@ function(
data.action = this.state.action;
var callback = function() {};
if(action['onSuccess'] !== undefined) {
data.onSuccess = action.onSuccess;
callback = action.onSuccess;
}
if(data.action) {
this.props.onBulkAction(selected_ids, data);
this.props.onBulkAction(selected_ids, data, callback);
}
this.setState({

View File

@ -36,6 +36,7 @@ function(
let default_value = false;
if(selected_filters[filter] !== undefined && selected_filters[filter]) {
default_value = selected_filters[filter]
} else {
jQuery(`select[name="${filter}"]`).val('');
}

View File

@ -46,8 +46,11 @@ define(
handleRestoreItem: function(id) {
this.props.onRestoreItem(id);
},
handleDeleteItem: function(id, confirm = false) {
this.props.onDeleteItem(id, confirm);
handleTrashItem: function(id) {
this.props.onTrashItem(id);
},
handleDeleteItem: function(id) {
this.props.onDeleteItem(id);
},
handleToggleItem: function(id) {
this.setState({ toggled: !this.state.toggled });
@ -86,10 +89,26 @@ define(
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
} else if(action.link) {
return (
<span
key={ 'action-'+index } className={ action.name }>
{ action.link(this.props.item) }
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
} else {
return (
<span key={ 'action-'+index } className={ action.name }>
{ action.link(this.props.item) }
<span
key={ 'action-'+index } className={ action.name }>
<a href="javascript:;" onClick={
(action.onClick !== undefined)
? action.onClick.bind(null,
this.props.item,
this.props.onRefreshItems
)
: false
}>{ action.label }</a>
{(index < (custom_actions.length - 1)) ? ' | ' : ''}
</span>
);
@ -123,8 +142,7 @@ define(
href="javascript:;"
onClick={ this.handleDeleteItem.bind(
null,
this.props.item.id,
true
this.props.item.id
)}
>Delete permanently</a>
</span>
@ -145,10 +163,9 @@ define(
<span className="trash">
<a
href="javascript:;"
onClick={ this.handleDeleteItem.bind(
onClick={ this.handleTrashItem.bind(
null,
this.props.item.id,
false
this.props.item.id
) }>
Trash
</a>
@ -233,7 +250,7 @@ define(
</td>
</tr>
{this.props.items.map(function(item) {
{this.props.items.map(function(item, index) {
item.id = parseInt(item.id, 10);
item.selected = (this.props.selected_ids.indexOf(item.id) !== -1);
@ -244,12 +261,13 @@ define(
onRenderItem={ this.props.onRenderItem }
onDeleteItem={ this.props.onDeleteItem }
onRestoreItem={ this.props.onRestoreItem }
onTrashItem={ this.props.onTrashItem }
onRefreshItems={ this.props.onRefreshItems }
selection={ this.props.selection }
is_selectable={ this.props.is_selectable }
item_actions={ this.props.item_actions }
group={ this.props.group }
key={ 'item-' + item.id }
key={ 'item-' + index }
item={ item } />
);
}.bind(this))}
@ -426,7 +444,27 @@ define(
this.getItems();
}.bind(this));
},
handleDeleteItem: function(id, confirm = false) {
handleTrashItem: function(id) {
this.setState({
loading: true,
page: 1
});
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'trash',
data: id
}).done(function(response) {
if(
this.props.messages !== undefined
&& this.props.messages['onTrash'] !== undefined
) {
this.props.messages.onTrash(response);
}
this.getItems();
}.bind(this));
},
handleDeleteItem: function(id) {
this.setState({
loading: true,
page: 1
@ -435,31 +473,18 @@ define(
MailPoet.Ajax.post({
endpoint: this.props.endpoint,
action: 'delete',
data: {
id: id,
confirm: confirm
}
data: id
}).done(function(response) {
if(confirm === true) {
if(
this.props.messages !== undefined
&& this.props.messages['onConfirmDelete'] !== undefined
) {
this.props.messages.onConfirmDelete(response);
}
} else {
if(
this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined
) {
this.props.messages.onDelete(response);
}
if(
this.props.messages !== undefined
&& this.props.messages['onDelete'] !== undefined
) {
this.props.messages.onDelete(response);
}
this.getItems();
}.bind(this));
},
handleBulkAction: function(selected_ids, params) {
handleBulkAction: function(selected_ids, params, callback) {
if(
this.state.selection === false
&& this.state.selected_ids.length === 0
@ -470,12 +495,6 @@ define(
this.setState({ loading: true });
var data = params || {};
var callback = ((data['onSuccess'] !== undefined)
? data['onSuccess']
: function() {}
);
delete data.onSuccess;
data.listing = {
offset: 0,
limit: 0,
@ -621,12 +640,9 @@ define(
onSuccess: this.props.messages.onRestore
},
{
name: 'trash',
name: 'delete',
label: 'Delete permanently',
onSuccess: this.props.messages.onConfirmDelete,
getData: function() {
return { confirm: true };
}
onSuccess: this.props.messages.onDelete
}
];
}
@ -702,6 +718,7 @@ define(
onRenderItem={ this.handleRenderItem }
onDeleteItem={ this.handleDeleteItem }
onRestoreItem={ this.handleRestoreItem }
onTrashItem={ this.handleTrashItem }
onRefreshItems={ this.handleRefreshItems }
columns={ this.props.columns }
is_selectable={ bulk_actions.length > 0 }

View File

@ -38,7 +38,7 @@ define(
];
var messages = {
onDelete: function(response) {
onTrash: function(response) {
var count = ~~response.newsletters;
var message = null;
@ -56,7 +56,7 @@ define(
MailPoet.Notice.success(message);
}
},
onConfirmDelete: function(response) {
onDelete: function(response) {
var count = ~~response.newsletters;
var message = null;
@ -98,12 +98,7 @@ define(
{
name: 'trash',
label: 'Trash',
getData: function() {
return {
confirm: false
}
},
onSuccess: messages.onDelete
onSuccess: messages.onTrash
}
];

View File

@ -41,58 +41,58 @@ var columns = [
];
var messages = {
onDelete: function(response) {
var count = ~~response.segments;
var message = null;
onTrash: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 segment was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d segments were moved to the trash.'
).replace('%$1d', ~~response);
}
if(count === 1 || response === true) {
message = (
'1 segment was moved to the trash.'
);
} else if(count > 1) {
message = (
'%$1d segments were moved to the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onConfirmDelete: function(response) {
var count = ~~response.segments;
var message = null;
onDelete: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 segment was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d segments were permanently deleted.'
).replace('%$1d', ~~response);
}
if(count === 1 || response === true) {
message = (
'1 segment was permanently deleted.'
);
} else if(count > 1) {
message = (
'%$1d segments were permanently deleted.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
var count = ~~response.segments;
var message = null;
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 segment has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d segments have been restored from the trash.'
).replace('%$1d', ~~response);
}
if(count === 1 || response === true) {
message = (
'1 segment has been restored from the trash.'
);
} else if(count > 1) {
message = (
'%$1d segments have been restored from the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
};
@ -100,6 +100,7 @@ var messages = {
var item_actions = [
{
name: 'edit',
label: 'Edit',
link: function(item) {
return (
<Link to={ `/edit/${item.id}` }>Edit</Link>
@ -108,24 +109,17 @@ var item_actions = [
},
{
name: 'duplicate_segment',
refresh: true,
link: function(item) {
return (
<a
href="javascript:;"
onClick={ this.onDuplicate.bind(null, item) }
>Duplicate</a>
);
},
onDuplicate: function(item) {
MailPoet.Ajax.post({
label: 'Duplicate',
onClick: function(item, refresh) {
return MailPoet.Ajax.post({
endpoint: 'segments',
action: 'duplicate',
data: item.id
}).done(function() {
}).done(function(response) {
MailPoet.Notice.success(
('List "%$1s" has been duplicated.').replace('%$1s', item.name)
('List "%$1s" has been duplicated.').replace('%$1s', response.name)
);
refresh();
});
}
},
@ -143,12 +137,7 @@ var bulk_actions = [
{
name: 'trash',
label: 'Trash',
getData: function() {
return {
confirm: false
}
},
onSuccess: messages.onDelete
onSuccess: messages.onTrash
}
];

View File

@ -38,58 +38,58 @@ const columns = [
];
const messages = {
onDelete: function(response) {
let count = ~~response.subscribers;
let message = null;
onTrash: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was moved to the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were moved to the trash.'
).replace('%$1d', ~~response);
}
if(count === 1) {
message = (
'1 subscriber was moved to the trash.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers were moved to the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onConfirmDelete: function(response) {
let count = ~~response.subscribers;
let message = null;
onDelete: function(response) {
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber was permanently deleted.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers were permanently deleted.'
).replace('%$1d', ~~response);
}
if(count === 1) {
message = (
'1 subscriber was permanently deleted.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers were permanently deleted.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
if(message !== null) {
MailPoet.Notice.success(message);
}
}
},
onRestore: function(response) {
let count = ~~response.subscribers;
let message = null;
if(response) {
var message = null;
if(~~response === 1) {
message = (
'1 subscriber has been restored from the trash.'
);
} else if(~~response > 1) {
message = (
'%$1d subscribers have been restored from the trash.'
).replace('%$1d', ~~response);
}
if(count === 1) {
message = (
'1 subscriber has been restored from the trash.'
).replace('%$1d', count);
} else if(count > 1) {
message = (
'%$1d subscribers have been restored from the trash.'
).replace('%$1d', count);
}
if(message !== null) {
MailPoet.Notice.success(message);
if(message !== null) {
MailPoet.Notice.success(message);
}
}
}
};
@ -179,8 +179,7 @@ const bulk_actions = [
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers were removed from all lists.'
.replace('%$1d', ~~response.subscribers)
.replace('%$2s', response.segment)
.replace('%$1d', ~~response)
);
}
},
@ -190,19 +189,14 @@ const bulk_actions = [
onSuccess: function(response) {
MailPoet.Notice.success(
'%$1d subscribers have been confirmed.'
.replace('%$1d', ~~response.subscribers)
.replace('%$1d', ~~response)
);
}
},
{
name: 'trash',
label: 'Trash',
getData: function() {
return {
confirm: false
}
},
onSuccess: messages.onDelete
onSuccess: messages.onTrash
}
];