I am a long time Linux user, and have been using it (or [Free,Dragon,Open]BSD) exclusively since around 2004. For nearly every situation I have either been able to find an open-source replacement, or alternative for most workflows I encounter. Finding alternative workflows happens to be one of my favorite exercises.

One of the more difficult ones to replace was Powerpoint. Luckily, once I was required to start doing presentations towards the end of my undergraduate career is when Google released Google Slides, which allowed me to author slides on the Web and export them as a PDF that I could display on my laptop. This was awkward, but allowed me to present my senior seminar and graduate.

While this worked well, there also existed the guilt that Google Slides was a centralized and proprietary service. I desired to have something open-source and usable to replace it. Once I started graduate school, I stumbled upon HTML slide decks and decided to try them out. I used my first few group meetings as a chance to experiment with them to very mixed results. My first set of slides was an adaption of S5, and it was horribly ugly, difficult to author, and difficult to share with coworkers.

Desperate to find a solution that was not Google Slides or OpenOffice Impress, I stumbled upon what seemed to be the popular Linux/FOSS alternative: Beamer. Beamer is a LaTeX extension/class that uses LaTeX document formatting to generate a set of PDF slides. The examples I saw were decent, but I had never used LaTex before, and it was extremely foreign to me. Around this time was also when I was exposed to Lisp, which led to my descent into becoming an Emacs user. I was exposed to Org-mode during this time and became a big fan of that as well. And lucky me, Org-mode had both LaTeX and Beamer export options. This helped greatly with my introduction into LaTeX, and I began with Beamer slide-deck exports from Org-Mode files.

However, after a long 4 years of Beamer presentations, I was growing weary of it. Between static content and difficulty with authoring, I began longing for something new.

Trying out HTML: the Bare Necessities

I was very disenfranchised with HTML slide decks after my S5 experience, but I finally began investigating HTML slide decks again. I came upon many promising solutions, but deck.js in particular caught my attention. Deck.js seemed to strike a wonderful balance of being minimal, modular, non-intrusive, and (most of all) beautiful.

As an organic chemist, my presentations almost always have at least 3 things: data plots, chemical structures/schemes, and citations. I needed to figure out how to easily implement these before I could begin using deck.js seriously. Data and structures always have the fall back of just exporting/inserting images of any particular format, but citations can be more difficult... I did not want to manually type and update references... Luckily, I discovered citeproc-js.

The use of citeproc-js along with some other tweaks has enabled me to make very decent slide-decks, and is something I've actually used for nearly every talk or presentation I've given since. My goal for the rest of this post is to go through how I made these components work together and how I write my slide decks. For those wishing to dive right in, I've uploaded an example slide deck and posted the source code to GitHub as well.

Working with citations

Citeproc is the specification of a citation style language (CSL), meant to tell a processor how to take bibliographic metadata and generate formatted text. There are different implementations, including one in javascript! This would end up being perfect, but require a little bit of glue to get working. For instance, citeproc-js consumes bibliographic data in a JSON format. Luckily, I use Zotero to manage my references, which can export the CSL styled JSON that citeproc-js consumes. I don't have experience with other sources of bibliographic data (even with LaTeX, I just export my BibTeX files from Zotero), but it should be easy enough to find a xxx2json converting script, or maybe a web API to interface with your software or database of choice.

Also, citeproc-js is just a processor, leaving the user to implement a way to load, store, and retrieve the bibliographic data. This is accomplished by making a sys object that will handle loading/storing the references. The only requirements of the sys object are that it implements the retrieveLocale(lang) and retrieveItem(id) methods. The former loads and returns the XML locale file, and the latter returns the CSL JSON entry corresponding to the item key given.

A barebones sys object that will load locales from a local directory, and references from a location given would look like this:

/*
 * The citeproc-js sys object, implementing retrieveItem and retriveLocale
 */

function citeProcSys (bibFile) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', bibFile, false);
    xhr.send(null);
    this.references = JSON.parse(xhr.responseText);
    this.retrieveLocale = function (lang) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'citeproc-js/locale/locales-' + lang + '.xml', false);
        xhr.send(null);
        return xhr.responseText;
    };
    this.retrieveItem = function (id){
        var refs = this.references.filter(function (value, index, arr) {
	    return id == value.id;
	});
	if (refs)
	    return refs[0];
	else
	    console.log("citeProcSys: itemID " + id + " not found");
    };
}

To use citeproc, you'd then create an Engine using this object, and the CSL of your choice (also the responsibility of the developer to load it). A simple generator that loads styles from a local directory would look like:

/*
 * Convenience function, generate a citeproc engine with given style
 */
