Monday, March 5, 2018

Looking at Moodle's new plugin development API's - Part 1

David Mudrack, of Moodle HQ released a plugin called the "My Todo List" block. While functional, its real purpose is to show off the latest advanced coding techniques available in recent versions of Moodle. See his Tweet. From the readme:
The main purpose of this plugin is to demonstrate usage of advanced coding techniques available in recent Moodle versions. Most notably:
  • Rendering HTML output via Mustache templates.
  • AJAX based workflow of the elementary CRUD operations.
  • Organising JS into AMD modules.
  • Organising external functions into traits.
  • Low-level access to the database via persistent models.
  • Using exporters for handling the data structures in rendering and AJAX.
For this post, I will install and examine the code with the goal of learning better how to use these concepts.

While I have been a plugin developer for many years, I have not been able to keep up with all of the latest techniques to enhance the plugins I maintain. I am personally really interested in getting better with AJAX and AMD, as many of my plugins could benefit from these features. I have already experimented and released code with renderers and Mustache templates (see the series beginning here), so I won't go deep into those unless I see something new.

To begin, I fork David's repository into my own Github repository. This gives me place to install and play with the code. Once I have a local copy, I load it into my IDE (I use PhpStorm) and take a look at the code.

A quick perusal of the code shows that this is indeed a simple plugin, and uses the techniques I'm most interested in! The main block code file,  block_todo.php, contains five methods, four of which I'm familiar with from standard block development: init(), get_content(), specialization(), and applicable_formats(). But the fifth, get_required_javascript(), I am not familiar with and it is not defined on the block development page. I suspect this has to do with the AMD module feature. And, while that may be true, it appears that it calls the parent method, meaning this function is part of the block class. I need to do some searching to see why this function is there.

I search the development documentation on Moodledocs, and find this function referred to on the "jQuery pre2.9" page. So, it must be a function that has remained even with the new AMD module addition. I think I may want to look for the AMD Module documentation first.

I find a page in the Moodle development docs wiki on Javascript Modules. This appears to be the main documentation for using AMD in plugins. Skimming through I see that initializing an AMD javascript function is done with the $this->page->requires->js_call_amd() function. This is done in the ToDo block's get_required_javascript method.

A closer look at the parent block class, defines the get_required_javascript method as:
Allows the block to load any JS it requires into the page.
By default this function simply permits the user to dock the block if it is dockable.
So, I'm not sure that this function is actually required, but may just be a convenient place to call the js_call_amd function from. I search all of the core blocks for js_call_amd and find two, both of which call it from the get_required_javascript method. So this seems to be the place to do that. Maybe later, I'll play with that to see if that is a required way to do it.

In any case, the function passes the arguments:
'block_todo/control', 'init', ['instanceid' => $this->instance->id]
This coincides with the file amd/src/control.js, which is the expected location within a plugin to find the AMD javascript files.

Getting back to the function of the plugin, the main block get_content function, is a very simple function that does two things: gets a list of todo items, and outputs them on the screen. The technique uses one technique I'm familiar with, templates, and two I am unfamiliar with, Moodle persistents and Moodle exporters.

