Listing & form
- improved Listing in order to make it more DRY - form builder with all field types - added support for data array in ValidModel->set() - updated models to comply with Listing & Form methods
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
.mailpoet_listing_loading tbody tr,
|
.mailpoet_listing_loading tbody tr,
|
||||||
.mailpoet_form_loading
|
.mailpoet_form_loading tbody tr
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
|
|
||||||
.widefat tfoot td.mailpoet_check_column,
|
.widefat tfoot td.mailpoet_check_column,
|
||||||
|
@ -3,17 +3,191 @@ define(
|
|||||||
'react',
|
'react',
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
'classnames',
|
'classnames',
|
||||||
'react-router'
|
'react-router',
|
||||||
|
'react-checkbox-group'
|
||||||
],
|
],
|
||||||
function(
|
function(
|
||||||
React,
|
React,
|
||||||
MailPoet,
|
MailPoet,
|
||||||
classNames,
|
classNames,
|
||||||
Router
|
Router,
|
||||||
|
CheckboxGroup
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
var FormFieldSelect = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
var options =
|
||||||
|
Object.keys(this.props.field.values).map(function(value, index) {
|
||||||
|
return (
|
||||||
|
<option
|
||||||
|
key={ 'option-' + index }
|
||||||
|
value={ value }>
|
||||||
|
{ this.props.field.values[value] }
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
name={ this.props.field.name }
|
||||||
|
id={ 'field_'+this.props.field.name }
|
||||||
|
value={ this.props.item[this.props.field.name] }
|
||||||
|
onChange={ this.props.onValueChange } >
|
||||||
|
{options}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var FormFieldRadio = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
var selected_value = this.props.item[this.props.field.name];
|
||||||
|
var count = Object.keys(this.props.field.values).length;
|
||||||
|
|
||||||
|
var options = Object.keys(this.props.field.values).map(
|
||||||
|
function(value, index) {
|
||||||
|
return (
|
||||||
|
<p key={ 'radio-' + index }>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
checked={ selected_value === value }
|
||||||
|
value={ value }
|
||||||
|
onChange={ this.props.onValueChange }
|
||||||
|
name={ this.props.field.name } />
|
||||||
|
{ this.props.field.values[value] }
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{ options }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var FormFieldCheckbox = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
var selected_values = this.props.item[this.props.field.name] || '';
|
||||||
|
if(
|
||||||
|
selected_values !== undefined
|
||||||
|
&& selected_values.constructor !== Array
|
||||||
|
) {
|
||||||
|
selected_values = selected_values.split(';').map(function(value) {
|
||||||
|
return value.trim();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var count = Object.keys(this.props.field.values).length;
|
||||||
|
|
||||||
|
var options = Object.keys(this.props.field.values).map(
|
||||||
|
function(value, index) {
|
||||||
|
return (
|
||||||
|
<p key={ 'checkbox-' + index }>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" value={ value } />
|
||||||
|
{ this.props.field.values[value] }
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CheckboxGroup
|
||||||
|
name={ this.props.field.name }
|
||||||
|
value={ selected_values }
|
||||||
|
ref={ this.props.field.name }
|
||||||
|
onChange={ this.handleValueChange }>
|
||||||
|
{ options }
|
||||||
|
</CheckboxGroup>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
handleValueChange: function() {
|
||||||
|
var field = this.props.field.name;
|
||||||
|
var group = this.refs[field];
|
||||||
|
var selected_values = [];
|
||||||
|
|
||||||
|
if(group !== undefined) {
|
||||||
|
selected_values = group.getCheckedValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.onValueChange({
|
||||||
|
target: {
|
||||||
|
name: field,
|
||||||
|
value: selected_values.join(';')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var FormFieldText = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="regular-text"
|
||||||
|
name={ this.props.field.name }
|
||||||
|
id={ 'field_'+this.props.field.name }
|
||||||
|
value={ this.props.item[this.props.field.name] }
|
||||||
|
onChange={ this.props.onValueChange } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var FormFieldTextarea = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
type="text"
|
||||||
|
className="regular-text"
|
||||||
|
name={ this.props.field.name }
|
||||||
|
id={ 'field_'+this.props.field.name }
|
||||||
|
value={ this.props.item[this.props.field.name] }
|
||||||
|
onChange={ this.props.onValueChange } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var FormField = React.createClass({
|
var FormField = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
||||||
|
var description = false;
|
||||||
|
if(this.props.field.description) {
|
||||||
|
description = (
|
||||||
|
<p className="description">{ this.props.field.description }</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var field = false;
|
||||||
|
|
||||||
|
switch(this.props.field.type) {
|
||||||
|
case 'text':
|
||||||
|
field = (<FormFieldText {...this.props} />);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'textarea':
|
||||||
|
field = (<FormFieldTextarea {...this.props} />);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'select':
|
||||||
|
field = (<FormFieldSelect {...this.props} />);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'radio':
|
||||||
|
field = (<FormFieldRadio {...this.props} />);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'checkbox':
|
||||||
|
field = (<FormFieldCheckbox {...this.props} />);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">
|
<th scope="row">
|
||||||
@ -22,12 +196,8 @@ define(
|
|||||||
>{ this.props.field.label }</label>
|
>{ this.props.field.label }</label>
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
<input
|
{ field }
|
||||||
type="text"
|
{ description }
|
||||||
name={ this.props.field.name }
|
|
||||||
id={ 'field_'+this.props.field.name }
|
|
||||||
value={ this.props.item[this.props.field.name] }
|
|
||||||
onChange={ this.props.onValueChange } />
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
@ -66,7 +236,7 @@ define(
|
|||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Ajax.post({
|
||||||
endpoint: 'subscribers',
|
endpoint: this.props.endpoint,
|
||||||
action: 'get',
|
action: 'get',
|
||||||
data: { id: id }
|
data: { id: id }
|
||||||
}).done(function(response) {
|
}).done(function(response) {
|
||||||
@ -91,7 +261,7 @@ define(
|
|||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Ajax.post({
|
||||||
endpoint: 'subscribers',
|
endpoint: this.props.endpoint,
|
||||||
action: 'save',
|
action: 'save',
|
||||||
data: this.state.item
|
data: this.state.item
|
||||||
}).done(function(response) {
|
}).done(function(response) {
|
||||||
@ -100,19 +270,21 @@ define(
|
|||||||
if(response === true) {
|
if(response === true) {
|
||||||
this.transitionTo('/');
|
this.transitionTo('/');
|
||||||
if(this.props.params.id !== undefined) {
|
if(this.props.params.id !== undefined) {
|
||||||
MailPoet.Notice.success('Subscriber succesfully updated!');
|
this.props.messages['updated']();
|
||||||
} else {
|
} else {
|
||||||
MailPoet.Notice.success('Subscriber succesfully added!');
|
this.props.messages['created']();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({ errors: response });
|
this.setState({ errors: response });
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
handleValueChange: function(e) {
|
handleValueChange: function(e) {
|
||||||
var item = this.state.item;
|
var item = this.state.item,
|
||||||
item[e.target.name] = e.target.value;
|
field = e.target.name;
|
||||||
|
|
||||||
|
item[field] = e.target.value;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
item: item
|
item: item
|
||||||
});
|
});
|
||||||
@ -121,7 +293,9 @@ define(
|
|||||||
render: function() {
|
render: function() {
|
||||||
var errors = this.state.errors.map(function(error, index) {
|
var errors = this.state.errors.map(function(error, index) {
|
||||||
return (
|
return (
|
||||||
<p key={'error-'+index} className="mailpoet_error">{ error }</p>
|
<p key={ 'error-'+index } className="mailpoet_error">
|
||||||
|
{ error }
|
||||||
|
</p>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -159,7 +159,30 @@ define(
|
|||||||
},
|
},
|
||||||
getItems: function() {
|
getItems: function() {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
this.props.items.bind(null, this)();
|
|
||||||
|
MailPoet.Ajax.post({
|
||||||
|
endpoint: this.props.endpoint,
|
||||||
|
action: 'listing',
|
||||||
|
data: {
|
||||||
|
offset: (this.state.page - 1) * this.state.limit,
|
||||||
|
limit: this.state.limit,
|
||||||
|
group: this.state.group,
|
||||||
|
search: this.state.search,
|
||||||
|
sort_by: this.state.sort_by,
|
||||||
|
sort_order: this.state.sort_order
|
||||||
|
},
|
||||||
|
onSuccess: function(response) {
|
||||||
|
if(this.isMounted()) {
|
||||||
|
this.setState({
|
||||||
|
items: response.items || [],
|
||||||
|
filters: response.filters || [],
|
||||||
|
groups: response.groups || [],
|
||||||
|
count: response.count || 0,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}.bind(this)
|
||||||
|
});
|
||||||
},
|
},
|
||||||
handleSearch: function(search) {
|
handleSearch: function(search) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -1,75 +1,50 @@
|
|||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'react',
|
'react',
|
||||||
'react-router',
|
'mailpoet',
|
||||||
'jquery',
|
'form/form.jsx'
|
||||||
'mailpoet'
|
|
||||||
],
|
],
|
||||||
function(
|
function(
|
||||||
React,
|
React,
|
||||||
Router,
|
MailPoet,
|
||||||
jQuery,
|
Form
|
||||||
MailPoet
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var Form = React.createClass({
|
var fields = [
|
||||||
mixins: [
|
{
|
||||||
Router.Navigation
|
name: 'subject',
|
||||||
],
|
label: 'Subject',
|
||||||
getInitialState: function() {
|
type: 'text'
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
errors: []
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
handleSubmit: function(e) {
|
{
|
||||||
e.preventDefault();
|
name: 'body',
|
||||||
|
label: 'Body',
|
||||||
|
type: 'textarea'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
this.setState({ loading: true });
|
var messages = {
|
||||||
|
updated: function() {
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Notice.success('Newsletter succesfully updated!');
|
||||||
endpoint: 'newsletters',
|
|
||||||
action: 'save',
|
|
||||||
data: {
|
|
||||||
subject: React.findDOMNode(this.refs.subject).value,
|
|
||||||
body: React.findDOMNode(this.refs.body).value
|
|
||||||
}
|
|
||||||
}).done(function(response) {
|
|
||||||
this.setState({ loading: false });
|
|
||||||
|
|
||||||
if(response === true) {
|
|
||||||
this.transitionTo('/');
|
|
||||||
} else {
|
|
||||||
this.setState({ errors: response });
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
},
|
},
|
||||||
|
created: function() {
|
||||||
|
MailPoet.Notice.success('Newsletter succesfully added!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var NewsletterForm = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
var errors = this.state.errors.map(function(error, index) {
|
|
||||||
return (
|
|
||||||
<p key={'error-'+index} className="mailpoet_error">{ error }</p>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={ this.handleSubmit }>
|
<Form
|
||||||
{ errors }
|
endpoint="newsletters"
|
||||||
<p>
|
fields={ fields }
|
||||||
<input type="text" placeholder="Subject" ref="subject" />
|
params={ this.props.params }
|
||||||
</p>
|
messages={ messages } />
|
||||||
<p>
|
|
||||||
<input type="text" placeholder="Body" ref="body" />
|
|
||||||
</p>
|
|
||||||
<input
|
|
||||||
className="button button-primary"
|
|
||||||
type="submit"
|
|
||||||
value="Save"
|
|
||||||
disabled={this.state.loading} />
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Form;
|
return NewsletterForm;
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -1,18 +1,17 @@
|
|||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'react',
|
'react',
|
||||||
'jquery',
|
'react-router',
|
||||||
'mailpoet',
|
|
||||||
'listing/listing.jsx',
|
'listing/listing.jsx',
|
||||||
'classnames'
|
'classnames',
|
||||||
],
|
],
|
||||||
function(
|
function(
|
||||||
React,
|
React,
|
||||||
jQuery,
|
Router,
|
||||||
MailPoet,
|
|
||||||
Listing,
|
Listing,
|
||||||
classNames
|
classNames
|
||||||
) {
|
) {
|
||||||
|
var Link = Router.Link;
|
||||||
|
|
||||||
var columns = [
|
var columns = [
|
||||||
{
|
{
|
||||||
@ -32,32 +31,7 @@ define(
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
var List = React.createClass({
|
var NewsletterList = React.createClass({
|
||||||
getItems: function(listing) {
|
|
||||||
MailPoet.Ajax.post({
|
|
||||||
endpoint: 'newsletters',
|
|
||||||
action: 'get',
|
|
||||||
data: {
|
|
||||||
offset: (listing.state.page - 1) * listing.state.limit,
|
|
||||||
limit: listing.state.limit,
|
|
||||||
group: listing.state.group,
|
|
||||||
search: listing.state.search,
|
|
||||||
sort_by: listing.state.sort_by,
|
|
||||||
sort_order: listing.state.sort_order
|
|
||||||
},
|
|
||||||
onSuccess: function(response) {
|
|
||||||
if(listing.isMounted()) {
|
|
||||||
listing.setState({
|
|
||||||
items: response.items || [],
|
|
||||||
filters: response.filters || [],
|
|
||||||
groups: response.groups || [],
|
|
||||||
count: response.count || 0,
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}.bind(listing)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
renderItem: function(newsletter) {
|
renderItem: function(newsletter) {
|
||||||
var rowClasses = classNames(
|
var rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
@ -71,6 +45,12 @@ define(
|
|||||||
<strong>
|
<strong>
|
||||||
<a>{ newsletter.subject }</a>
|
<a>{ newsletter.subject }</a>
|
||||||
</strong>
|
</strong>
|
||||||
|
|
||||||
|
<div className="row-actions">
|
||||||
|
<span className="edit">
|
||||||
|
<Link to="edit" params={{ id: newsletter.id }}>Edit</Link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="column-date" data-colname="Subscribed on">
|
<td className="column-date" data-colname="Subscribed on">
|
||||||
<abbr>{ newsletter.created_at }</abbr>
|
<abbr>{ newsletter.created_at }</abbr>
|
||||||
@ -84,6 +64,7 @@ define(
|
|||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<Listing
|
<Listing
|
||||||
|
endpoint="newsletters"
|
||||||
onRenderItem={this.renderItem}
|
onRenderItem={this.renderItem}
|
||||||
items={this.getItems}
|
items={this.getItems}
|
||||||
columns={columns} />
|
columns={columns} />
|
||||||
@ -91,6 +72,6 @@ define(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return List;
|
return NewsletterList;
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -2,20 +2,20 @@ define(
|
|||||||
[
|
[
|
||||||
'react',
|
'react',
|
||||||
'react-router',
|
'react-router',
|
||||||
'newsletters/form.jsx',
|
'newsletters/list.jsx',
|
||||||
'newsletters/list.jsx'
|
'newsletters/form.jsx'
|
||||||
],
|
],
|
||||||
function(
|
function(
|
||||||
React,
|
React,
|
||||||
Router,
|
Router,
|
||||||
Form,
|
List,
|
||||||
List
|
Form
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var DefaultRoute = Router.DefaultRoute;
|
var DefaultRoute = Router.DefaultRoute;
|
||||||
var Link = Router.Link;
|
var Link = Router.Link;
|
||||||
var Route = Router.Route;
|
var Route = Router.Route;
|
||||||
var RouteHandler = Router.RouteHandler;
|
var RouteHandler = Router.RouteHandler;
|
||||||
|
var NotFoundRoute = Router.NotFoundRoute;
|
||||||
|
|
||||||
var App = React.createClass({
|
var App = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
@ -24,7 +24,7 @@ define(
|
|||||||
<h1>
|
<h1>
|
||||||
{ MailPoetI18n.pageTitle }
|
{ MailPoetI18n.pageTitle }
|
||||||
|
|
||||||
<Link className="add-new-h2" to="form">New</Link>
|
<Link className="add-new-h2" to="new">New</Link>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<RouteHandler/>
|
<RouteHandler/>
|
||||||
@ -35,8 +35,9 @@ define(
|
|||||||
|
|
||||||
var routes = (
|
var routes = (
|
||||||
<Route name="app" path="/" handler={App}>
|
<Route name="app" path="/" handler={App}>
|
||||||
<Route name="list" handler={List} />
|
<Route name="new" path="/new" handler={Form} />
|
||||||
<Route name="form" handler={Form} />
|
<Route name="edit" path="/edit/:id" handler={Form} />
|
||||||
|
<NotFoundRoute handler={List} />
|
||||||
<DefaultRoute handler={List} />
|
<DefaultRoute handler={List} />
|
||||||
</Route>
|
</Route>
|
||||||
);
|
);
|
||||||
|
@ -1,71 +1,45 @@
|
|||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'react',
|
'react',
|
||||||
'react-router',
|
'mailpoet',
|
||||||
'jquery',
|
'form/form.jsx'
|
||||||
'mailpoet'
|
|
||||||
],
|
],
|
||||||
function(
|
function(
|
||||||
React,
|
React,
|
||||||
Router,
|
MailPoet,
|
||||||
jQuery,
|
Form
|
||||||
MailPoet
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var Form = React.createClass({
|
var fields = [
|
||||||
mixins: [
|
{
|
||||||
Router.Navigation
|
name: 'name',
|
||||||
],
|
label: 'Name',
|
||||||
getInitialState: function() {
|
type: 'text'
|
||||||
return {
|
}
|
||||||
loading: false,
|
];
|
||||||
errors: []
|
|
||||||
};
|
var messages = {
|
||||||
|
updated: function() {
|
||||||
|
MailPoet.Notice.success('Segment succesfully updated!');
|
||||||
},
|
},
|
||||||
handleSubmit: function(e) {
|
created: function() {
|
||||||
e.preventDefault();
|
MailPoet.Notice.success('Segment succesfully added!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.setState({ loading: true });
|
var SegmentForm = React.createClass({
|
||||||
|
|
||||||
MailPoet.Ajax.post({
|
|
||||||
endpoint: 'segments',
|
|
||||||
action: 'save',
|
|
||||||
data: {
|
|
||||||
name: React.findDOMNode(this.refs.name).value
|
|
||||||
}
|
|
||||||
}).done(function(response) {
|
|
||||||
this.setState({ loading: false });
|
|
||||||
|
|
||||||
if(response === true) {
|
|
||||||
this.transitionTo('/');
|
|
||||||
} else {
|
|
||||||
this.setState({ errors: response });
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
},
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var errors = this.state.errors.map(function(error, index) {
|
|
||||||
return (
|
|
||||||
<p key={'error-'+index} className="mailpoet_error">{ error }</p>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={ this.handleSubmit }>
|
<Form
|
||||||
{ errors }
|
endpoint="segments"
|
||||||
<p>
|
fields={ fields }
|
||||||
<input type="text" placeholder="Name" ref="name" />
|
params={ this.props.params }
|
||||||
</p>
|
messages={ messages } />
|
||||||
<input
|
|
||||||
className="button button-primary"
|
|
||||||
type="submit"
|
|
||||||
value="Save"
|
|
||||||
disabled={this.state.loading} />
|
|
||||||
</form>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Form;
|
return SegmentForm;
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -1,18 +1,17 @@
|
|||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'react',
|
'react',
|
||||||
'jquery',
|
'react-router',
|
||||||
'mailpoet',
|
|
||||||
'listing/listing.jsx',
|
'listing/listing.jsx',
|
||||||
'classnames'
|
'classnames',
|
||||||
],
|
],
|
||||||
function(
|
function(
|
||||||
React,
|
React,
|
||||||
jQuery,
|
Router,
|
||||||
MailPoet,
|
|
||||||
Listing,
|
Listing,
|
||||||
classNames
|
classNames
|
||||||
) {
|
) {
|
||||||
|
var Link = Router.Link;
|
||||||
|
|
||||||
var columns = [
|
var columns = [
|
||||||
{
|
{
|
||||||
@ -32,32 +31,7 @@ define(
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
var List = React.createClass({
|
var SegmentList = React.createClass({
|
||||||
getItems: function(listing) {
|
|
||||||
MailPoet.Ajax.post({
|
|
||||||
endpoint: 'segments',
|
|
||||||
action: 'get',
|
|
||||||
data: {
|
|
||||||
offset: (listing.state.page - 1) * listing.state.limit,
|
|
||||||
limit: listing.state.limit,
|
|
||||||
group: listing.state.group,
|
|
||||||
search: listing.state.search,
|
|
||||||
sort_by: listing.state.sort_by,
|
|
||||||
sort_order: listing.state.sort_order
|
|
||||||
},
|
|
||||||
onSuccess: function(response) {
|
|
||||||
if(listing.isMounted()) {
|
|
||||||
listing.setState({
|
|
||||||
items: response.items || [],
|
|
||||||
filters: response.filters || [],
|
|
||||||
groups: response.groups || [],
|
|
||||||
count: response.count || 0,
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}.bind(listing)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
renderItem: function(segment) {
|
renderItem: function(segment) {
|
||||||
var rowClasses = classNames(
|
var rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
@ -71,6 +45,12 @@ define(
|
|||||||
<strong>
|
<strong>
|
||||||
<a>{ segment.name }</a>
|
<a>{ segment.name }</a>
|
||||||
</strong>
|
</strong>
|
||||||
|
|
||||||
|
<div className="row-actions">
|
||||||
|
<span className="edit">
|
||||||
|
<Link to="edit" params={{ id: segment.id }}>Edit</Link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="column-date" data-colname="Subscribed on">
|
<td className="column-date" data-colname="Subscribed on">
|
||||||
<abbr>{ segment.created_at }</abbr>
|
<abbr>{ segment.created_at }</abbr>
|
||||||
@ -84,6 +64,7 @@ define(
|
|||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<Listing
|
<Listing
|
||||||
|
endpoint="segments"
|
||||||
onRenderItem={this.renderItem}
|
onRenderItem={this.renderItem}
|
||||||
items={this.getItems}
|
items={this.getItems}
|
||||||
columns={columns} />
|
columns={columns} />
|
||||||
@ -91,6 +72,6 @@ define(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return List;
|
return SegmentList;
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -11,11 +11,11 @@ define(
|
|||||||
List,
|
List,
|
||||||
Form
|
Form
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var DefaultRoute = Router.DefaultRoute;
|
var DefaultRoute = Router.DefaultRoute;
|
||||||
var Link = Router.Link;
|
var Link = Router.Link;
|
||||||
var Route = Router.Route;
|
var Route = Router.Route;
|
||||||
var RouteHandler = Router.RouteHandler;
|
var RouteHandler = Router.RouteHandler;
|
||||||
|
var NotFoundRoute = Router.NotFoundRoute;
|
||||||
|
|
||||||
var App = React.createClass({
|
var App = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
@ -24,7 +24,7 @@ define(
|
|||||||
<h1>
|
<h1>
|
||||||
{ MailPoetI18n.pageTitle }
|
{ MailPoetI18n.pageTitle }
|
||||||
|
|
||||||
<Link className="add-new-h2" to="form">New</Link>
|
<Link className="add-new-h2" to="new">New</Link>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<RouteHandler/>
|
<RouteHandler/>
|
||||||
@ -35,8 +35,9 @@ define(
|
|||||||
|
|
||||||
var routes = (
|
var routes = (
|
||||||
<Route name="app" path="/" handler={App}>
|
<Route name="app" path="/" handler={App}>
|
||||||
<Route name="list" handler={List} />
|
<Route name="new" path="/new" handler={Form} />
|
||||||
<Route name="form" handler={Form} />
|
<Route name="edit" path="/edit/:id" handler={Form} />
|
||||||
|
<NotFoundRoute handler={List} />
|
||||||
<DefaultRoute handler={List} />
|
<DefaultRoute handler={List} />
|
||||||
</Route>
|
</Route>
|
||||||
);
|
);
|
||||||
|
@ -1,41 +1,61 @@
|
|||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
'react',
|
'react',
|
||||||
'react-router',
|
|
||||||
'jquery',
|
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
'classnames',
|
|
||||||
'form/form.jsx'
|
'form/form.jsx'
|
||||||
],
|
],
|
||||||
function(
|
function(
|
||||||
React,
|
React,
|
||||||
Router,
|
|
||||||
jQuery,
|
|
||||||
MailPoet,
|
MailPoet,
|
||||||
classNames,
|
|
||||||
Form
|
Form
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var fields = [
|
var fields = [
|
||||||
{
|
{
|
||||||
name: 'email',
|
name: 'email',
|
||||||
label: 'E-mail'
|
label: 'E-mail',
|
||||||
|
type: 'text'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'first_name',
|
name: 'first_name',
|
||||||
label: 'Firstname'
|
label: 'Firstname',
|
||||||
|
type: 'text'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'last_name',
|
name: 'last_name',
|
||||||
label: 'Lastname'
|
label: 'Lastname',
|
||||||
|
type: 'text'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
label: 'Status',
|
||||||
|
type: 'select',
|
||||||
|
values: {
|
||||||
|
'subscribed': 'Subscribed',
|
||||||
|
'unconfirmed': 'Unconfirmed',
|
||||||
|
'unsubscribed': 'Unsubscribed'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
var messages = {
|
||||||
|
updated: function() {
|
||||||
|
MailPoet.Notice.success('Subscriber succesfully updated!');
|
||||||
|
},
|
||||||
|
created: function() {
|
||||||
|
MailPoet.Notice.success('Subscriber succesfully added!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var SubscriberForm = React.createClass({
|
var SubscriberForm = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form fields={ fields } params={ this.props.params } />
|
<Form
|
||||||
|
endpoint="subscribers"
|
||||||
|
fields={ fields }
|
||||||
|
params={ this.props.params }
|
||||||
|
messages={ messages } />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2,18 +2,15 @@ define(
|
|||||||
[
|
[
|
||||||
'react',
|
'react',
|
||||||
'react-router',
|
'react-router',
|
||||||
'mailpoet',
|
|
||||||
'listing/listing.jsx',
|
'listing/listing.jsx',
|
||||||
'classnames',
|
'classnames',
|
||||||
],
|
],
|
||||||
function(
|
function(
|
||||||
React,
|
React,
|
||||||
Router,
|
Router,
|
||||||
MailPoet,
|
|
||||||
Listing,
|
Listing,
|
||||||
classNames
|
classNames
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var Link = Router.Link;
|
var Link = Router.Link;
|
||||||
|
|
||||||
var columns = [
|
var columns = [
|
||||||
@ -65,31 +62,6 @@ define(
|
|||||||
];
|
];
|
||||||
|
|
||||||
var List = React.createClass({
|
var List = React.createClass({
|
||||||
getItems: function(listing) {
|
|
||||||
MailPoet.Ajax.post({
|
|
||||||
endpoint: 'subscribers',
|
|
||||||
action: 'listing',
|
|
||||||
data: {
|
|
||||||
offset: (listing.state.page - 1) * listing.state.limit,
|
|
||||||
limit: listing.state.limit,
|
|
||||||
group: listing.state.group,
|
|
||||||
search: listing.state.search,
|
|
||||||
sort_by: listing.state.sort_by,
|
|
||||||
sort_order: listing.state.sort_order
|
|
||||||
},
|
|
||||||
onSuccess: function(response) {
|
|
||||||
if(listing.isMounted()) {
|
|
||||||
listing.setState({
|
|
||||||
items: response.items || [],
|
|
||||||
filters: response.filters || [],
|
|
||||||
groups: response.groups || [],
|
|
||||||
count: response.count || 0,
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}.bind(listing)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
renderItem: function(subscriber) {
|
renderItem: function(subscriber) {
|
||||||
var rowClasses = classNames(
|
var rowClasses = classNames(
|
||||||
'manage-column',
|
'manage-column',
|
||||||
@ -151,6 +123,7 @@ define(
|
|||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<Listing
|
<Listing
|
||||||
|
endpoint="subscribers"
|
||||||
onRenderItem={ this.renderItem }
|
onRenderItem={ this.renderItem }
|
||||||
items={ this.getItems }
|
items={ this.getItems }
|
||||||
columns={ columns }
|
columns={ columns }
|
||||||
|
@ -11,7 +11,6 @@ define(
|
|||||||
List,
|
List,
|
||||||
Form
|
Form
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var DefaultRoute = Router.DefaultRoute;
|
var DefaultRoute = Router.DefaultRoute;
|
||||||
var Link = Router.Link;
|
var Link = Router.Link;
|
||||||
var Route = Router.Route;
|
var Route = Router.Route;
|
||||||
|
@ -35,4 +35,28 @@ class Newsletter extends Model {
|
|||||||
|
|
||||||
static function group($orm, $group = null) {
|
static function group($orm, $group = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function createOrUpdate($data = array()) {
|
||||||
|
$newsletter = false;
|
||||||
|
|
||||||
|
if(isset($data['id']) && (int)$data['id'] > 0) {
|
||||||
|
$newsletter = self::findOne((int)$data['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($newsletter === false) {
|
||||||
|
$newsletter = self::create();
|
||||||
|
$newsletter->hydrate($data);
|
||||||
|
} else {
|
||||||
|
unset($data['id']);
|
||||||
|
$newsletter->set($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$saved = $newsletter->save();
|
||||||
|
|
||||||
|
if($saved === false) {
|
||||||
|
return $newsletter->getValidationErrors();
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,20 +15,6 @@ class Segment extends Model {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function createOrUpdate($model) {
|
|
||||||
$exists = self::where('name', $model['name'])
|
|
||||||
->find_one();
|
|
||||||
|
|
||||||
if($exists === false) {
|
|
||||||
$new_model = self::create();
|
|
||||||
$new_model->name = $model['name'];
|
|
||||||
return $new_model->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
$exists->name = $model['name_updated'];
|
|
||||||
return $exists->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function subscribers() {
|
public function subscribers() {
|
||||||
return $this->has_many_through(
|
return $this->has_many_through(
|
||||||
__NAMESPACE__.'\Subscriber',
|
__NAMESPACE__.'\Subscriber',
|
||||||
@ -54,4 +40,28 @@ class Segment extends Model {
|
|||||||
|
|
||||||
static function group($orm, $group = null) {
|
static function group($orm, $group = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function createOrUpdate($data = array()) {
|
||||||
|
$segment = false;
|
||||||
|
|
||||||
|
if(isset($data['id']) && (int)$data['id'] > 0) {
|
||||||
|
$segment = self::findOne((int)$data['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($segment === false) {
|
||||||
|
$segment = self::create();
|
||||||
|
$segment->hydrate($data);
|
||||||
|
} else {
|
||||||
|
unset($data['id']);
|
||||||
|
$segment->set($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$saved = $segment->save();
|
||||||
|
|
||||||
|
if($saved === false) {
|
||||||
|
return $segment->getValidationErrors();
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,10 +80,18 @@ class Subscriber extends Model {
|
|||||||
|
|
||||||
if($subscriber === false) {
|
if($subscriber === false) {
|
||||||
$subscriber = self::create();
|
$subscriber = self::create();
|
||||||
|
$subscriber->hydrate($data);
|
||||||
|
} else {
|
||||||
|
unset($data['id']);
|
||||||
|
$subscriber->set($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$subscriber->hydrate($data);
|
$saved = $subscriber->save();
|
||||||
return $subscriber->save();
|
|
||||||
|
|
||||||
|
if($saved === false) {
|
||||||
|
return $subscriber->getValidationErrors();
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,17 @@ class Newsletters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function get($data = array()) {
|
function get($data = array()) {
|
||||||
|
$id = (isset($data['id']) ? (int)$data['id'] : 0);
|
||||||
|
|
||||||
|
$newsletter = Newsletter::findOne($id);
|
||||||
|
if($newsletter === false) {
|
||||||
|
wp_send_json(false);
|
||||||
|
} else {
|
||||||
|
wp_send_json($newsletter->asArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listing($data = array()) {
|
||||||
$listing = new Listing\Handler(
|
$listing = new Listing\Handler(
|
||||||
\Model::factory('\MailPoet\Models\Newsletter'),
|
\Model::factory('\MailPoet\Models\Newsletter'),
|
||||||
$data
|
$data
|
||||||
@ -24,11 +35,14 @@ class Newsletters {
|
|||||||
wp_send_json($collection);
|
wp_send_json($collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
function save($args) {
|
function save($data = array()) {
|
||||||
$model = Newsletter::create();
|
$result = Newsletter::createOrUpdate($data);
|
||||||
$model->hydrate($args);
|
|
||||||
$result = $model->save();
|
if($result !== true) {
|
||||||
wp_send_json($result);
|
wp_send_json($result);
|
||||||
|
} else {
|
||||||
|
wp_send_json(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function update($args) {
|
function update($args) {
|
||||||
|
@ -10,6 +10,17 @@ class Segments {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function get($data = array()) {
|
function get($data = array()) {
|
||||||
|
$id = (isset($data['id']) ? (int)$data['id'] : 0);
|
||||||
|
|
||||||
|
$segment = Segment::findOne($id);
|
||||||
|
if($segment === false) {
|
||||||
|
wp_send_json(false);
|
||||||
|
} else {
|
||||||
|
wp_send_json($segment->asArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listing($data = array()) {
|
||||||
$listing = new Listing\Handler(
|
$listing = new Listing\Handler(
|
||||||
\Model::factory('\MailPoet\Models\Segment'),
|
\Model::factory('\MailPoet\Models\Segment'),
|
||||||
$data
|
$data
|
||||||
@ -22,11 +33,14 @@ class Segments {
|
|||||||
wp_send_json($collection);
|
wp_send_json($collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
function save($args) {
|
function save($data = array()) {
|
||||||
$model = Segment::create();
|
$result = Segment::createOrUpdate($data);
|
||||||
$model->hydrate($args);
|
|
||||||
$result = $model->save();
|
if($result !== true) {
|
||||||
wp_send_json($result);
|
wp_send_json($result);
|
||||||
|
} else {
|
||||||
|
wp_send_json(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function update($args) {
|
function update($args) {
|
||||||
|
@ -10,7 +10,7 @@ abstract class ValidModel extends \Model
|
|||||||
'indexedErrors' => false, // If True getValidationErrors will return an array with the index
|
'indexedErrors' => false, // If True getValidationErrors will return an array with the index
|
||||||
// being the field name and the value the error. If multiple errors
|
// being the field name and the value the error. If multiple errors
|
||||||
// are triggered for a field only the first will be kept.
|
// are triggered for a field only the first will be kept.
|
||||||
'throw' => self::ON_SAVE // One of self::ON_SET|ON_SAVE|NEVER.
|
'throw' => self::ON_SAVE // One of self::ON_SET|ON_SAVE|NEVER.
|
||||||
// + ON_SET throws immediately when field is set()
|
// + ON_SET throws immediately when field is set()
|
||||||
// + ON_SAVE throws on save()
|
// + ON_SAVE throws on save()
|
||||||
// + NEVER means an exception is never thrown; check for ->getValidaionErrors()
|
// + NEVER means an exception is never thrown; check for ->getValidaionErrors()
|
||||||
@ -130,11 +130,17 @@ abstract class ValidModel extends \Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Overload set; to call validateAndSet
|
* Overload set; to call validateAndSet
|
||||||
* // TODO: handle multiple sets if $name is a field=>val array
|
|
||||||
*/
|
*/
|
||||||
public function set($name, $value = null)
|
public function set($key, $value = null)
|
||||||
{
|
{
|
||||||
$this->validateAndSet($name, $value);
|
if(is_array($key)) {
|
||||||
|
// multiple values
|
||||||
|
foreach($key as $field => $value) {
|
||||||
|
$this->validateAndSet($field, $value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->validateAndSet($key, $value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,10 +23,11 @@
|
|||||||
"napa": "^1.2.0",
|
"napa": "^1.2.0",
|
||||||
"papaparse": "4.1.1",
|
"papaparse": "4.1.1",
|
||||||
"react": "^0.13.3",
|
"react": "^0.13.3",
|
||||||
|
"react-checkbox-group": "^0.2.0",
|
||||||
"react-infinity": "^1.2.2",
|
"react-infinity": "^1.2.2",
|
||||||
"react-prefixr": "^0.1.0",
|
"react-prefixr": "^0.1.0",
|
||||||
"react-waypoint": "^1.0.2",
|
|
||||||
"react-router": "^0.13.3",
|
"react-router": "^0.13.3",
|
||||||
|
"react-waypoint": "^1.0.2",
|
||||||
"select2": "3.5.1",
|
"select2": "3.5.1",
|
||||||
"spectrum-colorpicker": "^1.6.2",
|
"spectrum-colorpicker": "^1.6.2",
|
||||||
"tinymce": "4.1.10",
|
"tinymce": "4.1.10",
|
||||||
|
Reference in New Issue
Block a user