Share report template functionality

This commit is contained in:
Tomas Bures 2017-07-24 07:03:32 +03:00
parent e6ad0e239e
commit 4822a50d0b
35 changed files with 614 additions and 128 deletions

View file

@ -23,7 +23,7 @@ function handleError(that, error) {
function withErrorHandling(target) {
const inst = target.prototype;
if (inst._withErrorHandlingApplied) return;
if (inst._withErrorHandlingApplied) return target;
inst._withErrorHandlingApplied = true;
const contextTypes = target.contextTypes || {};

View file

@ -356,7 +356,7 @@ class TreeTableSelect extends Component {
}
}
@translate()
@translate(null, { withRef: true })
class TableSelect extends Component {
constructor(props) {
super(props);
@ -369,6 +369,7 @@ class TableSelect extends Component {
static propTypes = {
dataUrl: PropTypes.string,
data: PropTypes.array,
columns: PropTypes.array,
selectionKeyIndex: PropTypes.number,
selectionLabelIndex: PropTypes.number,
@ -426,6 +427,10 @@ class TableSelect extends Component {
});
}
refresh() {
this.table.refresh();
}
render() {
const props = this.props;
const owner = this.context.formStateOwner;
@ -443,7 +448,7 @@ class TableSelect extends Component {
</span>
</div>
<div className={'mt-tableselect-table' + (this.state.open ? '' : ' mt-tableselect-table-hidden')}>
<Table dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} selectionAsArray={this.props.selectionAsArray} withHeader={props.withHeader} selection={owner.getFormValue(id)} onSelectionDataAsync={::this.onSelectionDataAsync} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
<Table ref={node => this.table = node} data={props.data} dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} selectionAsArray={this.props.selectionAsArray} withHeader={props.withHeader} selection={owner.getFormValue(id)} onSelectionDataAsync={::this.onSelectionDataAsync} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
</div>
</div>
);
@ -451,7 +456,7 @@ class TableSelect extends Component {
return wrapInput(id, htmlId, owner, props.label, props.help,
<div>
<div>
<Table dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} selectionAsArray={this.props.selectionAsArray} withHeader={props.withHeader} selection={owner.getFormValue(id)} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
<Table ref={node => this.table = node} data={props.data} dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} selectionAsArray={this.props.selectionAsArray} withHeader={props.withHeader} selection={owner.getFormValue(id)} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
</div>
</div>
);
@ -459,6 +464,15 @@ class TableSelect extends Component {
}
}
/*
Refreshes the table. This method is provided to allow programmatic refresh from a handler outside the table.
The reference to the table can be obtained by ref.
*/
TableSelect.prototype.refresh = function() {
this.getWrappedInstance().refresh()
};
class ACEEditor extends Component {
static propTypes = {
id: PropTypes.string.isRequired,

View file

@ -38,4 +38,9 @@
.mt-tableselect-dropdown input[readonly] {
background-color: white;
}
h3.legend {
font-size: 21px;
margin-bottom: 20px;
}

View file

@ -9,6 +9,7 @@ import './page.css';
import { withErrorHandling } from './error-handling';
import interoperableErrors from '../../../shared/interoperable-errors';
import { DismissibleAlert, Button } from './bootstrap-components';
import mailtrainConfig from 'mailtrainConfig';
class PageContent extends Component {
@ -200,10 +201,16 @@ class SectionContent extends Component {
this.setFlashMessage(severity, text);
}
errorHandler(error) {
if (error instanceof interoperableErrors.NotLoggedInError) {
ensureAuthenticated() {
if (!mailtrainConfig.isAuthenticated) {
/* FIXME, once we turn Mailtrain to single-page application, this should become navigateTo */
window.location = '/account/login?next=' + encodeURIComponent(this.props.root);
}
}
errorHandler(error) {
if (error instanceof interoperableErrors.NotLoggedInError) {
this.ensureAuthenticated();
} else if (error.response && error.response.data && error.response.data.message) {
console.error(error);
this.navigateToWithFlashMessage(this.props.root, 'danger', error.response.data.message);
@ -312,7 +319,7 @@ class DropdownLink extends Component {
}
function withPageHelpers(target) {
withErrorHandling(target);
target = withErrorHandling(target);
const inst = target.prototype;
@ -346,16 +353,32 @@ function withPageHelpers(target) {
return this.context.sectionContent.navigateToWithFlashMessage(path, severity, text);
}
inst.axios
return target;
}
function requiresAuthenticatedUser(target) {
const comp1 = withPageHelpers(target);
function comp2(props, context) {
comp1.call(this, props, context);
context.sectionContent.ensureAuthenticated();
}
comp2.prototype = comp1.prototype;
for (const attr in comp1) {
comp2[attr] = comp1[attr];
}
return comp2;
}
export {
Section,
Title,
Toolbar,
NavButton,
DropdownLink,
withPageHelpers
withPageHelpers,
requiresAuthenticatedUser
};

View file

@ -6,7 +6,6 @@ import { translate } from 'react-i18next';
import PropTypes from 'prop-types';
import jQuery from 'jquery';
import '../../public/jquery/jquery-ui-1.12.1.min.js';
import 'datatables.net';
import 'datatables.net-bs';
@ -82,13 +81,20 @@ class Table extends Component {
selMap.set(elem, undefined);
}
if (this.table) {
const self = this;
this.table.rows().every(function() {
const data = this.data();
const key = data[self.props.selectionKeyIndex];
if (props.data) {
for (const rowData of props.data) {
const key = rowData[props.selectionKeyIndex];
if (selMap.has(key)) {
selMap.set(key, data);
selMap.set(key, rowData);
}
}
} else if (this.table) {
this.table.rows().every(function() {
const rowData = this.data();
const key = rowData[props.selectionKeyIndex];
if (selMap.has(key)) {
selMap.set(key, rowData);
}
});
}
@ -118,26 +124,28 @@ class Table extends Component {
}
@withAsyncErrorHandler
async fetchSelectionData() {
async fetchAndNotifySelectionData() {
if (this.props.onSelectionDataAsync) {
const keysToFetch = [];
for (const pair of this.selectionMap.entries()) {
if (!pair[1]) {
keysToFetch.push(pair[0]);
if (!this.props.data) {
const keysToFetch = [];
for (const pair of this.selectionMap.entries()) {
if (!pair[1]) {
keysToFetch.push(pair[0]);
}
}
}
if (keysToFetch.length > 0) {
const response = await axios.post(this.props.dataUrl, {
operation: 'getBy',
column: this.props.selectionKeyIndex,
values: keysToFetch
});
if (keysToFetch.length > 0) {
const response = await axios.post(this.props.dataUrl, {
operation: 'getBy',
column: this.props.selectionKeyIndex,
values: keysToFetch
});
for (const row of response.data) {
const key = row[this.props.selectionKeyIndex];
if (this.selectionMap.has(key)) {
this.selectionMap.set(key, row);
for (const row of response.data) {
const key = row[this.props.selectionKeyIndex];
if (this.selectionMap.has(key)) {
this.selectionMap.set(key, row);
}
}
}
}
@ -293,7 +301,7 @@ class Table extends Component {
clearTimeout(this.refreshTimeoutId);
});
this.fetchSelectionData();
this.fetchAndNotifySelectionData();
}
componentDidUpdate() {
@ -314,7 +322,7 @@ class Table extends Component {
}
this.updateSelectInfo();
this.fetchSelectionData();
this.fetchAndNotifySelectionData();
}
async notifySelection(eventCallback, newSelectionMap) {