Picture of David Hodge
Adding MathJax support: needing js run after components' text has loaded
by David Hodge - Tuesday, 7 April 2015, 5:03 PM
 

I have an interest in being able to use the set of very powerful javascript-based package called MathJax which provides support for high-quality equation (e.g. mathematics) formatting.

Having read through the documentation and played around a bit I've only found one way to make it work, which feels unsatisfactory.

Essentially I need to load the MathJax libraries via a command like:

<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>

The current cheating way I have found to do this is merely to add it to the index.html page inside the src directory.

Unfortunately this doesn't actually work since it looks like MathJax runs on the page before all the articles, blocks and components have been loaded and thus doesn't render the equations in the components on the page. So here's for a dirty fix: I load the javascript on a 2000ms Timeout command after window.onload()

(see http://cdn.mathjax.org/mathjax/latest/test/sample-loader.html for an example)

I'm sure there must be a better approach, but I cannot work out what it is (I seem to need to guarantee the MathJax libraries are loaded after they can see all the page content).

If you need a test case, just insert this code: \( x^2 + y^2 \) into your text somewhere and it should render nicely, if you've got MathJax loaded.

PS - From searching there are a number of projects built with bower (not that I understand it) for which MathJax is already used. But I don't understand it well enough to know if the problem has already been fixed at a much higher level, loading some npm/bower/grunt command.

Picture of Matt Leathes
Re: Adding MathJax support: needing js run after components' text has loaded
by Matt Leathes - Wednesday, 8 April 2015, 10:49 AM
 

Hi David

The better way would be to build a plugin to handle this. Unfortunately the documentation on building plugins is embarrassingly sparse at the moment* - so it's probably easier to look at an extension someone else has built - there's one at https://github.com/cgkineo/adapt-documentMode that might be a good starting point given what you need to do.

Also note that, according to the documentation, you can make MathJax work even when loading it after the page has already rendered...

Hope this helps

* any assistance updating this would be most welcome!

Picture of x z
Re: Adding MathJax support: needing js run after components' text has loaded
by x z - Wednesday, 8 April 2015, 2:19 PM
 

I know that Moodle has a Math Jax plugin/filter (https://docs.moodle.org/27/en/MathJax_filter). Perhaps there's a way to adapt their code?

Picture of David Hodge
Re: Adding MathJax support: needing js run after components' text has loaded
by David Hodge - Saturday, 11 April 2015, 3:36 PM
 

Thanks this is helpful. Unfortunately I'm not a javascript developer so I keep bumping into errors which I'm sure are beginners errors. I seem to have worked out that I need to wrap even one-line commands inside a function(){ /* command here */ } line quite often, and I've just about got the hang of asynchronicity inside nodejs.

As you mention there is a method, once MathJax is already loaded, to call the MathJax queue to re-scan the whole page (or just a specified set of elements) for new equations to format (it's called Typeset and can be given to the MathJax.Hub.Queue()). I managed to sort a scoping problem when using the plug-in route but cannot make it work still without using a timeout (since things run asynchronously and I cannot find names of the necessary callbacks, or if they exist). I think I've found the best core event to attach it to "pageView:ready" which hopefully should be the point as which all the html content of the page's components has been loaded (but I don't think this is actually late enough).

Here's an example of my proposed plug-in code: (which works, but uses a Timeout)

define(function(require) {

var Adapt = require('coreJS/adapt');

function createMathJaxScript() { //This adds the script tag to the html to load the libraries
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
    document.getElementsByTagName("head")[0].appendChild(script); 
}

function renderThePage() {
    alert('Content has been loaded, time to try and MathJax it up');
    createMathJaxScript(); // Load the js libraries.

   // Next line is needed since 'pageView:ready' is probably not the right trigger for when the all the components are present.

    setTimeout(function() { window.MathJax.Hub.Queue(["Typeset",MathJax.Hub]); }, 2000);

}

Adapt.on('pageView:ready', renderThePage());

});

I wonder if I need to go and learn about whether MathJax provides a callback to say when it's finished, or maybe I just need to find a better (later) Adapt core event, because the above doesn't work without the extra call to re-render (which shouldn't be necessary if I only load the js libraries after the page's components have all been loaded).

Oddly, without a timeout command above it works "after a page refresh" (I guess since Adapt has cached the content so it's really present when the js libraries load).

On the plus side, I do now have this entirely inside a plug-in, so just inserting the plug-in directory in the right place enables the facility!

[PS - As Sue says in her reply there are more complicated implementations that already exist (for example for Moodle). From looking, these are much more elaborate, in that they set up the full range of triggered events like "content of a DOM has changed", "re-render just those elements where content has changed", "set appropriate languages/locales for the mathjax libraries" etc.. but I'm still grappling with trying to work out which Adapt core event is the one I need to use to determine when the page has indeed loaded all its content.]

 

Picture of Matt Leathes
Re: Adding MathJax support: needing js run after components' text has loaded
by Matt Leathes - Tuesday, 14 April 2015, 11:59 AM
 

I think the event you're using is fine, the problem you're running into is due to it having to load MathJax from a cdn - something that will inevitably take a little time to do.

It would probably be better to take a local copy and include it in your extension, that way you could be sure that Adapt has loaded it already by the time your code comes to run.

If you have a look at something like contrib-spoor, that will show you how to include an external library into Adapt.

Picture of David Hodge
Re: Adding MathJax support: needing js run after components' text has loaded
by David Hodge - Wednesday, 22 April 2015, 9:40 AM
 

So I've had a further play and the problem seems to be with my use of the Core Events, I think I cannot find one which is triggered after the html contains the content from all the components. The short version of what's below is "Is there maybe some other way in the Javascript from Adapt to know when the HTML for a page just visited has had the content of all its articles, blocks and components fully loaded and ready to be manipulated by an external script? (because I cannot get the current CoreEvents to do it)"

----------

The longer version:

The delay in loading the library from the CDN is beneficial as it makes it more likely that the page content (the articles, blocks and components) is present when the library loads. Here's what I've noticed:

Adapt.on("pageView:ready",...)

this occurs "only once" and at the moment this callback occurs the page is not ready to be parsed for mathematical content. I've experimented with the other events on the Core Events documentation page too. I experimented with Adapt.components.on(), along with componentView:postRender and app:dataReady.

So, if I use Adapt.on("pageView:ready", MyFunction) then I can make it work by adding delay inside MyFunction. However, I have also noticed that Adapt.on("pageView:ready",..) is only behaving like it is called ONCE, when you first visit the course but then when you browse between articles it isn't called again at each new page load, which is no good for me as I need each page (upon visit) to have my script run on it after its content is loaded into the HTML.

Picture of Matt Leathes
Re: Adding MathJax support: needing js run after components' text has loaded
by Matt Leathes - Wednesday, 22 April 2015, 10:42 AM
 

Have you tried using the various 'postRender' events? You've got:

  • pageView:postRender
  • articleView:postRender
  • blockView:postRender
  • componentView:postRender

You might have to experiment a bit to find out which one fires last though.

Also, I have a feeling that one of my colleagues here at Kineo is looking into using MathJax on a project - so it may well be that if you get really stuck you can wait a bit and we will solve this problem for you!

Picture of Matt Leathes
Re: Adding MathJax support: needing js run after components' text has loaded
by Matt Leathes - Wednesday, 6 May 2015, 1:59 PM
 

Hi David

Looks like the Kineo MathJax plugin has now been created: https://github.com/cgkineo/adapt-mathJax

Sorry but I'm not sure if this would work with Adapt v1.1 or not - you'll have to just give it a try.