Add Select All button to Nodes list view
* Adds SelectAllButton and SelectAllCheckBox components which can be used in Toolbars. Those use SelectAll HOC component which consolidates common functionality * Adds Select All button to Nodes listview * Resolves problem of removing form values in Nodes list when nodes collection changes Closes-Bug: #1634718 Change-Id: I0477f59a7ba2a16ddd3ad0acb3d58ea2b547d8bd
This commit is contained in:
parent
85f04eb9af
commit
e28dd6b402
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
SelectAllCheckBox and SelectAllButton components have been added. Those
|
||||
can be used to include SelectAll feature in item listings.
|
||||
Nodes list view now includes 'Select All' button
|
||||
fixes:
|
||||
- |
|
||||
Fixes `bug 1634718 <https://bugs.launchpad.net/tripleo/+bug/1634718>`__
|
||||
adds Select All button to Nodes list view
|
|
@ -16,13 +16,14 @@
|
|||
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Form, reduxForm } from 'redux-form';
|
||||
import { pickBy } from 'lodash';
|
||||
|
||||
import NodesActions from '../../../actions/NodesActions';
|
||||
import { nodesInProgress } from '../../../selectors/nodes';
|
||||
import { getFilteredNodes, nodesInProgress } from '../../../selectors/nodes';
|
||||
|
||||
const messages = defineMessages({
|
||||
selectNodes: {
|
||||
|
@ -36,6 +37,15 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
class NodesListForm extends React.Component {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// remove form values of nodes which are not listed any more
|
||||
const removedValues = this.props.nodes
|
||||
.keySeq()
|
||||
.toSet()
|
||||
.subtract(nextProps.nodes.keySeq());
|
||||
removedValues.map(v => this.props.change(`values.${v}`, false));
|
||||
}
|
||||
|
||||
handleFormSubmit(formData) {
|
||||
const nodeIds = Object.keys(pickBy(formData.values, value => !!value));
|
||||
|
||||
|
@ -70,11 +80,13 @@ class NodesListForm extends React.Component {
|
|||
}
|
||||
}
|
||||
NodesListForm.propTypes = {
|
||||
change: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
deleteNodes: PropTypes.func.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object,
|
||||
introspectNodes: PropTypes.func.isRequired,
|
||||
nodes: ImmutablePropTypes.map.isRequired,
|
||||
provideNodes: PropTypes.func.isRequired,
|
||||
reset: PropTypes.func.isRequired,
|
||||
tagNodes: PropTypes.func.isRequired
|
||||
|
@ -94,10 +106,14 @@ const validate = (formData, props) => {
|
|||
|
||||
const form = reduxForm({
|
||||
form: 'nodesListForm',
|
||||
initialValues: {
|
||||
values: {}
|
||||
},
|
||||
validate
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
nodes: getFilteredNodes(state),
|
||||
nodesInProgress: nodesInProgress(state)
|
||||
});
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import { getFilteredNodes, getNodes } from '../../../selectors/nodes';
|
|||
import { nodeColumnMessages } from '../messages';
|
||||
import NodesToolbarForm from './NodesToolbarForm';
|
||||
import NodesToolbarActions from './NodesToolbarActions';
|
||||
import { SelectAllButton } from '../../ui/Toolbar/SelectAll';
|
||||
import ToolbarFiltersForm from '../../ui/Toolbar/ToolbarFiltersForm';
|
||||
import {
|
||||
clearActiveFilters,
|
||||
|
@ -57,6 +58,7 @@ class NodesToolbar extends React.Component {
|
|||
activeFilters,
|
||||
clearActiveFilters,
|
||||
deleteActiveFilter,
|
||||
filteredNodes,
|
||||
filteredNodesCount,
|
||||
nodesToolbarFilter,
|
||||
intl,
|
||||
|
@ -138,6 +140,12 @@ class NodesToolbar extends React.Component {
|
|||
/>
|
||||
))}
|
||||
</ActiveFiltersList>
|
||||
<p className="pull-right">
|
||||
<SelectAllButton
|
||||
form="nodesListForm"
|
||||
items={filteredNodes.toList().toJS()}
|
||||
/>
|
||||
</p>
|
||||
</ToolbarResults>
|
||||
</Toolbar>
|
||||
);
|
||||
|
@ -148,6 +156,7 @@ NodesToolbar.propTypes = {
|
|||
addActiveFilter: PropTypes.func.isRequired,
|
||||
clearActiveFilters: PropTypes.func.isRequired,
|
||||
deleteActiveFilter: PropTypes.func.isRequired,
|
||||
filteredNodes: ImmutablePropTypes.map.isRequired,
|
||||
filteredNodesCount: PropTypes.number.isRequired,
|
||||
intl: PropTypes.object,
|
||||
nodesCount: PropTypes.number.isRequired,
|
||||
|
@ -169,6 +178,7 @@ const mapStateToProps = state => {
|
|||
return {
|
||||
activeFilters: getActiveFilters(state, 'nodesToolbar'),
|
||||
filteredNodesCount: getFilteredNodes(state).size,
|
||||
filteredNodes: getFilteredNodes(state),
|
||||
nodesToolbarFilter: getFilterByName(state, 'nodesToolbar').delete(
|
||||
'activeFilters'
|
||||
),
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* Copyright 2017 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { connect } from 'react-redux';
|
||||
import { getFormValues, change } from 'redux-form';
|
||||
import { pickBy } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
const messages = defineMessages({
|
||||
selectAll: {
|
||||
id: 'SelectAll.selectAllText',
|
||||
defaultMessage: 'Select All'
|
||||
},
|
||||
deselectAll: {
|
||||
id: 'SelectAll.deselectAllText',
|
||||
defaultMessage: 'Deselect All'
|
||||
}
|
||||
});
|
||||
|
||||
export const selectAll = WrappedComponent => {
|
||||
class SelectAllHOC extends Component {
|
||||
/*
|
||||
* Function to determine if items should be selected or deselected
|
||||
* Select all item if none of them is selected
|
||||
* Deselect all item if at least one of them is selected
|
||||
*/
|
||||
shouldSelectAll() {
|
||||
const { formData, items } = this.props;
|
||||
return (
|
||||
Object.keys(pickBy(formData.values)).length < items.length ||
|
||||
items.length === 0
|
||||
);
|
||||
}
|
||||
|
||||
toggleSelectAll() {
|
||||
const { items, itemProperty, selectItem } = this.props;
|
||||
items.map(item => selectItem(item[itemProperty], this.shouldSelectAll()));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<WrappedComponent
|
||||
disabled={this.props.items.length === 0}
|
||||
shouldSelectAll={this.shouldSelectAll()}
|
||||
toggleSelectAll={this.toggleSelectAll.bind(this)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
SelectAllHOC.propTypes = {
|
||||
form: PropTypes.string.isRequired,
|
||||
formData: PropTypes.object.isRequired,
|
||||
itemProperty: PropTypes.string.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
selectItem: PropTypes.func.isRequired
|
||||
};
|
||||
SelectAllHOC.defaultProps = {
|
||||
formData: {},
|
||||
itemProperty: 'uuid'
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
formData: getFormValues(ownProps.form)(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
selectItem: (key, value) =>
|
||||
dispatch(change(ownProps.form, `values.${key}`, value))
|
||||
});
|
||||
|
||||
return connect(mapStateToProps, mapDispatchToProps)(SelectAllHOC);
|
||||
};
|
||||
|
||||
export const SelectAllButton = selectAll(
|
||||
({ disabled, shouldSelectAll, toggleSelectAll }) => (
|
||||
<Button
|
||||
bsStyle="link"
|
||||
onClick={() => toggleSelectAll()}
|
||||
disabled={disabled}
|
||||
>
|
||||
{shouldSelectAll
|
||||
? <FormattedMessage {...messages.selectAll} />
|
||||
: <FormattedMessage {...messages.deselectAll} />}
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
|
||||
export const SelectAllCheckBox = selectAll(
|
||||
({ disabled, shouldSelectAll, toggleSelectAll }) => (
|
||||
<input
|
||||
disabled={disabled}
|
||||
type="checkbox"
|
||||
checked={!shouldSelectAll}
|
||||
onChange={toggleSelectAll}
|
||||
/>
|
||||
)
|
||||
);
|
Loading…
Reference in New Issue