/*
* This file is part of Cockpit.
*
* Copyright (C) 2016 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see .
*/
"use strict";
var React = require('react');
require('./listing.less');
/* entry for an alert in the listing, can be expanded (with details) or standard
* rowId optional: an identifier for the row which will be set as "data-row-id" attribute on the
* columns list of columns to show in the header
* columns to show, can be a string, react component or object with { name: 'name', 'header': false }
* 'header' (or if simple string) defaults to false
* in case 'header' is true, | is used for the entries, otherwise |
* tabRenderers optional: list of tab renderers for inline expansion, array of objects with
* - name tab name (has to be unique in the entry, used as react key)
* - renderer react component
* - data render data passed to the tab renderer
* - presence 'always', 'onlyActive', 'loadOnDemand', default: 'loadOnDemand'
* - 'always' once a row is expanded, this tab is always rendered, but invisible if not active
* - 'onlyActive' the tab is only rendered when active
* - 'loadOnDemand' the tab is first rendered when it becomes active, then follows 'always' behavior
* if tabRenderers isn't set, item can't be expanded inline
* navigateToItem optional: callback triggered when a row is clicked, pattern suggests navigation
* to view expanded item details, if not set, navigation isn't available
* listingDetail optional: text rendered next to action buttons, similar style to the tab headers
* listingActions optional: buttons that are presented as actions for the expanded item
* selectChanged optional: callback will be used when the "selected" state changes
* selected optional: true if the item is selected, can't be true if row has navigation or expansion
*/
var ListingRow = React.createClass({
propTypes: {
rowId: React.PropTypes.string,
columns: React.PropTypes.array.isRequired,
tabRenderers: React.PropTypes.array,
navigateToItem: React.PropTypes.func,
listingDetail: React.PropTypes.node,
listingActions: React.PropTypes.arrayOf(React.PropTypes.node),
selectChanged: React.PropTypes.func,
selected: React.PropTypes.bool
},
getDefaultProps: function () {
return {
tabRenderers: [],
navigateToItem: null,
};
},
getInitialState: function() {
return {
expanded: false, // show expanded view if true, otherwise one line compact
activeTab: 0, // currently active tab in expanded mode, defaults to first tab
loadedTabs: {}, // which tabs were already loaded - this is important for 'loadOnDemand' setting
// contains tab indices
selected: this.props.selected, // whether the current row is selected
};
},
handleNavigateClick: function(e) {
// only consider primary mouse button
if (!e || e.button !== 0)
return;
this.props.navigateToItem();
},
handleExpandClick: function(e) {
// only consider primary mouse button
if (!e || e.button !== 0)
return;
var willBeExpanded = !this.state.expanded && this.props.tabRenderers.length > 0;
this.setState( { expanded: willBeExpanded });
var loadedTabs = {};
// unload all tabs if not expanded
if (willBeExpanded) {
// see if we should preload some tabs
var tabIdx;
var tabPresence;
for (tabIdx = 0; tabIdx < this.props.tabRenderers.length; tabIdx++) {
if ('presence' in this.props.tabRenderers[tabIdx])
tabPresence = this.props.tabRenderers[tabIdx].presence;
else
tabPresence = 'default';
// the active tab is covered by separate logic
if (tabPresence == 'always')
loadedTabs[tabIdx] = true;
}
// ensure the active tab is loaded
loadedTabs[this.state.activeTab] = true;
}
this.setState( { loadedTabs: loadedTabs });
e.stopPropagation();
e.preventDefault();
},
handleSelectClick: function(e) {
// only consider primary mouse button
if (!e || e.button !== 0)
return;
var selected = !this.state.selected;
this.setState( { selected: selected });
if (this.props.selectChanged)
this.props.selectChanged(selected);
e.stopPropagation();
e.preventDefault();
},
handleTabClick: function(tabIdx, e) {
// only consider primary mouse button
if (!e || e.button !== 0)
return;
var prevTab = this.state.activeTab;
var prevTabPresence = 'default';
var loadedTabs = this.state.loadedTabs;
if (prevTab !== tabIdx) {
// see if we need to unload the previous tab
if ('presence' in this.props.tabRenderers[prevTab])
prevTabPresence = this.props.tabRenderers[prevTab].presence;
if (prevTabPresence == 'onlyActive')
delete loadedTabs[prevTab];
// ensure the new tab is loaded and update state
loadedTabs[tabIdx] = true;
this.setState({ loadedTabs: loadedTabs, activeTab: tabIdx });
}
e.stopPropagation();
e.preventDefault();
},
render: function() {
var self = this;
// only enable navigation if a function is provided and the row isn't expanded (prevent accidental navigation)
var allowNavigate = !!this.props.navigateToItem && !this.state.expanded;
var headerEntries = this.props.columns.map(function(itm) {
if (typeof itm === 'string' || typeof itm === 'number' || itm === null || itm === undefined || itm instanceof String || React.isValidElement(itm))
return ( | {itm} | );
else if ('header' in itm && itm.header)
return ({itm.name} | );
else if ('tight' in itm && itm.tight)
return ({itm.name || itm.element} | );
else
return ({itm.name} | );
});
var allowExpand = (this.props.tabRenderers.length > 0);
var expandToggle;
if (allowExpand) {
expandToggle =
| ;
} else {
expandToggle = | ;
}
var listingItemClasses = ["listing-ct-item"];
if (!allowNavigate)
listingItemClasses.push("listing-ct-nonavigate");
if (!allowExpand)
listingItemClasses.push("listing-ct-noexpand");
var allowSelect = !(allowNavigate || allowExpand) && (this.state.selected !== undefined);
var clickHandler;
if (allowSelect) {
clickHandler = this.handleSelectClick;
if (this.state.selected)
listingItemClasses.push("listing-ct-selected");
} else {
if (allowNavigate)
clickHandler = this.handleNavigateClick;
else
clickHandler = this.handleExpandClick;
}
var listingItem = (