many improvements on listing + cleaned up modal library

This commit is contained in:
Jonathan Labreuille
2015-08-26 19:10:09 +02:00
committed by marco
parent 739f06bad7
commit 0e2c04d97a
10 changed files with 796 additions and 753 deletions

View File

@ -1,8 +1,7 @@
define('admin', [ define('admin', [
'mailpoet',
'jquery' 'jquery'
], ],
function(MailPoet, jQuery) { function(jQuery) {
jQuery(function($) { jQuery(function($) {
// dom ready // dom ready
$(function() { $(function() {

View File

@ -1,5 +1,5 @@
define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) { define('ajax', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
"use strict"; 'use strict';
MailPoet.Ajax = { MailPoet.Ajax = {
version: 0.5, version: 0.5,
options: {}, options: {},

View File

@ -0,0 +1,48 @@
define('listing.column', ['react', 'classnames'], function(React, classNames) {
/*
props:
onSort -> callback(sort_by, sort_order)
column -> {
sorted: (string) asc | desc
sortable: (bool)
name: (string) field name
label: (string) displayed label
*/
var ListingColumn = React.createClass({
handleSort: function() {
var sort_by = this.props.column.name,
sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
this.props.onSort(sort_by, sort_order);
},
render: function() {
var classes = classNames(
'manage-column',
{ 'sortable': this.props.column.sortable },
this.props.column.sorted
);
var label;
if(this.props.column.sortable === true) {
label = (
<a onClick={this.handleSort}>
<span>{ this.props.column.label }</span>
<span className="sorting-indicator"></span>
</a>
);
} else {
label = this.props.column.label;
}
return (
<th
className={ classes }
id={this.props.column.name }
scope="col">
{label}
</th>
);
}
});
return ListingColumn;
});

View File

@ -0,0 +1,51 @@
define('listing.header', ['react', 'classnames'], function(React, classNames) {
/*
props:
onSort: callback(sort_by, sort_order)
onSelectAll: callback(is_checked)
sort_by: (string) field name
columns -> (array)
column -> {
sorted: (string) asc | desc
sortable: (bool)
name: (string) field name
label: (string) displayed label
}
*/
var ListingColumn = require('listing/column.jsx');
var ListingHeader = React.createClass({
handleSelectAll: function() {
return this.props.onSelectAll(
this.refs.select_all.getDOMNode().checked
);
},
render: function() {
var columns = this.props.columns.map(function(column) {
return (
<ListingColumn
onSort={this.props.onSort}
sort_by={this.props.sort_by}
key={column.name}
column={column} />
);
}.bind(this));
return (
<tr>
<td className="manage-column column-cb check-column" id="cb">
<label className="screen-reader-text">
{ 'Select All' }
</label>
<input
type="checkbox"
ref="select_all"
onChange={this.handleSelectAll} />
</td>
{columns}
</tr>
);
}
});
return ListingHeader;
});

View File

@ -0,0 +1,143 @@
define('listing.pages', ['react', 'classnames'], function(React, classNames) {
/*
props:
onSetPage -> callback(page)
page -> (int) current page
count -> (int) total number of items
limit -> (int) limit per page
*/
var ListingPages = React.createClass({
setFirstPage: function() {
this.props.onSetPage(1);
},
setLastPage: function() {
this.props.onSetPage(this.getLastPage());
},
setPreviousPage: function() {
this.props.onSetPage(this.constrainPage(this.props.page - 1));
},
setNextPage: function() {
this.props.onSetPage(this.constrainPage(this.props.page + 1));
},
constrainPage: function(page) {
return Math.min(Math.max(1, Math.abs(~~page)), this.getLastPage());
},
handleSetPage: function() {
this.props.onSetPage(
this.constrainPage(this.refs.page.getDOMNode().value)
);
},
getLastPage: function() {
return Math.ceil(this.props.count / this.props.limit);
},
render: function() {
if(this.props.count === 0) {
return (<div></div>);
} else {
var pagination,
firstPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">«</span>
),
previousPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
),
nextPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
),
lastPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">»</span>
);
if(this.props.count > this.props.limit) {
if(this.props.page > 1) {
previousPage = (
<a href="javascript:;"
onClick={this.setPreviousPage}
className="prev-page">
<span className="screen-reader-text">Previous page</span>
<span aria-hidden="true"></span>
</a>
);
}
if(this.props.page > 2) {
firstPage = (
<a href="javascript:;"
onClick={this.setFirstPage}
className="first-page">
<span className="screen-reader-text">First page</span>
<span aria-hidden="true">«</span>
</a>
);
}
if(this.props.page < this.getLastPage()) {
nextPage = (
<a href="javascript:;"
onClick={this.setNextPage}
className="next-page">
<span className="screen-reader-text">Next page</span>
<span aria-hidden="true"></span>
</a>
);
}
if(this.props.page < this.getLastPage() - 1) {
lastPage = (
<a href="javascript:;"
onClick={this.setLastPage}
className="last-page">
<span className="screen-reader-text">Last page</span>
<span aria-hidden="true">»</span>
</a>
);
}
pagination = (
<span className="pagination-links">
{firstPage}
{previousPage}
&nbsp;
<span className="paging-input">
<label
className="screen-reader-text"
htmlFor="current-page-selector">Current Page</label>
<input
type="text"
onChange={this.handleSetPage}
aria-describedby="table-paging"
size="1"
ref="page"
value={this.props.page}
name="paged"
id="current-page-selector"
className="current-page" />
&nbsp;of&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit)}
</span>
</span>
&nbsp;
{nextPage}
{lastPage}
</span>
);
}
var classes = classNames(
'tablenav-pages',
{ 'one-page': (this.props.count <= this.props.limit) }
);
return (
<div className={classes}>
<span className="displaying-num">{ this.props.count } item(s)</span>
{ pagination }
</div>
);
}
}
});
return ListingPages;
});

View File

@ -0,0 +1,36 @@
define('listing.search', ['react'], function(React) {
/*
props:
onSearch -> callback(search)
search -> string
*/
var ListingSearch = React.createClass({
handleSearch: function(e) {
e.preventDefault();
this.props.onSearch(
this.refs.search.getDOMNode().value
);
},
render: function() {
return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
Search
</label>
<input
type="search"
ref="search"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue="Search"
className="button" />
</p>
</form>
);
}
});
return ListingSearch;
});

View File

@ -1,84 +1,27 @@
define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) { define('modal', ['mailpoet', 'jquery'],
"use strict"; function(MailPoet, jQuery) {
/*================================================================================================== 'use strict';
/***************************************************************************
MailPoet Modal: MailPoet Modal:
version: 0.8 version: 0.9
author: Jonathan Labreuille author: Jonathan Labreuille
company: Wysija company: Wysija
dependencies: jQuery dependencies: jQuery
Options:
Mandatory:
// Modal window's title
(string) title: 'Modal title'
// template
(string) template: jQuery('#handlebars_template').html() or
literal html
Optional:
// jQuery cached element object node to be displayed,
// instead of creating a new one
(object) element: jQuery(selector)
// - data object that will be passed to the template when rendering
(object) data: {},
// - data will be loaded via this url and passed to the template
// when rendering
// - if a "data" option was specified, it will be merged with the
// ajax's response data
(string) url: '/url.json'
// ajax method
(string) method: 'post' (default: 'get')
// ajax post params
(object) params: {}
// - integers are expressed in pixels
(mixed) width: '50%' | 100 | '100px'
// - integers are expressed in pixels
// - will be ignored when in "panel" mode
(mixed) height: '50%' | 100 | '100px'
// - only used for "panel" mode
// - will be ignored in "popup" mode
(string) position: 'left' | 'right'
// display overlay or not
(boolean) overlay: true | false
// element(s) to be highlighted when the overlay is "on"
(object) highlight: jQuery element
// callbacks
(function) onInit: called when the modal is displayed
(function) onSuccess: called by calling MailPoet_Guide.success()
(function) onCancel: called when closing the popup
or by calling MailPoet_Guide.cancel()
Usage: Usage:
// popup mode // popup mode
MailPoet.Modal.popup(options); MailPoet.Modal.popup(options);
// panel mode // panel mode
MailPoet.Modal.panel(options); MailPoet.Modal.panel(options);
// loading states // loading mode
MailPoet.Modal.loading(true); // displays loading indicator MailPoet.Modal.loading(bool);
MailPoet.Modal.loading(false); // hides loading indicator ***************************************************************************/
==================================================================================================*/
MailPoet.Modal = { MailPoet.Modal = {
version: 0.8, version: 0.9,
// flags // flags
initialized: false, initialized: false,
@ -150,8 +93,12 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
'<div class="mailpoet_panel_body clearfix"></div>'+ '<div class="mailpoet_panel_body clearfix"></div>'+
'</div>' '</div>'
}, },
setRenderer: function() { getContentContainer: function() {
this.renderer = (typeof(Handlebars) === "undefined") ? 'html' : 'handlebars'; return jQuery('.mailpoet_'+this.options.type+'_body');
},
setRenderer: function(renderer) {
this.renderer = renderer;
return this;
}, },
compileTemplate: function(template) { compileTemplate: function(template) {
if(this.renderer === 'html') { if(this.renderer === 'html') {
@ -159,17 +106,17 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
} else { } else {
return Handlebars.compile(template); return Handlebars.compile(template);
} }
return false;
}, },
init: function(options) { init: function(options) {
if(this.initialized === true) { if(this.initialized === true) {
this.close(); this.close();
} }
// merge options // merge options
this.options = jQuery.extend({}, this.defaults, options); this.options = jQuery.extend({}, this.defaults, options);
// set renderer // set renderer
this.setRenderer(); this.setRenderer(this.options.renderer);
// init overlay // init overlay
this.initOverlay(); this.initOverlay();
@ -180,14 +127,19 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
if(this.options.type !== null) { if(this.options.type !== null) {
// insert modal depending on its type // insert modal depending on its type
if(this.options.type === 'popup') { if(this.options.type === 'popup') {
var modal = this.compileTemplate(this.templates[this.options.type]); var modal = this.compileTemplate(
this.templates[this.options.type]
);
// create modal // create modal
jQuery('#mailpoet_modal_overlay').append(modal(this.options)); jQuery('#mailpoet_modal_overlay')
.append(modal(this.options));
// set title // set title
jQuery('#mailpoet_popup_title h2').html(this.options.title); jQuery('#mailpoet_popup_title h2')
.html(this.options.title);
} else if(this.options.type === 'panel') { } else if(this.options.type === 'panel') {
// create panel // create panel
jQuery('#mailpoet_modal_overlay').after(this.templates[this.options.type]); jQuery('#mailpoet_modal_overlay')
.after(this.templates[this.options.type]);
} }
// add proper overlay class // add proper overlay class
@ -196,8 +148,6 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
.addClass('mailpoet_'+this.options.type+'_overlay'); .addClass('mailpoet_'+this.options.type+'_overlay');
} }
// render template if specified
if(this.options.template !== null) {
// set "success" callback if specified // set "success" callback if specified
if(options.onSuccess !== undefined) { if(options.onSuccess !== undefined) {
this.options.onSuccess = options.onSuccess; this.options.onSuccess = options.onSuccess;
@ -209,11 +159,12 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
} }
// compile template // compile template
this.options.body_template = this.compileTemplate(this.options.template); this.options.body_template = this.compileTemplate(
this.options.template
);
// setup events // setup events
this.setupEvents(); this.setupEvents();
}
// set popup as initialized // set popup as initialized
this.initialized = true; this.initialized = true;
@ -231,9 +182,11 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
}, },
toggleOverlay: function(toggle) { toggleOverlay: function(toggle) {
if(toggle === true) { if(toggle === true) {
jQuery('#mailpoet_modal_overlay').removeClass('mailpoet_overlay_hidden'); jQuery('#mailpoet_modal_overlay')
.removeClass('mailpoet_overlay_hidden');
} else { } else {
jQuery('#mailpoet_modal_overlay').addClass('mailpoet_overlay_hidden'); jQuery('#mailpoet_modal_overlay')
.addClass('mailpoet_overlay_hidden');
} }
return this; return this;
@ -245,8 +198,8 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
// close popup when user clicks on overlay // close popup when user clicks on overlay
jQuery('#mailpoet_modal_overlay').on('click', function(e) { jQuery('#mailpoet_modal_overlay').on('click', function(e) {
// we need to make sure that we are actually clicking on the overlay // we need to make sure that we are actually clicking on the overlay
// because when clicking on the popup content, it will trigger the click // because when clicking on the popup content, it will trigger
// event on the overlay // the click event on the overlay
if(e.target.id === 'mailpoet_modal_overlay') { this.cancel(); } if(e.target.id === 'mailpoet_modal_overlay') { this.cancel(); }
}.bind(this)); }.bind(this));
@ -292,15 +245,19 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
jQuery('.mailpoet_'+this.options.type+'_wrapper').hide(); jQuery('.mailpoet_'+this.options.type+'_wrapper').hide();
// add sub panel wrapper // add sub panel wrapper
jQuery('#mailpoet_'+this.options.type).append(this.templates['subpanel']); jQuery('#mailpoet_'+this.options.type)
.append(this.templates['subpanel']);
// add sub panel content // add sub panel content
jQuery('.mailpoet_'+this.options.type+'_body').last().html(this.subpanels[(this.subpanels.length - 1)].element); jQuery('.mailpoet_'+this.options.type+'_body').last()
.html(this.subpanels[(this.subpanels.length - 1)].element);
} else if (this.options.element) { } else if (this.options.element) {
jQuery('.mailpoet_'+this.options.type+'_body').empty(); jQuery('.mailpoet_'+this.options.type+'_body').empty();
jQuery('.mailpoet_'+this.options.type+'_body').append(this.options.element); jQuery('.mailpoet_'+this.options.type+'_body')
.append(this.options.element);
} else { } else {
jQuery('.mailpoet_'+this.options.type+'_body').html( jQuery('.mailpoet_'+this.options.type+'_body')
.html(
this.options.body_template( this.options.body_template(
this.options.data this.options.data
) )
@ -312,24 +269,27 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
loadUrl: function() { loadUrl: function() {
if(this.options.method === 'get') { if(this.options.method === 'get') {
// make ajax request // make ajax request
jQuery.getJSON(this.options.url, function(data) { jQuery.getJSON(this.options.url,
// merge returned data with existing data passed when calling the "open" method function(data) {
this.options.data = jQuery.extend({}, this.options.data, data); this.options.data = jQuery.extend({}, this.options.data, data);
// load template using fetched data // load template using fetched data
this.loadTemplate(); this.loadTemplate();
// show modal window // show modal window
this.showModal(); this.showModal();
}.bind(this)); }.bind(this)
);
} else if(this.options.method === 'post') { } else if(this.options.method === 'post') {
// make ajax request // make ajax request
jQuery.post(this.options.url, JSON.stringify(this.options.params), function(data) { jQuery.post(this.options.url, JSON.stringify(this.options.params),
// merge returned data with existing data passed when calling the "open" method function(data) {
this.options.data = jQuery.extend({}, this.options.data, data); this.options.data = jQuery.extend({}, this.options.data, data);
// load template using fetched data // load template using fetched data
this.loadTemplate(); this.loadTemplate();
// show modal window // show modal window
this.showModal(); this.showModal();
}.bind(this), 'json'); }.bind(this),
'json'
);
} }
return this; return this;
@ -343,7 +303,9 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
minHeight: this.options.height minHeight: this.options.height
}); });
// set popup wrapper height // set popup wrapper height
jQuery('#mailpoet_popup_wrapper').css({ height: this.options.height}); jQuery('#mailpoet_popup_wrapper').css({
height: this.options.height
});
break; break;
case 'panel': case 'panel':
// set dimensions // set dimensions
@ -389,13 +351,13 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
setTimeout(function() { setTimeout(function() {
// set position of popup depending on screen dimensions. // set position of popup depending on screen dimensions.
if(this.options.position === 'right') { if(this.options.position === 'right') {
jQuery('#mailpoet_panel').css( jQuery('#mailpoet_panel').css({
{ marginRight: 0 } marginRight: 0
); });
} else if(this.options.position === 'left') { } else if(this.options.position === 'left') {
jQuery('#mailpoet_panel').css( jQuery('#mailpoet_panel').css({
{ marginLeft: 0 } marginLeft: 0
); });
} }
}.bind(this), 0); }.bind(this), 0);
break; break;
@ -407,7 +369,7 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
// set modal dimensions // set modal dimensions
this.setDimensions(); this.setDimensions();
// add a flag on the body so that we can prevent scrolling (setting overflow hidden) // add a flag on the body so that we can prevent scrolling
jQuery('body').addClass('mailpoet_modal_opened'); jQuery('body').addClass('mailpoet_modal_opened');
// show popup // show popup
@ -431,7 +393,7 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
// trigger init event if specified // trigger init event if specified
if(this.options.onInit !== null) { if(this.options.onInit !== null) {
this.options.onInit(); this.options.onInit(this);
} }
return this; return this;
@ -441,7 +403,8 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
return this; return this;
}, },
highlightOff: function() { highlightOff: function() {
jQuery('.mailpoet_modal_highlight').removeClass('mailpoet_modal_highlight'); jQuery('.mailpoet_modal_highlight')
.removeClass('mailpoet_modal_highlight');
return this; return this;
}, },
hideModal: function(callback) { hideModal: function(callback) {
@ -530,7 +493,8 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
jQuery('#mailpoet_loading').show(); jQuery('#mailpoet_loading').show();
// add loading class to overlay // add loading class to overlay
jQuery('#mailpoet_modal_overlay').addClass('mailpoet_overlay_loading'); jQuery('#mailpoet_modal_overlay')
.addClass('mailpoet_overlay_loading');
return this; return this;
}, },
@ -538,7 +502,8 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
jQuery('#mailpoet_loading').hide(); jQuery('#mailpoet_loading').hide();
// remove loading class from overlay // remove loading class from overlay
jQuery('#mailpoet_modal_overlay').removeClass('mailpoet_overlay_loading'); jQuery('#mailpoet_modal_overlay')
.removeClass('mailpoet_overlay_loading');
return this; return this;
}, },
@ -602,10 +567,9 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
return this; return this;
}, },
close: function() { close: function() {
if(this.isLocked() === true) return this; if(this.isLocked() === true) { return this; }
if(this.subpanels.length > 0) { if(this.subpanels.length > 0) {
// close subpanel // close subpanel
jQuery('.mailpoet_'+this.options.type+'_wrapper').last().remove(); jQuery('.mailpoet_'+this.options.type+'_wrapper').last().remove();
@ -636,4 +600,6 @@ define('modal', ['mailpoet', 'jquery'], function(MailPoet, jQuery) {
return this; return this;
} }
}; };
}); }
);

View File

@ -2,6 +2,11 @@ define('subscribers.listing',
['mailpoet', 'jquery', 'react/addons', 'classnames'], ['mailpoet', 'jquery', 'react/addons', 'classnames'],
function(MailPoet, jQuery, React, classNames) { function(MailPoet, jQuery, React, classNames) {
var ListingSearch = require('listing/search.jsx');
var ListingPages = require('listing/pages.jsx');
var ListingColumn = require('listing/column.jsx');
var ListingHeader = require('listing/header.jsx');
var ListingGroups = React.createClass({ var ListingGroups = React.createClass({
render: function() { render: function() {
return ( return (
@ -23,35 +28,6 @@ define('subscribers.listing',
} }
}); });
var ListingSearch = React.createClass({
handleSearch: function(e) {
e.preventDefault();
this.props.onSearch(
this.refs.search.getDOMNode().value
);
},
render: function() {
return (
<form name="search" onSubmit={this.handleSearch}>
<p className="search-box">
<label htmlFor="search_input" className="screen-reader-text">
Search
</label>
<input
type="search"
ref="search"
id="search_input"
defaultValue={this.props.search} />
<input
type="submit"
defaultValue="Search"
className="button" />
</p>
</form>
);
}
});
var ListingFilters = React.createClass({ var ListingFilters = React.createClass({
render: function() { render: function() {
return ( return (
@ -60,222 +36,110 @@ define('subscribers.listing',
} }
}); });
var ListingPages = React.createClass({ var ListSelector = React.createClass({
setFirstPage: function() { getList: function(e) {
this.props.onSetPage(1); e.preventDefault();
}, MailPoet.Modal.popup({
setLastPage: function() { title: 'Bulk action',
this.props.onSetPage(this.getLastPage()); template: '',
}, onInit: function(modal) {
setPreviousPage: function() { var target = modal.getContentContainer();
this.props.onSetPage(this.constrainPage(this.props.page - 1)); React.render(
}, <ListSelector />,
setNextPage: function() { target[0]
this.props.onSetPage(this.constrainPage(this.props.page + 1));
},
constrainPage: function(page) {
return Math.min(Math.max(1, Math.abs(~~page)), this.getLastPage());
},
handleSetPage: function() {
this.props.onSetPage(
this.constrainPage(this.refs.page.getDOMNode().value)
); );
}, }
getLastPage: function() { })
return Math.ceil(this.props.count / this.props.limit);
}, },
render: function() { render: function() {
if(this.props.count === 0) {
return (<div></div>);
} else {
var pagination,
firstPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">«</span>
),
previousPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
),
nextPage = (
<span aria-hidden="true" className="tablenav-pages-navspan"></span>
),
lastPage = (
<span aria-hidden="true" className="tablenav-pages-navspan">»</span>
);
if(this.props.count > this.props.limit) {
if(this.props.page > 1) {
previousPage = (
<a href="javascript:;"
onClick={this.setPreviousPage}
className="prev-page">
<span className="screen-reader-text">Previous page</span>
<span aria-hidden="true"></span>
</a>
);
}
if(this.props.page > 2) {
firstPage = (
<a href="javascript:;"
onClick={this.setFirstPage}
className="first-page">
<span className="screen-reader-text">First page</span>
<span aria-hidden="true">«</span>
</a>
);
}
if(this.props.page < this.getLastPage()) {
nextPage = (
<a href="javascript:;"
onClick={this.setNextPage}
className="next-page">
<span className="screen-reader-text">Next page</span>
<span aria-hidden="true"></span>
</a>
);
}
if(this.props.page < this.getLastPage() - 1) {
lastPage = (
<a href="javascript:;"
onClick={this.setLastPage}
className="last-page">
<span className="screen-reader-text">Last page</span>
<span aria-hidden="true">»</span>
</a>
);
}
pagination = (
<span className="pagination-links">
{firstPage}
{previousPage}
&nbsp;
<span className="paging-input">
<label
className="screen-reader-text"
htmlFor="current-page-selector">Current Page</label>
<input
type="text"
onChange={this.handleSetPage}
aria-describedby="table-paging"
size="1"
ref="page"
value={this.props.page}
name="paged"
id="current-page-selector"
className="current-page" />
&nbsp;of&nbsp;
<span className="total-pages">
{Math.ceil(this.props.count / this.props.limit)}
</span>
</span>
&nbsp;
{nextPage}
{lastPage}
</span>
);
}
var classes = classNames(
'tablenav-pages',
{ 'one-page': (this.props.count <= this.props.limit) }
);
return ( return (
<div className={classes}> <div>
<span className="displaying-num">{ this.props.count } item(s)</span> <select >
{ pagination } <option>Select a list</option>
</select>
<a onClick={this.getList}>Test me</a>
</div> </div>
); );
} }
});
var ListingBulkAction = React.createClass({
render: function() {
return (
<option value={this.props.action.action}>
{ this.props.action.label }
</option>
);
} }
}); });
var ListingBulkActions = React.createClass({ var ListingBulkActions = React.createClass({
render: function() { handleAction: function() {
return ( var action = jQuery(this.refs.action.getDOMNode()).val();
<div></div> console.log(action, this.props.selected);
);
}
});
var ListingColumn = React.createClass({
handleSort: function() {
var sort_by = this.props.column.name,
sort_order = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
this.props.onSort(sort_by, sort_order);
}, },
render: function() { render: function() {
var classes = classNames(
'manage-column',
{ 'sortable': this.props.column.sortable },
this.props.column.sorted
);
var label;
if(this.props.column.sortable === true) {
label = (
<a onClick={this.handleSort}>
<span>{ this.props.column.label }</span>
<span className="sorting-indicator"></span>
</a>
);
} else {
label = this.props.column.label;
}
return ( return (
<th <div className="alignleft actions bulkactions">
className={ classes } <label
id={this.props.column.name } className="screen-reader-text"
scope="col"> htmlFor="bulk-action-selector-top">
{label} Select bulk action
</th>
);
}
});
var ListingHeader = React.createClass({
render: function() {
var columns = this.props.columns.map(function(column) {
return (
<ListingColumn
onSort={this.props.onSort}
sort_by={this.props.sort_by}
key={column.name}
column={column} />
);
}.bind(this));
return (
<tr>
<td className="manage-column column-cb check-column" id="cb">
<label htmlFor="cb-select-all-1" className="screen-reader-text">
Select All
</label> </label>
<input type="checkbox" id="cb-select-all-1" />
</td> <select ref="action">
{columns} <option>Bulk Actions</option>
</tr> {this.props.actions.map(function(action, index) {
return (
<ListingBulkAction
action={action}
key={index} />
);
}.bind(this))}
</select>
<input
onClick={this.handleAction}
type="submit"
defaultValue="Apply"
className="button action" />
</div>
); );
} }
}); });
var ListingItem = React.createClass({ var ListingItem = React.createClass({
handleSelect: function(e) {
var is_checked = jQuery(e.target).is(':checked');
this.props.onSelect(
parseInt(e.target.value, 10),
is_checked
);
return !e.target.checked;
},
render: function() { render: function() {
var rowClasses = classNames(
'title',
'column-title',
'has-row-actions',
'column-primary',
'page-title'
);
return ( return (
<tr> <tr>
<th className="check-column" scope="row"> <th className="check-column" scope="row">
<label htmlFor="cb-select-1" className="screen-reader-text"> <label className="screen-reader-text">
Select { this.props.item.email }</label> { 'Select ' + this.props.item.email }</label>
<input <input
type="checkbox" type="checkbox"
defaultValue={ this.props.item.id } defaultValue={ this.props.item.id }
name="item[]" id="cb-select-1" /> defaultChecked={ this.props.item.selected }
onChange={ this.handleSelect } />
</th> </th>
<td className="title column-title has-row-actions column-primary page-title"> <td className={rowClasses}>
<strong> <strong>
<a className="row-title">{ this.props.item.email }</a> <a className="row-title">{ this.props.item.email }</a>
</strong> </strong>
@ -311,9 +175,11 @@ define('subscribers.listing',
return ( return (
<tbody> <tbody>
{this.props.items.map(function(item) { {this.props.items.map(function(item) {
item.selected = (this.props.selected.indexOf(item.id) !== -1);
return ( return (
<ListingItem <ListingItem
columns={this.props.columns} columns={this.props.columns}
onSelect={this.props.onSelect}
key={item.id} key={item.id}
item={item} /> item={item} />
); );
@ -334,7 +200,8 @@ define('subscribers.listing',
limit: 10, limit: 10,
sort_by: 'email', sort_by: 'email',
sort_order: 'asc', sort_order: 'asc',
items: [] items: [],
selected: []
}; };
}, },
componentDidMount: function() { componentDidMount: function() {
@ -377,6 +244,31 @@ define('subscribers.listing',
this.getItems(); this.getItems();
}.bind(this)); }.bind(this));
}, },
handleSelect: function(id, is_checked) {
var selected = this.state.selected;
if(is_checked) {
selected = jQuery.merge(selected, [ id ]);
} else {
selected.splice(selected.indexOf(id), 1);
}
this.setState({
selected: selected
});
},
handleSelectAll: function(is_checked) {
if(is_checked === false) {
this.setState({ selected: [] });
} else {
var selected = this.state.items.map(function(item) {
return ~~item.id;
});
this.setState({
selected: selected
});
}
},
handleSetPage: function(page) { handleSetPage: function(page) {
this.setState({ page: page }, function() { this.setState({ page: page }, function() {
this.getItems(); this.getItems();
@ -408,7 +300,9 @@ define('subscribers.listing',
onSearch={this.handleSearch} onSearch={this.handleSearch}
search={this.state.search} /> search={this.state.search} />
<div className="tablenav top clearfix"> <div className="tablenav top clearfix">
<ListingBulkActions /> <ListingBulkActions
actions={this.props.actions}
selected={this.state.selected} />
<ListingFilters /> <ListingFilters />
<ListingPages <ListingPages
count={this.state.count} count={this.state.count}
@ -420,6 +314,7 @@ define('subscribers.listing',
<thead> <thead>
<ListingHeader <ListingHeader
onSort={this.handleSort} onSort={this.handleSort}
onSelectAll={this.handleSelectAll}
sort_by={this.state.sort_by} sort_by={this.state.sort_by}
sort_order={this.state.sort_order} sort_order={this.state.sort_order}
columns={this.props.columns} /> columns={this.props.columns} />
@ -427,11 +322,14 @@ define('subscribers.listing',
<ListingItems <ListingItems
columns={this.props.columns} columns={this.props.columns}
selected={this.state.selected}
onSelect={this.handleSelect}
items={items} /> items={items} />
<tfoot> <tfoot>
<ListingHeader <ListingHeader
onSort={this.handleSort} onSort={this.handleSort}
onSelectAll={this.handleSelectAll}
sort_by={this.state.sort_by} sort_by={this.state.sort_by}
sort_order={this.state.sort_order} sort_order={this.state.sort_order}
columns={this.props.columns} /> columns={this.props.columns} />
@ -439,7 +337,9 @@ define('subscribers.listing',
</table> </table>
<div className="tablenav bottom"> <div className="tablenav bottom">
<ListingBulkActions /> <ListingBulkActions
actions={this.props.actions}
selected={this.state.selected} />
<ListingPages <ListingPages
count={this.state.count} count={this.state.count}
page={this.state.page} page={this.state.page}
@ -480,11 +380,32 @@ define('subscribers.listing',
}, },
]; ];
var actions = [
{
label: 'Move to list...',
endpoint: 'subscribers',
action: 'move',
},
{
label: 'Add to list...',
endpoint: 'subscribers',
action: 'add',
},
{
label: 'Remove from list...',
endpoint: 'subscribers',
action: 'remove',
}
];
var element = jQuery('#mailpoet_subscribers_listing'); var element = jQuery('#mailpoet_subscribers_listing');
if(element.length > 0) { if(element.length > 0) {
React.render( React.render(
<Listing columns={columns} />, <Listing columns={columns} actions={actions} />,
element[0] element[0]
); );
} }

View File

@ -1,123 +0,0 @@
define('subscribers.table',
['mailpoet', 'jquery', 'react/addons', 'react-waypoint'],
function(MailPoet, jQuery, React, Waypoint) {
var InfiniteScrollExample = React.createClass({
_loadMoreItems: function() {
this.setState({ loading: true });
this.setState({ page: (this.state.page + 1) }, function() {
this.loadItems();
}.bind(this));
},
loadItems: function() {
MailPoet.Ajax.post({
endpoint: 'subscribers',
action: 'get',
data: {
offset: (this.state.page - 1) * this.state.limit,
limit: this.state.limit
},
onSuccess: function(response) {
if(this.isMounted()) {
var items = jQuery.merge(this.state.items, response);
this.setState({
items: items,
loading: false
});
}
}.bind(this)
});
},
componentDidMount: function() {
this.loadItems();
},
getInitialState: function() {
// set up list of items ...
return {
loading: false,
items: [],
page: 1,
limit: 50
};
},
_renderLoadingMessage: function() {
if (this.state.loading) {
return (
<p>
Loading {this.state.limit} more items
</p>
);
} else {
return (
<p>{this.state.items.length} items</p>
);
}
},
_renderItems: function() {
return this.state.items.map(function(subscriber, index) {
return (
<tr>
<th className="check-column" scope="row">
<label htmlFor="cb-select-1" className="screen-reader-text">
Select { subscriber.email }</label>
<input
type="checkbox"
value={ subscriber.id }
name="item[]" id="cb-select-1" />
</th>
<td className="title column-title has-row-actions column-primary page-title">
<strong>
<a className="row-title">{ subscriber.email }</a>
</strong>
</td>
<td>
{ subscriber.first_name }
</td>
<td>
{ subscriber.last_name }
</td>
<td className="date column-date">
<abbr title="">{ subscriber.created_at }</abbr>
</td>
<td className="date column-date">
<abbr title="">{ subscriber.updated_at }</abbr>
</td>
</tr>
);
});
},
_renderWaypoint: function() {
if (!this.state.loading) {
return (
<Waypoint
onEnter={this._loadMoreItems}
threshold={0.8} />
);
}
},
render: function() {
return (
<div>
{this._renderLoadingMessage()}
<div className="infinite-scroll-example">
<div className="infinite-scroll-example__scrollable-parent">
{this._renderItems()}
{this._renderWaypoint()}
</div>
</div>
</div>
);
}
});
React.render(
<InfiniteScrollExample />,
document.getElementById('example')
)
}
);

View File

@ -9,10 +9,12 @@ class Widget {
} }
function init() { function init() {
if(!is_admin() && !is_login_page()) {
add_action('widgets_init', array($this, 'registerWidget')); add_action('widgets_init', array($this, 'registerWidget'));
add_action('widgets_init', array($this, 'setupActions')); add_action('widgets_init', array($this, 'setupActions'));
add_action('widgets_init', array($this, 'setupDependencies')); add_action('widgets_init', array($this, 'setupDependencies'));
} }
}
function registerWidget() { function registerWidget() {
register_widget('\MailPoet\Form\Widget'); register_widget('\MailPoet\Form\Widget');