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.