This is what I am seeing in get_content:
// Load the list of persistent todo item models from the database.
$items = block_todo\item::get_my_todo_items();
This code uses the persistent class, which appears to be a new technique to manage database data using CRUD techniques. It will be interesting to learn why this exists and when it should be used.
// Prepare the exporter of the todo items list.
$list = new block_todo\external\list_exporter([
    'instanceid' => $this->instance->id
], [
    'items' => $items,
    'context' => $this->context
This code uses exporter class and appears to be a technique to manage data passed in and out of web service functions, used by external applications and AJAX.
// Render the list using a template and exported data.
$this->content->text = $OUTPUT->render_from_template('block_todo/content',
This code is using a Mustache template to display the block content and utilizes the exporter class to send the data rather than renderers defined for the plugin, as I am used to. Again, I will need to discover why this technique is used.

I have some concepts to learn, so I install the block just to see what it does and how it works. After adding it to my dashboard page, I play with it, and see that it is as advertises, a simple "To Do" list.

Hovering over the controls shows no obvious links, and a quick perusal of the page code shows a form, but not a standard functioning Moodle form. It looks like all of the controls are using Javascript, AJAX and web services to do the actual work. This will be all new to me. :-)

Next post, I will start my functional learning.

Monday, February 12, 2018

The Road Ahead

In October of 2017, I left the employment of Remote Learner. While I had technically not been working for them for almost two years, running Poet Open Source (previously The POET Group), my funding was coming directly from them. Our parting was civil, and they remain a company fully supportive of open source technologies such as Moodle and now Totara.

So where does that leave me? Well, I haven’t completely decided what I want to do with the rest of my life - but to be fair, I never really have. ;-)

I remain a firm believer in open source and its ability to strengthen, change and improve the world.

Since my discovery of open source software over fifteen years ago, and Moodle shortly after that, I have become a supporter and proponent of free ideas, open collaboration and sharing of results. I believe that open source ideals can be applied to almost any solution, innovation and need - not just software.

Going forward, I would like to see Poet Open Source grow as an organization that champions and helps move important projects forward, using open source methods and ideals. I would like to collaborate with and help organize like minded individuals and organizations. What and how Poet works on will be part of that journey.

Moodle will remain to be an important part of my and Poet’s journey; I continue to develop and maintain important Moodle plugins, and I am on the Moodle Users Association committee. But I also believe there is more Poet can do, and will strive to find and contribute to those things.

If you’re interested in the same and would like to join the discussion, drop me a line.

Friday, May 19, 2017

Things I learned upgrading to Moodle 3.3

As the Moodle 3.3 launch approached, I decided to check on a few of my plugins and ensure they were ready for 3.3.

The good news is that having had everything pretty much up to 3.2 standards, the effort to reach 3.3 was minimal. In fact, with the two plugins I worked on for the coveted "Early bird 3.3" award, questionnaire module and oembed filter, I could have submitted them as is and they would have passed fine. But, that's not my way, and besides, it would have made this post even shorter than it is.

For the questionnaire module, I first installed the 3.2 version on a new Moodle 3.3 test site, and ran the unit tests and Behat tests. Having automated testing is a great way to manage upgrading, as these tests will hopefully uncover any problems introduced by the Moodle upgrade or by any changes made to the plugin for the new version. In the questionnaire case, I had made a number of improvements to the code, continuing my efforts of paying down "technical debt" and making the code easier to maintain going forward.

The unit tests ran fine, but the Behat tests failed. Additionally, there were messages being kicked out by the developer level debugging.

There were two issues in Behat  I needed to fix. The first is a change to the Behat tests in Moodle 3.3. Where previously a step would say:
'And I follow "Course 1"' 
For 3.3, they need to say:
'And I am on "Course 1" course homepage'
This is due to the new course dashboard introduced in 3.3 that replaces some of the navigation in 3.2.

The other issue I needed to change was places that would say:
'I navigate to "Questions" node in "Questionnaire administration"'
Now need to say:
'I navigate to "Questions" in current page administration'
This was actually a change in 3.2 that didn't affect me until 3.3. I believe I was supposed to have made the change in 3.2, but now it is mandatory.

Making those changes fixed the Behat errors.

The debugging warning I was getting was:
pix_url is deprecated. Use image_url for images and pix_icon for icons.
    line 267 of /lib/outputrenderers.php: call to debugging()
    line 182 of /mod/questionnaire/classes/questions_form.php: call to renderer_base->pix_url()
    line 204 of /lib/formslib.php: call to mod_questionnaire_questions_form->definition()
    line 32 of /mod/questionnaire/classes/questions_form.php: call to moodleform->__construct()
    line 153 of /mod/questionnaire/questions.php: call to mod_questionnaire_questions_form->__construct()
This is telling me that places I am using the function pix_url will eventually (in some future release) fail. And they should be replaced with either pix_icon or image_url. For my case, image_url is the easiest and correct fix. The previous function, pix_url, returned an URL to be used in the output. The new function, image_url, likewise returns an URL. The new function pix_icon returns the entire line of code including the image tags. Using it would require a significant amount or re-coding, and in reality, I am not using icons.

Once I have completed those changes, everything seems fine with questionnaire.

For the Oembed filter, I ran into Behat issues caused by the HTML that is output with 3.3. There has been changes made to some of the core editing features, that changed the xpath layout for the Oembed editing page. As such, I was getting an error like:
001 Scenario: Admin user carries out various provider management tasks. # /Users/mikechurchward/www/moodlehq.git/filter/oembed/tests/behat/management.feature:26
      And the provider "Vimeo" is disabled                              # /Users/mikechurchward/www/moodlehq.git/filter/oembed/tests/behat/management.feature:36
        The "//td/a[text()='Vimeo']/parent::td/div/a[contains(@class,'filter-oembed-visibility')]/i[@title='Show']" element does not exist and should exist (Behat\Mink\Exception\ExpectationException)
The Behat tests for the filter had a custom test called "the_provider_is_disabled" which depended on a specific xpath output that was now no longer happening. This required me to rewrite the Behat function to change the xpath definition to reflect the new output. Manual testing of the function proved that the actual functionality had not been broken, and once the new xpath definition was in place, the Behat tests passed as well.

And that was it. Both plugins were ready for 3.3 and made it in time to get the early bird award. As I continue with other plugins, I will document any new changes required by 3.3 I find in new posts.

Friday, April 21, 2017

Add mobile support to your Moodle plugin - part four

Part Four - Adding More Services

In the previous part of this series, I modified my mobile addon so that clicking on a plugin instance loaded and displayed a "Hello World!" screen. Now, I will add some services so that it will retrieve the number of responses made to the particular questionnaire and display them, when a user clicks on an instance. I'm also going to add a link handler, that will handle the questionnaire when it is selected by a link in other site content.

First, I'm going to add the link function. In all of the other addons I've looked at, a handler was defined for links. In the forum, I asked about this, and learned that it was so the mobile plugin was handled properly if it was reached from a link other than the course instance.

Adding the link function is apparently very easy now. The latest release of the mobile app has a simplified handler function that manages the links for me. I really just have to add one line to my main.js file and one line to my services/handlers.js file, as follows:

.config(function($mmCourseDelegateProvider, $mmContentLinksDelegateProvider) {    $mmCourseDelegateProvider.registerContentHandler( 'mmaModQuestionnaire', 'questionnaire', '$mmaModQuestionnaireHandlers.courseContent');    $mmContentLinksDelegateProvider.registerLinkHandler( 'mmaModQuestionnaire', '$mmaModQuestionnaireHandlers.linksHandler');
self.linksHandler = $mmContentLinksHelper.createModuleIndexLinkHandler( 'mmaModQuestionnaire', 'questionnaire', $mmaModQuestionnaire);
Now, when I create a link in my course that goes to a questionnaire instance, instead of launching in the browser, it uses the questionnaire mobile plugin.

In the last iteration, I left the isPluginEnabled function in a state where it always returned true. Now, I need to make it do something that actually checks with the site to see if the plugin really is enabled. This will require changing this function in the mobile plugin, and adding a web service to the questionnaire module.

Both the certificate and feedback call the 'mod_[pluginname]_get_[pluginname]_by_courses' web service, so I'll start there. Note that feedback also calls mod_feedback_get_feedback service, but I'll look at that later.

Starting with the mobile plugin, I'll update the services/questionnaire.js::isPluginEnabled function as follows:
self.isPluginEnabled = function(siteId) {
    siteId = siteId || $mmSite.getId();
    return $mmSitesManager.getSite(siteId).then(function(site) {
        return site.wsAvailable( 'mod_questionnaire_get_questionnaires_by_courses');
What this code does, is verify that the questionnaire module is enabled on the Moodle site by checking for the availability of the web service, mod_quesrionnaire_get_questionnaire_by_courses, on the Moodle site. This means I will need to create that web service in the main questionnaire activity plugin. For this one, I am going to copy from the certificate module, but simplify it down for now to only provide the basic module information and only check for 'view' capabilities.

To add the web service to my module, I do the following:

Add the necessary external services to the classes/external.php file:

This requires creating the file with the external class extending the core external_api class in the mod_questionnaire namespace. In that class, I need to add the function for get_questionnaires_by_courses as well as two other helper functions, get_questionnaires_by_courses_parameters and get_questionnaires_by_courses_returns. These three functions are required to fully define the web service as described in the web services developer documentation.

It is the job of this function to return result information for all questionnaire instances in the identified courses. You can see the full code for this here.

Add the db/services.php file to define the services available by web services:

Web services need to be described in this file. When a plugin is installed or upgraded, the Moodle system uses this file to add the services described in this file to its known, available services. This is more fully described here. Once the Moodle site knows about them, it can allow them to be used by external systems.

Since I already have the questionnaire module installed on my Moodle site, I will need to bump up the version number in my version.php file, to trigger a module upgrade, and get the Moodle site to load my new web service. Once I have done this, I should be able to see that my new service has been installed by going to my site's admin/webservice/service_functions.php screen and looking for the web service there. I upgrade my site with new version and check for the service on the admin screen. Successfully, I see the following:

Since I'm writing web services, I might as well create one that provides some meaningful information from the Moodle site that I can display in my mobile plugin.

Currently, when I click on a questionnaire in the mobile app, I just get the "Hello World!" screen. I am going to modify it so that it tells me how many responses I have made to that instance. To do that, I will need a web service in the Moodle plugin to provide that information back to the mobile plugin.

Back at my db/services.php file, I define a new service called 'mod_questionnaire_get_user_responses' and define it as a service that returns 'a count of the current user responses'.

Then, I code the new service in my classes/external.php file. Its a simple function, that simply returns the count of responses for the user / questionnaire instance in question.

Lastly, I perform a version bump to force an upgrade and verify that the service has been added to the Moodle site:

Now, I need to modify the mobile plugin to use this new service and display the information appropriately. Starting with templates/index.html file, I modify so that it will display a count of the user's responses:
    <ion-nav-title>{{ title }}</ion-nav-title>
    <ion-content padding="true" mm-state-class>
        <mm-course-mod-description description="description"></mm-course-mod-description>
        {{mymessage}}<br />
        You have {{responses}} responses.
The template will look for a value in the {{responses}} tag, which means I need to add code to the controllers/index.js file to populate that. The code includes a function that uses the promise mechanism to add the response count to the template scope variable, and a helper function to call the plugin service function, $mmaModQuestionnaire.getUserResponses located in services/questionnaire.js.

In the services/questionnaire.js file, I add the code that will call the new web service I created in the Moodle plugin. This is the part that will actually call the site service and extract the response information. The actual call to the web service is in this line:
return $'mod_questionnaire_get_user_responses', params, preSets).then(function(response) {
This should be all I need to change the behaviour of the questionnaire module in the mobile app, so that it will also show me the number of responses.

With my new code added to the mobile plugin, and the new services added to the Moodle module, I need to verify that I can still access the questionnaire in the mobile app, and that I can see the number of responses.

I ramp up my mobile app, click the questionnaire instance, and achieve success:

I still have a lot of work ahead of me to make my questionnaire plugin functional in the mobile app, but I at least now have a good working understanding of what needs to be done, how to use web services, and how to make them useful in the mobile plugin. I will get back to this (eventually), and document my learnings in future posts.

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.

Tuesday, March 21, 2017

Add mobile support to your Moodle plugin - part two

Part Two - Exploring the Moodle Mobile Add-on

In the first part of this series, I set up a development environment on my local machine. I am going to use this same environment to begin coding the mobile add-on portion of my Moodle questionnaire plug-in.

The documentation says I need to develop the mobile side as a standard Moodle Mobile add-on and then package it as a remote add-on, with four steps:
  1. Develop the required Moodle Web Services
  2. Develop a standard Moodle Mobile add-on
  3. Package the Moodle Mobile add-on as a remote add-on
  4. Include the remote add-on in your Moodle plugin
The first two steps are where my work will start. The second two steps are what I will need when I am ready to distribute what I have developed for anyone to use in their mobile apps with their Moodle sites. I am also going to start with step two, as this will help me understand what is needed in the mobile portion and what is needed from the Moodle plugin portion.

Recall, that when I try to access my plugin in the mobile app now, I get the screen:

My goal, for the start, is to provide enough code so that screen changes.

To start, I look at the current structure of the mobile app on my local server. Within it is a www/addons directory that contains all of the core mobile addons. I can see, that like a Moodle site, there is a mod subdirectory, and within that, a number of subdirectories corresponding to each of the supported core activity plugins.

For ease of development, I am going to add my plugin to the same location. That way, I can update my code within the mobile app, and access it with the browser, to see my work. Essentially, my local files will stand-in as the mobile app, and the questionnaire mobile addon will act as a core activity plugin.

For purposes of code storage, I am going to add an addons subdirectory to my questionnaire repository and add the code that will go into the mobile addon there. This code will not be required, nor run, on a Moodle site, but I want to keep it together with my Moodle plugin for development ease.

Through a productive discussion with the mobile development team, I have determined that getting my add-on registered with the mobile app requires code that registers a content handler. In the add-on structure, this happens in the main.js file in a config statement (full code here):
.config(function($mmCourseDelegateProvider) {
    $mmCourseDelegateProvider.registerContentHandler('mmaModQuestionnaire', 'questionnaire', '$mmaModQuestionnaireHandlers.courseContent');
Essentially, the mobile framework translates this code such that it knows there must be a services subdirectory and within that a file named handlers.js that contains a courseContent function. I create that structure with a very simple courseContent function as follows (full code here):
self.courseContent = function() {
    var self = {};
     * Whether or not the module is enabled for the site.
     * @return {Boolean}
    self.isEnabled = function() {
        return true;
    return self;
In conversations with the developers, I have determined that I need an isEnabled function, that returns true if the plugin is enabled, for my addon to be acknowledged and not display the "plugin that is not yet supported" message. For now, I just send true with no logic confirming that. Eventually, this will call a web service from the actual questionnaire plugin on the Moodle site to determine this. I also know that when I run my local version of the mobile app using the ionic server, it will build my addons code into the app's main code file, www/build/mm.bundle.js.

After dropping these two files into my mobile app's www/addons/mod/questionnaire directory, I run the ionic server (removed some messages for brevity):
ionic serve --browser chromium
Running 'serve:before' gulp task before serve
[13:43:32] Starting 'build'...
[13:43:32] Starting 'sass-build'...
[13:43:32] Starting 'lang'...
[13:43:32] Starting 'watch'...
[13:43:41] Finished 'watch' after 9.04 s
[13:43:47] Finished 'sass-build' after 15 s
[13:43:47] Starting 'sass'...
[13:43:48] Finished 'lang' after 15 s
[13:43:49] Finished 'sass' after 1.92 s
[13:43:50] Finished 'build' after 18 s
[13:43:50] Starting 'config'...
[13:43:50] Finished 'config' after 17 ms
[13:43:50] Starting 'default'...
[13:43:50] Finished 'default' after 6.02 μs
[13:43:50] Starting 'serve:before'...
[13:43:50] Finished 'serve:before' after 3.15 μs
Running live reload server: http://localhost:35729
Watching: www/**/*.html, www/build/**/*, www/index.html, !www/lib/**/*
√ Running dev server:  http://localhost:8100
Ionic server commands, enter:
  restart or r to restart the client app from the root
  goto or g and a url to have the app navigate to the given url
  consolelogs or c to enable/disable console log output
  serverlogs or s to enable/disable server log output
  quit or q to shutdown the server and exit
ionic $
I can see a lot of build activity happening. I then point my browser to the server instance and see a lot more activity kick in. When I access my course, and then click on my questionnaire activity, I no longer get the "plugin that is not yet supported" message:

I don't get the questionnaire displayed, but I didn't expect to. I have at least learned how to impact the mobile app with my addon.

I also take a look at the www/build/mm.bundle.js to see if it now contains the code I created for the questionnaire addon. A quick search of the code reveals that it does. So I have confirmed that the mobile app has successfully picked up the questionnaire addon code and added it to the app.

In the next post, I will flesh out more of the mobile add-on code, and begin to look what the web services needed on the questionnaire plugin side.

Friday, March 3, 2017

Add mobile support to your Moodle plugin - part one

Part One - Setting up a Development Environment

With version 3 of Moodle came the Moodle Mobile app. Out of the box, most of the core Moodle plugins came supported. But, third-party plugins do not function by default in the mobile app.

In Moodle 3.1, the mobile app added support for "Remote add-ons". This support allows plugin providers to add support so that their plugin can function within the mobile app.

Now, I should point out that Moodle is currently working on a much simpler system for adding mobile support to your plugin, but it will be a while before it is ready. When it is ready, the work I am about to undertake will likely be moot.

This post will be the first in a series where I will attempt to add mobile support for my questionnaire module. I say "attempt", because this will be using some technologies that I am not familiar with so I will be learning as I go. I won't guarantee success, but I will document the efforts.

To start with, the main developer documents for Moodle Mobile are here. Looking through that list, I'm going to start with setting up a development environment on my Mac.

I already have Chrome installed on my Mac, so I can skip that step. The next step it says is to install "Node.js". Keep in mind, I don't really know what I am doing here (yet), so I'm not sure how this comes into play, but I'm going to follow instructions. :-)

The documentation suggest using Macports to install Nodejs. It also says I need to use version "v0.12.7". But, it looks like Macports only has versions from 4.* to 7.*. So, Macports is out. The documentation also provides a direct download link for "v0.12.7", and it has a Mac "pkg". So I download that and run the Mac installer. As a result, I have "node" installed at /usr/local/bin/node and "npm" installed at /usr/local/bin/npm.

The next step asks me to run the command npm cache clean. When I do this, I get a series of errors. which seem to indicate permission problems. Likely I need to elevate the permission level this command runs at, so I add a sudo to the front of the command and try again. The new command, sudo npm cache clean works fine. I'm going to assume I will need sudo for the rest of the commands in the document as well.

Next, I am asked to install "ionic". I excute the command:
sudo npm install -g cordova ionic
I get some issues displayed:

npm WARN engine cordova@6.5.0: wanted: {"node":">=4.0.0"} (current: {"node":"0.12.7","npm":"2.11.3"})
npm WARN engine request@2.79.0: wanted: {"node":">= 4"} (current: {"node":"0.12.7","npm":"2.11.3"})
npm WARN deprecated node-uuid@1.4.7: use uuid module instead
/usr/local/bin/cordova -> /usr/local/lib/node_modules/cordova/bin/cordova
/usr/local/bin/ionic -> /usr/local/lib/node_modules/ionic/bin/ionic
cordova@6.5.0 /usr/local/lib/node_modules/cordova
├── underscore@1.7.0
├── q@1.0.1
├── nopt@3.0.1 (abbrev@1.1.0)
├── update-notifier@0.5.0 (is-npm@1.0.0, semver-diff@2.1.0, chalk@1.1.3, string-length@1.0.1, repeating@1.1.3, configstore@1.4.0, latest-version@1.0.1)
├── insight@0.8.4 (object-assign@4.1.1, uuid@3.0.1, lodash.debounce@3.1.1, async@1.5.2, chalk@1.1.3, configstore@1.4.0, os-name@1.0.3, tough-cookie@2.3.2, request@2.79.0, inquirer@0.10.1)
├── cordova-common@2.0.0 (cordova-registry-mapper@1.1.15, ansi@0.3.1, semver@5.3.0, osenv@0.1.4, underscore@1.8.3, q@1.4.1, unorm@1.4.1, shelljs@0.5.3, minimatch@3.0.3, glob@5.0.15, bplist-parser@0.1.1, elementtree@0.1.7, plist@1.2.0)
└── cordova-lib@6.5.0 (valid-identifier@0.0.1, opener@1.4.1, cordova-registry-mapper@1.1.15, properties-parser@0.2.3, nopt@3.0.6, unorm@1.3.3, shelljs@0.3.0, semver@4.3.6, glob@5.0.15, dep-graph@1.1.0, xcode@0.9.1, elementtree@0.1.6, init-package-json@1.9.4, cordova-serve@1.0.1, request@2.47.0, tar@1.0.2, cordova-fetch@1.0.2, aliasify@1.9.0, plist@1.2.0, cordova-js@4.2.1, cordova-create@1.0.2, npm@2.15.11)

ionic@2.2.1 /usr/local/lib/node_modules/ionic
└── @ionic/app-generators@0.0.3
But they seem to be warnings about outdated "node". I know we are using an older version of "node", so I believe this will not be an issue.

Now I need to install the "bower" and "gulp" node packages.

sudo npm install -g bower
/usr/local/bin/bower -> /usr/local/lib/node_modules/bower/bin/bower
bower@1.8.0 /usr/local/lib/node_modules/bower

sudo npm install -g gulp
npm WARN deprecated minimatch@2.0.10: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
npm WARN deprecated minimatch@0.2.14: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
npm WARN deprecated graceful-fs@1.2.3: graceful-fs v3.0.0 and before will fail on node releases >= v7.0. Please update to graceful-fs@^4.0.0 as soon as possible. Use 'npm ls graceful-fs' to find it in the tree.
/usr/local/bin/gulp -> /usr/local/lib/node_modules/gulp/bin/gulp.js
gulp@3.9.1 /usr/local/lib/node_modules/gulp
├── interpret@1.0.1
├── pretty-hrtime@1.0.3
├── deprecated@0.0.1
├── archy@1.0.0
├── tildify@1.2.0 (os-homedir@1.0.2)
├── minimist@1.2.0
├── v8flags@2.0.11 (user-home@1.1.1)
├── chalk@1.1.3 (escape-string-regexp@1.0.5, supports-color@2.0.0, ansi-styles@2.2.1, has-ansi@2.0.0, strip-ansi@3.0.1)
├── semver@4.3.6
├── orchestrator@0.3.8 (sequencify@0.0.7, stream-consume@0.1.0, end-of-stream@0.1.5)
├── gulp-util@3.0.8 (object-assign@3.0.0, array-differ@1.0.0, lodash._reescape@3.0.0, lodash._reinterpolate@3.0.0, lodash._reevaluate@3.0.0, beeper@1.1.1, array-uniq@1.0.3, replace-ext@0.0.1, dateformat@2.0.0, has-gulplog@0.1.0, fancy-log@1.3.0, vinyl@0.5.3, gulplog@1.0.0, lodash.template@3.6.2, through2@2.0.3, multipipe@0.1.2)
├── liftoff@2.3.0 (lodash.isstring@4.0.1, lodash.isplainobject@4.0.6, rechoir@0.6.2, extend@3.0.0, flagged-respawn@0.3.2, lodash.mapvalues@4.6.0, resolve@1.3.2, fined@1.0.2, findup-sync@0.4.3)
└── vinyl-fs@0.3.14 (strip-bom@1.0.0, graceful-fs@3.0.11, vinyl@0.4.6, defaults@1.0.3, mkdirp@0.5.1, through2@0.6.5, glob-stream@3.1.18, glob-watcher@0.0.6)
Again, there are some warnings about outdated releases. But I'm just going to leave them as is for now.

The next step talks about "Push notifications for Mac", and refers me to "" and the Moodle tracker item MOBILE-1970. It looks like this will resolve a problem I may have later on when running iOS versions of the app. So, I execute the command:
sudo gem install cocoapods
At the end of that, I have 27 new gems installed in /Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/.

I already have my git clone of the moodlemobile2 repository, so I set my current directory to it. From there, I execute:
sudo npm install
There are a number of warnings and errors that are output as this executes. Most of them appear to be problems with outdated modules. I am going to disregard those for now.

Now I run the following three commands, one after the other:
sudo ionic platform add android@5.1.1
sudo ionic platform add ios@4.1.0
sudo ionic state restore
All three seem to install okay. The only warnings I receive are for outdated dependencies.

So, I execute:
sudo bower install
And I get:
bower ESUDO         Cannot be run with sudo
So, guess I won't be using sudo here.

I rerun the command without sudo, and get a long list of files being installed. All of them went into the local repo below www/lib/.

So, gulp:

[16:30:43] Using gulpfile ~/www/moodlemobile2/gulpfile.js
[16:30:43] Starting 'build'...
[16:30:43] Starting 'sass-build'...
[16:30:43] Starting 'lang'...
[16:30:48] Finished 'sass-build' after 5.38 s
[16:30:48] Starting 'sass'...
[16:30:49] Finished 'lang' after 5.59 s
[16:30:50] Finished 'sass' after 1.7 s
[16:30:50] Finished 'build' after 7.46 s
[16:30:50] Starting 'config'...
[16:30:51] Finished 'config' after 16 ms
[16:30:51] Starting 'default'...
[16:30:51] Finished 'default' after 5.49 μs
Which again added files below my local repo.

At this point, I think I have everything ready to be able to access the Moodle Mobile app from my local development environment. The links tell me to start Chrome using:

    open -a "Google Chrome" --args --allow-file-access-from-files --disable-web-security --user-data-dir

That seems to just move my Chrome window to the front. Probably because I already had it open? I'll assume it's all good.

Now, to run the ionic server:
ionic serve --browser chromium
 Dependency warning - for the CLI to run correctly,    
 it is highly recommended to install/upgrade the following:  
 Please install your Cordova CLI to version  >=4.2.0 `npm install -g cordova`
 Install ios-sim to deploy iOS applications.`npm install -g ios-sim` (may require sudo)
 Install ios-deploy to deploy iOS applications to devices.  `npm install -g ios-deploy` (may require sudo)
WARN: ionic.project has been renamed to ionic.config.json, please rename it.
WARN: ionic.project has been renamed to ionic.config.json, please rename it.
Multiple addresses available.
Please select which address to use by entering its number from the list below:
 1) (en4)
 2) (en0)
 3) localhost
Address Selection: 3
Selected address: localhost
Running live reload server: http://localhost:35729
Watching: www/**/*, !www/lib/**/*
√ Running dev server:  http://localhost:8100
Ionic server commands, enter:
  restart or r to restart the client app from the root
  goto or g and a url to have the app navigate to the given url
  consolelogs or c to enable/disable console log output
  serverlogs or s to enable/disable server log output
  quit or q to shutdown the server and exit
Since I chose the 'localhost' option I enter 'http://localhost:8100' in my chrome browser window. The browser displays the Moodle Mobile app! I configure it to my local running copy of Moodle, and I'm in business!

Using the simulated mobile browser, I navigate to my test course:

I can see a "questionnaire" instance, so I select that:

And, I see that I really do need to add mobile support to the plugin!

Next posts will be me trying to make this happen.