HTML5 Apps

Kolibri supports web content through the use of HTML5AppNode, which renders the contents of a HTMLZipFile in a sandboxed iframe. The Kolibri HTML5 viewer will load the index.html file which is assumed to be in the root of the zip file. All hrefs and other src attributes must be relative links to resources within the zip file. The iframe rendering of the content in Kolibri is sandbox so there are some limitations about use of plugins and parts of the web API.

Technical specifications

  • An HTMLZipFile must have an index.html file at the root of the zip file.

  • A web application packaged as a HTMLZipFile must not depend on network calls for it to work (cannot load resources references via http/https links)

  • A web application packaged as a HTMLZipFile should not make unnecessary network calls (analytics scripts, social sharing functionality, tracking pixels). In an offline setting none of these functions would work so it is considered best practices to “clean up” the web apps as part of packaging for offline use.

  • The web application must not use plugins like swf/flash.

HTML5AppNode examples

  • A raw HTML example that consists of basic unstyled HTML content taken from the “Additional Online Resources” section of this source page. Note links are disabled (removed blue link, and replaced by display of target URL. If the links were to useful resources (documents, worksheets, sound clips), they could be included in the zip file (deep scraping) with link changed to a relative path. By modern cheffing standards, this HTML node would be flagged as “deficient” since it lacks basic styled and readability. See the recommended approach to basic HTML styling in the next example.

  • A basic styled HTML example. The code uses a basic template which was copy-pasted from html-app-starter. This presentation applies basic fonts, margins, and layout to make HTML content more readable. See the section “Usability guidelines” below for more details.

  • An example of an interactive app. Complete javascript interactive application packaged as a zip file. Source: sushi-chef-phet.

  • A flipbook reader application that is built by this code.

  • A section from a math textbook that includes text, images, and scripts for rendering math equations.

  • A interactive training activity. The code for packaging this HTML app ensures all js, css, and media assets are included in the .zip file.

  • Proof of concept of a Vue.js App. This is a minimal webapp example based on the vue.js framework. Note the shell script used to tweak the links inside index.html and build.js to make references relative paths.

  • Proof of concept React App: A minimal webapp example based on the React framework. Note the shell script tweaks required to make paths relative.

  • A complete task-oriented coding environment which is obtained by taking the source page content and packaging it for offline use.

  • A powerpoint sideshow presentation packaged as a standalone zip with PREV/NEXT buttons.

Extracting Web Content

Most content integration scripts for web content require some combination of crawling (visiting web pages on the source website to extract the structure), and scraping (extracting the metadata and files from detail pages).

The two standard tools for these tasks in the Python community are the requests library for making HTTP requests, and the BeautifulSoup library.

The page Parsing HTML contains some basic info and code examples that will allow you to get started with crawling and scraping. You can also watch this cheffing video tutorial that will show the basic steps of using requests and BeautifulSoup for crawling a website. See the sushi-chef-shls code repo for the final version of the web crawling code that was used for this content source.

Static assets download utility

We have a handy function for fetching all of a webpage’s static assets (JS, CSS, images, etc.), so that, in theory, you could scrape a webpage and display it in Kolibri exactly as you see it in the website itself in your browser.

See the source in utils/downloader.py, example usage in a simple app: MEET chef, which comprises articles with text and images, and another example in a complex app: Blockly Games chef, an interactive JS game with images and sounds.

Usability guidelines

  • Text should be legible (high contrast, reasonable font size)

  • Responsive: text should reflow to fit screens of different sizes. You can preview on a mobile device (or use Chrome’s mobile emulation mode) and ensure that the text fits in the viewport and doesn’t require horizontal scrolling (a maximum width is OK but minimum widths can cause trouble).

  • Ensure navigation within HTML5App is easy to use:

    • consistent use of navigation links (e.g. side menu with sections)

    • consistent use of previous/next links

  • Ensure links to external websites are disabled (remove <a></a> tag), and instead show the href in brackets next to the link text (so that users could potentially access the URL by some other means). For example “some other text link text(http://link.url) and more text continues”

Packaging considerations

It’s important to “cut” the source websites content into appropriately sized chunks:

  • As small as possible so that resources are individually trackable, assignable, remixable, and reusable accross channels and in lessons.

  • But not too small, e.g., if a lesson contains three parts intended to be followed one after the other, then all three parts should be included in a same HTML5App with internal links.

  • Use nested folder structure to represent complex sources. Whenever an HTML page that acts as a “container” with links to other pages and PDFs, turn it into a TopicNode (Folder) and put content items inside it.

Starter template

We also have a starter template for apps, particularly helpful for displaying content that’s mostly text and images, such as articles. It applies some default styling on text to ensure readability, consistency, and mobile responsiveness.

It also includes a sidebar for those apps where you may want internal navigation. However, consider if it would be more appropriate to turn each page into its own content item and grouping them together into a single folder (topic).

How to decide between the static assets downloader (above) and this starter template? Prefer the static assets downloader if it makes sense to keep the source styling or JS, such as in the case of an interactive app (e.g. Blockly Games) or an app-like reader (e.g. African Storybook). If the source is mostly a text blob or an article – and particularly if the source styling is not readable or appealing—using the template could make sense, especially given that the template is designed for readability.

The bottom line is ensure the content meets the usability guidelines above: legible, responsive, easy to navigate, and “look good” (you define “good” :P). Fulfilling that, use your judgment on whatever approach makes sense and that you can use effectively!

Using Local Kolibri Preview

The kolibripreview.py script can be used to test the contents of webroot/ in a local installation of Kolibri without needing to go through the whole content pipeline.

Creating a HTMLZipFile

No special technique is required to create HTMLZipFile files—as long as the .zip file contain the index.html in it’s root (not in a subfolder), it can be used as a HTMLZipFile and added as a file to an HTML5AppNode.

Since creating the zip files is such a common task of the cheffing process, we provide two helpers to save you time: the create_predictable_zip method and the HTMLWriter class.

Zipping a folder

The function create_predictable_zip can be used to create a zip file from a given directory. This is the recommended approach for creating zip files since it strips out file timestamps to ensure that the content hash will not change every time the chef script runs.

Here is some sample code that show how to use this function:

# 1. Create a temporary directory
webroot = tempfile.mkdtemp()

# 2. Create the index.html file inside the temporary directory
indexhtmlpath = os.path.join(webroot, 'index.html')
with open(indexhtmlpath, 'w') as indexfile:
    indexfile.write("<html><head></head><body>Hello, World!</body></html>")
# add images the webroot dir
# add css files the webroot dir
# add js files to the webroot dir
# ...

# 3. Zip it!                      (see https://youtu.be/BODSCrj9FHQ for a laugh)
zippath = create_predictable_zip(webroot)

You can then use this zippath as follows zipfile = HTMLZipFile(path=zippath, ...) and add the zipfile to a HTML5AppNode object using its add_file method. See here for a full code sample.

The HTMLWriter utility class

The class HTMLWriter in ricecooker.utils.html_writer provides a basic helper methods for creating zip files directly in compressed form, without the need for creating a temporary directory first.

To use the HTMLWriter class, you must enter the HTMLWriter context:

from ricecooker.utils.html_writer import HTMLWriter
with HTMLWriter('./myzipfile.zip') as zipper:
    # Add your code here

To write the main file (index.html in the root of the zip file), use the write_index_contents method:

contents = "<html><head></head><body>Hello, World!</body></html>"
zipper.write_index_contents(contents)

You can also add other files (images, stylesheets, etc.) using write_file, write_contents, and write_url methods:

# Returns path to file "styles/style.css"
css_path = zipper.write_contents("style.css", "body{padding:30px}", directory="styles")
extra_head = "<link href='{}' rel='stylesheet'></link>".format(css_path)         # Can be inserted into <head>

img_path = zipper.write_file("path/to/img.png")                                  # Note: file must be local
img_tag = "<img src='{}'>...".format(img_path)                                   # Can be inserted as image

script_path = zipper.write_url("src.js", "http://example.com/src.js", directory="src")
script = "<script src='{}' type='text/javascript'></script>".format(script_path) # Can be inserted into html

To check if a file exists in the zipfile, use the contains method:

# Zipfile has "index.html" file
zipper.contains('index.html')     # Returns True
zipper.contains('css/style.css')  # Returns False

You can then call zipfile = HTMLZipFile(path=''./myzipfile.zip', ...) and add the zipfile to a HTML5AppNode object using its add_file method.

See the source code for more details: ricecooker/utils/html_writer.py.

Further reading

  • Conceptually, we could say that .epub files are a subkind of the .zip file format, but Kolibri handles them differently, using EPubFile.

  • The new H5P content format (experimental support) is also conceptually similar but contains much more structure and metadata about the javascript libraries that are used. See H5PAppNode and the H5PFile for more info.