Skip to content

Commit

Permalink
Merge pull request #136 from creative-commoners/pulls/1.0/react-router
Browse files Browse the repository at this point in the history
API Implement react-router in frontend example app
  • Loading branch information
ScopeyNZ committed Jan 18, 2019
2 parents 6dd01c8 + a0e21dc commit 5851ba9
Show file tree
Hide file tree
Showing 18 changed files with 421 additions and 38 deletions.
2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions client/src/bundles/bundle.js
@@ -1,4 +1,2 @@
// Components that are only used on the frontend view
require('legacy/CKANRegistryDisplay-loader.js');
require('components/CKANDropdownFilter');
require('components/CKANTextFilter');
// An example React app that consumes a CKAN registry
require('legacy/CKANExampleApp-loader.js');
108 changes: 108 additions & 0 deletions client/src/components/CKANExampleApp.js
@@ -0,0 +1,108 @@
/* global document */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { BrowserRouter, Route, Redirect } from 'react-router-dom';
import CKANRegistryDisplay from 'components/CKANRegistryDisplay';
import CKANRegistryDetailView from 'components/CKANRegistryDetailView';

/**
* An example React app showing how you could build a frontend for the CKAN registry
* on the current page.
*
* This app has two main components:
* - CKANRegistryDisplay: renders the display of the resource data and filters
* - CKANRegistryDetailView: renders a detailed view of the selected resource entry
*
* The react-router-dom package controls the routing between these components in this
* example.
*/
class CKANExampleApp extends Component {
/**
* Returns a title to display for each page
*
* @returns {string}
*/
getTitle() {
return `${this.props.name} / ${this.props.resourceName}`;
}

/**
* Renders the header section, containing the page title
*
* @returns {HTMLElement}
*/
renderHeader() {
return (
<section className="ckan-registry__header">
<h2 className="ckan-registry__title">{ this.getTitle() }</h2>
</section>
);
}

/**
* Renders the content container and content components conditional on the
* current route matching
*
* @returns {HTMLElement}
*/
renderContent() {
const { basePath } = this.props;

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

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

<Route
path={`${basePath}/view`}
exact
render={() => <Redirect to={basePath} />}
/>
</div>
);
}

/**
* Returns a routed/routable app with a header and a content section
*
* @returns {BrowserRouter}
*/
render() {
return (
<BrowserRouter>
<div className="ckan-registry">
{ this.renderHeader() }
{ this.renderContent() }
</div>
</BrowserRouter>
);
}
}

CKANExampleApp.propTypes = {
basePath: PropTypes.string,
name: PropTypes.string,
resourceName: PropTypes.string,
spec: PropTypes.shape({
dataset: PropTypes.string,
endpoint: PropTypes.string,
identifier: PropTypes.string,
}),
};

CKANExampleApp.defaultProps = {
basePath: '/',
name: '',
resourceName: '',
spec: {},
};

export default CKANExampleApp;
21 changes: 21 additions & 0 deletions client/src/components/CKANRegistryDetailView.js
@@ -0,0 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';

const CKANRegistryDetailView = ({ basePath, match }) => (
<div className="ckan-registry__detail">
<h3>Details of { match.params.item } </h3>
<p>Watch this space!</p>
<Link to={basePath}>&lt; Back</Link>
</div>
);

CKANRegistryDetailView.propTypes = {
basePath: PropTypes.string,
};

CKANRegistryDetailView.defaultProps = {
basePath: '/',
};

export default CKANRegistryDetailView;
17 changes: 17 additions & 0 deletions client/src/components/CKANRegistryDisplay.js
@@ -1,7 +1,9 @@
/* global window */
import React from 'react';
import PropTypes from 'prop-types';
import Griddle from 'griddle-react';
import classnames from 'classnames';
import { Link } from 'react-router-dom';