function getProcessor (styleID, bibFile) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'styles/' + styleID + '.csl', false);
    xhr.send(null);
    var styleAsText = xhr.responseText;
    var citeproc = new CSL.Engine(new citeProcSys(bibFile),styleAsText, "en-US");
    return citeproc;
};

This Engine object is then updated with keys/IDs of items (this is what is used as the argument to retriveItem), and a bibliography can be generated. Now we just need to populate this engine with bibliographic keys!

I implement this in my slides by adding a referenced class and a bibliography class, and use of the <cite> tag and its id attribute. The referenced class is what I use to indicate separate sections where citations should be processed and put the formatted bibliography in any bibliography child element. Doing it this way allows separately numbered and formatted bibliographies for each referenced section, in particular I make each slide of class referenced to have separate numbering per slide (however, it is possible to set the body as referenced and get global citation numbering and an overall bibliography).

An example slide would then look like this:

<section class="slide referenced">
    <h2>A slide</h2>
    <p>This sentence makes a reference to some thing<cite id="key" /></p>
    <footer class="bibliography"></footer>
</section>

which would look like:

Slide Title

This sentence makes a reference to some thing1

(1) Berry, J. et al. Adv. Mater. 2015, 27 (35), 5102-5112.

The work horse for this is a processCitations(parent, engine) function that takes a parent element, finds any <cite> tags underneath it, grabs the id attribute, and adds that to the citeproc engine. It then uses the citeproc engine to generate and insert the superscript citation format into each <cite> tag, and then finds any tags of the bibliography class and inserts the final generated bibliography.

/*
 * Generate a bibliography and insert/format citations for parent.
 * Uses the <cite> tags with their 'id' attr matching the id
 * of the citation in the citeproc engine
 */
function processCitations(parent, citeproc) {
    var citeTags = parent.getElementsByTagName("cite");
    var itemIDs = [];

    //citeproc silently fails if ID doesn't exits in bibfile...
    //will need to either check here, or fix citeproc to not fail
    for (var i=0; i<citeTags.length; i++) {
	if (citeTags[i].id)
	    itemIDs.push(citeTags[i].id);
    }

    if (itemIDs)
	citeproc.updateItems(itemIDs);
    for (var i=0; i<citeTags.length; ++i) {
	if (citeTags[i].id) {
	    var citation = {
		citationItems : [ {"id" : citeTags[i].id} ],
		properties : {}
	    };
	    var result = citeproc.appendCitationCluster(citation);
	    citeTags[i].innerHTML = result[0][1];
	}
    }

    var bibs = parent.getElementsByClassName("bibliography");
    var bibresult = citeproc.makeBibliography();
    
    for (var j=0; j<bibs.length; ++j) {
	bibs[j].innerHTML = bibresult[1].join('\n');
    }
}

The comment points out something I have not yet fixed. If there is a key/ID added to the engine that does not exist, the Engine errors out and does not do any citation processing (so all references on the slide will not appear).

Lastly, we need a way to find any tags of the referenced class and call processCitations on them. I did this by adding two custom attributes: data-bibfile to specify the CSL JSON bibliography data to use, and data-csl to specify the desired style. While I use local files for these, this becomes an XMLHTTPRequest, so any url should work. This becomes:

function processReferenced() {
    var referencedSlides = document.getElementsByClassName("referenced");
    for (var i=0; i<referencedSlides.length; ++i) {
	var bibfile = referencedSlides[i].getAttribute("data-bibfile");
	var csl = referencedSlides[i].getAttribute("data-csl");
	if (!bibfile)
	    bibfile = "refs.json";
	if (!csl)
	    csl = "american-chemical-society";
	processCitations(referencedSlides[i], getProcessor(csl, bibfile));
    };
}

All of that code is reusable, so I keep it in a separate cite.js file that I load at the bottom of the document: <script src="cite.js" />

The last task is to start this process once the document has finished loading, which is carried out by having

<script>
document.onreadystatechange = function () {
    if (document.readyState == "complete") {
	processReferenced();
    }
}
</script>

at the end of the main document.

Setting up a Slide Deck

I have a GitHub repo setup with a barebones slide deck. As a quick tangent, here's how you go about starting this from scratch.

In an empty directory, add index.html, cite.js, tweak.css, and refs.json. Git init, add, and commit

Add the following as submodules using git submodule add <url>:

In citeproc-js, git submodule update to further grab the locales needed.

Note: this is all wonderfully locally hosted, but comes in at a whopping 208M! This is mostly the styles/locales, and the git history for all the submodules.

This can easily be pruned by only pulling in the styles/locales you need, not using git, or just using a CDN.

However, I like to have a consistent but up-to-date setup for these (so ideally, should jQuery update or whatever, old slides will still work, and git can keep track of my changes, as well as my dependencies).

