diff --git a/.eslintrc.js b/.eslintrc.js index f57d0279c..63009638d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -45,6 +45,9 @@ module.exports = { "vars": "local" } ], + "react/no-danger": [ + "error" + ], "react/forbid-prop-types": [ "off" ], @@ -60,9 +63,6 @@ module.exports = { "no-useless-escape": [ "off" ], - "react/no-danger": [ - "error" - ], }), "settings": { "import/extensions": [ diff --git a/client/src/components/Breadcrumb/Breadcrumb.js b/client/src/components/Breadcrumb/Breadcrumb.js index 0432c8657..93c3aa093 100644 --- a/client/src/components/Breadcrumb/Breadcrumb.js +++ b/client/src/components/Breadcrumb/Breadcrumb.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; class Breadcrumb extends Component { @@ -44,7 +44,7 @@ class Breadcrumb extends Component { className={iconClassNames.join(' ')} role="button" tabIndex={0} - onClick={crumb.icon.action} + onClick={crumb.icon.onClick} /> )} @@ -67,7 +67,15 @@ class Breadcrumb extends Component { } Breadcrumb.propTypes = { - crumbs: React.PropTypes.array, + crumbs: PropTypes.arrayOf(PropTypes.shape({ + onClick: PropTypes.func, + text: PropTypes.string, + icon: PropTypes.shape({ + className: PropTypes.string, + onClick: PropTypes.func, + action: (props) => { if (props.action) { throw new Error('action: no longer used'); } }, + }) + })), }; function mapStateToProps(state) { diff --git a/client/src/components/Breadcrumb/tests/breadcrumb-test.js b/client/src/components/Breadcrumb/tests/breadcrumb-test.js index 7fa790661..2acbd6273 100644 --- a/client/src/components/Breadcrumb/tests/breadcrumb-test.js +++ b/client/src/components/Breadcrumb/tests/breadcrumb-test.js @@ -25,7 +25,7 @@ describe('BreadcrumbsComponent', () => { href: 'href3', icon: { className: 'breadcrumb3icon', - action: jest.genMockFunction(), + onClick: jest.fn(), }, }, ]; diff --git a/client/src/components/CheckboxField/CheckboxField.js b/client/src/components/CheckboxField/CheckboxField.js index 315765d8a..ba8353e2c 100644 --- a/client/src/components/CheckboxField/CheckboxField.js +++ b/client/src/components/CheckboxField/CheckboxField.js @@ -1,15 +1,13 @@ -import React, { Component } from 'react'; +import React from 'react'; import OptionField from '../OptionsetField/OptionField'; import fieldHolder from 'components/FieldHolder/FieldHolder'; -class CheckboxField extends Component { - render() { - // Build standard checkbox with fieldholder - const FieldHolder = fieldHolder(OptionField); +const CheckboxField = () => { + // Build standard checkbox with fieldholder + const FieldHolder = fieldHolder(OptionField); - // set to not show field holder labels, as checkbox already generates a label - return ; - } -} + // set to not show field holder labels, as checkbox already generates a label + return ; +}; export default CheckboxField; diff --git a/client/src/components/CheckboxSetField/CheckboxSetField.js b/client/src/components/CheckboxSetField/CheckboxSetField.js index 6e5400c25..c4b02de9a 100644 --- a/client/src/components/CheckboxSetField/CheckboxSetField.js +++ b/client/src/components/CheckboxSetField/CheckboxSetField.js @@ -44,30 +44,6 @@ class CheckboxSetField extends Component { return []; } - /** - * Handler for sorting what the value of the field will be, this flows on from the - * OptionField (single checkbox) event handler and adding or removing the corresponding value the - * single checkbox represented to suit the multiple checkbox group. - * - * @param {Event} event - * @param {object} field - */ - handleChange(event, field) { - if (typeof this.props.onChange === 'function') { - const oldValue = this.getValues(); - const newValue = this.props.source - .filter((item, index) => { - if (this.getItemKey(item, index) === field.id) { - return field.value === 1; - } - return oldValue.indexOf(`${item.value}`) > -1; - }) - .map((item) => `${item.value}`); - - this.props.onChange(newValue); - } - } - /** * Fetches properties for an item * @@ -93,6 +69,30 @@ class CheckboxSetField extends Component { }; } + /** + * Handler for sorting what the value of the field will be, this flows on from the + * OptionField (single checkbox) event handler and adding or removing the corresponding value the + * single checkbox represented to suit the multiple checkbox group. + * + * @param {Event} event + * @param {object} field + */ + handleChange(event, field) { + if (typeof this.props.onChange === 'function') { + const oldValue = this.getValues(); + const newValue = this.props.source + .filter((item, index) => { + if (this.getItemKey(item, index) === field.id) { + return field.value === 1; + } + return oldValue.indexOf(`${item.value}`) > -1; + }) + .map((item) => `${item.value}`); + + this.props.onChange(newValue); + } + } + render() { if (!this.props.source) { return null; diff --git a/client/src/components/FieldHolder/FieldHolder.js b/client/src/components/FieldHolder/FieldHolder.js index a172199a4..3d2402374 100644 --- a/client/src/components/FieldHolder/FieldHolder.js +++ b/client/src/components/FieldHolder/FieldHolder.js @@ -27,10 +27,36 @@ function fieldHolder(Field) { return message; } + /** + * Generates the properties for the field holder + * + * @returns {object} + */ + getHolderProps() { + // The extraClass property is defined on both the holder and element + // for legacy reasons (same behaviour as PHP rendering) + const classNames = [ + 'field', + this.props.extraClass, + ]; + if (this.props.readOnly) { + classNames.push('readonly'); + } + + return { + bsClass: this.props.bsClass, + bsSize: this.props.bsSize, + validationState: this.props.validationState, + className: classNames.join(' '), + controlId: this.props.id, + id: this.props.holderId, + }; + } + /** * Build description * - * @returns {Component} + * @returns {React} */ renderDescription() { if (this.props.description === null) { @@ -47,7 +73,7 @@ function fieldHolder(Field) { /** * Build a FormAlert * - * @returns {Component} + * @returns {React} */ renderMessage() { const message = this.getMessage(); @@ -66,7 +92,7 @@ function fieldHolder(Field) { /** * Build title label * - * @returns {Component} + * @returns {React} */ renderLeftTitle() { const labelText = this.props.leftTitle !== null @@ -87,7 +113,7 @@ function fieldHolder(Field) { /** * Build title label * - * @returns {Component} + * @returns {React} */ renderRightTitle() { if (!this.props.rightTitle || this.props.hideLabels) { @@ -101,32 +127,6 @@ function fieldHolder(Field) { ); } - /** - * Generates the properties for the field holder - * - * @returns {object} - */ - getHolderProps() { - // The extraClass property is defined on both the holder and element - // for legacy reasons (same behaviour as PHP rendering) - const classNames = [ - 'field', - this.props.extraClass, - ]; - if (this.props.readOnly) { - classNames.push('readonly'); - } - - return { - bsClass: this.props.bsClass, - bsSize: this.props.bsSize, - validationState: this.props.validationState, - className: classNames.join(' '), - controlId: this.props.id, - id: this.props.holderId, - }; - } - renderField() { const hasMessage = Boolean(this.getMessage()); const props = { @@ -137,7 +137,7 @@ function fieldHolder(Field) { ), }; - const field = ; + const field = ; const prefix = this.props.data.prefix; const suffix = this.props.data.suffix; if (!prefix && !suffix) { diff --git a/client/src/components/Form/Form.js b/client/src/components/Form/Form.js index d3f1340bc..7fa05cf0a 100644 --- a/client/src/components/Form/Form.js +++ b/client/src/components/Form/Form.js @@ -92,6 +92,7 @@ Form.propTypes = { method: PropTypes.string.isRequired, }), fields: PropTypes.array.isRequired, + // props is named `handleSubmit` as it is recieved from redux-form handleSubmit: PropTypes.func, mapActionsToComponents: PropTypes.func.isRequired, mapFieldsToComponents: PropTypes.func.isRequired, diff --git a/client/src/components/FormAction/FormAction.js b/client/src/components/FormAction/FormAction.js index 37a36bfe8..510ccacf6 100644 --- a/client/src/components/FormAction/FormAction.js +++ b/client/src/components/FormAction/FormAction.js @@ -9,17 +9,6 @@ class FormAction extends Component { this.handleClick = this.handleClick.bind(this); } - render() { - const title = this.props.title; - - return ( - - {this.getLoadingIcon()} - {castStringToElement('span', title, { className: 'btn__title' })} - - ); - } - /** * Get props for the button * @@ -71,17 +60,6 @@ class FormAction extends Component { return classnames(buttonClasses); } - /** - * @return {boolean} - */ - isPrimary() { - const extraClasses = this.props.extraClass.split(' '); - return ( - this.props.name === 'action_save' || - extraClasses.find(className => className === 'ss-ui-action-constructive') - ); - } - /** * Gets the bootstrap classname for this action * @@ -140,6 +118,17 @@ class FormAction extends Component { return null; } + /** + * @return {boolean} + */ + isPrimary() { + const extraClasses = this.props.extraClass.split(' '); + return ( + this.props.name === 'action_save' || + extraClasses.find(className => className === 'ss-ui-action-constructive') + ); + } + /** * Event handler triggered when a user clicks the button. * @@ -147,16 +136,28 @@ class FormAction extends Component { * @return undefined */ handleClick(event) { - if (typeof this.props.handleClick === 'function') { - this.props.handleClick(event, this.props.name || this.props.id); + if (typeof this.props.onClick === 'function') { + this.props.onClick(event, this.props.name || this.props.id); } } + + render() { + const title = this.props.title; + + return ( + + {this.getLoadingIcon()} + {castStringToElement('span', title, { className: 'btn__title' })} + + ); + } } FormAction.propTypes = { id: React.PropTypes.string, name: React.PropTypes.string, - handleClick: React.PropTypes.func, + onClick: React.PropTypes.func, + handleClick: () => { throw new Error('no longer used'); }, title: React.PropTypes.string, type: React.PropTypes.string, loading: React.PropTypes.bool, diff --git a/client/src/components/FormAlert/FormAlert.js b/client/src/components/FormAlert/FormAlert.js index c6850bdea..afcffb48a 100644 --- a/client/src/components/FormAlert/FormAlert.js +++ b/client/src/components/FormAlert/FormAlert.js @@ -17,17 +17,6 @@ class FormAlert extends Component { }; } - /** - * Handler for when the message box is dismissed and hidden - */ - handleDismiss() { - if (typeof this.props.onDismiss === 'function') { - this.props.onDismiss(); - } else { - this.setState({ visible: false }); - } - } - /** * Determines the style for the alert box to be shown based on messageType or other property * by default use "danger". @@ -71,6 +60,17 @@ class FormAlert extends Component { }; } + /** + * Handler for when the message box is dismissed and hidden + */ + handleDismiss() { + if (typeof this.props.onDismiss === 'function') { + this.props.onDismiss(); + } else { + this.setState({ visible: false }); + } + } + render() { // @todo default this.props.visible as null if ((typeof this.props.visible !== 'boolean' && this.state.visible) || this.props.visible) { diff --git a/client/src/components/FormBuilder/FormBuilder.js b/client/src/components/FormBuilder/FormBuilder.js index 951449a7f..bade3e177 100644 --- a/client/src/components/FormBuilder/FormBuilder.js +++ b/client/src/components/FormBuilder/FormBuilder.js @@ -212,8 +212,8 @@ class FormBuilder extends Component { */ handleAction(event) { // Custom handlers - if (typeof this.props.handleAction === 'function') { - this.props.handleAction(event, this.props.values); + if (typeof this.props.onAction === 'function') { + this.props.onAction(event, this.props.values); } // Allow custom handlers to cancel event @@ -257,8 +257,8 @@ class FormBuilder extends Component { throw reason; }); - if (typeof this.props.handleSubmit === 'function') { - return this.props.handleSubmit(dataWithAction, action, submitFn); + if (typeof this.props.onSubmit === 'function') { + return this.props.onSubmit(dataWithAction, action, submitFn); } return submitFn(); @@ -277,7 +277,7 @@ class FormBuilder extends Component { if (action.children) { props.children = this.mapActionsToComponents(action.children); } else { - props.handleClick = this.handleAction; + props.onClick = this.handleAction; // Reset through componentWillReceiveProps() if (this.props.submitting && this.state.submittingAction === action.name) { @@ -396,8 +396,10 @@ const schemaPropType = PropTypes.shape({ const basePropTypes = { createFn: PropTypes.func, - handleSubmit: PropTypes.func, - handleAction: PropTypes.func, + onSubmit: PropTypes.func, + handleSubmit: (props) => { if (props.handleSubmit) { throw new Error('handleSubmit: no longer used'); } }, + onAction: PropTypes.func, + handleAction: (props) => { if (props.handleAction) { throw new Error('handleAction: no longer used'); } }, asyncValidate: PropTypes.func, onSubmitFail: PropTypes.func, onSubmitSuccess: PropTypes.func, @@ -437,5 +439,9 @@ FormBuilder.defaultProps = { autoFocus: false, }; -export { basePropTypes, schemaPropType }; +export { + FormBuilder as Component, + basePropTypes, + schemaPropType +}; export default withInjector(FormBuilder); diff --git a/client/src/components/FormBuilder/tests/FormBuilder-test.js b/client/src/components/FormBuilder/tests/FormBuilder-test.js index 6c6184572..953656e38 100644 --- a/client/src/components/FormBuilder/tests/FormBuilder-test.js +++ b/client/src/components/FormBuilder/tests/FormBuilder-test.js @@ -2,7 +2,7 @@ import React from 'react'; import ReactTestUtils from 'react-addons-test-utils'; -import FormBuilder from '../FormBuilder'; +import { Component as FormBuilder } from '../FormBuilder'; import schemaFieldValues, { findField, schemaMerge } from 'lib/schemaFieldValues'; describe('FormBuilder', () => { diff --git a/client/src/components/FormBuilderModal/FormBuilderModal.js b/client/src/components/FormBuilderModal/FormBuilderModal.js index bb1451a85..610e1bc63 100644 --- a/client/src/components/FormBuilderModal/FormBuilderModal.js +++ b/client/src/components/FormBuilderModal/FormBuilderModal.js @@ -28,8 +28,8 @@ class FormBuilderModal extends Component { return ( @@ -84,13 +84,26 @@ class FormBuilderModal extends Component { }); } + handleLoadingError(schema) { + if (this.props.showErrorMessage) { + const error = schema.errors && schema.errors[0]; + this.setState({ + response: error.value, + error: true, + }); + } + if (typeof this.props.onLoadingError === 'function') { + this.props.onLoadingError(schema); + } + } + /** * Call the callback for hiding this Modal */ handleHide() { this.clearResponse(); - if (typeof this.props.handleHide === 'function') { - this.props.handleHide(); + if (typeof this.props.onHide === 'function') { + this.props.onHide(); } } @@ -105,8 +118,8 @@ class FormBuilderModal extends Component { handleSubmit(data, action, submitFn) { this.clearResponse(); let promise = null; - if (typeof this.props.handleSubmit === 'function') { - promise = this.props.handleSubmit(data, action, submitFn); + if (typeof this.props.onSubmit === 'function') { + promise = this.props.onSubmit(data, action, submitFn); } else { promise = submitFn(); } @@ -145,7 +158,7 @@ class FormBuilderModal extends Component { ); } - if (typeof this.props.handleHide === 'function') { + if (typeof this.props.onHide === 'function') { return ( { if (props.handleHide) { throw new Error('handleHide: no longer used'); } }, schemaUrl: React.PropTypes.string, - handleSubmit: React.PropTypes.func, - handleAction: React.PropTypes.func, + handleSubmit: (props) => { if (props.handleSubmit) { throw new Error('handleSubmit: no longer used'); } }, + onSubmit: React.PropTypes.func, + handleAction: (props) => { if (props.handleAction) { throw new Error('handleAction: no longer used'); } }, + onAction: React.PropTypes.func, responseClassGood: React.PropTypes.string, responseClassBad: React.PropTypes.string, identifier: React.PropTypes.string, diff --git a/client/src/components/FormBuilderModal/tests/FormBuilderModal-test.js b/client/src/components/FormBuilderModal/tests/FormBuilderModal-test.js index 40163ecc4..1e8cad1a7 100644 --- a/client/src/components/FormBuilderModal/tests/FormBuilderModal-test.js +++ b/client/src/components/FormBuilderModal/tests/FormBuilderModal-test.js @@ -15,7 +15,7 @@ describe('FormBuilderModal', () => { props = { title: '', show: false, - handleHide: jest.genMockFunction(), + onHide: jest.fn(), identifier: 'FormModalTest', }; }); diff --git a/client/src/components/GridField/GridField.js b/client/src/components/GridField/GridField.js index 107eb6dc7..facc35a03 100644 --- a/client/src/components/GridField/GridField.js +++ b/client/src/components/GridField/GridField.js @@ -42,42 +42,17 @@ class GridField extends Component { ); } - render() { - if (this.props.records === NotYetLoaded) { - // TODO Replace with better loading indicator - return { i18n._t('CampaignAdmin.LOADING', 'Loading...') }; - } - - if (!this.props.records.length) { - return { i18n._t('CampaignAdmin.NO_RECORDS', 'No campaigns created yet.') }; - } - - // Placeholder to align the headers correctly with the content - const actionPlaceholder = ; - const headerCells = this.props.data.columns.map((column) => - {column.name} - ); - const header = {headerCells.concat(actionPlaceholder)}; - const rows = this.props.records.map((record) => - this.createRow(record) - ); - - return ( - - ); - } - createRowActions(record) { return ( @@ -85,10 +60,10 @@ class GridField extends Component { } createCell(record, column) { - const handleDrillDown = this.props.data.handleDrillDown; + const handleDrillDown = this.props.data.onDrillDown; const cellProps = { className: handleDrillDown ? 'grid-field__cell--drillable' : '', - handleDrillDown: handleDrillDown ? (event) => handleDrillDown(event, record) : null, + onDrillDown: handleDrillDown ? (event) => handleDrillDown(event, record) : null, key: `${column.name}`, width: column.width, }; @@ -102,7 +77,7 @@ class GridField extends Component { */ createRow(record) { const rowProps = { - className: this.props.data.handleDrillDown ? 'grid-field__row--drillable' : '', + className: this.props.data.onDrillDown ? 'grid-field__row--drillable' : '', key: `${record.ID}`, }; const cells = this.props.data.columns.map((column) => @@ -150,12 +125,37 @@ class GridField extends Component { editRecord(event, id) { event.preventDefault(); - if (typeof this.props.data === 'undefined' || - typeof this.props.data.handleEditRecord === 'undefined') { + if (!this.props.data) { return; } + if (typeof this.props.data.onEditRecord === 'function') { + this.props.data.onEditRecord(event, id); + } + } - this.props.data.handleEditRecord(event, id); + render() { + if (this.props.records === NotYetLoaded) { + // TODO Replace with better loading indicator + return { i18n._t('CampaignAdmin.LOADING', 'Loading...') }; + } + + if (!this.props.records.length) { + return { i18n._t('CampaignAdmin.NO_RECORDS', 'No campaigns created yet.') }; + } + + // Placeholder to align the headers correctly with the content + const actionPlaceholder = ; + const headerCells = this.props.data.columns.map((column) => + {column.name} + ); + const header = {headerCells.concat(actionPlaceholder)}; + const rows = this.props.records.map((record) => + this.createRow(record) + ); + + return ( + + ); } } @@ -164,8 +164,10 @@ GridField.propTypes = { recordType: React.PropTypes.string.isRequired, headerColumns: React.PropTypes.array, collectionReadEndpoint: React.PropTypes.object, - handleDrillDown: React.PropTypes.func, - handleEditRecord: React.PropTypes.func, + onDrillDown: React.PropTypes.func, + handleDrillDown: () => { throw new Error('no longer used'); }, + onEditRecord: React.PropTypes.func, + handleEditRecord: () => { throw new Error('no longer used'); }, }), }; diff --git a/client/src/components/GridField/GridFieldAction.js b/client/src/components/GridField/GridFieldAction.js index 0e79f4e60..0a23b0013 100644 --- a/client/src/components/GridField/GridFieldAction.js +++ b/client/src/components/GridField/GridFieldAction.js @@ -6,6 +6,10 @@ class GridFieldAction extends Component { this.handleClick = this.handleClick.bind(this); } + handleClick(event) { + this.props.onClick(event, this.props.record.ID); + } + render() { return ( ); } - - handleClick(event) { - this.props.handleClick(event, this.props.record.ID); - } } GridFieldAction.PropTypes = { - handleClick: React.PropTypes.func.isRequired, + handleClick: () => { throw new Error('no longer used'); }, + Click: React.PropTypes.func.isRequired, }; export default GridFieldAction; diff --git a/client/src/components/GridField/GridFieldCell.js b/client/src/components/GridField/GridFieldCell.js index 72a87bf5f..a62cabfcc 100644 --- a/client/src/components/GridField/GridFieldCell.js +++ b/client/src/components/GridField/GridFieldCell.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import classnames from 'classnames'; class GridFieldCell extends Component { constructor(props) { @@ -8,22 +9,16 @@ class GridFieldCell extends Component { } handleDrillDown(event) { - if (typeof this.props.handleDrillDown === 'undefined') { - return; + if (typeof this.props.onDrillDown === 'function') { + this.props.onDrillDown(event); } - - this.props.handleDrillDown(event); } render() { - const classNames = ['grid-field__cell']; - - if (typeof this.props.className !== 'undefined') { - classNames.push(this.props.className); - } + const classNames = ['grid-field__cell', this.props.className]; const props = { - className: classNames.join(' '), + className: classnames(classNames), onClick: this.handleDrillDown, }; @@ -35,8 +30,7 @@ class GridFieldCell extends Component { GridFieldCell.PropTypes = { className: React.PropTypes.string, - width: React.PropTypes.number, - handleDrillDown: React.PropTypes.func, + onDrillDown: React.PropTypes.func, }; export default GridFieldCell; diff --git a/client/src/components/ListGroup/ListGroupItem.js b/client/src/components/ListGroup/ListGroupItem.js index 84673cb11..010f78c81 100644 --- a/client/src/components/ListGroup/ListGroupItem.js +++ b/client/src/components/ListGroup/ListGroupItem.js @@ -7,8 +7,8 @@ class ListGroupItem extends Component { } handleClick(event) { - if (this.props.handleClick) { - this.props.handleClick(event, this.props.handleClickArg); + if (this.props.onClick) { + this.props.onClick(event, this.props.onClickArg); } } @@ -23,8 +23,10 @@ class ListGroupItem extends Component { } ListGroupItem.propTypes = { - handleClickArg: React.PropTypes.any, - handleClick: React.PropTypes.func, + onClickArg: React.PropTypes.any, + handleClickArg: () => { throw new Error('no longer used'); }, + onClick: React.PropTypes.func, + handleClick: () => { throw new Error('no longer used'); }, }; export default ListGroupItem; diff --git a/client/src/components/MobileMenuToggle/MobileMenuToggle.js b/client/src/components/MobileMenuToggle/MobileMenuToggle.js index c8516a2be..647019b9b 100644 --- a/client/src/components/MobileMenuToggle/MobileMenuToggle.js +++ b/client/src/components/MobileMenuToggle/MobileMenuToggle.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { PropTypes, Component } from 'react'; import classnames from 'classnames'; class MobileMenuToggle extends Component { @@ -27,7 +27,7 @@ class MobileMenuToggle extends Component { href="#toggle-mobile-menu" onClick={this.handleClick} aria-controls={this.props.controls} - aria-expanded={!!this.props.isOpen} + aria-expanded={Boolean(this.props.isOpen)} > @@ -39,13 +39,12 @@ class MobileMenuToggle extends Component { } MobileMenuToggle.propTypes = { - isOpen: React.PropTypes.bool.isRequired, - onClick: React.PropTypes.func.isRequired, - controls: React.PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onClick: PropTypes.func.isRequired, + controls: PropTypes.string, }; MobileMenuToggle.defaultProps = { - isOpen: false, controls: '', }; diff --git a/client/src/components/OptionsetField/OptionField.js b/client/src/components/OptionsetField/OptionField.js index 2ad40996f..72dd75e9e 100644 --- a/client/src/components/OptionsetField/OptionField.js +++ b/client/src/components/OptionsetField/OptionField.js @@ -9,27 +9,6 @@ class OptionField extends Component { this.handleChange = this.handleChange.bind(this); } - /** - * React recommends using `onClick`, however react-bootstrap uses `onChange` - * - * @param {Event} event - */ - handleChange(event) { - if (typeof this.props.onChange === 'function') { - // call onChange for `FormBuilder` to work - this.props.onChange(event, { - id: this.props.id, - value: event.target.checked ? 1 : 0, - }); - } else if (typeof this.props.onClick === 'function') { - // for other React components which needs compatibility with this component - this.props.onClick(event, { - id: this.props.id, - value: event.target.checked ? 1 : 0, - }); - } - } - /** * Fetches the properties for the field * @@ -57,6 +36,27 @@ class OptionField extends Component { }; } + /** + * React recommends using `onClick`, however react-bootstrap uses `onChange` + * + * @param {Event} event + */ + handleChange(event) { + if (typeof this.props.onChange === 'function') { + // call onChange for `FormBuilder` to work + this.props.onChange(event, { + id: this.props.id, + value: event.target.checked ? 1 : 0, + }); + } else if (typeof this.props.onClick === 'function') { + // for other React components which needs compatibility with this component + this.props.onClick(event, { + id: this.props.id, + value: event.target.checked ? 1 : 0, + }); + } + } + render() { const labelText = this.props.leftTitle !== null ? this.props.leftTitle @@ -90,7 +90,7 @@ OptionField.propTypes = { title: React.PropTypes.any, extraClass: React.PropTypes.string, id: React.PropTypes.string, - name: React.PropTypes.string.isRequired, + name: React.PropTypes.string, onChange: React.PropTypes.func, value: React.PropTypes.oneOfType([ React.PropTypes.string, diff --git a/client/src/components/OptionsetField/OptionsetField.js b/client/src/components/OptionsetField/OptionsetField.js index 010beba8b..b98ec7a30 100644 --- a/client/src/components/OptionsetField/OptionsetField.js +++ b/client/src/components/OptionsetField/OptionsetField.js @@ -19,24 +19,8 @@ class OptionsetField extends Component { * @returns {string} key */ getItemKey(item, index) { - return `${this.props.id}-${item.value || `empty${index}`}`; - } - - /** - * Handler for sorting what the value of the field will be - * - * @param {Event} event - * @param {object} field - */ - handleChange(event, field) { - if (typeof this.props.onChange === 'function') { - if (field.value === 1) { - const sourceItem = this.props.source - .find((item, index) => this.getItemKey(item, index) === field.id); - - this.props.onChange(sourceItem.value); - } - } + const value = item.value || `empty${index}`; + return `${this.props.id}-${value}`; } /** @@ -63,6 +47,23 @@ class OptionsetField extends Component { }; } + /** + * Handler for sorting what the value of the field will be + * + * @param {Event} event + * @param {object} field + */ + handleChange(event, field) { + if (typeof this.props.onChange === 'function') { + if (field.value === 1) { + const sourceItem = this.props.source + .find((item, index) => this.getItemKey(item, index) === field.id); + + this.props.onChange(sourceItem.value); + } + } + } + render() { if (!this.props.source) { return null; diff --git a/client/src/components/Toolbar/Toolbar.js b/client/src/components/Toolbar/Toolbar.js index 835a9a35e..1308221a3 100644 --- a/client/src/components/Toolbar/Toolbar.js +++ b/client/src/components/Toolbar/Toolbar.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { PropTypes, Component } from 'react'; class Toolbar extends Component { constructor(props) { @@ -13,8 +13,8 @@ class Toolbar extends Component { * @param {Object} event */ handleBackButtonClick(event) { - if (typeof this.props.handleBackButtonClick !== 'undefined') { - this.props.handleBackButtonClick(event); + if (typeof this.props.onBackButtonClick !== 'undefined') { + this.props.onBackButtonClick(event); return; } @@ -51,9 +51,10 @@ class Toolbar extends Component { } Toolbar.propTypes = { - handleBackButtonClick: React.PropTypes.func, - showBackButton: React.PropTypes.bool, - breadcrumbs: React.PropTypes.array, + handleBackButtonClick: () => { throw new Error('no longer used'); }, + onBackButtonClick: PropTypes.func, + showBackButton: PropTypes.bool, + breadcrumbs: PropTypes.array, }; Toolbar.defaultProps = { diff --git a/client/src/components/TreeDropdownField/tests/TreeDropdownFieldMenu-test.js b/client/src/components/TreeDropdownField/tests/TreeDropdownFieldMenu-test.js index 1181b1234..2fb8ef3ff 100644 --- a/client/src/components/TreeDropdownField/tests/TreeDropdownFieldMenu-test.js +++ b/client/src/components/TreeDropdownField/tests/TreeDropdownFieldMenu-test.js @@ -129,9 +129,9 @@ describe('TreeDropdownField', () => { field = ReactTestUtils.renderIntoDocument( ); - const node = field.render(); + const node = ReactTestUtils.scryRenderedDOMComponentsWithClass(field, 'Select-option')[0]; - expect(node.props.children).toBe('Failed to load'); + expect(node.textContent).toBe('Failed to load'); }); it('should notify if list is empty', () => { @@ -142,9 +142,9 @@ describe('TreeDropdownField', () => { field = ReactTestUtils.renderIntoDocument( ); - const node = field.render(); + const node = ReactTestUtils.scryRenderedDOMComponentsWithClass(field, 'Select-option')[0]; - expect(node.props.children).toBe('No children'); + expect(node.textContent).toBe('No children'); }); it('should return a list of the tree children plus breadcrumbs', () => { diff --git a/client/src/containers/FormBuilderLoader/FormBuilderLoader.js b/client/src/containers/FormBuilderLoader/FormBuilderLoader.js index c2a1e69de..a0e54b46e 100644 --- a/client/src/containers/FormBuilderLoader/FormBuilderLoader.js +++ b/client/src/containers/FormBuilderLoader/FormBuilderLoader.js @@ -115,8 +115,8 @@ class FormBuilderLoader extends Component { }) ); - if (typeof this.props.handleSubmit === 'function') { - promise = this.props.handleSubmit(data, action, newSubmitFn); + if (typeof this.props.onSubmit === 'function') { + promise = this.props.onSubmit(data, action, newSubmitFn); } else { promise = newSubmitFn(); } @@ -397,7 +397,7 @@ class FormBuilderLoader extends Component { const props = Object.assign({}, this.props, { form: this.getIdentifier(), onSubmitSuccess: this.props.onSubmitSuccess, - handleSubmit: this.handleSubmit, + onSubmit: this.handleSubmit, onAutofill: this.handleAutofill, }); diff --git a/client/src/containers/InsertLinkModal/InsertLinkModal.js b/client/src/containers/InsertLinkModal/InsertLinkModal.js index 6e548519b..d64f3e2ce 100644 --- a/client/src/containers/InsertLinkModal/InsertLinkModal.js +++ b/client/src/containers/InsertLinkModal/InsertLinkModal.js @@ -30,12 +30,11 @@ class InsertLinkModal extends Component { {}, this.props, { - handleSubmit: this.handleSubmit, - handleHide: this.props.onHide, + onSubmit: this.handleSubmit, + onHide: this.props.onHide, showErrorMessage: true, } ); - delete props.onHide; delete props.onInsert; delete props.sectionConfig; diff --git a/client/src/legacy/AddToCampaignForm.js b/client/src/legacy/AddToCampaignForm.js index da199dbde..aabf9646a 100644 --- a/client/src/legacy/AddToCampaignForm.js +++ b/client/src/legacy/AddToCampaignForm.js @@ -76,8 +76,8 @@ jQuery.entwine('ss', ($) => { = 0 && parsed < 10) { + if (num !== null && parsed >= 0 && parsed < 10) { return [ i18n._t('Admin.WRITTEN_NUMBER_ZERO', 'zero'), i18n._t('Admin.WRITTEN_NUMBER_ONE', 'one'), diff --git a/package.json b/package.json index c2224f0aa..e24ae6ec2 100644 --- a/package.json +++ b/package.json @@ -76,11 +76,7 @@ }, "devDependencies": { "@silverstripe/webpack-config": "file:../../webpack-config", - "babel-core": "^6.26.0", "babel-jest": "^20.0.3", - "babel-preset-es2015": "^6.24.1", - "babel-preset-es2016": "^6.24.1", - "babel-preset-react": "^6.24.1", "jest-cli": "^19.0.2" }, "resolutions": { @@ -92,7 +88,8 @@ ], "moduleDirectories": [ "client/src", - "node_modules" + "node_modules", + "node_modules/@silverstripe/webpack-config/node_modules" ], "testMatch": [ "**/tests/**/*-test.js?(x)" @@ -103,7 +100,7 @@ }, "babel": { "presets": [ - ["env", { "modules": false }], + "env", "react" ], "plugins": [ diff --git a/yarn.lock b/yarn.lock index 835661e84..fcf570762 100644 --- a/yarn.lock +++ b/yarn.lock @@ -640,17 +640,7 @@ babel-plugin-transform-es2015-block-scoping@^6.23.0: babel-types "^6.26.0" lodash "^4.17.4" -babel-plugin-transform-es2015-block-scoping@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz#76c295dc3a4741b1665adfd3167215dcff32a576" - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - lodash "^4.2.0" - -babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-classes@^6.24.1: +babel-plugin-transform-es2015-classes@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" dependencies: @@ -664,33 +654,33 @@ babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-cla babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.24.1: +babel-plugin-transform-es2015-computed-properties@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" dependencies: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.23.0: +babel-plugin-transform-es2015-destructuring@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2015-duplicate-keys@^6.24.1: +babel-plugin-transform-es2015-duplicate-keys@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" dependencies: babel-runtime "^6.22.0" babel-types "^6.24.1" -babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.23.0: +babel-plugin-transform-es2015-for-of@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-function-name@^6.22.0, babel-plugin-transform-es2015-function-name@^6.24.1: +babel-plugin-transform-es2015-function-name@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" dependencies: @@ -730,7 +720,7 @@ babel-plugin-transform-es2015-modules-commonjs@^6.24.1: babel-template "^6.24.1" babel-types "^6.24.1" -babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1: +babel-plugin-transform-es2015-modules-systemjs@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" dependencies: @@ -738,7 +728,7 @@ babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-e babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015-modules-umd@^6.24.1: +babel-plugin-transform-es2015-modules-umd@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" dependencies: @@ -746,14 +736,14 @@ babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015 babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.24.1: +babel-plugin-transform-es2015-object-super@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" dependencies: babel-helper-replace-supers "^6.24.1" babel-runtime "^6.22.0" -babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-parameters@^6.24.1: +babel-plugin-transform-es2015-parameters@^6.23.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" dependencies: @@ -764,7 +754,7 @@ babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015- babel-traverse "^6.24.1" babel-types "^6.24.1" -babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.24.1: +babel-plugin-transform-es2015-shorthand-properties@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" dependencies: @@ -777,7 +767,7 @@ babel-plugin-transform-es2015-spread@^6.22.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-sticky-regex@^6.22.0, babel-plugin-transform-es2015-sticky-regex@^6.24.1: +babel-plugin-transform-es2015-sticky-regex@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" dependencies: @@ -791,13 +781,13 @@ babel-plugin-transform-es2015-template-literals@^6.22.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.23.0: +babel-plugin-transform-es2015-typeof-symbol@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es2015-unicode-regex@^6.24.1: +babel-plugin-transform-es2015-unicode-regex@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" dependencies: @@ -805,7 +795,7 @@ babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es20 babel-runtime "^6.22.0" regexpu-core "^2.0.0" -babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-exponentiation-operator@^6.24.1: +babel-plugin-transform-exponentiation-operator@^6.22.0: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" dependencies: @@ -861,12 +851,6 @@ babel-plugin-transform-regenerator@^6.22.0: dependencies: regenerator-transform "^0.10.0" -babel-plugin-transform-regenerator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz#b8da305ad43c3c99b4848e4fe4037b770d23c418" - dependencies: - regenerator-transform "0.9.11" - babel-plugin-transform-strict-mode@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" @@ -917,41 +901,6 @@ babel-preset-env@^1.6.0: invariant "^2.2.2" semver "^5.3.0" -babel-preset-es2015@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939" - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.24.1" - babel-plugin-transform-es2015-classes "^6.24.1" - babel-plugin-transform-es2015-computed-properties "^6.24.1" - babel-plugin-transform-es2015-destructuring "^6.22.0" - babel-plugin-transform-es2015-duplicate-keys "^6.24.1" - babel-plugin-transform-es2015-for-of "^6.22.0" - babel-plugin-transform-es2015-function-name "^6.24.1" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.24.1" - babel-plugin-transform-es2015-modules-commonjs "^6.24.1" - babel-plugin-transform-es2015-modules-systemjs "^6.24.1" - babel-plugin-transform-es2015-modules-umd "^6.24.1" - babel-plugin-transform-es2015-object-super "^6.24.1" - babel-plugin-transform-es2015-parameters "^6.24.1" - babel-plugin-transform-es2015-shorthand-properties "^6.24.1" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.24.1" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.22.0" - babel-plugin-transform-es2015-unicode-regex "^6.24.1" - babel-plugin-transform-regenerator "^6.24.1" - -babel-preset-es2016@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-es2016/-/babel-preset-es2016-6.24.1.tgz#f900bf93e2ebc0d276df9b8ab59724ebfd959f8b" - dependencies: - babel-plugin-transform-exponentiation-operator "^6.24.1" - babel-preset-flow@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d" @@ -4805,14 +4754,6 @@ regenerator-runtime@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" -regenerator-transform@0.9.11: - version "0.9.11" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.9.11.tgz#3a7d067520cb7b7176769eb5ff868691befe1283" - dependencies: - babel-runtime "^6.18.0" - babel-types "^6.19.0" - private "^0.1.6" - regenerator-transform@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"