Friday, March 31, 2017

Add mobile support to your Moodle plugin - part three

Part Three - Starting to Build the Moodle Mobile Add-on

In the previous part of this series, I created just enough code to see an impact on the mobile app. Now, I want to begin to actually do things that will lead me to a solution.

To start with, I decided to take an introductory lesson in AngularJS. If you are not familiar, I highly recommend Code School's Shaping up with AngularJS. This helped me to understand a lot of the javascript I am looking at with other mobile add-ons.

Learning this little bit, has helped me to understand the code I am looking at, and helps me to make good guesses about what is going on with the Moodle specific code.

I'm going to use some existing plugins as a starting point for the code I need. Specifically, the certificate module, which is a third party activity plugin, and the feedback module, which will be included in core.

The main.js file defines the necessary parts to the mobile app. The stateProvider configuration sets up the module for displaying when it is clicked in the course. In the certificate code, you can see a controller and a template defined to do this:
controller: 'mmaModCertificateIndexCtrl',templateUrl: 'addons/mod/certificate/templates/index.html'
In AngularJS, this indicates that the display setup code will be in controllers/index.js and that will use templates/index.html to display that code. When a user clicks on a questionnaire instance, these will be the main pieces to determine what is displayed.

The other part of the main.js file, registers the various handlers needed by the mobile app. In the certificate example, you can see there are two: a "course delegate provider" and a "links delegate provider". The course delegate provider is the part responsible to provide functionality for the module to that mobile app. Its job is to handle a click, and use the module plugin code. Without this, the mobile app will display the "not available" message. The links delegate provider provides function for other links to a module instance, not from the main course display. For example, if a link is put into a forum post. Without this, a link will simply launch a web link in the default browser.

For my module, I'm going to focus on the course delegate provider first. And I will use the index files to affect what is displayed in the mobile app.

For this part of the discussion, I have posted my files here. This is the minimum I have determined I need to have my plugin work in the mobile app without any errors.

To start with, I have created my main.js file to define that the questionnaire plugin should be available, that it uses an index controller and that it has a course content handler. I have taken what I started in the last post, and extended out to provide what is absolutely required.

Next, I will further flesh out my services/handlers.js file. In the first iteration, I only provided an isEnabled function, and it simply returned true. Now I have improved that to call a specific function provided by my questionnaire handler:
self.isEnabled = function() {
    return $mmaModQuestionnaire.isPluginEnabled();
That function will eventually call web services from the Moodle site to verify that the module is enabled.

I have also added a getController function, which is required by the framework to provide information for displaying and launching my plugin code, as follows:
self.getController = function(module, courseid) {
    return function($scope) {
        $scope.title =;
        $scope.icon = 'addons/mod/questionnaire/icon.svg'
        $scope.class = 'mma-mod_questionnaire-handler';
        $scope.action = function() {
            $state.go('site.mod_questionnaire', {module: module, courseid: courseid});
This code tells the framework that my main handler class will be located in services/questionnaire.js and will be called mmaModQuestionnaire. I have also added an icon to be displayed on the course page, and indicated that it will be located in the root of my plugin code. I copied that icon from the main questionnaire plugin. When I run the mobile app with this code, I should now see the questionnaire icon, rather than the default icon, on the course page.

The changes I have made here, mean that I need to create a services/questionnaire.js file, with an mmaModQuestionnaire class, that provides an isPluginEnabled method. You can see that file here. For now, I am keeping the function simple:
self.isPluginEnabled = function(siteId) {
    siteId = siteId || $mmSite.getId();
    return Promise.resolve(true);
The function returns a Javascript Promise (if, like me, you aren't familiar with that, this helped me). Essentially, somewhere in the framework, there is code that will call isEnabled in my course handler and will expect a Promise construct. Since I have not written the necessary web services on the other end to verify that, I am simply going to return a resolved promise to indicate my questionnaire plugin should work.

The last parts I need to create are the index display handlers I described in the main.js file. I have created the controllers/index.js file here, and the templates/index.html file here.
As I mentioned previously, these files work together to define and display what is output when a user clicks on a questionnaire instance in the mobile app. For now, I will set it up to simple display "Hello world!".

The key elements in index.js are the properties of the $scope variable:
$scope.title =;
$scope.description = module.description;
$scope.courseid = courseId;
$scope.mymessage = "Hello World!";
These are the bits that I can use as variables in the index.html file to display specific information. In Angular, this means parts contained in "{{}}" or defined as tag elements. The template I create looks like this:
    <ion-nav-title>{{ title }}</ion-nav-title>
    <ion-content padding="true" mm-state-class>
        <mm-course-mod-description description="description"></mm-course-mod-description>
The {{ title }}, <mm-course-mod-description description="description"> and the {{mymessage}} elements should be replaced by the corresponding $scope element.

All of this should be enough for me to reload my mobile app questionnaire plugin code into the mobile app, run it and when I click on a questionnaire link, see a page that says "Hello world!". So I copy my code into the app www/addons/mod/questionnaire directory, run the server and check out my app.

The course page now displays this:

Note, that my questionnaire icon is now displaying, rather than the default puzzle piece. Clicking on the questionnaire instance, shows me this:

Which shows me the title and my "Hello World!" message. Looks like I have succeeded.

I'm going to leave it there for this post. In my next post, I will build some services to interact with, and build out the link delegate handler as well.