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:
Jiri Tomasek 2017-06-14 12:18:25 +02:00
parent 85f04eb9af
commit e28dd6b402
4 changed files with 150 additions and 1 deletions

View File

@ -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

View File

@ -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)
});

View File

@ -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'
),

View File

@ -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}
/>
)
);