basic listing files

This commit is contained in:
Jonathan Labreuille
2015-10-28 13:18:56 +01:00
parent 01e6a5e6b2
commit 103da61d45
10 changed files with 457 additions and 2 deletions

View File

@ -0,0 +1,56 @@
import React from 'react'
import ReactDOM from 'react-dom'
import Router from 'react-router'
import MailPoet from 'mailpoet'
import Form from 'form/form.jsx'
const fields = [
{
name: 'name',
label: 'Name',
type: 'text'
},
{
name: 'segments',
label: 'Lists',
type: 'selection',
endpoint: 'segments'
}
]
const messages = {
updated: function() {
MailPoet.Notice.success('Form successfully updated!');
},
created: function() {
MailPoet.Notice.success('Form successfully added!');
}
}
const FormForm = React.createClass({
mixins: [
Router.History
],
render() {
return (
<div>
<h2 className="title">
Form <a
href="javascript:;"
className="add-new-h2"
onClick={ this.history.goBack }
>Back to list</a>
</h2>
<Form
endpoint="forms"
fields={ fields }
params={ this.props.params }
messages={ messages }
onSuccess={ this.history.goBack } />
</div>
);
}
});
module.exports = FormForm

View File

@ -0,0 +1,29 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute, Link } from 'react-router'
import FormList from 'forms/list.jsx'
import FormForm from 'forms/form.jsx'
import createHashHistory from 'history/lib/createHashHistory'
let history = createHashHistory({ queryKey: false })
const App = React.createClass({
render() {
return this.props.children
}
});
let container = document.getElementById('forms_container');
if(container) {
ReactDOM.render((
<Router history={ history }>
<Route path="/" component={ App }>
<IndexRoute component={ FormList } />
<Route path="new" component={ FormForm } />
<Route path="edit/:id" component={ FormForm } />
<Route path="*" component={ FormList } />
</Route>
</Router>
), container);
}

View File

@ -0,0 +1,202 @@
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
}
];
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;
}
);

View File

@ -39,6 +39,7 @@ class Initializer {
$newsletters = Env::$db_prefix . 'newsletters'; $newsletters = Env::$db_prefix . 'newsletters';
$newsletter_templates = Env::$db_prefix . 'newsletter_templates'; $newsletter_templates = Env::$db_prefix . 'newsletter_templates';
$segments = Env::$db_prefix . 'segments'; $segments = Env::$db_prefix . 'segments';
$forms = Env::$db_prefix . 'forms';
$subscriber_segment = Env::$db_prefix . 'subscriber_segment'; $subscriber_segment = Env::$db_prefix . 'subscriber_segment';
$newsletter_segment = Env::$db_prefix . 'newsletter_segment'; $newsletter_segment = Env::$db_prefix . 'newsletter_segment';
$custom_fields = Env::$db_prefix . 'custom_fields'; $custom_fields = Env::$db_prefix . 'custom_fields';
@ -50,6 +51,7 @@ class Initializer {
define('MP_SETTINGS_TABLE', $settings); define('MP_SETTINGS_TABLE', $settings);
define('MP_NEWSLETTERS_TABLE', $newsletters); define('MP_NEWSLETTERS_TABLE', $newsletters);
define('MP_SEGMENTS_TABLE', $segments); define('MP_SEGMENTS_TABLE', $segments);
define('MP_FORMS_TABLE', $forms);
define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment); define('MP_SUBSCRIBER_SEGMENT_TABLE', $subscriber_segment);
define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates); define('MP_NEWSLETTER_TEMPLATES_TABLE', $newsletter_templates);
define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment); define('MP_NEWSLETTER_SEGMENT_TABLE', $newsletter_segment);

View File

@ -41,6 +41,14 @@ class Menu {
'mailpoet-newsletters', 'mailpoet-newsletters',
array($this, 'newsletters') array($this, 'newsletters')
); );
add_submenu_page(
'mailpoet',
__('Forms'),
__('Forms'),
'manage_options',
'mailpoet-forms',
array($this, 'forms')
);
add_submenu_page( add_submenu_page(
'mailpoet', 'mailpoet',
__('Subscribers'), __('Subscribers'),
@ -163,6 +171,13 @@ class Menu {
echo $this->renderer->render('segments.html', $data); echo $this->renderer->render('segments.html', $data);
} }
function forms() {
$data = array();
$data['segments'] = Segment::findArray();
echo $this->renderer->render('forms.html', $data);
}
function newsletters() { function newsletters() {
global $wp_roles; global $wp_roles;

View File

@ -202,8 +202,7 @@ class Migrator {
'created_at TIMESTAMP NOT NULL DEFAULT 0,', 'created_at TIMESTAMP NOT NULL DEFAULT 0,',
'deleted_at TIMESTAMP NULL DEFAULT NULL,', 'deleted_at TIMESTAMP NULL DEFAULT NULL,',
'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,', 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,',
'PRIMARY KEY (id),', 'PRIMARY KEY (id)'
'UNIQUE KEY name (name)'
); );
return $this->sqlify(__FUNCTION__, $attributes); return $this->sqlify(__FUNCTION__, $attributes);
} }