const getGriddleProperties = (props) => ({
data: [],
Expand All @@ -24,7 +26,22 @@ const CKANRegistryDisplay = (props) => (
</a>
)}
</div>

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

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

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

export default CKANRegistryDisplay;
18 changes: 18 additions & 0 deletions client/src/components/tests/CKANExampleApp-test.js
@@ -0,0 +1,18 @@
import React from 'react';
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-15.4/build/index';
import CKANExampleApp from '../CKANExampleApp';

Enzyme.configure({ adapter: new Adapter() });

describe('CKANExampleApp', () => {
describe('getTitle()', () => {
it('concatenates the dataset and resource name', () => {
const wrapper = shallow(
<CKANExampleApp name="Ministry of Silly Walks" resourceName="Lunging" />
);

expect(wrapper.instance().getTitle()).toBe('Ministry of Silly Walks / Lunging');
});
});
});
19 changes: 19 additions & 0 deletions client/src/legacy/CKANExampleApp-loader.js
@@ -0,0 +1,19 @@
/* global window */
import React from 'react';
import ReactDOM from 'react-dom';
import CKANExampleApp from 'components/CKANExampleApp';

window.document.addEventListener('DOMContentLoaded', () => {
const registries = document.querySelectorAll('.ckan-registry-holder');

[...registries].forEach(element => {
// Prevents processing the same container more than once
if (element.classList.contains('loaded')) {
return;
}
element.classList.add('loaded');

const configuration = JSON.parse(element.dataset.configuration);
ReactDOM.render(<CKANExampleApp {...configuration} />, element);
});
});
12 changes: 0 additions & 12 deletions client/src/legacy/CKANRegistryDisplay-loader.js

This file was deleted.

5 changes: 4 additions & 1 deletion package.json
Expand Up @@ -30,6 +30,7 @@
"prop-types": "^15.6.2",
"react": "15.4.2",
"react-dom": "15.4.2",
"react-router-dom": "^4.3.1",
"reactstrap": "^5.0.0-beta"
},
"devDependencies": {
Expand Down Expand Up @@ -71,7 +72,9 @@
"transform": {
".*": "babel-jest"
},
"setupFiles": ["raf/polyfill"]
"setupFiles": [
"raf/polyfill"
]
},
"babel": {
"presets": [
Expand Down
15 changes: 0 additions & 15 deletions src/Model/Resource.php
Expand Up @@ -82,19 +82,4 @@ public function onBeforeWrite()

parent::onBeforeWrite();
}

/**
* Loads model data encapsulated as JSON in order to power front end technologies used to render that
* data. Includes critical info such as the CKAN site to query (e.g. which domain, datastore, etc.)
* but also can be extended to be used for configuring the component used to show this (e.g. React.js
* or Vue.js component configuration).
*
* @return string
*/
public function getCKANClientConfig()
{
$config = '{}';
$this->extend('updateCKANClientConfig', $config);
return $config;
}
}
1 change: 0 additions & 1 deletion src/Page/CKANRegistryPage.php
Expand Up @@ -19,7 +19,6 @@
use SilverStripe\Forms\GridField\GridFieldPageCount;
use SilverStripe\Forms\GridField\GridFieldToolbarHeader;
use SilverStripe\Forms\TextField;
use SilverStripe\View\Requirements;
use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass;
use Symbiote\GridFieldExtensions\GridFieldEditableColumns;
use Symbiote\GridFieldExtensions\GridFieldOrderableRows;
Expand Down
66 changes: 66 additions & 0 deletions src/Page/CKANRegistryPageController.php
@@ -0,0 +1,66 @@
<?php

namespace SilverStripe\CKANRegistry\Page;

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

class CKANRegistryPageController extends PageController
{
private static $url_handlers = [
// Route all requests for this page, including sub-URLs, to the index action.
// The frontend components should take care of handling sub-URL routing from here.
'view/$Item' => 'index',
];

/**
* Loads model data encapsulated as JSON in order to power front end technologies used to render that
* data. Includes critical info such as the CKAN site to query (e.g. which domain, datastore, etc.)
* but also can be extended to be used for configuring the component used to show this (e.g. React.js
* or Vue.js component configuration).
*
* @param DataObject $holder
* @return array
*/
public function getCKANClientConfig(DataObject $holder = null)
{
if (!$holder) {
$holder = $this->data();
}

/** @var Resource $resource */
$resource = $holder->getComponent('DataResource');

$config = [
'spec' => [
'endpoint' => $resource->Endpoint,
'dataset' => $resource->DataSet,
'identifier' => $resource->Identifier,
],
'name' => $resource->Name,
'resourceName' => $resource->ResourceName,
'basePath' => $this->getBasePath($holder),
];

$this->extend('updateCKANClientConfig', $config);

return $config;
}

/**
* Returns the base path for the resource's page with a leading slash
*
* @param DataObject $holder
* @return string
*/
public function getBasePath(DataObject $holder = null)
{
if (!$holder) {
return '/';
}

$link = $holder->RelativeLink();
return '/' . trim($link, '/');
}
}
@@ -1,5 +1,5 @@
<% require javascript('silverstripe/admin: client/dist/js/i18n.js') %>
<% require javascript('silverstripe/ckan-registry: client/lang') %>
<% require javascript('silverstripe/ckan-registry: client/dist/js/bundle.js') %>
<div class="ckan-registry" data-configuration="$CKANClientConfig">
<div class="ckan-registry-holder" data-configuration="$Record.CKANClientConfig.JSON">
</div>
Expand Up @@ -6,7 +6,7 @@
$Content
<% end_if %>
<% with $DataResource %>
<% include SilverStripe\CKANRegistry\CKANRegistry %>
<% include SilverStripe\CKANRegistry\CKANRegistry Record=$Up %>
<% end_with %>
$Form
</section>
1 change: 1 addition & 0 deletions tests/Model/ResourceTest.yml
@@ -1,6 +1,7 @@
SilverStripe\CKANRegistry\Model\Resource:
teachers:
Name: Teachers
ResourceName: Class sizes
Endpoint: https://foo.ckan.gov/
DataSet: teachers
Identifier: 26f44973-b06d-479d-b697-8d7943c97c5a
Expand Down

0 comments on commit 5851ba9

Please sign in to comment.