-
-
Notifications
You must be signed in to change notification settings - Fork 135
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #764 from neos/full-select-editor
FEATURE: Full select editor (with data providers and multiselect support)
- Loading branch information
Showing
17 changed files
with
638 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
packages/neos-ui-editors/src/SelectBox/DataSourceBasedSelectBoxEditor.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import React, {PureComponent} from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import {$transform} from 'plow-js'; | ||
import {connect} from 'react-redux'; | ||
import SelectBox from '@neos-project/react-ui-components/src/SelectBox/'; | ||
import MultiSelectBox from '@neos-project/react-ui-components/src/MultiSelectBox/'; | ||
import {selectors} from '@neos-project/neos-ui-redux-store'; | ||
import {neos} from '@neos-project/neos-ui-decorators'; | ||
import {shouldDisplaySearchBox, searchOptions, processSelectBoxOptions} from './SelectBoxHelpers'; | ||
|
||
@neos(globalRegistry => ({ | ||
i18nRegistry: globalRegistry.get('i18n'), | ||
dataSourcesDataLoader: globalRegistry.get('dataLoaders').get('DataSources') | ||
})) | ||
@connect($transform({ | ||
focusedNodePath: selectors.CR.Nodes.focusedNodePathSelector | ||
})) | ||
export default class DataSourceBasedSelectBoxEditor extends PureComponent { | ||
static propTypes = { | ||
commit: PropTypes.func.isRequired, | ||
value: PropTypes.oneOfType([ | ||
PropTypes.string, | ||
PropTypes.arrayOf(PropTypes.string) | ||
]), | ||
options: PropTypes.shape({ | ||
allowEmpty: PropTypes.bool, | ||
placeholder: PropTypes.string, | ||
|
||
multiple: PropTypes.bool, | ||
|
||
dataSourceIdentifier: PropTypes.string, | ||
dataSourceUri: PropTypes.string, | ||
dataSourceAdditionalData: PropTypes.objectOf(PropTypes.any), | ||
|
||
minimumResultsForSearch: PropTypes.number, | ||
|
||
values: PropTypes.objectOf( | ||
PropTypes.shape({ | ||
label: PropTypes.string, | ||
icon: PropTypes.string, | ||
|
||
// TODO | ||
group: PropTypes.string | ||
}) | ||
) | ||
|
||
}).isRequired, | ||
|
||
i18nRegistry: PropTypes.object.isRequired, | ||
dataSourcesDataLoader: PropTypes.shape({ | ||
resolveValue: PropTypes.func.isRequired | ||
}).isRequired, | ||
|
||
focusedNodePath: PropTypes.string.isRequired | ||
}; | ||
|
||
static defaultOptions = { | ||
// Use "5" as minimum result for search default; same as with old UI | ||
minimumResultsForSearch: 5 | ||
}; | ||
|
||
constructor(props) { | ||
super(props); | ||
|
||
this.state = { | ||
searchTerm: '', | ||
isLoading: false, | ||
selectBoxOptions: {} | ||
}; | ||
} | ||
|
||
getDataLoaderOptions() { | ||
return { | ||
contextNodePath: this.props.focusedNodePath, | ||
dataSourceIdentifier: this.props.options.dataSourceIdentifier, | ||
dataSourceUri: this.props.options.dataSourceUri, | ||
dataSourceAdditionalData: this.props.options.dataSourceAdditionalData | ||
}; | ||
} | ||
|
||
componentDidMount() { | ||
this.setState({isLoading: true}); | ||
this.props.dataSourcesDataLoader.resolveValue(this.getDataLoaderOptions(), this.props.value) | ||
.then(selectBoxOptions => { | ||
this.setState({ | ||
isLoading: false, | ||
selectBoxOptions | ||
}); | ||
}); | ||
} | ||
|
||
render() { | ||
const {commit, value, i18nRegistry} = this.props; | ||
const options = Object.assign({}, this.defaultOptions, this.props.options); | ||
|
||
const processedSelectBoxOptions = processSelectBoxOptions(i18nRegistry, this.state.selectBoxOptions); | ||
|
||
// Placeholder text must be unescaped in case html entities were used | ||
const placeholder = options && options.placeholder && i18nRegistry.translate(unescape(options.placeholder)); | ||
|
||
if (options.multiple) { | ||
return (<MultiSelectBox | ||
options={processedSelectBoxOptions} | ||
values={value || []} | ||
onValuesChange={commit} | ||
displayLoadingIndicator={this.state.isLoading} | ||
placeholder={placeholder} | ||
|
||
allowEmpty={options.allowEmpty} | ||
displaySearchBox={shouldDisplaySearchBox(options, processedSelectBoxOptions)} | ||
searchOptions={searchOptions(this.state.searchTerm, processedSelectBoxOptions)} | ||
searchTerm={this.state.searchTerm} | ||
onSearchTermChange={this.handleSearchTermChange} | ||
/>); | ||
} | ||
|
||
// multiple = FALSE | ||
return (<SelectBox | ||
options={this.state.searchTerm ? searchOptions(this.state.searchTerm, processedSelectBoxOptions) : processedSelectBoxOptions} | ||
value={value} | ||
onValueChange={commit} | ||
displayLoadingIndicator={this.state.isLoading} | ||
placeholder={placeholder} | ||
|
||
allowEmpty={options.allowEmpty} | ||
displaySearchBox={shouldDisplaySearchBox(options, processedSelectBoxOptions)} | ||
searchTerm={this.state.searchTerm} | ||
onSearchTermChange={this.handleSearchTermChange} | ||
/>); | ||
} | ||
|
||
handleSearchTermChange = searchTerm => { | ||
this.setState({searchTerm}); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
packages/neos-ui-editors/src/SelectBox/SelectBoxHelpers.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export const shouldDisplaySearchBox = (options, processedSelectBoxOptions) => options.minimumResultsForSearch >= 0 && processedSelectBoxOptions.length >= options.minimumResultsForSearch; | ||
|
||
// Currently, we're doing an extremely simple lowercase substring matching; of course this could be improved a lot! | ||
export const searchOptions = (searchTerm, processedSelectBoxOptions) => | ||
processedSelectBoxOptions.filter(option => option.label && option.label.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1); | ||
|
||
export const processSelectBoxOptions = (i18nRegistry, selectBoxOptions) => | ||
Object.keys(selectBoxOptions) | ||
.filter(k => selectBoxOptions[k]) | ||
// Filter out items without a label | ||
.map(k => selectBoxOptions[k].label && Object.assign( | ||
{value: k}, | ||
selectBoxOptions[k], | ||
{label: i18nRegistry.translate(selectBoxOptions[k].label)} | ||
)) | ||
.filter(k => k); |
91 changes: 91 additions & 0 deletions
91
packages/neos-ui-editors/src/SelectBox/SimpleSelectBoxEditor.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import React, {PureComponent} from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import SelectBox from '@neos-project/react-ui-components/src/SelectBox/'; | ||
import MultiSelectBox from '@neos-project/react-ui-components/src/MultiSelectBox/'; | ||
import {neos} from '@neos-project/neos-ui-decorators'; | ||
import {shouldDisplaySearchBox, searchOptions, processSelectBoxOptions} from './SelectBoxHelpers'; | ||
|
||
@neos(globalRegistry => ({ | ||
i18nRegistry: globalRegistry.get('i18n') | ||
})) | ||
export default class SimpleSelectBoxEditor extends PureComponent { | ||
static propTypes = { | ||
commit: PropTypes.func.isRequired, | ||
value: PropTypes.any, | ||
options: PropTypes.shape({ | ||
allowEmpty: PropTypes.bool, | ||
placeholder: PropTypes.string, | ||
|
||
multiple: PropTypes.bool, | ||
|
||
minimumResultsForSearch: PropTypes.number, | ||
|
||
values: PropTypes.objectOf( | ||
PropTypes.shape({ | ||
label: PropTypes.string, | ||
icon: PropTypes.string, | ||
|
||
// TODO | ||
group: PropTypes.string | ||
}) | ||
) | ||
|
||
}).isRequired, | ||
|
||
i18nRegistry: PropTypes.object.isRequired | ||
}; | ||
|
||
static defaultOptions = { | ||
// Use "5" as minimum result for search default; same as with old UI | ||
minimumResultsForSearch: 5 | ||
}; | ||
|
||
constructor(props) { | ||
super(props); | ||
|
||
this.state = { | ||
searchTerm: '' | ||
}; | ||
} | ||
|
||
render() { | ||
const {commit, value, i18nRegistry} = this.props; | ||
const options = Object.assign({}, this.defaultOptions, this.props.options); | ||
|
||
const processedSelectBoxOptions = processSelectBoxOptions(i18nRegistry, options.values); | ||
|
||
// Placeholder text must be unescaped in case html entities were used | ||
const placeholder = options && options.placeholder && i18nRegistry.translate(unescape(options.placeholder)); | ||
|
||
if (options.multiple) { | ||
return (<MultiSelectBox | ||
options={processedSelectBoxOptions} | ||
values={value || []} | ||
onValuesChange={commit} | ||
placeholder={placeholder} | ||
allowEmpty={options.allowEmpty} | ||
displaySearchBox={shouldDisplaySearchBox(options, processedSelectBoxOptions)} | ||
searchOptions={searchOptions(this.state.searchTerm, processedSelectBoxOptions)} | ||
searchTerm={this.state.searchTerm} | ||
onSearchTermChange={this.handleSearchTermChange} | ||
/>); | ||
} | ||
|
||
// multiple == FALSE | ||
return (<SelectBox | ||
options={this.state.searchTerm ? searchOptions(this.state.searchTerm, processedSelectBoxOptions) : processedSelectBoxOptions} | ||
value={value} | ||
onValueChange={commit} | ||
placeholder={placeholder} | ||
|
||
allowEmpty={options.allowEmpty} | ||
displaySearchBox={shouldDisplaySearchBox(options, processedSelectBoxOptions)} | ||
searchTerm={this.state.searchTerm} | ||
onSearchTermChange={this.handleSearchTermChange} | ||
/>); | ||
} | ||
|
||
handleSearchTermChange = searchTerm => { | ||
this.setState({searchTerm}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,22 @@ | ||
import React, {PureComponent} from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import I18n from '@neos-project/neos-ui-i18n'; | ||
import SelectBox from '@neos-project/react-ui-components/src/SelectBox/'; | ||
import {neos} from '@neos-project/neos-ui-decorators'; | ||
import SimpleSelectBoxEditor from './SimpleSelectBoxEditor'; | ||
import DataSourceBasedSelectBoxEditor from './DataSourceBasedSelectBoxEditor'; | ||
|
||
@neos(globalRegistry => ({ | ||
i18nRegistry: globalRegistry.get('i18n') | ||
})) | ||
export default class SelectBoxEditor extends PureComponent { | ||
static propTypes = { | ||
commit: PropTypes.func.isRequired, | ||
value: PropTypes.any, | ||
options: PropTypes.any.isRequired, | ||
|
||
i18nRegistry: PropTypes.object.isRequired | ||
options: PropTypes.shape({ | ||
dataSourceIdentifier: PropTypes.string, | ||
dataSourceUri: PropTypes.string | ||
}).isRequired | ||
}; | ||
|
||
render() { | ||
const {commit, value, options, i18nRegistry} = this.props; | ||
const selectBoxOptions = Object.keys(options.values) | ||
.filter(k => options.values[k]) | ||
// Filter out items without a label | ||
.map(k => options.values[k].label && Object.assign( | ||
{value: k}, | ||
options.values[k], | ||
{label: <I18n id={options.values[k].label}/>} | ||
) | ||
).filter(k => k); | ||
// Placeholder text must be unescaped in case html entities were used | ||
const placeholder = options && options.placeholder && i18nRegistry.translate(unescape(options.placeholder)); | ||
const {options} = this.props; | ||
|
||
return (<SelectBox | ||
options={selectBoxOptions} | ||
value={value} | ||
onValueChange={commit} | ||
placeholder={placeholder} | ||
/>); | ||
if (options.dataSourceIdentifier || options.dataSourceUri) { | ||
return <DataSourceBasedSelectBoxEditor {...this.props}/>; | ||
} | ||
return <SimpleSelectBoxEditor {...this.props}/>; | ||
} | ||
} |
Oops, something went wrong.