2017-09-05 04:38:38 +02:00
|
|
|
import i18n from 'i18n';
|
|
|
|
import React from 'react';
|
|
|
|
import fetch from 'isomorphic-fetch';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { bindActionCreators } from 'redux';
|
|
|
|
import { formValueSelector } from 'redux-form';
|
|
|
|
import SilverStripeComponent from 'lib/SilverStripeComponent';
|
|
|
|
import * as anchorSelectorActions from 'state/anchorSelector/AnchorSelectorActions';
|
|
|
|
import anchorSelectorStates from 'state/anchorSelector/AnchorSelectorStates';
|
|
|
|
import fieldHolder from 'components/FieldHolder/FieldHolder';
|
|
|
|
import { Creatable } from 'react-select';
|
|
|
|
import getFormState from 'lib/getFormState';
|
|
|
|
import classnames from 'classnames';
|
2018-09-14 03:16:55 +02:00
|
|
|
import PropTypes from 'prop-types';
|
2017-09-05 04:38:38 +02:00
|
|
|
|
|
|
|
const noop = () => null;
|
|
|
|
|
|
|
|
class AnchorSelectorField extends SilverStripeComponent {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.handleChange = this.handleChange.bind(this);
|
|
|
|
this.handleLoadingError = this.handleLoadingError.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
this.ensurePagesLoaded();
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
|
|
// Reload if pageId changes
|
|
|
|
if (this.props.pageId !== nextProps.pageId) {
|
|
|
|
this.ensurePagesLoaded(nextProps);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Lazy-triggers load of the dropdown based on pageId
|
|
|
|
*
|
|
|
|
* @param {Object} props - Props to check
|
|
|
|
* @return {Promise} The promise object
|
|
|
|
*/
|
|
|
|
ensurePagesLoaded(props = this.props) {
|
|
|
|
// Only load if dirty and a valid ID
|
|
|
|
if (props.loadingState !== anchorSelectorStates.DIRTY || !props.pageId) {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark page updating
|
|
|
|
props.actions.anchorSelector.beginUpdating(props.pageId);
|
|
|
|
|
|
|
|
// Query endpoint for anchors for this page
|
|
|
|
const fetchURL = props.data.endpoint.replace(/:id/, props.pageId);
|
|
|
|
return fetch(fetchURL, { credentials: 'same-origin' })
|
|
|
|
.then(response => response.json())
|
|
|
|
.then((anchors) => {
|
|
|
|
// Update anchors
|
|
|
|
props.actions.anchorSelector.updated(props.pageId, anchors);
|
|
|
|
return anchors;
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
props.actions.anchorSelector.updateFailed(props.pageId);
|
|
|
|
this.handleLoadingError(error, props);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get options
|
|
|
|
*
|
|
|
|
* @return {Array}
|
|
|
|
*/
|
|
|
|
getDropdownOptions() {
|
|
|
|
const options = this.props.anchors.map(value => ({ value }));
|
|
|
|
// Ensure value is available in the list
|
|
|
|
if (this.props.value && !this.props.anchors.find(value => value === this.props.value)) {
|
|
|
|
options.unshift({ value: this.props.value });
|
|
|
|
}
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles changes to the selected anchor
|
|
|
|
*
|
|
|
|
* @param {String} value
|
|
|
|
*/
|
|
|
|
handleChange(value) {
|
|
|
|
if (typeof this.props.onChange === 'function') {
|
|
|
|
this.props.onChange(value ? value.value : '');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleLoadingError(error, props = this.props) {
|
|
|
|
if (props.onLoadingError === noop) {
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Custom error handling
|
|
|
|
return props.onLoadingError({
|
|
|
|
errors: [
|
|
|
|
{
|
|
|
|
value: error.message,
|
|
|
|
type: 'error',
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const inputProps = {
|
|
|
|
id: this.props.id,
|
|
|
|
};
|
|
|
|
const className = classnames('anchorselectorfield', this.props.extraClass);
|
|
|
|
const options = this.getDropdownOptions();
|
|
|
|
const value = this.props.value || '';
|
|
|
|
const placeholder = i18n._t('CMS.ANCHOR_SELECT_OR_TYPE', 'Select or enter anchor');
|
|
|
|
return (
|
|
|
|
<Creatable
|
|
|
|
searchable
|
|
|
|
options={options}
|
|
|
|
className={className}
|
|
|
|
name={this.props.name}
|
|
|
|
inputProps={inputProps}
|
|
|
|
onChange={this.handleChange}
|
|
|
|
onBlurResetsInput
|
|
|
|
value={value}
|
|
|
|
placeholder={placeholder}
|
|
|
|
labelKey="value"
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AnchorSelectorField.propTypes = {
|
2018-09-14 03:16:55 +02:00
|
|
|
extraClass: PropTypes.string,
|
|
|
|
id: PropTypes.string,
|
|
|
|
name: PropTypes.string.isRequired,
|
|
|
|
onChange: PropTypes.func,
|
|
|
|
value: PropTypes.string,
|
|
|
|
attributes: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
|
|
|
pageId: PropTypes.number,
|
|
|
|
anchors: PropTypes.array,
|
|
|
|
loadingState: PropTypes.oneOf(Object
|
2017-09-05 04:38:38 +02:00
|
|
|
.keys(anchorSelectorStates)
|
2018-09-04 03:13:33 +02:00
|
|
|
.map((key) => anchorSelectorStates[key])),
|
2018-09-14 03:16:55 +02:00
|
|
|
onLoadingError: PropTypes.func,
|
|
|
|
data: PropTypes.shape({
|
|
|
|
endpoint: PropTypes.string,
|
|
|
|
targetFieldName: PropTypes.string,
|
2017-09-05 04:38:38 +02:00
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
|
|
|
AnchorSelectorField.defaultProps = {
|
|
|
|
value: '',
|
|
|
|
extraClass: '',
|
|
|
|
onLoadingError: noop,
|
|
|
|
attributes: {},
|
|
|
|
};
|
|
|
|
|
|
|
|
function mapStateToProps(state, ownProps) {
|
|
|
|
// Get pageId From selector field
|
|
|
|
const selector = formValueSelector(ownProps.formid, getFormState);
|
|
|
|
const targetFieldName = (ownProps && ownProps.data && ownProps.data.targetFieldName) || 'PageID';
|
|
|
|
const pageId = Number(selector(state, targetFieldName) || 0);
|
|
|
|
|
|
|
|
// Load anchors from page
|
|
|
|
let anchors = [];
|
|
|
|
const page = pageId
|
|
|
|
? state.cms.anchorSelector.pages.find(next => next.id === pageId)
|
|
|
|
: null;
|
|
|
|
if (page && page.loadingState === anchorSelectorStates.SUCCESS) {
|
2018-09-04 03:13:33 +02:00
|
|
|
// eslint-disable-next-line prefer-destructuring
|
2017-09-05 04:38:38 +02:00
|
|
|
anchors = page.anchors;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check status
|
|
|
|
let loadingState = null;
|
|
|
|
if (page) {
|
2018-09-04 03:13:33 +02:00
|
|
|
// eslint-disable-next-line prefer-destructuring
|
2017-09-05 04:38:38 +02:00
|
|
|
loadingState = page.loadingState;
|
|
|
|
} else if (pageId) {
|
|
|
|
// Triggers an update
|
|
|
|
loadingState = anchorSelectorStates.DIRTY;
|
|
|
|
} else {
|
|
|
|
// No page = success
|
|
|
|
loadingState = anchorSelectorStates.SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
return { pageId, anchors, loadingState };
|
|
|
|
}
|
|
|
|
|
|
|
|
function mapDispatchToProps(dispatch) {
|
|
|
|
return {
|
|
|
|
actions: {
|
|
|
|
anchorSelector: bindActionCreators(anchorSelectorActions, dispatch),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const ConnectedAnchorSelectorField
|
|
|
|
= connect(mapStateToProps, mapDispatchToProps)(AnchorSelectorField);
|
|
|
|
|
|
|
|
export { AnchorSelectorField as Component, ConnectedAnchorSelectorField };
|
|
|
|
|
|
|
|
export default fieldHolder(ConnectedAnchorSelectorField);
|