112
lib/Models/Form.php Normal file
View File

@ -0,0 +1,112 @@
<?php
namespace MailPoet\Models;
if(!defined('ABSPATH')) exit;
class Form extends Model {
static $_table = MP_FORMS_TABLE;
function __construct() {
parent::__construct();
$this->addValidations('name', array(
'required' => __('You need to specify a name.')
));
}
static function search($orm, $search = '') {
return $orm->where_like('name', '%'.$search.'%');
}
static function groups() {
return array(
array(
'name' => 'all',
'label' => __('All'),
'count' => Form::whereNull('deleted_at')->count()
),
array(
'name' => 'trash',
'label' => __('Trash'),
'count' => Form::whereNotNull('deleted_at')->count()
)
);
}
static function groupBy($orm, $group = null) {
if($group === 'trash') {
return $orm->whereNotNull('deleted_at');
} else {
$orm = $orm->whereNull('deleted_at');
}
}
static function createOrUpdate($data = array()) {
$form = false;
if(isset($data['id']) && (int)$data['id'] > 0) {
$form = self::findOne((int)$data['id']);
}
if($form === false) {
$form = self::create();
$form->hydrate($data);
} else {
unset($data['id']);
$form->set($data);
}
$saved = $form->save();
if($saved === true) {
return true;
} else {
$errors = $form->getValidationErrors();
if(!empty($errors)) {
return $errors;
}
}
return false;
}
static function trash($listing, $data = array()) {
$confirm_delete = filter_var($data['confirm'], FILTER_VALIDATE_BOOLEAN);
if($confirm_delete) {
// delete relations with all segments
$forms = $listing->getSelection()->findResultSet();
if(!empty($forms)) {
$forms_count = 0;
foreach($forms as $form) {
if($form->delete()) {
$forms_count++;
}
}
return array(
'segments' => $forms_count
);
}
return false;
} else {
// soft delete
$forms = $listing->getSelection()
->findResultSet()
->set_expr('deleted_at', 'NOW()')
->save();
return array(
'segments' => $forms->count()
);
}
}
static function restore($listing, $data = array()) {
$forms = $listing->getSelection()
->findResultSet()
->set_expr('deleted_at', 'NULL')
->save();
return array(
'segments' => $forms->count()
);
}
}

View File

@ -21,6 +21,29 @@ class Model extends \Sudzy\ValidModel {
} }
} }
static function restore($orm) {
$models = $orm->findResultSet();
if(empty($models)) return false;
$models->setExpr('deleted_at', 'NULL')->save();
return $models->count();
}
static function trash($orm, $confirm = false) {
$models = $orm->findResultSet();
if(empty($models)) return false;
if($confirm === true) {
foreach($models as $model) {
$model->delete();
}
} else {
$models = $orm->findResultSet();
$models->setExpr('deleted_at', 'NOW()')->save();
}
return $models->count();
}
private function setTimestamp() { private function setTimestamp() {
if($this->created_at === null) { if($this->created_at === null) {
$this->set_expr('created_at', 'NOW()'); $this->set_expr('created_at', 'NOW()');

16
views/forms.html Normal file
View File

@ -0,0 +1,16 @@
<% extends 'layout.html' %>
<% block content %>
<div id="forms_container"></div>
<%= localize({
'pageTitle': __('Forms'),
'searchLabel': __('Search'),
'loadingItems': __('Loading forms...'),
'noItemsFound': __('No forms found.')
}) %>
<script type="text/javascript">
var mailpoet_segments = <%= json_encode(segments) %>;
</script>
<% endblock %>

View File

@ -69,6 +69,7 @@ config.push(_.extend({}, baseConfig, {
'subscribers/subscribers.jsx', 'subscribers/subscribers.jsx',
'newsletters/newsletters.jsx', 'newsletters/newsletters.jsx',
'segments/segments.jsx', 'segments/segments.jsx',
'forms/forms.jsx',
'settings/tabs.js' 'settings/tabs.js'
], ],
newsletter_editor: [ newsletter_editor: [