Routing and Data Access¶
We have already defined the basic routing structure. To define more routes, we simply extend the index.js
file and add more components. We will start by building the list of modules. Create a new file src/js/routes/modules/index.jsx
and add the following code:
import m from "mithril";
import api from '../../api.js';
import Athena from "../../athena.jsx";
import AriaMenu from "../../components/aria-menu.jsx";
export default function ModulesIndex() {
return {
oninit: (vnode) => {
},
view: (vnode) => {
let modules = [];
if (vnode.state.modules) {
modules = vnode.state.modules.data.map((module) => {
let key = `module-${module.id}`;
return (
<tr key={key}>
<td>
<a>{module.attributes.code}</a>
</td>
<td>
<a>{module.attributes.name}</a>
</td>
<td>
<nav>
<ul role="menu" class="horizontal" aria-label="Sections">
<li>
<a
href=""
role="menuitem"
aria-label="Dates"
title="Dates"
class="mdi mdi-calendar-clock"
tabindex="0"
></a>
</li>
<li>
<a
href=""
role="menuitem"
aria-label="Documents"
title="Documents"
class="mdi mdi-file-document-box-multiple-outline"
tabindex="-1"
></a>
</li>
<li>
<a
href=""
role="menuitem"
aria-label="Exercises"
title="Exercises"
class="mdi mdi-test-tube"
tabindex="-1"
></a>
</li>
<li>
<a
href=""
role="menuitem"
aria-label="Teilnehmerinnen"
title="Teilnehmer_innen"
class="mdi mdi-account-multiple"
tabindex="-1"
></a>
</li>
</ul>
</nav>
</td>
<td>
<nav>
<ul role="menu" class="horizontal" aria-label="Actions">
<li>
<a
role="menuitem"
aria-label="Leave"
title="Leave"
class="mdi mdi-delete warning"
tabindex="0"
></a>
</li>
</ul>
</nav>
</td>
</tr>
);
});
}
return (
<Athena>
<div className="row">
<div className="modules-menu col-lg-3 col-md-2 bg-light">
<h1>My modules</h1>
<nav className="navbar navbar-light" aria-label="Modules">
<AriaMenu role="menu" aria-label="Modules" class="vertical">
<li>
<a tabIndex="0" role="menuitem">
Current Semester
</a>
</li>
<li>
<a tabIndex="-1" role="menuitem">
Last Semester
</a>
</li>
<li role="separator"></li>
<li>
<a tabIndex="0" role="menuitem">
Enroll in Menu
</a>
</li>
<li>
<a tabIndex="-1" role="menuitem">
Create new Module
</a>
</li>
</AriaMenu>
</nav>
</div>
<section className="col-lg-9 col-md-10 bg-light">
<table>
<thead>
<tr>
<th>Code</th>
<th>Name</th>
<th>Sections</th>
<th>Actions</th>
</tr>
</thead>
<tbody>{modules}</tbody>
</table>
</section>
</div>
</Athena>
);
},
};
}
Here we see one of the main differences between React and Mithril. Because the routing is not defined within the JSX code, but externally, there is also no nesting of templates. Thus to use a application-level template that provides the generic structure, we invert the pattern and in the athena.jsx
implemented the application-level template and then use that as a wrapping component for our new Index
component.
Note, that there is a way to nest routes within the index.js
, that is not used in this tutorial, though. For further information consult the Mithril documentation.
With the component in place, we need to update the index.js
to route to our new component:
import m from "mithril";
import Athena from "./athena.jsx";
import ModulesIndex from "./routes/modules/index.jsx";
import '../styles/app.scss'
const root = document.getElementById("root");
m.route(root, "/", {
"/": Athena,
"/modules": ModulesIndex
});
Next update the athena.jsx
to link the “My Modules” to our new route:
...
<li className="navbar-brand">Athena - Study Portal</li>
<li className="nav-item">
<m.route.Link href={'/modules'} className="nav-link active" role="menuitem" tabIndex="0">My Modules</m.route.Link>
</li>
<li className="nav-item"><a className="nav-link" role="menuitem" tabIndex="-1">My Exams</a></li>
...
As you can see, to link to another route, we provide the route path into the href
attribute as part of a mithril.route.Link
Element, that will create the prefix “/#!/”. This needs to be included wherever we create a link to another route.
If you now try out the application, you will be able to navigate to the list of modules by clicking on the link. You will also see that the last part of the browser URL changes to “#!/modules”. This is known as hash-bang (or just hash) routing. In this structure the main URL is the path to where the application is served from and the part after the hash (the so-called fragment) is used to implement the routing within the application. While full URL routing as in Ember or React looks a bit cleaner, functionally there is no difference between the two.
Loading Data¶
At the moment our list of modules is empty, so we need to load some from the backend. To do this, we will add some code into the oninit
function, which is automatically called when the component is initialised:
oninit: (vnode) => {
m.request({
method: "GET",
url: `${api.base}/modules`,
}).then((data) => {
vnode.state.modules = data;
});
},
Unlike React, Mithril comes with data access included at a very basic level. Via mithril.request
we can send arbitrary requests to the backend server. As you can see, the url
is composed of the value api.base
plus the specific path for all modules '/modules'
. For this to work, we need to import the api
definition at the top of the index.jsx
:
import api from '../../api.js';
Then we need to create src/js/api.js
to define the basic backend API parameters:
export default {
base: 'https://mht.uzi.uni-halle.de/client-seitige-web-anwendungen/api/example'
}
As in the past tutorials, you can replace the “example” part of the URL to get your own API instance. If you now look at the application, it should have loaded the data from the API.
In contrast to working with Ember and Redux, here you are working at a very low level, dealing directly with the JSONAPI response. You can see the effect of this if you look into the view
method on the src/js/routes/modules/index.jsx
, where to access the list of modules, we use vnode.state.modules.data
. The first part vnode.state
fetches the state and then in the code above we have assigned the list of modules to the modules
key in the state. However, every JSONAPI response is an object with a single key data
, and only inside this key is the actual list of responses contained. As a result, when accessing the data, we have to navigate quite deep into the structure.
If, while developing, you are unsure about what the exact data structure is that the API returns, via the developer tools in the browser, you can always inspect the network traffic as well. If you select a request that goes to the backend API, then on the right you can see the actual JSON data that was returned in the response. The structure of the JSON data is then directly mirrored in what you get in the data
parameter to the then
callback function.