feat(xo-web/editable): Select supports multiple values (#3623)

This commit is contained in:
Rajaa.BARHTAOUI
2019-01-22 10:03:43 +01:00
committed by Pierre Donias
parent c613b4cab3
commit 449dd2998b
5 changed files with 119 additions and 79 deletions
@@ -0,0 +1,23 @@
## Select
### Usage
```js
<Select
value={this.state.selectedOption}
onChange={selectedOption => this.setState({ selectedOption })}
optionRenderer={option => option.label}
options={[
{ value: 'foo', label: 'Foo' },
{ value: 'bar', label: 'Bar' },
]}
/>
```
### Props
Name | Type | Default | Description
------- | --- | --- | ---------
`options` | `Array` of `Object`s | | Required. Options that can be selected. `label` and `value` properties are required for each option.
`multi`| `Boolean` | `false` | Allow to select multiple values.
`value` | `Object` or `Array` of `Object`s when `multi` is `true` | | Required. Current value.
`onChange` | `Function` | | Manage the changed value. Parameters: selected value(s).
`optionRenderer` | `Function` | | Manage option display. Parameter: an element of `options`.
`children` | | | How the component will be rendered. Will fallback to `optionRenderer` if not used.
+70 -59
View File
@@ -1,24 +1,15 @@
import classNames from 'classnames'
import React from 'react'
import PropTypes from 'prop-types'
import {
findKey,
isEmpty,
isFunction,
isString,
map,
pick,
startsWith,
} from 'lodash'
import { isEmpty, isFunction, isString, map, pick, startsWith } from 'lodash'
import _ from '../intl'
import Component from '../base-component'
import getEventValue from '../get-event-value'
import Icon from '../icon'
import logError from '../log-error'
import Tooltip from '../tooltip'
import { formatSize } from '../utils'
import { SizeInput } from '../form'
import { Select as FormSelect, SizeInput } from '../form'
import {
SelectHost,
SelectIp,
@@ -341,74 +332,94 @@ export class Number extends Component {
}
}
export class Select extends Editable {
class SimpleSelect_ extends Editable {
static propTypes = {
options: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
.isRequired,
renderer: PropTypes.func,
}
componentWillReceiveProps(props) {
if (
props.value !== this.props.value ||
props.options !== this.props.options
) {
this.setState({
valueKey: findKey(props.options, option => option === props.value),
})
}
optionRenderer: PropTypes.func,
value: PropTypes.oneOfType([PropTypes.oneOf([null]), PropTypes.object]),
}
get value() {
return this.props.options[this.state.valueKey]
return this.state.value === undefined ? this.props.value : this.state.value
}
_onChange = event => {
this.setState({ valueKey: getEventValue(event) }, this._save)
}
_optionToJsx = (option, key) => {
const { renderer } = this.props
_onChange = value => this.setState({ value }, this._save)
_renderDisplay() {
const { children, optionRenderer, value } = this.props
return (
<option key={key} value={key}>
{renderer ? renderer(option) : option}
</option>
children || (
<span>
{optionRenderer !== undefined
? optionRenderer(value)
: value != null
? value.label
: _('noValue')}
</span>
)
)
}
_onEditionMount = ref => {
// Seems to work in Google Chrome (not in Firefox)
ref && ref.dispatchEvent(new window.MouseEvent('mousedown'))
_renderEdition = () => (
<FormSelect
{...this.props}
autoFocus
onBlur={this._closeEdition}
onChange={this._onChange}
onKeyDown={this._onKeyDown}
openOnFocus
/>
)
}
class MultiSelect_ extends Editable {
static propTypes = {
optionRenderer: PropTypes.func,
value: PropTypes.array,
}
get value() {
return this.state.nextValue === undefined
? this.props.value
: this.state.nextValue
}
_renderDisplay() {
const { children, renderer, value } = this.props
return children || <span>{renderer ? renderer(value) : value}</span>
}
_renderEdition() {
const { saving, valueKey } = this.state
const { options } = this.props
const { children, optionRenderer, value } = this.props
return (
<select
autoFocus
className={classNames('form-control', styles.select)}
onBlur={this._closeEdition}
onChange={this._onChange}
onKeyDown={this._onKeyDown}
readOnly={saving}
ref={this._onEditionMount}
value={valueKey}
>
{map(options, this._optionToJsx)}
</select>
children || (
<span>
{!isEmpty(value)
? map(value, optionRenderer || 'label').join(', ')
: _('noValue')}
</span>
)
)
}
_onBlur = () => {
this._save().then(() => this.setState({ nextValue: undefined }))
}
_renderEdition = () => (
<FormSelect
{...this.props}
autoFocus
multi
onBlur={this._onBlur}
onChange={this.linkState('nextValue')}
openOnFocus
value={this.state.nextValue || this.props.value}
/>
)
}
export const Select = ({ multi, ...props }) =>
multi ? <MultiSelect_ {...props} /> : <SimpleSelect_ {...props} />
Select.defaultProps = { multi: false }
Select.propTypes = { multi: PropTypes.bool }
const MAP_TYPE_SELECT = {
host: SelectHost,
ip: SelectIp,
@@ -19,6 +19,7 @@ const messages = {
editableClickPlaceholder: 'Click to edit',
browseFiles: 'Browse files',
showLogs: 'Show logs',
noValue: 'None',
// ----- Modals -----
alertOk: 'OK',
+18 -6
View File
@@ -11,7 +11,7 @@ import Tooltip from 'tooltip'
import { confirm } from 'modal'
import { connectStore, noop } from 'utils'
import { Container, Row, Col } from 'grid'
import { createGetObjectsOfType } from 'selectors'
import { createGetObjectsOfType, createSelector } from 'selectors'
import { error } from 'notification'
import { get } from '@xen-orchestra/defined'
import { Select, Number } from 'editable'
@@ -165,14 +165,26 @@ class PifItemMode extends Component {
getIpv4ConfigModes().then(configModes => this.setState({ configModes }))
}
_configIp = mode => reconfigureIp(this.props.pif, mode)
_configIp = mode => mode != null && reconfigureIp(this.props.pif, mode.value)
_getOptions = createSelector(
() => this.state.configModes,
configModes => configModes.map(mode => ({ label: mode, value: mode }))
)
_getValue = createSelector(
() => this.props.pif.mode,
mode => ({ label: mode, value: mode })
)
render() {
const { pif } = this.props
const { configModes } = this.state
return (
<Select onChange={this._configIp} options={configModes} value={pif.mode}>
{pif.mode}
<Select
onChange={this._configIp}
options={this._getOptions()}
value={this._getValue()}
>
{this.props.pif.mode}
</Select>
)
}
+7 -14
View File
@@ -9,7 +9,8 @@ import Page from '../page'
import PropTypes from 'prop-types'
import React, { cloneElement } from 'react'
import VmActionBar from './action-bar'
import { Select, Text } from 'editable'
import { Host } from 'render-xo-item'
import { Text, XoSelect } from 'editable'
import { assign, isEmpty, map, pick } from 'lodash'
import { editVm, fetchVmStats, isVmRunning, migrateVm } from 'xo'
import { Container, Row, Col } from 'grid'
@@ -69,8 +70,6 @@ import TabAdvanced from './tab-advanced'
const getVmTotalDiskSpace = createSumBy(createGetVmDisks(getVm), 'size')
const getHosts = createGetObjectsOfType('host')
return (state, props) => {
const vm = getVm(state, props)
if (!vm) {
@@ -80,7 +79,6 @@ import TabAdvanced from './tab-advanced'
return {
checkPermissions: getCheckPermissions(state, props),
container: getContainer(state, props),
hosts: getHosts(state, props),
isAdmin: isAdmin(state, props),
pool: getPool(state, props),
srs: getSrs(state, props),
@@ -166,10 +164,8 @@ export default class Vm extends BaseComponent {
_setNameLabel = nameLabel => editVm(this.props.vm, { name_label: nameLabel })
_migrateVm = host => migrateVm(this.props.vm, host)
_selectOptionRenderer = option => option.name_label
header() {
const { vm, container, pool, hosts } = this.props
const { vm, container, pool } = this.props
if (!vm) {
return <Icon icon='loading' />
}
@@ -198,17 +194,14 @@ export default class Vm extends BaseComponent {
{vm.power_state === 'Running' && container && (
<span>
<span> - </span>
<Select
<XoSelect
onChange={this._migrateVm}
options={hosts}
renderer={this._selectOptionRenderer}
useLongClick
value={container}
xoType='host'
>
<Link to={`/${container.type}s/${container.id}`}>
{container.name_label}
</Link>
</Select>
<Host id={container.id} pool={false} link />
</XoSelect>
</span>
)}{' '}
{pool && (