Viewing and Updating¶
With that in place, we will now implement the viewing and updating of modules.
Viewing a Module¶
First create a new file src/js/routes/modules/view.jsx
and add the following code:
import m from "mithril";
import Athena from "../../athena.jsx";
import api from "../../api.js";
export default function View() {
let module = null;
let teacher = null;
return {
oninit: (vnode) => {
m.request({
method: "GET",
url: `${api.base}/modules/${vnode.attrs.mid}`,
}).then((data) => {
module = data;
m.request({
method: "GET",
url: `${api.base}/users/${data.data.relationships.teacher.data.id}`,
}).then((data) => {
teacher = data;
});
});
},
view: (vnode) => {
if (module && teacher) {
return (
<Athena>
<main className="col-lg-9">
<div className="row d-sm-flex justify-content-center">
<div className="col-6">
<h1>
{module.data.attributes.code} {module.data.attributes.name}
</h1>
<dl>
<dt>Semester</dt>
<dd>{module.data.attributes.semester}</dd>
<dt>Contact</dt>
<dd>{teacher.data.attributes.email}</dd>
</dl>
<div>
<m.route.Link href={`/modules/${module.data.id}/edit`} role="button">
Edit
</m.route.Link>
</div>
</div>
</div>
</main>
</Athena>
);
} else {
return <div>Loading...</div>;
}
},
};
}
The code again demonstrates the high level of similarity between React and Mithril, with the only difference in the data acquisition. Most of that you are already familiar with, but as we are now accessing a single module, we will want to have a dynamic route segment with the module id, just as we did in Ember and React. This time, we stored our data inside two variables, however, thus we can access the vnode
properties through these variables. In Mithril these dynamic route segments are available via the vnode.attrs
property:
mithril.request({
method: 'GET',
url: `${api.base}/modules/${vnode.attrs.mid}`
})
Here we have used a mid
key to access the module id, so when we define the dynamic route in the src/js/index.js
, we need to use the same name:
import m from "mithril";
import Athena from "./athena.jsx";
import ModulesIndex from "./routes/modules/index.jsx";
import New from "./routes/modules/new.jsx";
import View from "./routes/modules/view.jsx";
import '../styles/app.scss'
const root = document.getElementById("root");
m.route(root, "/", {
"/": Athena,
"/modules": ModulesIndex,
"/modules/new": New,
"/modules/:mid": View
});
As you can see, all frameworks use the same colon + identifier pattern (':mid'
) to define dynamic route segments. One difference between Mithril and React Class Components is that routes here are always matched exactly, thus unlike in these Class Components there is no need for an exact
keyword. Functional Components did not need this keyword as well.
Before you can navigate, we need to link the view from the src/js/routes/modules/index.jsx
, updating the following lines:
...
if(vnode.state.modules) {
modules = vnode.state.modules.data.map((module) => {
let key = `module-${module.id}`;
return (
<tr key={key}>
<td><m.route.Link href={`/modules/${module.id}`} >{module.attributes.code}</m.route.Link></td>
<td><m.route.Link href={`/modules/${module.id}`} >{module.attributes.name}</m.route.Link></td>
...
You can now try out navigating to the individual modules.
Updating a Module¶
If you look at the view.jsx
JSX code, you will see that there is already a link to the edit functionality route, so let us now implement that in a new file src/js/routes/modules/edit.jsx
:
import m from "mithril";
import Athena from "../../athena.jsx";
import api from "../../api.js";
export default function Edit() {
let module = null;
let teacher = null;
function setCode(ev) {
module.data.attributes.code = ev.target.value;
}
function setName(ev) {
module.data.attributes.name = ev.target.value;
}
function setSemester(ev) {
module.data.attributes.semester = ev.target.value;
}
function updateModule(ev) {
ev.preventDefault();
m.request({
method: "PATCH",
url: `${api.base}/modules/${module.data.id}`,
body: module,
}).then((data) => {
m.route.set(`/modules/${data.data.id}`);
});
}
return {
oninit: (vnode) => {
m.request({
method: "GET",
url: `${api.base}/modules/${vnode.attrs.mid}`,
}).then((data) => {
module = data;
m.request({
method: "GET",
url: `${api.base}/users/${data.data.relationships.teacher.data.id}`,
}).then((data) => {
teacher = data;
});
});
},
/**
* Render the Component
*/
view: (vnode) => {
if (module && teacher) {
return (
<Athena>
<main className="row justify-content-center">
<div className="col-lg-6">
<h1>Edit {module.data.attributes.name}</h1>
<form onsubmit={updateModule}>
<div className="form-group">
<label>
Code
<input value={module.data.attributes.code} type="text" className="form-control" onchange={ev => setCode(ev)} />
</label>
<label>
Name
<input value={module.data.attributes.name} type="text" className="form-control" onchange={ev => setName(ev)} />
</label>
<label>
Semester
<select value={module.data.attributes.semester} onchange={ev => setSemester(ev)}>
<option value="WS18/19">Wintersemester 18/19</option>
<option value="SS18">Summersemester 18</option>
</select>
</label>
</div>
<div className="btn-container">
<button type="button" className="btn btn-secondary">
<m.route.Link href={`/modules/${module.data.id}`}>Don't Update</m.route.Link>
</button>
<button type="submit" role="button">Edit</button>
</div>
</form>
</div>
</main>
</Athena>
);
} else {
return <div>Loading...</div>;
}
},
};
}
The edit functionality demonstrates a few differences from the equivalent React code. First of all, we don’t have to work with setState
, we can simply modify the value of the module
object that we fetched from the remote API. The advantage of that is that when we actually send the update request, we can simply pass module
as the data
and a correctly formated module object will be sent to the server. Also, it means that we do not have to deal with default and changed data, as the module
object immediately contains both. At the same time, because we are dealing with raw JSONAPI data, the paths to access individual objects are much longer ({module.data.attributes.name}
).
One thing to note here is just a JSONAPI specific aspect, namely that if you want to update a single object, you use the same URL as for accessing that single object, but this time you use the 'PATCH'
request method.
We still need to link the new component into our routes, so update the src/js/index.js
import m from 'mithril';
import Athena from './athena.jsx';
import ModulesIndex from './routes/modules/index.jsx';
import New from './routes/modules/new.jsx';
import View from './routes/modules/view.jsx';
import Edit from './routes/modules/edit.jsx';
const root = document.getElementById('app-entry-point)
m.route(root, '/', {
'/': Athena,
'/modules': ModulesIndex,
'/modules/new': New,
'/modules/:mid': View,
'/modules/:mid/edit': Edit
})
and then you will be able to view and edit your modules.