Migrating MedLearn to Moodle

Medical Sciences Division hosts its teaching material in a system called MedLearn. The pages are mixed with 2D and 3D images, video, interactive content and self test questions.

For 10 years we’ve used Magnolia CMS. It was a good, simple system. In the last 4 years they’ve released 5.x.x which requires significant effort to migrate. We tried to find an upgrade path but couldn’t without spending more than our budget. Staying on the 4.x.x branch was fine until a recent Java security changed stopped us from publishing new pages. We have decided to migrate.

There are plenty of CMS and VLEs to choose from but they need to be maintainable and free. We chose Moodle because it’s free, extensible and fits with our technology strategy. We were particularly happy with the variety of questions available through the Question Bank.

Moodle doesn’t really support our mix of teaching materials with self test question. The Book module provides a good structure for the teaching material; the Quiz Activity provides assessment. We wanted to mix these together. Lesson tool was discounted because the questions are too restrictive, the authoring is confusing and the questions are on separate pages

We think that we’ve found the solution with the Generico filter plugin and its accompanying Atto plugin. The filter will write in an iFrame showing a preview of the question. In due course we may change it to call Javascript which writes preview content into the page. All that’s missing is a method of browsing the Question Bank.

CakePHP 3: Date/Time Disappearing during Hydration

When generating a date/time for saving to a MySQL database, I’ve always tended to generate it with the PHP date method (date('Y-m-d H:i:s')). However, using CakePHP 3, with dateTime validation set on the field, the date/time would disappear during hydration of the entity, but without a validation error.

I still suspected that validation was the issue here, so started having a look at the validation method to try to work out what was going wrong. Before I got very far, I noticed that the datetime validation method validates any value that is a Cake DateTime object. Therefore, the simple solution is to create the date/time using Cake’s built-in Time class:

use Cake\I18n\Time;
$datetime = Time::now();

Cake then happily saves the DateTime in the standard MySQL format.

Moodle evaluation and testing using Vagrant

I wanted a Moodle environment for testing the latest STACK question code. The solution is to use a Vagrant box.

Vagrant allows for provisioning by a number of methods. I found a shell script developed by DigitalSparky which was ready to use. I added a build script for Maxima (to ensure it has the correct version) and made STACK part of the Moodle install.

I’ve forked the repository to my Github account. I hope that you find it useful.

To build this you will need:

The adapted script uses Ubuntu 14.04 LTS Trusty. The Moodle build is cloned from Github and will default to the latest version. The script can be changed to force an earlier version if needed. You can also look in the Hashicorp directory for other pre-built Vagrant boxes.

To complete the Install (assuming that your host system is Ubuntu):

  • install VirtualBox, Vagrant and git
    sudo apt-get install virtualbox vagrant git
  • clone the Vagrant script
    git clone https://github.com/whanrott/moodle-vagrant
  • change to the script’s directory
    cd moodle-vagrant/
  • start the Vagrant box and run the build script
    vagrant up
  • edit your hosts file to enable the URL http://moodle.local/ on your local machine. Add this line
    192.168.33.10   moodle.local

    • As an alternative you can install the Vagrant hostmanager Plug-In. Either approach will require administrator permissions to modify the Hosts file.
  • open http://moodle.local/ and complete the install. You will need to look at the notifications to install the plugins.

That should be all that’s needed for a Moodle development box.

ReactJS – Invariant Violation due to refs or multiple copies of React

I am using ReactJS, with Gulp, Browserify, etc, all managed by NPM. I just updated my packages (npm update), but got the following error:

