Webpack refactor
- added JSX support (webpack + robofile - added public.js bundle - refactored webpack config for clarity - added settings.jsx form example - fixed acceptance test failing on login
This commit is contained in:
@@ -9,7 +9,9 @@ class RoboFile extends \Robo\Tasks {
|
|||||||
|
|
||||||
private $js_files = array(
|
private $js_files = array(
|
||||||
'assets/js/src/*.js',
|
'assets/js/src/*.js',
|
||||||
'assets/js/src/**/*.js'
|
'assets/js/src/*.jsx',
|
||||||
|
'assets/js/src/**/*.js',
|
||||||
|
'assets/js/src/**/*.jsx'
|
||||||
);
|
);
|
||||||
|
|
||||||
function install() {
|
function install() {
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
define('admin', [
|
define('admin', [
|
||||||
'mailpoet',
|
'mailpoet',
|
||||||
'jquery',
|
'jquery'
|
||||||
'handlebars',
|
],
|
||||||
], function(MailPoet, jQuery, Handlebars) {
|
function(MailPoet, jQuery) {
|
||||||
jQuery(function($) {
|
jQuery(function($) {
|
||||||
// dom ready
|
// dom ready
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
});
|
);
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
jQuery(function($) {
|
define('public', ['jquery'], function(jQuery) {
|
||||||
|
jQuery(function($) {
|
||||||
// dom ready
|
// dom ready
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
203
assets/js/src/settings.jsx
Normal file
203
assets/js/src/settings.jsx
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
define('settings', ['react'], function(React) {
|
||||||
|
|
||||||
|
var STATES = [
|
||||||
|
'AL', 'AK', 'AS', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FL', 'GA', 'HI',
|
||||||
|
'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS',
|
||||||
|
'MO', 'MT', 'NE', 'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR',
|
||||||
|
'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY'
|
||||||
|
]
|
||||||
|
|
||||||
|
var Example = React.createClass({
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
email: true
|
||||||
|
, question: true
|
||||||
|
, submitted: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
var submitted
|
||||||
|
if (this.state.submitted !== null) {
|
||||||
|
submitted = <div className="alert alert-success">
|
||||||
|
<p>ContactForm data:</p>
|
||||||
|
<pre><code>{JSON.stringify(this.state.submitted, null, ' ')}</code></pre>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<div className="panel panel-default">
|
||||||
|
<div className="panel-heading clearfix">
|
||||||
|
<h3 className="panel-title pull-left">Contact Form</h3>
|
||||||
|
<div className="pull-right">
|
||||||
|
<label className="checkbox-inline">
|
||||||
|
<input type="checkbox"
|
||||||
|
checked={this.state.email}
|
||||||
|
onChange={this.handleChange.bind(this, 'email')}
|
||||||
|
/> Email
|
||||||
|
</label>
|
||||||
|
<label className="checkbox-inline">
|
||||||
|
<input type="checkbox"
|
||||||
|
checked={this.state.question}
|
||||||
|
onChange={this.handleChange.bind(this, 'question')}
|
||||||
|
/> Question
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="panel-body">
|
||||||
|
<ContactForm ref="contactForm"
|
||||||
|
email={this.state.email}
|
||||||
|
question={this.state.question}
|
||||||
|
company={this.props.company}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="panel-footer">
|
||||||
|
<button type="button" className="btn btn-primary btn-block" onClick={this.handleSubmit}>Submit</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{submitted}
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
handleChange: function(field, e) {
|
||||||
|
var nextState = {}
|
||||||
|
nextState[field] = e.target.checked
|
||||||
|
this.setState(nextState)
|
||||||
|
},
|
||||||
|
handleSubmit: function() {
|
||||||
|
if (this.refs.contactForm.isValid()) {
|
||||||
|
this.setState({submitted: this.refs.contactForm.getFormData()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A contact form with certain optional fields.
|
||||||
|
*/
|
||||||
|
var ContactForm = React.createClass({
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
email: true
|
||||||
|
, question: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getInitialState: function() {
|
||||||
|
return {errors: {}}
|
||||||
|
},
|
||||||
|
isValid: function() {
|
||||||
|
var fields = ['firstName', 'lastName', 'phoneNumber', 'address', 'city', 'state', 'zipCode']
|
||||||
|
if (this.props.email) fields.push('email')
|
||||||
|
if (this.props.question) fields.push('question')
|
||||||
|
|
||||||
|
var errors = {}
|
||||||
|
fields.forEach(function(field) {
|
||||||
|
var value = trim(this.refs[field].getDOMNode().value)
|
||||||
|
if (!value) {
|
||||||
|
errors[field] = 'This field is required'
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
this.setState({errors: errors})
|
||||||
|
|
||||||
|
var isValid = true
|
||||||
|
for (var error in errors) {
|
||||||
|
isValid = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return isValid
|
||||||
|
},
|
||||||
|
getFormData: function() {
|
||||||
|
var data = {
|
||||||
|
firstName: this.refs.firstName.getDOMNode().value
|
||||||
|
, lastName: this.refs.lastName.getDOMNode().value
|
||||||
|
, phoneNumber: this.refs.phoneNumber.getDOMNode().value
|
||||||
|
, address: this.refs.address.getDOMNode().value
|
||||||
|
, city: this.refs.city.getDOMNode().value
|
||||||
|
, state: this.refs.state.getDOMNode().value
|
||||||
|
, zipCode: this.refs.zipCode.getDOMNode().value
|
||||||
|
, currentCustomer: this.refs.currentCustomerYes.getDOMNode().checked
|
||||||
|
}
|
||||||
|
if (this.props.email) data.email = this.refs.email.getDOMNode().value
|
||||||
|
if (this.props.question) data.question = this.refs.question.getDOMNode().value
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
render: function() {
|
||||||
|
return <div className="form-horizontal">
|
||||||
|
{this.renderTextInput('firstName', 'First Name')}
|
||||||
|
{this.renderTextInput('lastName', 'Last Name')}
|
||||||
|
{this.renderTextInput('phoneNumber', 'Phone number')}
|
||||||
|
{this.props.email && this.renderTextInput('email', 'Email')}
|
||||||
|
{this.props.question && this.renderTextarea('question', 'Question')}
|
||||||
|
{this.renderTextInput('address', 'Address')}
|
||||||
|
{this.renderTextInput('city', 'City')}
|
||||||
|
{this.renderSelect('state', 'State', STATES)}
|
||||||
|
{this.renderTextInput('zipCode', 'Zip Code')}
|
||||||
|
{this.renderRadioInlines('currentCustomer', 'Are you currently a ' + this.props.company + ' Customer?', {
|
||||||
|
values: ['Yes', 'No']
|
||||||
|
, defaultCheckedValue: 'No'
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
renderTextInput: function(id, label) {
|
||||||
|
return this.renderField(id, label,
|
||||||
|
<input type="text" className="form-control" id={id} ref={id}/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderTextarea: function(id, label) {
|
||||||
|
return this.renderField(id, label,
|
||||||
|
<textarea className="form-control" id={id} ref={id}/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderSelect: function(id, label, values) {
|
||||||
|
var options = values.map(function(value) {
|
||||||
|
return <option value={value}>{value}</option>
|
||||||
|
})
|
||||||
|
return this.renderField(id, label,
|
||||||
|
<select className="form-control" id={id} ref={id}>
|
||||||
|
{options}
|
||||||
|
</select>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderRadioInlines: function(id, label, kwargs) {
|
||||||
|
var radios = kwargs.values.map(function(value) {
|
||||||
|
var defaultChecked = (value == kwargs.defaultCheckedValue)
|
||||||
|
return <label className="radio-inline">
|
||||||
|
<input type="radio" ref={id + value} name={id} value={value} defaultChecked={defaultChecked}/>
|
||||||
|
{value}
|
||||||
|
</label>
|
||||||
|
})
|
||||||
|
return this.renderField(id, label, radios)
|
||||||
|
},
|
||||||
|
renderField: function(id, label, field) {
|
||||||
|
return <div className={$c('form-group', {'has-error': id in this.state.errors})}>
|
||||||
|
<label htmlFor={id} className="col-sm-4 control-label">{label}</label>
|
||||||
|
<div className="col-sm-6">
|
||||||
|
{field}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
React.render(<Example company="FakeCo"/>, document.getElementById('mailpoet_settings'))
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
var trim = function() {
|
||||||
|
var TRIM_RE = /^\s+|\s+$/g
|
||||||
|
return function trim(string) {
|
||||||
|
return string.replace(TRIM_RE, '')
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
function $c(staticClassName, conditionalClassNames) {
|
||||||
|
var classNames = []
|
||||||
|
if (typeof conditionalClassNames == 'undefined') {
|
||||||
|
conditionalClassNames = staticClassName
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
classNames.push(staticClassName)
|
||||||
|
}
|
||||||
|
for (var className in conditionalClassNames) {
|
||||||
|
if (!!conditionalClassNames[className]) {
|
||||||
|
classNames.push(className)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return classNames.join(' ')
|
||||||
|
}
|
||||||
|
});
|
88
package.json
88
package.json
@@ -1,43 +1,45 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "napa"
|
"install": "napa"
|
||||||
},
|
},
|
||||||
"napa": {
|
"napa": {
|
||||||
"sticky-kit": "leafo/sticky-kit.git",
|
"sticky-kit": "leafo/sticky-kit.git",
|
||||||
"jquery-validation-engine": "posabsolute/jQuery-Validation-Engine.git",
|
"interact.js": "taye/interact.js.git"
|
||||||
"interact.js": "taye/interact.js.git"
|
},
|
||||||
},
|
"dependencies": {
|
||||||
"dependencies": {
|
"backbone": "1.2.0",
|
||||||
"backbone": "1.2.0",
|
"backbone.marionette": "2.4.2",
|
||||||
"backbone.marionette": "2.4.2",
|
"backbone.radio": "0.9.0",
|
||||||
"backbone.radio": "0.9.0",
|
"backbone.supermodel": "1.2.0",
|
||||||
"backbone.supermodel": "1.2.0",
|
"c3": "~0.4.10",
|
||||||
"c3": "~0.4.10",
|
"codemirror": "^5.5.0",
|
||||||
"codemirror": "^5.5.0",
|
"d3": "~3.5.5",
|
||||||
"d3": "~3.5.5",
|
"handlebars": "3.0.3",
|
||||||
"handlebars": "3.0.3",
|
"html2canvas": "latest",
|
||||||
"html2canvas": "latest",
|
"moment": "^2.10.3",
|
||||||
"moment": "^2.10.3",
|
"napa": "^1.2.0",
|
||||||
"papaparse": "4.1.1",
|
"papaparse": "4.1.1",
|
||||||
"select2": "3.5.1",
|
"react": "^0.13.3",
|
||||||
"spectrum-colorpicker": "^1.6.2",
|
"select2": "3.5.1",
|
||||||
"tinymce": "4.1.10",
|
"spectrum-colorpicker": "^1.6.2",
|
||||||
"underscore": "1.8.3",
|
"tinymce": "4.1.10",
|
||||||
"napa": "^1.2.0"
|
"underscore": "1.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "2.2.0",
|
"babel-core": "^5.8.22",
|
||||||
"chai-jq": "0.0.8",
|
"babel-loader": "^5.3.2",
|
||||||
"grunt": "^0.4.5",
|
"chai": "2.2.0",
|
||||||
"jquery": "2.1.4",
|
"chai-jq": "0.0.8",
|
||||||
"jsdom": "3.1.2",
|
"grunt": "^0.4.5",
|
||||||
"mocha": "2.2.1",
|
"jquery": "2.1.4",
|
||||||
"nib": "latest",
|
"jsdom": "3.1.2",
|
||||||
"sinon": "1.14.1",
|
"mocha": "2.2.1",
|
||||||
"sinon-chai": "2.7.0",
|
"nib": "latest",
|
||||||
"stylus": "latest",
|
"sinon": "1.14.1",
|
||||||
"swag": "~0.7.0",
|
"sinon-chai": "2.7.0",
|
||||||
"webpack": "1.11.0"
|
"stylus": "latest",
|
||||||
}
|
"swag": "~0.7.0",
|
||||||
}
|
"webpack": "1.11.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -7,8 +7,9 @@
|
|||||||
class_name: AcceptanceTester
|
class_name: AcceptanceTester
|
||||||
modules:
|
modules:
|
||||||
enabled:
|
enabled:
|
||||||
- WebDriver:
|
- \Helper\Acceptance
|
||||||
|
- WebDriver:
|
||||||
url: 'http://localhost'
|
url: 'http://localhost'
|
||||||
browser: phantomjs
|
browser: phantomjs
|
||||||
restart: true
|
capabilities:
|
||||||
- \Helper\Acceptance
|
webStorageEnabled: true
|
||||||
|
@@ -1,84 +1,77 @@
|
|||||||
var path = require('path'),
|
var webpack = require('webpack'),
|
||||||
fs = require('fs'),
|
|
||||||
webpack = require("webpack"),
|
|
||||||
_ = require('underscore'),
|
_ = require('underscore'),
|
||||||
baseConfig;
|
baseConfig = {},
|
||||||
|
config = [];
|
||||||
|
|
||||||
baseConfig = {
|
baseConfig = {
|
||||||
name: 'main',
|
context: __dirname,
|
||||||
context: __dirname ,
|
|
||||||
entry: {
|
|
||||||
vendor: ['handlebars', 'handlebars_helpers'],
|
|
||||||
mailpoet: ['mailpoet', 'ajax', 'modal', 'notice'],
|
|
||||||
admin: 'admin.js',
|
|
||||||
},
|
|
||||||
output: {
|
output: {
|
||||||
path: './assets/js',
|
path: './assets/js',
|
||||||
filename: '[name].js',
|
filename: '[name].js',
|
||||||
},
|
},
|
||||||
|
resolve: {
|
||||||
|
modulesDirectories: [
|
||||||
|
'node_modules',
|
||||||
|
'assets/js/src'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
fs: 'empty'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
test: /\.jsx$/,
|
||||||
|
loader: 'babel-loader'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Admin
|
||||||
|
config.push(_.extend({}, baseConfig, {
|
||||||
|
name: 'admin',
|
||||||
|
entry: {
|
||||||
|
vendor: ['handlebars', 'handlebars_helpers'],
|
||||||
|
mailpoet: ['mailpoet', 'ajax', 'modal', 'notice'],
|
||||||
|
admin: ['settings.jsx']
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.js")
|
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'),
|
||||||
],
|
|
||||||
loaders: [
|
|
||||||
{
|
|
||||||
test: /\.js$/i,
|
|
||||||
loader: 'js'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.css$/i,
|
|
||||||
loader: 'css'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.jpe?g$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$|\.wav$|\.mp3$/i,
|
|
||||||
loader: 'file'
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
|
externals: {
|
||||||
|
'jquery': 'jQuery'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Public
|
||||||
|
config.push(_.extend({}, baseConfig, {
|
||||||
|
name: 'public',
|
||||||
|
entry: {
|
||||||
|
public: 'public.js'
|
||||||
|
},
|
||||||
|
externals: {
|
||||||
|
'jquery': 'jQuery'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Test
|
||||||
|
config.push(_.extend({}, baseConfig, {
|
||||||
|
name: 'test',
|
||||||
|
entry: {
|
||||||
|
testAjax: 'testAjax.js',
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: './tests/javascript/testBundles',
|
||||||
|
filename: '[name].js',
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
modulesDirectories: [
|
modulesDirectories: [
|
||||||
'node_modules',
|
'node_modules',
|
||||||
'assets/js/src',
|
'assets/js/src',
|
||||||
'assets/css/lib'
|
'tests/javascript/newsletter_editor'
|
||||||
],
|
]
|
||||||
fallback: path.join(__dirname, 'node_modules'),
|
|
||||||
alias: {
|
|
||||||
'handlebars': 'handlebars/dist/handlebars.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolveLoader: {
|
|
||||||
fallback: path.join(__dirname, 'node_modules'),
|
|
||||||
alias: {
|
|
||||||
'hbs': 'handlebars-loader'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
externals: {
|
|
||||||
'jquery': 'jQuery',
|
|
||||||
}
|
}
|
||||||
};
|
}));
|
||||||
|
|
||||||
module.exports = [
|
module.exports = config;
|
||||||
baseConfig,
|
|
||||||
|
|
||||||
// Configuration specific for testing
|
|
||||||
_.extend({}, baseConfig, {
|
|
||||||
name: 'test',
|
|
||||||
entry: {
|
|
||||||
testAjax: 'testAjax.js',
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: './tests/javascript/testBundles',
|
|
||||||
filename: '[name].js',
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
modulesDirectories: [
|
|
||||||
'node_modules',
|
|
||||||
'assets/js/src',
|
|
||||||
'tests/javascript/newsletter_editor'
|
|
||||||
],
|
|
||||||
fallback: path.join(__dirname, 'node_modules'),
|
|
||||||
alias: {
|
|
||||||
'handlebars': 'handlebars/dist/handlebars.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
})
|
|
||||||
];
|
|
Reference in New Issue
Block a user