Static Image Geogebra Problems

Usage

Convert a problem with a static (non-interactable, no toolbars or panning) Geogebra app to a problem embedding an image of that applet.

This dramatically speeds up page loads after the image is generated and cached.

To use, import the library:

<import>/res/purdue/purdue_math/math15300/lib/staticgeogebra.library</import>

Then replace the injection

applet.inject(<container>,'preferHTML5');

with

embedStaticGgbApp(applet, container);

Motivation

Geogebra is a fantastic tool for education, and a good choice to have students interact with or draw graphs. To present the same axes and image when they are reading graphs, we would like to use Geogebra for graph reading problems as well; however, loading the full applet on each reload (and, with the way LonCapa unfortunately works, that can be frequent) is time-consuming and can frustrate students.

Under the Hood

The solution for static-image projects is to cache the image so that it only is constructed once per session, and reused on subsequent visits. To do that, we use the sessionStorage html5 feature available in most modern browsers. It has a simple interface:

// To store
sessionStorage.setItem(key,value);
// To retrieve
value = sessionStorage.getItem(key);

Though nobody should still use ancient browsers, we can check for compatiblility directly using a simple if statement:

if (typeof(Storage) !== "undefined"){
    ...
}

Combining, we can now store and retrieve an image:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var applet = new GGBApplet({});
var screenshot_key = "some_database_key";
function embedApplet(){
    // Check for compatibility before using function.
    if (typeof(Storage) !== "undefined"){
          var screenshot = sessionStorage.getItem(screenshot_key);
          if (screenshot) {
              // Embed the image in the container directly
              $('#applet_container')[0].innerHTML = '<img src="data:image/png;base64,'+screenshot+'"/>';
          } else {
              applet.embed('applet_container');
          }
    } else {
        // For other browsers
        applet.embed('applet_container'):
    }
}
$(embedApplet) // Jquery runs the function when the document is "ready".

function ggbOnInit(){
   // Initialize app
   ...
   // Cache image
   var screenshot = document.ggbApplet.getPNGBase64(1,false,72);
  sessionStorage.setItem(screenshot_key, screenshot);
  // For consistency between reloads, swap in the screenshot.
  $('#applet_container')[0].innerHTML = '<img src="data:image/png;base64,'+screenshot+'"/>';
}

Note that the student only sees the Geogebra applet while it is loading; to be more consistent, we swap in the screenshot.

The last difficult decision is the screenshot key. The sessionStorage store is domain-wide, so we need to prevent collisions with other problems, and possibly with other variations of the same problem.

For this, I currently use a two-part key:

  • The current path window.location.pathname, which prevents collisions on different pages;
  • The seed, assigned in perl to $seed = $external::seed;
var screenshot_key = window.location.pathname + " " + "$seed";

Recall that LonCapa replaces $seed with the text of the variable before the page even loads, so the quotation marks are necessary to move a variable.

Source

We can clean this up and package it togther:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var screenshot_key = window.location.pathname + " " + "$seed";

function base64ToImg(source){
    var img = new Image();
    img.src = 'data:image/png;base64,'+source;
    return img;
}

function embedStaticGgbApp(applet, container){
    var screenshot;

    // Update ggbOnInit to cache screenshots.
    var old_ggbOnInit = window.ggbOnInit;
    window.ggbOnInit = function ggbOnInit(){
        old_ggbOnInit.apply(this, arguments);
        var screenshot = document.ggbApplet.getPNGBase64(1,false,72);
        sessionStorage.setItem(screenshot_key, screenshot);
        $('#'+container).html(base64ToImg(screenshot));
    }

    if ((typeof(Storage) !== "undefined") &&
        (screenshot = sessionStorage.getItem(screenshot_key))){
        $('#'+container).html(base64ToImg(screenshot));
    } else {
        applet.inject(container,'preferHTML5');
    }
}

Which (with previously setting $seed as above) allows us to simply replace applet.inject(container) with embedStaticGgbApp(applet,container) to achieve the desired functionality.