“Uncaught Invariant Violation: addComponentAsRefTo(…): Only a ReactOwner can have refs. You might be adding a ref to a component that was not created inside a component’s `render` method, or you have multiple copies of React loaded (details: https://fb.me/react-refs-must-have-owner).”

I was quite confused by this, as I am not using refs anywhere, and I thought NPM was meant to stop me from having multiple copies of things loaded. It turns out that isn’t the case – https://gist.github.com/jimfb/4faa6cbfb1ef476bd105 – and NPM is not great at deduplicating dependencies. Therefore, the update had introduced a second version of react (under material-ui), which was causing this error.

Thankfully the fix is pretty easy, and is now my standard NPM equivalent of switch it off and on again:

  • Delete the node_modules directory
  • Run npm install

This should give you a clean set of dependencies and no duplication of react. Of course, if you are using gulp or similar, remember to re-build your bundles before reloading your app to check that the error has gone away. This caused me a moment of frustration before I realised my schoolboy error!

Bootstrap image header with title and coloured bar

It’s not rocket-science but, in case anyone else is looking how to do this, based on this post: http://forums.asp.net/t/2067649.aspx?bootstrap+thumbnail+image+with+text+inside+and+green+big+line+on+the+bottom

calendarandforum

Html:

<div class="row">
 <div class="col-md-6">
  <div class="forum-image header-image">
   <div class="h3 heading-text">FORUM</div>
   <div class="heading-border"></div>
  </div>
  <div class="content-container"></div>
 </div>
 <div class="col-md-6">
  <div class="calendar-image header-image">
   <div class="h3 heading-text">CALENDAR</div>
   <div class="heading-border"></div>
  </div>
  <div class="content-container"></div>
 </div>
</div>

Css:

div.header-image{
 position: relative;
 width:100%;
 height: 150px;
 background-position: center;
 background-size: cover;
}
div.heading-text{
 position: absolute;
 bottom: 10px;
 width: 100%;
 padding: 10px;
 margin: 0px !important;
 opacity: 0.8;
 color: #373737;
 background-color: #d8d8d8;
}
div.heading-border{
 position: absolute;
 bottom: 0px;
 width: 100%;
 height: 10px;
}
div.content-container{
 min-height: 200px;
 margin-bottom: 15px;
 border: 1px solid #cccccc;
 padding: 10px;
}
div.calendar-image{
 background-image: url(../img/calendar.jpg);
}
div.calendar-image div.heading-border{
 background-color: #00147a;
}
div.forum-image{
 background-image: url(../img/students_chatting.jpg);
}
div.forum-image div.heading-border{
 background-color: #be0f35;
}

Acknowledgements

Calendar image by Dafne Cholet under Creative Commons Attribution 2.0 Generic

Forum image by Knight Foundation under Creative Commons Attribution-ShareAlike 2.0 Generic

Moodle, STACK and Maxima: Custom Data Sets

Maxima is extended through packages. The Moodle STACK plug has statistical support packages loaded by default. These are Stats, Descriptive and Distrib. We preload these packages using the optimisation steps.

We can load your data sets as part of the optimisation process. These will then be available as a matrix variable which you can use in questions. Manipulation of matrices isn’t as nice in Maxima as it is in the R environment, but it has a good range of functions.

Moodle, STACK and Maxima: Random Variables

The rand() function is STACK’s implementation of Maxima’s random() function.  Random variables are zero-numbered. You may need to use simplification to reduce the output to a single value.

x:rand(5);            /* pick a random number between 0 and 4 */
y:ev(rand(5)+1,simp); /* pick a random number between 1 and 5 */

NB: if the simplification is off then the result of y would be 4+1

The rand() function in STACK also picks from a list:

x:rand([a,b,c,d]);                   /* pick a random item from a list. 
                                        This list shows lower case letters */
y:ev((rand(5)+1)*rand([1,-1]),simp); /* pick a random positive or negative
                                        number between 1 and 5 */

You can apply this behaviour of the rand() function to any list object. For example

w:makelist(x,x,1,10);          /* make a list of integers between 1 and 10 */
x:makelist(2^x,x,1,8);         /* make a list of powers of 2 from 2^1 to 2^8 */
y:primes(2,30);                /* use the primes() function to generate a
                                  prime number between 2 and 30. */
z:map(fib,makelist(x,x,1,10)); /* use the map() and fib() functions to make a 
                                  list of the first 10 Fibonacci numbers */

NB: not all Maxima functions are allowed in STACK, but they can be enabled by MSD LT team if needed.

Moodle, STACK and Maxima: Simplification

Maxima will automatically simplify expressions when simplification is switched on.

Switch simplification off so that you can pre-calculate the question and any workings. Declare this option in the Question variables:

simp:false;

Simplification can be switched on an off in expressions:

(%i1) simp:false;
(%o1) false
(%i2) ev(2*x);
       2
(%o2) x  x
(%i3) ev(2*x,simp:true);
       3
(%o3) x

NB: the lines %i are input lines. Those with %o are output from maxima

Maxima cannot display intermediate steps for worked answers. It can be made to calculate those steps, if you’re willing to write them. This is much easier when simplification is switched off.

Moodle, STACK and Maxima: Getting Started

The STACK plugin for Moodle brings the Maxima to Moodle. Maxima is a Computer Algebra System which will calculate question variables and test student answers.

This is a first blog post in a set which will give tips for anyone wanting to start writing questions using STACK. There is also an Authoring Quick Start guide included in the documentation.

1. Write all your calculations into the Question variables field

It’s easiest to do all your calculations in one place and then includes these in the model answer and answer test fields. For worked answers, calculate intermediate results.

NB: all statements are terminated with a semi-colon; comments are shown by /* some text */

simp:false;        /* turn simplification off */
x:2;               /* define x variable */
y:3;               /* define y variable */
Ques:x*y;          /* product of x and y with simplification off */
Ans:ev(Ques,simp); /* product of x and y evaluated with simplification on */

You can now put Ans into the model answer field, and test against Ans in the potential response tree.

AngularJS: labelled empty option in select box when using ng-options

I’m using ng-options to generate the options for select boxes in AngularJS. By default, when a selected value is not defined, Angular adds a blank option, without a label. I wanted to give this blank option a label, e.g. “Select something”, but couldn’t see how to do it.

Not surprisingly it’s very easy, and is actually mentioned in the docs (https://docs.angularjs.org/api/ng/directive/select – 4th paragraph, beginning “Optionally” at time of writing), but like, I guess, most people, I never read the docs properly and just scanned the examples, which don’t show this.

All you need to do is add the labelled blank option in the select box, as you normally would if you were just writing the HTML, e.g.:

<select name="myselect" ng-model="MyCtrl.selected" 
   ng-options="option.value as option.label for option in MyCtrl.options">
   <option value="">Select something</option>
</select>

Angular will then generate the remaining options from the options array that you pass to it, and leave the blank option at the top.

In the above example, to not show a blank option at all, remove the option tag and set Ctrl.selected to be equal to the value that you want to be selected by default in the controller, e.g.

In controller.js

this.options = [ { value: 1, label: 'Yes' }, { value: 0, label: 'No' } ];
this.selected = 1;

In view.html

<select name="myselect" ng-model="MyCtrl.selected" 
   ng-options="option.value as option.label for option in MyCtrl.options">
</select>