Working with Chemical Structures

This turned out to be very easy and my favorite thing about this! I use BKChem for all my chemical structure editing, and BKChem happens to use a custom SVG container as it's native format: storing structures as graphic data, with metadata hidden in other custom tags. This way I can just include the file as a figure. Nice, scalable vector images with no need to export as another format.

Columns

This was a common enough layout I use that was not straight forward in plain-HTML.

I made a CSS rule for a series class that changes the display to flexbox, and would include child <section> tags that become the column content (in Firefox, this is necessary as flexbox can be broken on some elements, and requires wrapping in a block element like <section> or <figure>). Another caveat was list items needed to be in block display, so an additional css rule of .series li { display: block; } was needed.

Lastly, the flexbox algorithm can be unfair sometimes, necessitating that one manually set the relative widths of columns using some inline CSS on the child sections: <section style="width: 50%">.

I discovered flexbox can be controlled with the "flex" attribute of its children, determining how much to grow/shrink a box and relative to what amount. I added ".series > * { flex: 1 1;}" to "tweak.css" which forces all immediate children to evenly spread out.

So, to do two columns next to each other would look like

<section class="series">
    <figure>
        <img src="bah.png" />
    </figure>
    <section>
        <ul>
            <li>Stuff!</li>
        <ul>
    </section>
</section>

Future Work: Plotting Data, Distributing, Printing, etc.

Plotting Data

For the time being, I've just been using exported PNG images of data plots. Data plotting can be an annoying and tedious task (I still have not found a program or package or paradigm I enjoy and grok... I tend to utilize and bounce between many different things).

I had wanted to do something with D3.js. I thought it would be great to have raw data stored in a database that D3 would plot out for me. Allowing me to adjust views for the particular presentation, or change on the fly to answer questions. Something fun I still have on my TODO list ;]

Distribution/Archival

I'd say this is something Beamer had right. PDFs were very easy to distribute to other people, and backup and archive. This is not so for deck.js slide decks.

I worked hard to make sure the included scripts were all locally hosted in an individual slide decks folder, so old slide decks should still work (assuming the browser is properly backwards compatible). However, this locally hosted git repo version pulls in at 200M, meaning 200M per slide deck. That is quite unwieldly! This could be cut down by excluding the styles and locales not used, and by removing the git history. I should make a script that would make a stripped down directory for storage purposes...

However, this still doesn't help distribution of slides. I could host the page somewhere and hand out urls: however, for sensitive content I would need to secure the page and setup auth tokens and such. Also, sending an URL to a web page would seem more odd than attaching a PDF/PPT.

I had previously attempted to send zip archives of these directories, with instructions to unzip and open index.html, but this was also met with confusion...

deck.js is supposed to have PDF export support... but only supported in Chrome with their PDF extension.

Luckily! deck.js has a print.css that gets included instead when the media is print! So, I should be able to just print the slides to PDF for storage/sending, correct?

Printing

This did not turn out to be quite correct... The printing style does implement some nice styles (such as page-breaking after each slide) however, without the scaling extension the result is a horrible mess of slides running over into next pages, and not representative of the beautiful slides I carefully put together.

My current hack solution is to meticulously re-style and scale the page for a better print layout to get something good enough to print to a PDF...

However, the last time I did this I received an immediate reply, "please send me the original PPT file." ...errrr, hmmmm...

I need to dig more to see about getting the CSS scaling extension to work with printing. This has been a big TODO on my list.

Citation Helpers

Something else I skipped over was adding citations. I currently do this manually: finding the reference in the JSON file I want to cite, copying the id, and then pasting it in the <cite> tag in HTML file.

What I have wanted to do was to make a quick and dirty Emacs function to automate this for me, something like RefTeX, but for JSON and HTML :D This is still a TODO...

What else might be interesting is to make a Zotero plugin, and in this way, either set the Zotero database as the URL for the bibfile, or have the extension build the JSON document as the citations are made (as opposed to manually exporting from Zotero).

Table helpers

While not as common for my work, the need to present tabular data does arise. This can become tedious, and I have considered making some sort of table generator function (possibly D3 assisted).

The other option is taking the time to master emacs more advanced editing modes for columnar text.

Conclusion

While there are still a few things to improve upon, this has been my favorite way to prepare slideshows used for presentations. It gives me a dynamic and interactive experience that has the possibilites and portability of a web platform. For example, I have embedded youtube videos and interactive elements in certain situations, and it would also be possible to embed a WebGL backed canvas to interactively show a 3D model of some of my computational results!

Hopefully, the above instructions will make it easier for other academics to use HTML5 slidedecks, and begin to topple the hegemony of office-suites.