diff --git a/assets/css/src/common.styl b/assets/css/src/common.styl index 1717b5222e..622e6906ce 100644 --- a/assets/css/src/common.styl +++ b/assets/css/src/common.styl @@ -28,6 +28,20 @@ a:focus .select2-container width: 25em !important +placeholder-color = #999 /* default Select2 placeholder color for single dropdown */ + +input.select2-search__field::-webkit-input-placeholder + color: placeholder-color +input.select2-search__field:-moz-placeholder + color: placeholder-color +input.select2-search__field::-moz-placeholder + color: placeholder-color +input.select2-search__field:-ms-input-placeholder + color: placeholder-color + +.select2-container--default.select2-container--focus .select2-selection--multiple + border: 1px solid #aaa; /* default Select2 border for single dropdown */ + // textareas textarea.regular-text width: 25em !important diff --git a/assets/js/src/form/fields/selection.jsx b/assets/js/src/form/fields/selection.jsx index 54f3fcde4c..e61f41a7a1 100644 --- a/assets/js/src/form/fields/selection.jsx +++ b/assets/js/src/form/fields/selection.jsx @@ -10,20 +10,11 @@ define([ jQuery ) => { const Selection = React.createClass({ - getInitialState: function () { - return { - items: [], - select2: false, - }; - }, - componentWillMount: function () { - this.loadCachedItems(); - }, allowMultipleValues: function () { return (this.props.field.multiple === true); }, isSelect2Initialized: function () { - return (this.state.select2 === true); + return (jQuery(`#${this.refs.select.id}`).hasClass('select2-hidden-accessible') === true); }, componentDidMount: function () { if (this.allowMultipleValues() || this.props.field.forceSelect2) { @@ -31,31 +22,57 @@ define([ } }, componentDidUpdate: function (prevProps) { - if ( - (this.props.item !== undefined && prevProps.item !== undefined) + if ((this.props.item !== undefined && prevProps.item !== undefined) && (this.props.item.id !== prevProps.item.id) ) { jQuery(`#${this.refs.select.id}`) .val(this.getSelectedValues()) .trigger('change'); } + + if (this.isSelect2Initialized() && + (this.getFieldId(this.props) !== this.getFieldId(prevProps)) && + this.props.field.resetSelect2OnUpdate !== undefined + ) { + this.resetSelect2(); + } }, componentWillUnmount: function () { if (this.allowMultipleValues() || this.props.field.forceSelect2) { this.destroySelect2(); } }, + getFieldId: function (data) { + const props = data || this.props; + return props.field.id || props.field.name; + }, + resetSelect2: function () { + this.destroySelect2(); + this.setupSelect2(); + }, destroySelect2: function () { if (this.isSelect2Initialized()) { jQuery(`#${this.refs.select.id}`).select2('destroy'); + this.cleanupAfterSelect2(); } }, + cleanupAfterSelect2: function () { + // remove DOM elements created by Select2 that are not tracked by React + jQuery(`#${this.refs.select.id}`) + .find('option:not(.default)') + .remove(); + + // unbind events (https://select2.org/programmatic-control/methods#event-unbinding) + jQuery(`#${this.refs.select.id}`) + .off('select2:unselecting') + .off('select2:opening'); + }, setupSelect2: function () { if (this.isSelect2Initialized()) { return; } - const select2 = jQuery(`#${this.refs.select.id}`).select2({ + let select2Options = { width: (this.props.width || ''), templateResult: function (item) { if (item.element && item.element.selected) { @@ -65,7 +82,45 @@ define([ } return item.text; }, - }); + }; + + const remoteQuery = this.props.field.remoteQuery || null; + if (remoteQuery) { + select2Options = Object.assign(select2Options, { + ajax: { + url: window.ajaxurl, + type: 'POST', + dataType: 'json', + data: function (params) { + return { + action: 'mailpoet', + api_version: window.mailpoet_api_version, + token: window.mailpoet_token, + endpoint: remoteQuery.endpoint, + method: remoteQuery.method, + data: Object.assign( + remoteQuery.data, + { query: params.term } + ), + }; + }, + processResults: function (response) { + return { + results: response.data.map(item => ( + { id: item.id || item.value, text: item.name || item.text } + )), + }; + }, + }, + minimumInputLength: remoteQuery.minimumInputLength || 2, + }); + } + + if (this.props.field.extendSelect2Options !== undefined) { + select2Options = Object.assign(select2Options, this.props.field.extendSelect2Options); + } + + const select2 = jQuery(`#${this.refs.select.id}`).select2(select2Options); let hasRemoved = false; select2.on('select2:unselecting', () => { @@ -79,8 +134,6 @@ define([ }); select2.on('change', this.handleChange); - - this.setState({ select2: true }); }, getSelectedValues: function () { if (this.props.field.selected !== undefined) { @@ -96,7 +149,7 @@ define([ } return null; }, - loadCachedItems: function () { + getItems: function () { let items; if (typeof (window[`mailpoet_${this.props.field.endpoint}`]) !== 'undefined') { items = window[`mailpoet_${this.props.field.endpoint}`]; @@ -108,11 +161,9 @@ define([ if (this.props.field.filter !== undefined) { items = items.filter(this.props.field.filter); } - - this.setState({ - items: items, - }); } + + return items; }, handleChange: function (e) { let value; @@ -163,11 +214,12 @@ define([ // For single selects only, in order for the placeholder value to appear, // we must have a blank