Skip to content

Commit

Permalink
Merge pull request #137 from creative-commoners/pulls/1.0/griddle-me-…
Browse files Browse the repository at this point in the history
…that

NEW Show results on the front-end of a CKAN Registry Page
  • Loading branch information
ScopeyNZ committed Jan 22, 2019
2 parents 5851ba9 + e92c46d commit f5a6ee4
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 33 deletions.
2 changes: 1 addition & 1 deletion client/dist/js/bundle-admin.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions client/src/components/CKANExampleApp.js
Expand Up @@ -48,17 +48,21 @@ class CKANExampleApp extends Component {
renderContent() {
const { basePath } = this.props;

const passProps = {
...this.props,
};

return (
<div className="ckan-registry__content">
<Route
path={basePath}
exact
render={props => <CKANRegistryDisplay {...props} basePath={basePath} />}
render={props => <CKANRegistryDisplay {...props} {...passProps} />}
/>

<Route
path={`${basePath}/view/:item`}
render={props => <CKANRegistryDetailView {...props} basePath={basePath} />}
render={props => <CKANRegistryDetailView {...props} {...passProps} />}
/>

<Route
Expand Down
201 changes: 178 additions & 23 deletions client/src/components/CKANRegistryDisplay.js
@@ -1,47 +1,202 @@
/* global window */
import React from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Griddle from 'griddle-react';
import classnames from 'classnames';
import CKANApi from 'lib/CKANApi';
import { Link } from 'react-router-dom';

const getGriddleProperties = (props) => ({
data: [],
pageSize: 30,
currentPage: 1,
recordCount: 0,
...props,
});

const CKANRegistryDisplay = (props) => (
<div>
<Griddle {...getGriddleProperties(props)} />
<div className={classnames('ckan-registry__export', props.className)}>
{props.downloadLink && (
class CKANRegistryDisplay extends Component {
constructor(props) {
super(props);

this.state = {
data: [],
loading: true,
currentPage: 1,
recordCount: 0,
};

this.handleGetPage = this.handleGetPage.bind(this);
}

componentDidMount() {
this.loadData();
}

componentDidUpdate(prevProps, prevState) {
if (prevState.currentPage !== this.state.currentPage) {
this.loadData();
}
}

getGriddleProps() {
const { pageSize } = this.props;
const { data, currentPage, recordCount } = this.state;

const GriddleLayout = ({ Table, Pagination, Filter }) => (
<div>
<div className="ckan-registry__filters">
<Filter />
</div>
<div className="ckan-registry__table">
<Table />
</div>
<div className="ckan-registry__pagination">
<Pagination />
</div>
</div>
);

return {
data,
pageProperties: {
currentPage,
recordCount,
pageSize,
},
events: {
onGetPage: this.handleGetPage,
onNext: () => { this.handleGetPage(this.state.currentPage + 1); },
onPrevious: () => { this.handleGetPage(this.state.currentPage - 1); },
},
components: {
Layout: GriddleLayout,
},
};
}

handleGetPage(pageNumber) {
this.setState({
currentPage: pageNumber,
});
}

loadData() {
const { spec: { endpoint, identifier }, fields, pageSize } = this.props;
const { currentPage } = this.state;

const recordMapper = record => {
const newRecord = {};
Object.entries(record).forEach(([key, value]) => {
const readableLabel = fields
.find(field => field.OriginalLabel === key)
.ReadableLabel;
newRecord[readableLabel] = value;
});
return newRecord;
};

this.setState({ loading: true });
CKANApi
.loadDatastore(endpoint, identifier)
.search(
fields
.filter(field => parseInt(field.ShowInResultsView, 10) === 1)
.map(field => field.OriginalLabel), // fields (select)
null, // search term (where)
false, // distinct
pageSize, // limit
(currentPage - 1) * pageSize // offset
)
.then(result => {
this.setState({
data: result.records ? result.records.map(recordMapper) : [],
recordCount: result.total,
loading: false,
});
});
}

/**
* Renders a loading message if "loading" is true in the state
*
* @returns {HTMLElement|null}
*/
renderLoading() {
const { loading } = this.state;
if (!loading) {
return null;
}

return (
<p className="ckan-registry__loading">
{ window.i18n._t('CKANRegistryDisplay.LOADING', 'Loading...') }
</p>
);
}

/**
* Renders a download/export to CSV link
*
* @returns {HTMLElement|null}
*/
renderDownloadLink() {
const { downloadLink } = this.props;
if (!downloadLink) {
return null;
}

return (
<div className="ckan-registry__export">
<a
className="ckan-registry__button--export"
href={props.downloadLink}
className="ckan-registry__button ckan-registry__button--export"
href={downloadLink}
>
{window.i18n._t('CKANRegistryDisplay.DOWNLOAD', 'Export results to CSV')}
{ window.i18n._t('CKANRegistryDisplay.DOWNLOAD', 'Export results to CSV') }
</a>
)}
</div>
</div>
);
}

render() {
const { className, fields } = this.props;

{ /* example for adding a link using react-router */ }
<Link to={`${props.basePath}/view/123`}>Go to item 123</Link>
</div>
);
const invalidConfig = !fields || !fields.length;
const classes = classnames(
'ckan-registry',
{ 'ckan-registry__error': invalidConfig },
className
);

if (!fields || !fields.length) {
const errorMessage = window.i18n._t(
'CKANRegistryDisplay.NO_FIELDS',
'There are no columns to show in this table.'
);

return (
<div className={classes}>
<p>{errorMessage}</p>
</div>
);
}

return (
<div className={classes}>
{ this.renderLoading() }
<Griddle {...this.getGriddleProps()} />
{ this.renderDownloadLink() }

{ /* example for adding a link using react-router */ }
<Link to={`${this.props.basePath}/view/123`}>Go to item 123</Link>
</div>
);
}
}

CKANRegistryDisplay.propTypes = {
basePath: PropTypes.string,
className: PropTypes.string,
downloadLink: PropTypes.string,
pageSize: PropTypes.number,
};

CKANRegistryDisplay.defaultProps = {
basePath: '/',
className: '',
downloadLink: '',
pageSize: 30,
};

export default CKANRegistryDisplay;
12 changes: 9 additions & 3 deletions client/src/lib/CKANApi/DataStore.js
Expand Up @@ -16,16 +16,20 @@ export default class {
* @param {string[]} fields - The fields to return for each record
* @param {string|object} term - A string term to search globally or an object of field : term
* @param {boolean} distinct
* @param {number} limit
* @param {number} offset
* @return {Promise}
*/
search(fields, term = null, distinct = false) {
search(fields, term = null, distinct = false, limit = 100, offset = 0) {
if (!Array.isArray(fields) || !fields.length) {
return Promise.resolve(false);
return Promise.reject(false);
}

const options = {
id: this.resource,
fields: fields.map(encodeURIComponent).join(','),
// Always ensure a total is requested
include_total: true,
};

// If an invalid term is given then assume that it will return nothing
Expand All @@ -46,10 +50,12 @@ export default class {
}
}

// Add the distinct var if requested
// Add the distinct, limit and offset vars if requested
if (distinct) {
options.distinct = true;
}
options.limit = limit;
options.offset = offset;

// Make the request and parse the result into a more usable format
return CKANApi.makeRequest(this.endpoint, 'datastore_search', options).then(
Expand Down
9 changes: 6 additions & 3 deletions client/src/lib/CKANApi/tests/DataStore-test.js
Expand Up @@ -21,9 +21,12 @@ describe('DataStore', () => {
it('returns false when fields param is invalid or no fields are given', (done) => {
const datastore = makeDataStore();

const promises = [null, false, 5, []].map(item => datastore.search(item).then(result => {
expect(result).toBe(false);
}));
const promises = [null, false, 5, []].map(item => datastore.search(item).then(
() => null,
result => {
expect(result).toBe(false);
}
));

Promise.all(promises).then(() => {
expect(CKANApi.makeRequest.mock.calls).toHaveLength(0);
Expand Down
2 changes: 2 additions & 0 deletions src/Page/CKANRegistryPage.php
Expand Up @@ -7,6 +7,7 @@
use SilverStripe\CKANRegistry\Forms\GridFieldResourceTitle;
use SilverStripe\CKANRegistry\Forms\ResourceLocatorField;
use SilverStripe\CKANRegistry\Model\Resource;
use SilverStripe\CKANRegistry\Model\ResourceField;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\GridField\GridField;
Expand All @@ -28,6 +29,7 @@
* and display a set of CMS configured columns.
*
* @method Resource DataResource
* @property Resource DataResource
*/
class CKANRegistryPage extends Page
{
Expand Down
18 changes: 18 additions & 0 deletions src/Page/CKANRegistryPageController.php
Expand Up @@ -4,6 +4,7 @@

use PageController;
use SilverStripe\CKANRegistry\Model\Resource;
use SilverStripe\CKANRegistry\Model\ResourceField;
use SilverStripe\ORM\DataObject;

class CKANRegistryPageController extends PageController
Expand Down Expand Up @@ -41,6 +42,23 @@ public function getCKANClientConfig(DataObject $holder = null)
'name' => $resource->Name,
'resourceName' => $resource->ResourceName,
'basePath' => $this->getBasePath($holder),
'fields' => array_map(
function (ResourceField $field) {
return [
'OriginalLabel' => $field->OriginalLabel,
'ReadableLabel' => $field->ReadableLabel,
'ShowInResultsView' => $field->ShowInResultsView,
'ShowInDetailView' => $field->ShowInDetailView,
'DisplayConditions' => $field->DisplayConditions,
'RemoveDuplicates' => $field->RemoveDuplicates,
];
},
$resource->Fields()->filterAny([
'ShowInResultsView' => true,
'ShowInDetailView' => true,
'RemoveDuplicates' => true,
])->Sort('Position', 'ASC')->toArray()
),
];

$this->extend('updateCKANClientConfig', $config);
Expand Down
3 changes: 3 additions & 0 deletions templates/Includes/CKANRegistry.ss
@@ -0,0 +1,3 @@
<% require javascript('silverstripe/ckan-registry: client/dist/js/bundle.js') %>
<div class="ckan-registry" data-configuration="$CKANClientConfig">
</div>

0 comments on commit f5a6ee4

Please sign in to comment.